-
-
Save sidharthkuruvila/3154845 to your computer and use it in GitHub Desktop.
/** | |
* Takes a camel cased identifier name and returns an underscore separated | |
* name | |
* | |
* Example: | |
* camelToUnderscores("thisIsA1Test") == "this_is_a_1_test" | |
*/ | |
def camelToUnderscores(name: String) = "[A-Z\\d]".r.replaceAllIn(name, {m => | |
"_" + m.group(0).toLowerCase() | |
}) | |
/* | |
* Takes an underscore separated identifier name and returns a camel cased one | |
* | |
* Example: | |
* underscoreToCamel("this_is_a_1_test") == "thisIsA1Test" | |
*/ | |
def underscoreToCamel(name: String) = "_([a-z\\d])".r.replaceAllIn(name, {m => | |
m.group(1).toUpperCase() | |
}) |
I had to support some cases where words were all upper case:
/**
* Converts from camelCase to snake_case
* e.g.: camelCase => camel_case
*
* @param name the camelCase name to convert
* @return snake_case version of the string passed
*/
def camelToSnake(name: String): String = {
@tailrec
def go(accDone: List[Char], acc: List[Char]): List[Char] = acc match {
case Nil => accDone
case a::b::c::tail if a.isUpper && b.isUpper && c.isLower => go(accDone ++ List(a, '_', b, c), tail)
case a::b::tail if a.isLower && b.isUpper => go(accDone ++ List(a, '_', b), tail)
case a::tail => go(accDone :+ a, tail)
}
go(Nil, name.toList).mkString.toLowerCase
}
"camelToSnake" should "process right camel case + all upper case + mixed" in {
camelToSnake("COLUMN") shouldBe "column"
camelToSnake("someColumnNameRespectingCamel") shouldBe "some_column_name_respecting_camel"
camelToSnake("columnWITHSomeALLUppercaseWORDS") shouldBe "column_with_some_all_uppercase_words"
}
@dmateusp 👍 exactly what I needed! Would be great if worked with numeric text:
actual: DirectlyEmployedOr1099Resources => directly_employed_or1099resources
expected: DirectlyEmployedOr1099Resources => directly_employed_or_1099_resources
@dmateusp FYI, that causes a problem with things like camelToSnake("IsAString") => is_astring, when it should be is_a_string.
I prefer not to use regex, this approach runs in n time, it's tail recursive and does not convert consecutive upper chars, e.g. ThisIsCAmel -> this_is_camel:
def camel2Underscore(s: String): String = {
@tailrec def camel2Underscore(s: String, output: String, lastUppercase: Boolean): String =
if (s.isEmpty) output
else {
val c = if (s.head.isUpper && !lastUppercase) "_" + s.head.toLower else s.head.toLower
camel2Underscore(s.tail, output + c, s.head.isUpper && !lastUppercase)
}
camel2Underscore(s, "", true)
}
@ruloweb This is nice but won't work if the two consecutive uppercase chars are at the start (ie. THisIsCamel) or if there are greater than two consecutive uppercase characters elsewhere (ie. ThisIsCAMel). Consequently, this will fail for converting all caps as well.
I adjusted the code to support the all caps case:
def camel2Snake(str: String): String = {
@tailrec
def camel2SnakeRec(s: String, output: String, lastUppercase: Boolean): String =
if (s.isEmpty) output
else {
val c = if (s.head.isUpper && !lastUppercase) "_" + s.head.toLower else s.head.toLower
camel2SnakeRec(s.tail, output + c, s.head.isUpper && !lastUppercase)
}
if (str.forall(_.isUpper)) str.map(_.toLower)
else {
camel2SnakeRec(str, "", true)
}
}
@amackillop, it doesnt't work for "THISIsADog" (it gives "t_hi_sis_adog")
i suggest the following code :
def camel2Snake(str: String): String = {
val headInUpperCase = str.takeWhile(c => c.isUpper || c.isDigit)
val tailAfterHeadInUppercase = str.dropWhile(c => c.isUpper || c.isDigit)
if (tailAfterHeadInUppercase.isEmpty) headInUpperCase.toLowerCase else {
val firstWord = if (!headInUpperCase.dropRight(1).isEmpty) {
headInUpperCase.last match {
case c: Any if (c.isDigit) => headInUpperCase
case _ => headInUpperCase.dropRight(1).toLowerCase
}
} else {
headInUpperCase.toLowerCase + tailAfterHeadInUppercase.takeWhile(c => c.isLower)
}
if (firstWord == str.toLowerCase) {
firstWord
} else {
s"${firstWord}_${camel2Snake(str.drop(firstWord.length))}"
}
}
}
@0dilon you version stack-overflows if the camelCase already contains an underscore.
e.g. camel2Snake("foo_BarBaz")
Late to the party, but iterate w/ Char
s seems fine too if you're ok w/ no handling -
s in the String
s
val camelToKebab: String => String = _.foldLeft("") {
case (acc, chr) if chr.isUpper => acc :+ '-' :+ chr.toLower
case (acc, chr) => acc :+ chr
}
val camelToSnake : String => String = _.foldLeft( "" ){ (acc, c) =>
( c.isUpper, acc.isEmpty, acc.takeRight(1) == "_" ) match {
case (true, false, false) => acc + "_" + c.toLower
case (true, _, _) => acc + c.toLower
case (false, _, _) => acc + c
}
}
Maybe a little less concise but I prefer this;