Skip to content

Commit

Permalink
Backported Case2. Left 5 tests failing. (#46)
Browse files Browse the repository at this point in the history
* Backported Case2
  • Loading branch information
Aleksei Matiushkin authored and sobolevn committed Feb 14, 2019
1 parent d28dbc1 commit aea2153
Show file tree
Hide file tree
Showing 15 changed files with 277 additions and 137 deletions.
21 changes: 11 additions & 10 deletions lib/recase.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ defmodule Recase do
iex> Recase.to_pascal("some value")
"SomeValue"
"""
@spec to_pascal(String.t) :: String.t
@spec to_pascal(String.t()) :: String.t()
def to_pascal(value), do: PascalCase.convert(value)

@doc """
Expand All @@ -42,7 +42,7 @@ defmodule Recase do
iex> Recase.to_camel("Some Value")
"someValue"
"""
@spec to_camel(String.t) :: String.t
@spec to_camel(String.t()) :: String.t()
def to_camel(value), do: CamelCase.convert(value)

@doc """
Expand All @@ -56,8 +56,9 @@ defmodule Recase do
iex> Recase.to_snake("someValue")
"some_value"
"""
@spec to_snake(String.t) :: String.t
@spec to_snake(String.t()) :: String.t()
def to_snake(value), do: SnakeCase.convert(value)
defdelegate underscore(value), to: Recase, as: :to_snake

@doc """
Converts string to kebab-case.
Expand All @@ -70,7 +71,7 @@ defmodule Recase do
iex> Recase.to_kebab("some value")
"some-value"
"""
@spec to_kebab(String.t) :: String.t
@spec to_kebab(String.t()) :: String.t()
def to_kebab(value), do: KebabCase.convert(value)

@doc """
Expand All @@ -84,7 +85,7 @@ defmodule Recase do
iex> Recase.to_constant("some value")
"SOME_VALUE"
"""
@spec to_constant(String.t) :: String.t
@spec to_constant(String.t()) :: String.t()
def to_constant(value), do: ConstantCase.convert(value)

@doc ~S"""
Expand All @@ -98,9 +99,9 @@ defmodule Recase do
iex> Recase.to_path("some value", "\\")
"some\\value"
"""
@spec to_path(String.t, String.t) :: String.t
@spec to_path(String.t(), String.t()) :: String.t()
def to_path(value, separator), do: PathCase.convert(value, separator)
@spec to_path(String.t) :: String.t
@spec to_path(String.t()) :: String.t()
def to_path(value), do: PathCase.convert(value)

@doc """
Expand All @@ -114,7 +115,7 @@ defmodule Recase do
iex> Recase.to_dot("some value")
"some.value"
"""
@spec to_dot(String.t) :: String.t
@spec to_dot(String.t()) :: String.t()
def to_dot(value), do: DotCase.convert(value)

@doc """
Expand All @@ -128,7 +129,7 @@ defmodule Recase do
iex> Recase.to_sentence("some value")
"Some value"
"""
@spec to_sentence(String.t) :: String.t
@spec to_sentence(String.t()) :: String.t()
def to_sentence(value), do: SentenceCase.convert(value)

@doc """
Expand All @@ -142,6 +143,6 @@ defmodule Recase do
iex> Recase.to_title("some value")
"Some Value"
"""
@spec to_title(String.t) :: String.t
@spec to_title(String.t()) :: String.t()
def to_title(value), do: TitleCase.convert(value)
end
20 changes: 11 additions & 9 deletions lib/recase/cases/camel_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ defmodule Recase.CamelCase do
This module should not be used directly.
## Examples
iex> Recase.to_camel "foo_barBaz-λambdaΛambda-привет-Мир"
"fooBarBazΛambdaΛambdaПриветМир"
Read about `camelCase` here:
https://en.wikipedia.org/wiki/Camel_case
"""

import Recase.Replace
import Recase.Generic, only: [rejoin: 2]

@spec convert(String.t) :: String.t
@spec convert(String.t()) :: String.t()
def convert(""), do: ""
def convert(value) do
value
|> String.trim
|> replace(~r/^[_\.\-\s]+/, "")
|> replace(~r/([a-zA-Z]+)([A-Z][a-z\d]+)/, "\\1-\\2")
|> String.downcase
|> replace(~r/[_\.\-\s]+(\w|$)/, fn(_, x) -> String.upcase(x) end)

def convert(value) when is_binary(value) do
with <<char::utf8, rest::binary>> <- rejoin(value, separator: "", case: :title),
do: String.downcase(<<char::utf8>>) <> rest
end
end
16 changes: 9 additions & 7 deletions lib/recase/cases/constant_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ defmodule Recase.ConstantCase do
This module should not be used directly.
## Examples
iex> Recase.to_constant "foo_barBaz-λambdaΛambda-привет-Мир"
"FOO_BAR_BAZ_ΛAMBDA_ΛAMBDA_ПРИВЕТ_МИР"
Constant case is the same as `snake_case`,
but uppercased.
"""

alias Recase.SnakeCase
import Recase.Generic, only: [rejoin: 2]

@spec convert(String.t) :: String.t
def convert(value) do
value
|> SnakeCase.convert
|> String.upcase
end
@spec convert(String.t()) :: String.t()
def convert(value) when is_binary(value),
do: rejoin(value, separator: "_", case: :up)
end
16 changes: 9 additions & 7 deletions lib/recase/cases/dot_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@ defmodule Recase.DotCase do
This module should not be used directly.
## Examples
iex> Recase.to_dot "foo_barBaz-λambdaΛambda-привет-Мир"
"foo.bar.baz.λambda.λambda.привет.мир"
`DotCase` is the same as `KebabCase` and `SnakeCase`.
But uses `.` as a separator.
"""

alias Recase.SnakeCase
import Recase.Generic, only: [rejoin: 2]

@sep "."

@spec convert(String.t) :: String.t
def convert(value) do
value
|> SnakeCase.convert
|> String.replace("_", @sep)
end
@spec convert(String.t()) :: String.t()
def convert(value) when is_binary(value),
do: rejoin(value, separator: @sep, case: :down)
end
129 changes: 129 additions & 0 deletions lib/recase/cases/generic.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
defmodule Recase.Generic do
@moduledoc """
Generic module to split and join strings back.
This module should not be used directly.
"""

@splitters Application.get_env(:recase, :delimiters, [?\s, ?\n, ?\t, ?_, ?., ?-, ?#, ??, ?!])

@delimiters (case @splitters do
list when is_list(list) ->
list

:symbol ->
[all, digits, down, up] =
Enum.map([32..127, ?0..?9, ?a..?z, ?A..?Z], &Enum.to_list/1)

all -- (digits ++ down ++ up)
end)

@doc """
Splits the input into **`list`**. Utility function.
## Examples
iex> Recase.Generic.split "foo_barBaz-λambdaΛambda-привет-Мир"
["foo", "bar", "Baz", "λambda", "Λambda", "привет", "Мир"]
"""
@spec split(input :: String.t()) :: [String.t()]
def split(input) when is_binary(input), do: do_split(input)

@doc """
Splits the input and **`rejoins`** it with a separator given. Optionally
converts parts to `downcase`, `upcase` or `titlecase`.
- `opts[:case] :: [:down | :up | :title | :none]`
- `opts[:separator] :: binary() | integer()`
Default separator is `?_`, default conversion is `:downcase` so that
it behaves the same way as `to_snake/1`.
## Examples
iex> Recase.Generic.rejoin "foo_barBaz-λambdaΛambda-привет-Мир", separator: "__"
"foo__bar__baz__λambda__λambda__привет__мир"
"""
@spec rejoin(input :: String.t(), opts :: Keyword.t()) :: String.t()
def rejoin(input, opts \\ []) when is_binary(input) do
mapper =
case Keyword.get(opts, :case, :down) do
:down ->
&String.downcase/1

:title ->
fn <<char::utf8, rest::binary>> ->
String.upcase(<<char::utf8>>) <> String.downcase(rest)
end

:up ->
&String.upcase/1

_ ->
& &1
end

input
|> do_split()
|> Enum.map(mapper)
|> Enum.join(Keyword.get(opts, :separator, ?_))
end

##############################################################################

@spec do_split(input :: String.t(), acc :: [String.t()]) :: [String.t()]
defp do_split(string, acc \\ {"", []})

defp do_split("", {"", acc}), do: Enum.reverse(acc)

defp do_split("", {curr, acc}),
do: do_split("", {"", [curr | acc]})

Enum.each(@delimiters, fn delim ->
defp do_split(<<unquote(delim)::utf8, rest::binary>>, {"", acc}),
do: do_split(rest, {"", acc})

defp do_split(<<unquote(delim), rest::binary>>, {curr, acc}),
do: do_split(rest, {"", [curr | acc]})
end)

Enum.each(?A..?Z, fn char ->
defp do_split(<<unquote(char), rest::binary>>, {"", acc}),
do: do_split(rest, {<<unquote(char)::utf8>>, acc})

defp do_split(<<unquote(char), rest::binary>>, {curr, acc}) do
<<c::utf8, _::binary>> = String.reverse(curr)

if c in ?A..?Z do
do_split(rest, {curr <> <<unquote(char)::utf8>>, acc})
else
do_split(rest, {<<unquote(char)::utf8>>, [curr | acc]})
end
end
end)

[32..64, 91..127]
|> Enum.map(&Enum.to_list/1)
|> Enum.reduce(&Kernel.++/2)
|> Kernel.--(@delimiters)
|> Enum.each(fn char ->
defp do_split(<<unquote(char)::utf8, rest::binary>>, {"", acc}),
do: do_split(rest, {<<unquote(char)::utf8>>, acc})

defp do_split(<<unquote(char), rest::binary>>, {curr, acc}),
do: do_split(rest, {curr <> <<unquote(char)::utf8>>, acc})
end)

defp do_split(<<char::utf8, rest::binary>>, {"", acc}),
do: do_split(rest, {<<char::utf8>>, acc})

@upcase ~r/(?<!\p{Lu})\p{Lu}/u

defp do_split(<<char::utf8, rest::binary>>, {curr, acc}) do
if Regex.match?(@upcase, <<char::utf8>>) do
do_split(rest, {<<char::utf8>>, [curr | acc]})
else
do_split(rest, {curr <> <<char::utf8>>, acc})
end
end
end
16 changes: 9 additions & 7 deletions lib/recase/cases/kebab_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@ defmodule Recase.KebabCase do
This module should not be used directly.
## Examples
iex> Recase.to_kebab "foo_barBaz-λambdaΛambda-привет-Мир"
"foo-bar-baz-λambda-λambda-привет-мир"
Read about `kebab-case` here:
https://en.wikipedia.org/wiki/Kebab_case
"""

alias Recase.SnakeCase
import Recase.Generic, only: [rejoin: 2]

@sep "-"

@spec convert(String.t) :: String.t
def convert(value) do
value
|> SnakeCase.convert
|> String.replace("_", @sep)
end
@spec convert(String.t()) :: String.t()
def convert(value) when is_binary(value),
do: rejoin(value, separator: @sep, case: :down)
end
16 changes: 9 additions & 7 deletions lib/recase/cases/pascal_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ defmodule Recase.PascalCase do
This module should not be used directly.
## Examples
iex> Recase.to_pascal "foo_barBaz-λambdaΛambda-привет-Мир"
"FooBarBazΛambdaΛambdaПриветМир"
Read about `PascalCase` here:
https://en.wikipedia.org/wiki/PascalCase
Expand All @@ -21,12 +26,9 @@ defmodule Recase.PascalCase do
For other details see: https://github.com/sobolevn/recase/issues/2
"""

alias Recase.CamelCase
import Recase.Generic, only: [rejoin: 2]

@spec convert(String.t) :: String.t
def convert(""), do: ""
def convert(value) do
<<char::binary-size(1), rest::binary>> = CamelCase.convert(value)
String.upcase(char) <> rest
end
@spec convert(String.t()) :: String.t()
def convert(value) when is_binary(value),
do: rejoin(value, separator: "", case: :title)
end
23 changes: 13 additions & 10 deletions lib/recase/cases/path_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@ defmodule Recase.PathCase do
but inserts path separator to appropriate places.
By default uses `/` as a path separator.
## Examples
iex> Recase.to_path "foo_barBaz-λambdaΛambda-привет-Мир"
"foo/bar/Baz/λambda/Λambda/привет/Мир"
"""

import Recase.Replace
import Recase.Generic, only: [rejoin: 2]

@sep "/"

@spec convert(String.t) :: String.t
@spec convert(String.t(), String.t()) :: String.t()
def convert(value, separator \\ @sep)
def convert("", _), do: ""
def convert(value, separator) do
value
|> String.trim
|> replace(~r/[\s\.\-_]/, separator)
|> replace(~r/([a-z\d])([A-Z])/, "\\1#{separator}\\2")
|> replace(~r/([A-Z]+)([A-Z][a-z\d]+)/, "\\1#{separator}\\2")
end

def convert(<<separator::binary-size(1), rest::binary>>, separator),
do: separator <> convert(rest, separator)

def convert(value, separator) when is_binary(value),
do: rejoin(value, separator: separator, case: :none)
end
Loading

0 comments on commit aea2153

Please sign in to comment.