Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refine the api #1

Merged
merged 1 commit into from
Sep 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}
Loading