Skip to content

Commit

Permalink
Merge pull request #1 from ranyitz/refine-api
Browse files Browse the repository at this point in the history
refine the api
  • Loading branch information
ranyitz committed Sep 30, 2023
2 parents 9887c35 + 45cd176 commit f0b1de1
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 99 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Changelog

## 0.2.0 (Sep 30, 2023)
Refine the API

## 0.1.0 (Sep 30, 2023)
Initial version
43 changes: 21 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,13 @@ libraryDependencies += "io.github.ranyitz" %% "casing" % "0.1.0"
```scala
import casing._

Casing.camelCase("foo_bar") // fooBar
Casing.pascalCase("foo bar") // FooBar
Casing.snakeCase("fooBar") // foo_bar
Casing.kebabCase("foo-bar") // foo-bar
Casing.constantCase("foo.bar") // FOO_BAR

Casing // foo.bar.baz
.split("fooBarBaz")
camelCase("foo_bar") // fooBar
pascalCase("foo bar") // FooBar
snakeCase("fooBar") // foo_bar
kebabCase("foo-bar") // foo-bar
constantCase("foo.bar") // FOO_BAR

caseSplit("fooBarBaz") // foo.bar.baz
.map(_.toLowerCase())
.mkString(".")
```
Expand All @@ -42,56 +41,56 @@ splits a string into words based on the casing pattern
> can be used to create any custom naming pattern
```scala
Casing.split("fooBarBaz") // Seq(foo, Bar, Baz)
Casing.split("foo_bar_baz") // Seq(foo, bar, baz)
Casing.split("foo-bar-baz") // Seq(foo, bar, baz)
Casing.split("FOO_BAR_BAZ") // Seq(FOO, BAR, BAZ)
caseSplit("fooBarBaz") // Seq(foo, Bar, Baz)
caseSplit("foo_bar_baz") // Seq(foo, bar, baz)
caseSplit("foo-bar-baz") // Seq(foo, bar, baz)
caseSplit("FOO_BAR_BAZ") // List(FOO, BAR, BAZ)
```

### camelCase
converts a string to [camelCase](https://en.wikipedia.org/wiki/Camel_case)

```scala
Casing.camelCase("foo_bar") // fooBar
camelCase("foo_bar") // fooBar
```

### pascalCase
converts a string to [PascalCase](https://en.wikipedia.org/wiki/Camel_case)

```scala
Casing.pascalCase("foo_bar") // FooBar
pascalCase("foo_bar") // FooBar
```

### snakeCase
converts a string to [snake_case](https://en.wikipedia.org/wiki/Snake_case)

```scala
Casing.snakeCase("fooBar") // foo_bar
snakeCase("fooBar") // foo_bar
```

### kebabCase
converts a string to [kebab-case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles)

```scala
Casing.kebabCase("fooBar") // foo-bar
kebabCase("fooBar") // foo-bar
```

### constantCase
converts a string to CONSTANT_CASE or [SCREAMING_SNAKE_CASE](https://en.wikipedia.org/wiki/Snake_case)

```scala
Casing.constantCase("fooBar") // FOO_BAR
constantCase("fooBar") // FOO_BAR
```

### Validation Functions
validates a string against a specific naming convention

```scala
Casing.isCamelCase("fooBar") // true
Casing.isPascalCase("FooBar") // true
Casing.isSnakeCase("foo_bar") // true
Casing.isKebabCase("foo-bar") // true
Casing.isConstantCase("FOO_BAR") // true
isCamelCase("fooBar") // true
isPascalCase("FooBar") // true
isSnakeCase("foo_bar") // true
isKebabCase("foo-bar") // true
isConstantCase("FOO_BAR") // true
```

### Inspiration
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ val Scala213: String = "2.13.11"
val Scala3: String = "3.3.0"

ThisBuild / scalaVersion := Scala213
ThisBuild / version := "0.1.0"
ThisBuild / version := "0.2.0"

lazy val root = (project in file("."))
.settings(
Expand Down
28 changes: 14 additions & 14 deletions src/main/scala/casing/Casing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,26 @@ package casing

import scala.util.matching.Regex

class Casing {
protected class Casing {
// finds a lower char followed by an upper char and returns them as capture groups
// e.g. fooBar -> foo, Bar
val LOWER_UPPER_REGEX: Regex = "([\\p{Ll}])(\\p{Lu})".r
private val LOWER_UPPER_REGEX: Regex = "([\\p{Ll}])(\\p{Lu})".r
// finds an upper char followed by and upper and lower char and return them as capture groups
// e.g. FOOBar -> FOO, Bar
val UPPER_UPPER_LOWER_REGEX: Regex = "(\\p{Lu})([\\p{Lu}][\\p{Ll}])".r
private val UPPER_UPPER_LOWER_REGEX: Regex = "(\\p{Lu})([\\p{Lu}][\\p{Ll}])".r
// finds a number followed by a letter and returns them as capture groups
val NUMBER_LETTER_REGEX: Regex = "(\\d)(\\p{L})".r
private val NUMBER_LETTER_REGEX: Regex = "(\\d)(\\p{L})".r
// finds a letter followed by a number and returns them as capture groups
val LETTER_NUMBER_REGEX: Regex = "(\\p{L})(\\d)".r
private val LETTER_NUMBER_REGEX: Regex = "(\\p{L})(\\d)".r
// finds all non-alphanumeric characters (including non-ASCII)
val NON_ALPHANUMERIC_REGEXP: Regex = "[^\\p{L}\\d]+".r
private val NON_ALPHANUMERIC_REGEXP: Regex = "[^\\p{L}\\d]+".r
// use a null character as a delimeter
val DELIMETER = "\u0000"
private val DELIMETER = "\u0000"
// a regex replacement value that will be used to replace the matched value with
// the first capture group followed by a delimeter and the second capture group
val REPLACEMENT_SEPARATOR: String = s"$$1$DELIMETER$$2"
private val REPLACEMENT_SEPARATOR: String = s"$$1$DELIMETER$$2"

def split(input: String, options: SplitOptions = SplitOptions()): Seq[String] = {
def caseSplit(input: String, options: SplitOptions = SplitOptions()): Seq[String] = {
var result = input
.replaceAll(LOWER_UPPER_REGEX.pattern.pattern(), REPLACEMENT_SEPARATOR)
.replaceAll(UPPER_UPPER_LOWER_REGEX.pattern.pattern(), REPLACEMENT_SEPARATOR)
Expand All @@ -39,7 +39,7 @@ class Casing {

// foo bar -> fooBar
def camelCase(input: String): String = {
split(input).zipWithIndex.map { case (word, index) =>
caseSplit(input).zipWithIndex.map { case (word, index) =>
if (index == 0) word.toLowerCase
else word.toLowerCase.capitalize
}.mkString
Expand All @@ -51,7 +51,7 @@ class Casing {

// foo bar -> FooBar
def pascalCase(input: String): String = {
split(input).map(_.toLowerCase.capitalize).mkString
caseSplit(input).map(_.toLowerCase.capitalize).mkString
}

def isPascalCase(input: String): Boolean = {
Expand All @@ -60,7 +60,7 @@ class Casing {

// foo bar -> foo_bar
def snakeCase(input: String, options: SplitOptions = SplitOptions()): String = {
split(input, options).map(_.toLowerCase).mkString("_")
caseSplit(input, options).map(_.toLowerCase).mkString("_")
}

def isSnakeCase(input: String, options: SplitOptions = SplitOptions()): Boolean = {
Expand All @@ -69,7 +69,7 @@ class Casing {

// foo bar -> FOO_BAR
def constantCase(input: String, options: SplitOptions = SplitOptions()): String = {
split(input, options).map(_.toUpperCase).mkString("_")
caseSplit(input, options).map(_.toUpperCase).mkString("_")
}

def isConstantCase(input: String, options: SplitOptions = SplitOptions()): Boolean = {
Expand All @@ -78,7 +78,7 @@ class Casing {

// foo bar -> foo-bar
def kebabCase(input: String, options: SplitOptions = SplitOptions()): String = {
split(input, options).map(_.toLowerCase).mkString("-")
caseSplit(input, options).map(_.toLowerCase).mkString("-")
}

def isKebabCase(input: String, options: SplitOptions = SplitOptions()): Boolean = {
Expand Down
15 changes: 7 additions & 8 deletions src/main/scala/example/Example.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import casing.Casing
import casing._

object Example extends App {
println(Casing.camelCase("foo_bar")) // fooBar
println(Casing.pascalCase("foo bar")) // FooBar
println(Casing.snakeCase("fooBar")) // foo_bar
println(Casing.kebabCase("foo-bar")) // foo-bar
println(Casing.constantCase("foo.bar")) // FOO_BAR
println(camelCase("foo_bar")) // fooBar
println(pascalCase("foo bar")) // FooBar
println(snakeCase("fooBar")) // foo_bar
println(kebabCase("foo-bar")) // foo-bar
println(constantCase("foo.bar")) // FOO_BAR
println(
Casing
.split("fooBarBaz")
caseSplit("fooBarBaz")
.map(_.toLowerCase())
.mkString(".")
) // foo.bar.baz
Expand Down
108 changes: 54 additions & 54 deletions src/test/scala/casing/CasingTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,110 +4,110 @@ import casing._

object CasingTest extends SimpleTestSuite {
test("split") {
expect(Casing.split("fooBar"), Seq("foo", "Bar"))
expect(Casing.split("FooBar"), Seq("Foo", "Bar"))
expect(Casing.split("FOOBar"), Seq("FOO", "Bar"))
expect(Casing.split("foo_bar"), Seq("foo", "bar"))
expect(Casing.split("foo-bar"), Seq("foo", "bar"))
expect(Casing.split("foo.bar"), Seq("foo", "bar"))
expect(Casing.split("Foo Bar"), Seq("Foo", "Bar"))
expect(Casing.split("foo bar"), Seq("foo", "bar"))
expect(Casing.split("FOO_BAR"), Seq("FOO", "BAR"))
expect(Casing.split("foo/bar"), Seq("foo", "bar"))
expect(Casing.split(" foo bar "), Seq("foo", "bar"))
expect(Casing.split("_foo-Bar.Baz_Qux "), Seq("foo", "Bar", "Baz", "Qux"))
expect(caseSplit("fooBar"), Seq("foo", "Bar"))
expect(caseSplit("FooBar"), Seq("Foo", "Bar"))
expect(caseSplit("FOOBar"), Seq("FOO", "Bar"))
expect(caseSplit("foo_bar"), Seq("foo", "bar"))
expect(caseSplit("foo-bar"), Seq("foo", "bar"))
expect(caseSplit("foo.bar"), Seq("foo", "bar"))
expect(caseSplit("Foo Bar"), Seq("Foo", "Bar"))
expect(caseSplit("foo bar"), Seq("foo", "bar"))
expect(caseSplit("FOO_BAR"), Seq("FOO", "BAR"))
expect(caseSplit("foo/bar"), Seq("foo", "bar"))
expect(caseSplit(" foo bar "), Seq("foo", "bar"))
expect(caseSplit("_foo-Bar.Baz_Qux "), Seq("foo", "Bar", "Baz", "Qux"))
}

test("split with numbers=false") {
expect(Casing.split("fooBar123", SplitOptions(numbers = false)), Seq("foo", "Bar123"))
expect(Casing.split("123foo bar", SplitOptions(numbers = false)), Seq("123foo", "bar"))
expect(Casing.split("123.foo.bar", SplitOptions(numbers = false)), Seq("123", "foo", "bar"))
expect(caseSplit("fooBar123", SplitOptions(numbers = false)), Seq("foo", "Bar123"))
expect(caseSplit("123foo bar", SplitOptions(numbers = false)), Seq("123foo", "bar"))
expect(caseSplit("123.foo.bar", SplitOptions(numbers = false)), Seq("123", "foo", "bar"))
expect(
Casing.split("Scala2.13.11", SplitOptions(numbers = false)),
caseSplit("Scala2.13.11", SplitOptions(numbers = false)),
Seq("Scala2", "13", "11")
)
expect(Casing.split("1V", SplitOptions(numbers = false)), Seq("1V"))
expect(caseSplit("1V", SplitOptions(numbers = false)), Seq("1V"))
}

test("split with numbers=true (default)") {
expect(Casing.split("fooBar123"), Seq("foo", "Bar", "123"))
expect(Casing.split("123foo bar"), Seq("123", "foo", "bar"))
expect(caseSplit("fooBar123"), Seq("foo", "Bar", "123"))
expect(caseSplit("123foo bar"), Seq("123", "foo", "bar"))
expect(
Casing.split("Scala2.13.11"),
caseSplit("Scala2.13.11"),
Seq("Scala", "2", "13", "11")
)
expect(Casing.split("1V"), Seq("1", "V"))
expect(caseSplit("1V"), Seq("1", "V"))
}

test("camelCase") {
assertEquals(Casing.camelCase("foo bar"), "fooBar")
assertEquals(Casing.camelCase("FOO BAR"), "fooBar")
assertEquals(Casing.camelCase("foo bar 123"), "fooBar123")
assertEquals(camelCase("foo bar"), "fooBar")
assertEquals(camelCase("FOO BAR"), "fooBar")
assertEquals(camelCase("foo bar 123"), "fooBar123")
}

test("isCamelCase") {
assert(Casing.isCamelCase("fooBar"))
assert(Casing.isCamelCase("FooBar") == false)
assert(Casing.isCamelCase("foo_bar") == false)
assert(isCamelCase("fooBar"))
assert(isCamelCase("FooBar") == false)
assert(isCamelCase("foo_bar") == false)
}

test("PascalCase") {
assertEquals(Casing.pascalCase("foo bar"), "FooBar")
assertEquals(Casing.pascalCase("FOO BAR"), "FooBar")
assertEquals(pascalCase("foo bar"), "FooBar")
assertEquals(pascalCase("FOO BAR"), "FooBar")
}

test("isPascalCase") {
assert(Casing.isPascalCase("FooBar"))
assert(Casing.isPascalCase("fooBar") == false)
assert(Casing.isPascalCase("foo_bar") == false)
assert(isPascalCase("FooBar"))
assert(isPascalCase("fooBar") == false)
assert(isPascalCase("foo_bar") == false)
}

test("snake_case") {
assertEquals(Casing.snakeCase("foo bar"), "foo_bar")
assertEquals(Casing.snakeCase("FooBar"), "foo_bar")
assertEquals(Casing.snakeCase("FooBar123"), "foo_bar_123")
assertEquals(snakeCase("foo bar"), "foo_bar")
assertEquals(snakeCase("FooBar"), "foo_bar")
assertEquals(snakeCase("FooBar123"), "foo_bar_123")
assertEquals(
Casing.snakeCase("FooBar123", options = SplitOptions(numbers = false)),
snakeCase("FooBar123", options = SplitOptions(numbers = false)),
"foo_bar123"
)
}

test("isSnakeCase") {
assert(Casing.isSnakeCase("foo_bar"))
assert(Casing.isSnakeCase("fooBar") == false)
assert(Casing.isSnakeCase("FOO_BAR") == false)
assert(isSnakeCase("foo_bar"))
assert(isSnakeCase("fooBar") == false)
assert(isSnakeCase("FOO_BAR") == false)
}

test("CONSTANT_CASE") {
assertEquals(Casing.constantCase("foo bar"), "FOO_BAR")
assertEquals(Casing.constantCase("FooBar"), "FOO_BAR")
assertEquals(Casing.constantCase("FooBar123"), "FOO_BAR_123")
assertEquals(constantCase("foo bar"), "FOO_BAR")
assertEquals(constantCase("FooBar"), "FOO_BAR")
assertEquals(constantCase("FooBar123"), "FOO_BAR_123")
assertEquals(
Casing.constantCase("FooBar123", options = SplitOptions(numbers = false)),
constantCase("FooBar123", options = SplitOptions(numbers = false)),
"FOO_BAR123"
)
}

test("isConstantCase") {
assert(Casing.isConstantCase("FOO_BAR"))
assert(Casing.isConstantCase("fooBar") == false)
assert(Casing.isConstantCase("foo_bar") == false)
assert(isConstantCase("FOO_BAR"))
assert(isConstantCase("fooBar") == false)
assert(isConstantCase("foo_bar") == false)
}

test("kebab-case") {
assertEquals(Casing.kebabCase("foo bar"), "foo-bar")
assertEquals(Casing.kebabCase("FooBar"), "foo-bar")
assertEquals(Casing.kebabCase("FooBar123"), "foo-bar-123")
assertEquals(kebabCase("foo bar"), "foo-bar")
assertEquals(kebabCase("FooBar"), "foo-bar")
assertEquals(kebabCase("FooBar123"), "foo-bar-123")
assertEquals(
Casing.kebabCase("FooBar123", options = SplitOptions(numbers = false)),
kebabCase("FooBar123", options = SplitOptions(numbers = false)),
"foo-bar123"
)
}

test("isKebabCase") {
assert(Casing.isKebabCase("foo-bar"))
assert(Casing.isKebabCase("FOO-BAR") == false)
assert(Casing.isKebabCase("FOO_BAR") == false)
assert(Casing.isKebabCase("fooBar") == false)
assert(isKebabCase("foo-bar"))
assert(isKebabCase("FOO-BAR") == false)
assert(isKebabCase("FOO_BAR") == false)
assert(isKebabCase("fooBar") == false)
}
}

0 comments on commit f0b1de1

Please sign in to comment.