Skip to content

Commit

Permalink
Update to enumerable functionality and support atom keys (#103)
Browse files Browse the repository at this point in the history
* add atomizing keys

* update readme

* update references to atomize_keys

* update readme

* discovered logic error in casting atoms to strings

* Fix typo in README

* Typo

* added atom handling to CamelCase & SnakeCase modules; added stringify_keys function to Enumerable module

* updated specs for new functions

* fix spec for stringify_keys

* added default callbacks for atomize_keys and stringify_keys functions; created  atomize helper module with function to use existing atoms if possible when converting from strings; added tests for camelcase & snakecase atom handling; added tests for stringify keys; added test for atomizing without a callback passed

* removed Atomize module & moved safe_atom function to Recase.Generic - updated moduledoc; added atom handling  to PascalCase & ConstantCase modules - added tests

* Revert "VE-1004 Replace CaseFormatter with Recase"

* update:

* default function

* added mo's tests

* finalize tests

* update function

* update tests

* add coverage

* fix credo warnings

Co-authored-by: Maurice-Roy <[email protected]>
  • Loading branch information
BlueCollarChris and Maurice-Roy authored Oct 6, 2020
1 parent c1ced8a commit 0e924a1
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 13 deletions.
4 changes: 4 additions & 0 deletions lib/recase/cases/camel_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ defmodule Recase.CamelCase do
rejoin(value, separator: "", case: :title),
do: String.downcase(<<char::utf8>>) <> rest
end

@spec convert(atom()) :: atom()
def convert(value) when is_atom(value),
do: convert(Atom.to_string(value)) |> Recase.Generic.safe_atom()
end
4 changes: 4 additions & 0 deletions lib/recase/cases/constant_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ defmodule Recase.ConstantCase do
@spec convert(String.t()) :: String.t()
def convert(value) when is_binary(value),
do: rejoin(value, separator: "_", case: :up)

@spec convert(atom()) :: atom()
def convert(value) when is_atom(value),
do: convert(Atom.to_string(value)) |> Recase.Generic.safe_atom()
end
20 changes: 12 additions & 8 deletions lib/recase/cases/generic.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
defmodule Recase.Generic do
@moduledoc """
Generic module to split and join strings back.
Generic module to split and join strings back or convert strings to atoms.
This module should not be used directly.
"""

Expand Down Expand Up @@ -33,9 +32,7 @@ defmodule Recase.Generic do

@doc """
Splits the input into **`list`**. Utility function.
## Examples
iex> Recase.Generic.split "foo_barBaz-λambdaΛambda-привет-Мир"
["foo", "bar", "Baz", "λambda", "Λambda", "привет", "Мир"]
"""
Expand All @@ -45,15 +42,11 @@ defmodule Recase.Generic do
@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__привет__мир"
"""
Expand Down Expand Up @@ -82,6 +75,17 @@ defmodule Recase.Generic do
|> Enum.join(Keyword.get(opts, :separator, ?_))
end

@doc """
Atomizes a string value.
Uses an existing atom if possible.
"""
@spec safe_atom(String.t()) :: atom()
def safe_atom(string_value) do
String.to_existing_atom(string_value)
rescue
ArgumentError -> String.to_atom(string_value)
end

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

@spec do_split(input :: String.t(), {binary(), acc :: [String.t()]}) :: [
Expand Down
4 changes: 4 additions & 0 deletions lib/recase/cases/pascal_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ defmodule Recase.PascalCase do
@spec convert(String.t()) :: String.t()
def convert(value) when is_binary(value),
do: rejoin(value, separator: "", case: :title)

@spec convert(atom()) :: atom()
def convert(value) when is_atom(value),
do: convert(Atom.to_string(value)) |> Recase.Generic.safe_atom()
end
4 changes: 4 additions & 0 deletions lib/recase/cases/snake_case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ defmodule Recase.SnakeCase do
@spec convert(String.t()) :: String.t()
def convert(value) when is_binary(value),
do: rejoin(value, separator: @sep, case: :down)

@spec convert(atom()) :: atom()
def convert(value) when is_atom(value),
do: convert(Atom.to_string(value)) |> Recase.Generic.safe_atom()
end
38 changes: 36 additions & 2 deletions lib/recase/utils/enumerable.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ defmodule Recase.Enumerable do
@doc """
Invoke fun for each keys of the enumerable and cast keys to atoms.
"""
@spec atomize_keys(Enumerable.t()) :: Enumerable.t()
def atomize_keys(enumerable),
do: atomize_keys(enumerable, fn x -> x end)

@spec atomize_keys(Enumerable.t(), fun) :: Enumerable.t()
def atomize_keys(enumerable, fun) when is_map(enumerable) do
enumerable
Expand All @@ -20,14 +24,43 @@ defmodule Recase.Enumerable do
end)
end

def atomize_keys(enumerable, fun) when is_list(enumerable) do
def atomize_keys(enumerable, fun)
when is_list(enumerable) do
enumerable
|> Enum.map(fn value -> handle_value(value, fun, &atomize_keys/2) end)
end

@spec stringify_keys(Enumerable.t()) :: Enumerable.t()
def stringify_keys(enumerable),
do: stringify_keys(enumerable, fn x -> x end)

@spec stringify_keys(Enumerable.t(), fun) :: Enumerable.t()
def stringify_keys(enumerable, fun)
when is_map(enumerable) do
enumerable
|> Enum.into(%{}, fn {key, value} ->
string_key =
key
|> cast_string()
|> fun.()

{string_key, handle_value(value, fun, &stringify_keys/2)}
end)
end

def stringify_keys(enumerable, fun)
when is_list(enumerable) do
enumerable
|> Enum.map(fn value -> handle_value(value, fun, &stringify_keys/2) end)
end

@doc """
Invoke fun for each keys of the enumerable.
"""
@spec convert_keys(Enumerable.t()) :: Enumerable.t()
def convert_keys(enumerable),
do: convert_keys(enumerable, fn x -> x end)

@spec convert_keys(Enumerable.t(), fun) :: Enumerable.t()
def convert_keys(enumerable, fun) when is_map(enumerable) do
enumerable
Expand All @@ -36,7 +69,8 @@ defmodule Recase.Enumerable do
end)
end

def convert_keys(enumerable, fun) when is_list(enumerable) do
def convert_keys(enumerable, fun)
when is_list(enumerable) do
enumerable
|> Enum.map(fn value -> handle_value(value, fun, &convert_keys/2) end)
end
Expand Down
7 changes: 7 additions & 0 deletions test/recase_test/camel_case_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,11 @@ defmodule Recase.CamelCaseTest do
test "should return empty string" do
assert convert("") == ""
end

test "should camel case atoms" do
assert convert(:camel_case) == :camelCase
assert convert(:CamelCase) == :camelCase
assert convert(:camelCase) == :camelCase
assert convert(:CAMelCase) == :camelCase
end
end
7 changes: 7 additions & 0 deletions test/recase_test/constant_case_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,11 @@ defmodule Recase.ConstantCaseTest do
test "should return empty string" do
assert convert("") == ""
end

test "should constant case atoms" do
expected = :CONSTANT_CASE
assert convert(:constantCase) == expected
assert convert(:constant_Case) == expected
assert convert(:ConstantCase) == expected
end
end
19 changes: 19 additions & 0 deletions test/recase_test/generic_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule Recase.GenericTest do
use ExUnit.Case

import Recase.Generic

doctest Recase.Generic

describe "safe_atom/1" do
test "known atom" do
known_atom = :test
assert known_atom == safe_atom(Atom.to_string(known_atom))
end

test "unknown atom" do
assert :random_string_that_has_no_existing_atom ==
safe_atom("random_string_that_has_no_existing_atom")
end
end
end
6 changes: 6 additions & 0 deletions test/recase_test/pascal_case_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,10 @@ defmodule Recase.PascalCaseTest do
test "should return empty string" do
assert convert("") == ""
end

test "should pascal case atoms" do
assert convert(:upper_case) == :UpperCase
assert convert(:upperCase) == :UpperCase
assert convert(:UpperCase) == :UpperCase
end
end
7 changes: 7 additions & 0 deletions test/recase_test/snake_case_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,11 @@ defmodule Recase.SnakeCaseTest do
test "should return empty string" do
assert convert("") == ""
end

test "should snake case atoms" do
assert convert(:snakeCase) == :snake_case
assert convert(:Snake_Case) == :snake_case
assert convert(:SnakeCase) == :snake_case
assert convert(:SNAKE_CASE) == :snake_case
end
end
70 changes: 67 additions & 3 deletions test/utils_test/enumerable_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ defmodule RecaseEnumerableTest do
&Recase.to_pascal/1
) == %{"UpperCase" => datetime}
end

test "should return value if no callback given" do
assert Recase.Enumerable.convert_keys(%{
"upper case" => %{"upper-case2" => "value"}
}) == %{"upper case" => %{"upper-case2" => "value"}}
end
end

describe "atomize_keys/2" do
Expand All @@ -69,8 +75,8 @@ defmodule RecaseEnumerableTest do
test "should convert keys of a nested map" do
assert Recase.Enumerable.atomize_keys(
%{"upper case" => %{"upper-case2" => "value"}},
&Recase.to_path(&1, "\\")
) == %{:"upper\\case" => %{:"upper\\case2" => "value"}}
&Recase.to_pascal/1
) == %{:UpperCase => %{:UpperCase2 => "value"}}
end

test "should convert keys of a map in list" do
Expand All @@ -94,11 +100,69 @@ defmodule RecaseEnumerableTest do
) == ["upper case", "upper-case2"]
end

test "should worked with mixed key formats (atom, string)" do
test "should work with mixed key formats (atom, string)" do
assert Recase.Enumerable.atomize_keys(
%{:atom_key => "value", "string_key" => "value"},
&Recase.to_pascal/1
) == %{AtomKey: "value", StringKey: "value"}
end

test "should atomize without formatting if no callback given" do
assert Recase.Enumerable.atomize_keys(%{
:atom_key => "value",
"string_key" => "value"
}) == %{atom_key: "value", string_key: "value"}
end
end

describe "stringify_keys/2" do
test "should convert keys of a map" do
assert Recase.Enumerable.stringify_keys(
%{upperCase: "value", upper_case2: "value"},
&Recase.to_pascal/1
) == %{"UpperCase" => "value", "UpperCase2" => "value"}
end

test "should convert keys of a nested map" do
assert Recase.Enumerable.stringify_keys(
%{upperCase: %{upper_case2: "value"}},
&Recase.to_pascal/1
) == %{"UpperCase" => %{"UpperCase2" => "value"}}
end

test "should convert keys of a map in list" do
assert Recase.Enumerable.stringify_keys(
[%{upperCase: "value", upper_case2: "value"}],
&Recase.to_pascal/1
) == [%{"UpperCase" => "value", "UpperCase2" => "value"}]
end

test "should convert keys of a nested map in list" do
assert Recase.Enumerable.stringify_keys(
[%{upperCase: %{upper_case2: "value"}}],
&Recase.to_pascal/1
) == [%{"UpperCase" => %{"UpperCase2" => "value"}}]
end

test "should return the same list when list items are atoms" do
assert Recase.Enumerable.stringify_keys(
[:upperCase, :upper_case2],
&Recase.to_pascal/1
) == [:upperCase, :upper_case2]
end

test "should work with mixed key formats (atom, string)" do
assert Recase.Enumerable.stringify_keys(
%{:atom_key => "value", "string_key" => "value"},
&Recase.to_pascal/1
) == %{"AtomKey" => "value", "StringKey" => "value"}
end

test "should stringify without formatting if no callback given" do
assert Recase.Enumerable.stringify_keys(%{
:atom_key => "value",
"string_key" => "value"
}) == %{"atom_key" => "value", "string_key" => "value"}
end
end
end

0 comments on commit 0e924a1

Please sign in to comment.