From 3971f2c0bdd4657c65f0db2d6ef9591cfbe5ee3c Mon Sep 17 00:00:00 2001 From: bolek Date: Wed, 15 Feb 2023 11:34:10 -0500 Subject: [PATCH 1/2] Handle multiple upcase letters Example: Without change: `Recase.to_snake("SnakeACase") == "snake_acase"` With change: `Recase.to_snake("SnakeACase") == "snake_a_case"` --- lib/recase/cases/generic.ex | 26 ++++++++++++++++++-------- test/recase_test/snake_case_test.exs | 2 ++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/recase/cases/generic.ex b/lib/recase/cases/generic.ex index d4ccc58..4637bf0 100644 --- a/lib/recase/cases/generic.ex +++ b/lib/recase/cases/generic.ex @@ -107,16 +107,19 @@ defmodule Recase.Generic do end) Enum.each(?A..?Z, fn char -> - defp do_split(<>, {"", acc}), - do: do_split(rest, {<>, acc}) + defp do_split(<> = input, {"", acc}) do + {upcase_streak, rest} = upcase_streak(input, "") + + case byte_size(upcase_streak) do + 1 -> + do_split(rest, {<>, acc}) - defp do_split(<>, {curr, acc}) do - <> = String.reverse(curr) + 2 -> + <> = upcase_streak + do_split(rest, {<>, [<> | acc]}) - if c in ?A..?Z do - do_split(rest, {curr <> <>, acc}) - else - do_split(rest, {<>, [curr | acc]}) + _ -> + do_split(rest, {<>, acc}) end end end) @@ -145,4 +148,11 @@ defmodule Recase.Generic do do_split(rest, {curr <> <>, acc}) end end + + Enum.each(?A..?Z, fn char -> + defp upcase_streak(<>, curr), + do: upcase_streak(rest, curr <> <>) + end) + + defp upcase_streak(rest, upcase_streak), do: {upcase_streak, rest} end diff --git a/test/recase_test/snake_case_test.exs b/test/recase_test/snake_case_test.exs index 5734dcd..396e41e 100644 --- a/test/recase_test/snake_case_test.exs +++ b/test/recase_test/snake_case_test.exs @@ -16,6 +16,8 @@ defmodule Recase.SnakeCaseTest do assert convert("--snake-case--") == "snake_case" assert convert("snake#case") == "snake_case" assert convert("snake?!case") == "snake_case" + assert convert("SnakeACase") == "snake_a_case" + assert convert("CurrencyISOCode") == "currency_i_s_o_code" end test "should return single letter" do From e1f9a13f7177cbfe3dcb408427692a20408ae05a Mon Sep 17 00:00:00 2001 From: bolek Date: Thu, 16 Feb 2023 12:50:47 -0500 Subject: [PATCH 2/2] Handle upcase strings --- lib/recase/cases/generic.ex | 68 ++++++++++++++++++---------- test/recase_test/snake_case_test.exs | 6 +++ 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/lib/recase/cases/generic.ex b/lib/recase/cases/generic.ex index 4637bf0..a73d333 100644 --- a/lib/recase/cases/generic.ex +++ b/lib/recase/cases/generic.ex @@ -71,7 +71,7 @@ defmodule Recase.Generic do end input - |> do_split() + |> split() |> Enum.map_join(Keyword.get(opts, :separator, ?_), mapper) end @@ -88,38 +88,60 @@ defmodule Recase.Generic do ############################################################################## - @spec do_split(input :: String.t(), {binary(), acc :: [String.t()]}) :: [ + defp do_split(string) do + is_all_upcase = String.upcase(string) == string + acc = {"", []} + meta = %{is_all_upcase: is_all_upcase} + + do_split_r(string, acc, meta) + end + + @spec do_split_r( + input :: String.t(), + {binary(), acc :: [String.t()]}, + meta :: %{is_all_upcase: boolean()} + ) :: [ String.t() ] - defp do_split(string, acc \\ {"", []}) + defp do_split_r(string, acc, meta) - defp do_split("", {"", acc}), do: Enum.reverse(acc) + defp do_split_r("", {"", acc}, _meta), do: Enum.reverse(acc) - defp do_split("", {curr, acc}), - do: do_split("", {"", [curr | acc]}) + defp do_split_r("", {curr, acc}, meta), + do: do_split_r("", {"", [curr | acc]}, meta) Enum.each(@delimiters, fn delim -> - defp do_split(<>, {"", acc}), - do: do_split(rest, {"", acc}) + defp do_split_r(<>, {"", acc}, meta), + do: do_split_r(rest, {"", acc}, meta) - defp do_split(<>, {curr, acc}), - do: do_split(rest, {"", [curr | acc]}) + defp do_split_r(<>, {curr, acc}, meta), + do: do_split_r(rest, {"", [curr | acc]}, meta) end) Enum.each(?A..?Z, fn char -> - defp do_split(<> = input, {"", acc}) do + defp do_split_r( + <>, + {curr, acc}, + %{ + is_all_upcase: true + } = meta + ) do + do_split_r(rest, {curr <> <>, acc}, meta) + end + + defp do_split_r(<> = input, {"", acc}, meta) do {upcase_streak, rest} = upcase_streak(input, "") case byte_size(upcase_streak) do 1 -> - do_split(rest, {<>, acc}) + do_split_r(rest, {<>, acc}, meta) 2 -> <> = upcase_streak - do_split(rest, {<>, [<> | acc]}) + do_split_r(rest, {<>, [<> | acc]}, meta) _ -> - do_split(rest, {<>, acc}) + do_split_r(rest, {<>, acc}, meta) end end end) @@ -129,23 +151,23 @@ defmodule Recase.Generic do |> Enum.reduce(&Kernel.++/2) |> Kernel.--(@delimiters) |> Enum.each(fn char -> - defp do_split(<>, {"", acc}), - do: do_split(rest, {<>, acc}) + defp do_split_r(<>, {"", acc}, meta), + do: do_split_r(rest, {<>, acc}, meta) - defp do_split(<>, {curr, acc}), - do: do_split(rest, {curr <> <>, acc}) + defp do_split_r(<>, {curr, acc}, meta), + do: do_split_r(rest, {curr <> <>, acc}, meta) end) - defp do_split(<>, {"", acc}), - do: do_split(rest, {<>, acc}) + defp do_split_r(<>, {"", acc}, meta), + do: do_split_r(rest, {<>, acc}, meta) @upcase ~r/(?>, {curr, acc}) do + defp do_split_r(<>, {curr, acc}, meta) do if Regex.match?(@upcase, <>) do - do_split(rest, {<>, [curr | acc]}) + do_split_r(rest, {<>, [curr | acc]}, meta) else - do_split(rest, {curr <> <>, acc}) + do_split_r(rest, {curr <> <>, acc}, meta) end end diff --git a/test/recase_test/snake_case_test.exs b/test/recase_test/snake_case_test.exs index 396e41e..fdfc79c 100644 --- a/test/recase_test/snake_case_test.exs +++ b/test/recase_test/snake_case_test.exs @@ -33,6 +33,12 @@ defmodule Recase.SnakeCaseTest do assert convert("") == "" end + test "should handle all upcase strings" do + assert convert("CREATE_D") == "create_d" + assert convert("CREATE_DT") == "create_dt" + assert convert("CREATE_DATE") == "create_date" + end + test "should snake case atoms" do assert convert(:snakeCase) == :snake_case assert convert(:Snake_Case) == :snake_case