diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..9510ba0 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: test + +on: + push: + branches: + - master + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: erlef/setup-beam@v1 + with: + otp-version: "26.0.2" + gleam-version: "0.33.0" + rebar3-version: "3" + # elixir-version: "1.15.4" + - run: gleam deps download + - run: gleam test + - run: gleam format --check src test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..170cca9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.beam +*.ez +build +erl_crash.dump diff --git a/README.md b/README.md new file mode 100644 index 0000000..5db44d9 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# justin + +[![Package Version](https://img.shields.io/hexpm/v/justin)](https://hex.pm/packages/justin) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/justin/) + +## Quick start + +```sh +gleam run # Run the project +gleam test # Run the tests +gleam shell # Run an Erlang shell +``` + +## Installation + +If available on Hex this package can be added to your Gleam project: + +```sh +gleam add justin +``` + +and its documentation can be found at . diff --git a/gleam.toml b/gleam.toml new file mode 100644 index 0000000..7ee4a80 --- /dev/null +++ b/gleam.toml @@ -0,0 +1,16 @@ +name = "justin" +version = "1.0.0" + +# Fill out these fields if you intend to generate HTML documentation or publish +# your project to the Hex package manager. +# +# description = "" +# licences = ["Apache-2.0"] +# repository = { type = "github", user = "username", repo = "project" } +# links = [{ title = "Website", href = "https://gleam.run" }] + +[dependencies] +gleam_stdlib = "~> 0.32" + +[dev-dependencies] +gleeunit = "~> 1.0" diff --git a/manifest.toml b/manifest.toml new file mode 100644 index 0000000..e1aa028 --- /dev/null +++ b/manifest.toml @@ -0,0 +1,11 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" }, + { name = "gleeunit", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D364C87AFEB26BDB4FB8A5ABDE67D635DC9FA52D6AB68416044C35B096C6882D" }, +] + +[requirements] +gleam_stdlib = { version = "~> 0.32" } +gleeunit = { version = "~> 1.0" } diff --git a/src/justin.gleam b/src/justin.gleam new file mode 100644 index 0000000..5391b57 --- /dev/null +++ b/src/justin.gleam @@ -0,0 +1,101 @@ +import gleam/string +import gleam/io +import gleam/list + +// TODO: document +pub fn snake_case(text: String) -> String { + text + |> split_words + |> string.join("_") + |> string.lowercase +} + +// TODO: document +pub fn camel_case(text: String) -> String { + text + |> split_words + |> list.index_map(fn(word, i) { + case i { + 0 -> string.lowercase(word) + _ -> string.capitalise(word) + } + }) + |> string.concat +} + +// TODO: document +pub fn pascal_case(text: String) -> String { + text + |> split_words + |> list.map(string.capitalise) + |> string.concat +} + +// TODO: document +pub fn kebab_case(text: String) -> String { + text + |> split_words + |> string.join("-") + |> string.lowercase +} + +// TODO: document +pub fn sentence_case(text: String) -> String { + text + |> split_words + |> string.join(" ") + |> string.capitalise +} + +fn split_words(text: String) -> List(String) { + text + |> string.to_graphemes + |> split(False, "", []) +} + +fn split( + in: List(String), + up: Bool, + word: String, + words: List(String), +) -> List(String) { + case in { + [] if word == "" -> list.reverse(words) + [] -> list.reverse(add(words, word)) + + ["\n", ..in] + | ["\t", ..in] + | ["!", ..in] + | ["?", ..in] + | ["#", ..in] + | [".", ..in] + | ["-", ..in] + | ["_", ..in] + | [" ", ..in] -> split(in, False, "", add(words, word)) + + [g, ..in] -> { + io.println(string.inspect(#(g, is_upper(g)))) + case is_upper(g) { + // Lowercase, not a new word + False -> split(in, False, word <> g, words) + + // Uppercase and inside an uppercase word, not a new word + True if up -> split(in, up, word <> g, words) + + // Uppercase otherwise, a new word + True -> split(in, True, g, add(words, word)) + } + } + } +} + +fn add(words: List(String), word: String) -> List(String) { + case word { + "" -> words + _ -> [word, ..words] + } +} + +fn is_upper(g: String) -> Bool { + string.lowercase(g) != g +} diff --git a/test/justin_test.gleam b/test/justin_test.gleam new file mode 100644 index 0000000..6571756 --- /dev/null +++ b/test/justin_test.gleam @@ -0,0 +1,121 @@ +import gleeunit +import gleam/list +import justin + +pub fn main() { + gleeunit.main() +} + +const snake_cases = [ + #("", ""), + #("snake case", "snake_case"), + #("snakeCase", "snake_case"), + #("Snake-Case", "snake_case"), + #("Snake_Case", "snake_case"), + #("SnakeCase", "snake_case"), + #("Snake.Case", "snake_case"), + #("SNAKE_CASE", "snake_case"), + #("--snake-case--", "snake_case"), + #("snake#case", "snake_case"), + #("snake?!case", "snake_case"), + #("snake\tcase", "snake_case"), + #("snake\ncase", "snake_case"), + #("λambdaΛambda", "λambda_λambda"), +] + +const camel_cases = [ + #("", ""), + #("snake case", "snakeCase"), + #("snakeCase", "snakeCase"), + #("Snake-Case", "snakeCase"), + #("Snake_Case", "snakeCase"), + #("SnakeCase", "snakeCase"), + #("Snake.Case", "snakeCase"), + #("SNAKE_CASE", "snakeCase"), + #("--snake-case--", "snakeCase"), + #("snake#case", "snakeCase"), + #("snake?!case", "snakeCase"), + #("snake\tcase", "snakeCase"), + #("snake\ncase", "snakeCase"), + #("λambda_λambda", "λambdaΛambda"), +] + +const pascal_cases = [ + #("", ""), + #("snake case", "SnakeCase"), + #("snakeCase", "SnakeCase"), + #("Snake-Case", "SnakeCase"), + #("Snake_Case", "SnakeCase"), + #("SnakeCase", "SnakeCase"), + #("Snake.Case", "SnakeCase"), + #("SNAKE_CASE", "SnakeCase"), + #("--snake-case--", "SnakeCase"), + #("snake#case", "SnakeCase"), + #("snake?!case", "SnakeCase"), + #("snake\tcase", "SnakeCase"), + #("snake\ncase", "SnakeCase"), + #("λambda_λambda", "ΛambdaΛambda"), +] + +const kebab_cases = [ + #("", ""), + #("snake case", "snake-case"), + #("snakeCase", "snake-case"), + #("Snake-Case", "snake-case"), + #("Snake_Case", "snake-case"), + #("SnakeCase", "snake-case"), + #("Snake.Case", "snake-case"), + #("SNAKE_CASE", "snake-case"), + #("--snake-case--", "snake-case"), + #("snake#case", "snake-case"), + #("snake?!case", "snake-case"), + #("snake\tcase", "snake-case"), + #("snake\ncase", "snake-case"), + #("λambda_λambda", "λambda-λambda"), +] + +const sentence_cases = [ + #("", ""), + #("snake case", "Snake case"), + #("snakeCase", "Snake case"), + #("Snake-Case", "Snake case"), + #("Snake_Case", "Snake case"), + #("SnakeCase", "Snake case"), + #("Snake.Case", "Snake case"), + #("SNAKE_CASE", "Snake case"), + #("--snake-case--", "Snake case"), + #("snake#case", "Snake case"), + #("snake?!case", "Snake case"), + #("snake\tcase", "Snake case"), + #("snake\ncase", "Snake case"), + #("λambda_λambda", "Λambda λambda"), +] + +fn run_cases(cases: List(#(String, String)), function: fn(String) -> String) { + use #(in, out) <- list.each(cases) + let real = function(in) + case real == out { + True -> Nil + False -> panic as { in <> " should be " <> out <> ", got " <> real } + } +} + +pub fn snake_test() { + run_cases(snake_cases, justin.snake_case) +} + +pub fn camel_test() { + run_cases(camel_cases, justin.camel_case) +} + +pub fn pascal_test() { + run_cases(pascal_cases, justin.pascal_case) +} + +pub fn kebab_test() { + run_cases(kebab_cases, justin.kebab_case) +} + +pub fn sentence_test() { + run_cases(sentence_cases, justin.sentence_case) +}