From c59255b9cb9b2e5fbd88a15100a251d00f2e5e5c Mon Sep 17 00:00:00 2001 From: mydearxym Date: Tue, 30 Mar 2021 15:32:41 +0800 Subject: [PATCH 1/2] refactor(utils): re-org based on usage --- lib/helper/utils/map.ex | 149 +++++++++++++++++++++++++++++++ lib/helper/{ => utils}/utils.ex | 150 +++----------------------------- 2 files changed, 159 insertions(+), 140 deletions(-) create mode 100644 lib/helper/utils/map.ex rename lib/helper/{ => utils}/utils.ex (59%) diff --git a/lib/helper/utils/map.ex b/lib/helper/utils/map.ex new file mode 100644 index 000000000..673cd125f --- /dev/null +++ b/lib/helper/utils/map.ex @@ -0,0 +1,149 @@ +defmodule Helper.Utils.Map do + @moduledoc """ + unitil functions + """ + import Ecto.Query, warn: false + import Helper.ErrorHandler + import Helper.ErrorCode + + import Helper.Validator.Guards, only: [g_none_empty_str: 1] + + alias Helper.Cache + + def map_key_stringify(%{__struct__: _} = map) when is_map(map) do + map = Map.from_struct(map) + map |> Enum.reduce(%{}, fn {key, val}, acc -> Map.put(acc, to_string(key), val) end) + end + + def map_key_stringify(map) when is_map(map) do + map |> Enum.reduce(%{}, fn {key, val}, acc -> Map.put(acc, to_string(key), val) end) + end + + @doc """ + see https://stackoverflow.com/a/61559842/4050784 + adjust it for map keys from atom to string + """ + def keys_to_atoms(json) when is_map(json) do + Map.new(json, &reduce_keys_to_atoms/1) + end + + def keys_to_atoms(string) when is_binary(string), do: string + + defp reduce_keys_to_atoms({key, val}) when is_map(val), + # do: {String.to_existing_atom(key), keys_to_atoms(val)} + do: {String.to_atom(key), keys_to_atoms(val)} + + defp reduce_keys_to_atoms({key, val}) when is_list(val), + do: {String.to_atom(key), Enum.map(val, &keys_to_atoms(&1))} + + defp reduce_keys_to_atoms({key, val}), do: {String.to_atom(key), val} + + @doc """ + see https://stackoverflow.com/a/61559842/4050784 + adjust it for map keys from atom to string + """ + @spec keys_to_strings(map) :: map + def keys_to_strings(json) when is_map(json) do + Map.new(json, &reduce_keys_to_strings/1) + end + + defp reduce_keys_to_strings({key, val}) when is_map(val), + do: {Atom.to_string(key), keys_to_strings(val)} + + defp reduce_keys_to_strings({key, val}) when is_list(val), + do: {Atom.to_string(key), Enum.map(val, &keys_to_strings(&1))} + + defp reduce_keys_to_strings({key, val}), do: {Atom.to_string(key), val} + + @doc """ + Recursivly camelize the map keys + usage: convert factory attrs to used for simu Graphql parmas + """ + def camelize_map_key(map, v_trans \\ :ignore) do + map_list = + Enum.map(map, fn {k, v} -> + v = + cond do + is_datetime?(v) -> + DateTime.to_iso8601(v) + + is_map(v) -> + camelize_map_key(safe_map(v)) + + is_binary(v) -> + handle_camelize_value_trans(v, v_trans) + + true -> + v + end + + map_to_camel({k, v}) + end) + + Enum.into(map_list, %{}) + end + + defp handle_camelize_value_trans(v, :ignore), do: v + defp handle_camelize_value_trans(v, :downcase), do: String.downcase(v) + defp handle_camelize_value_trans(v, :upcase), do: String.upcase(v) + + defp safe_map(%{__struct__: _} = map), do: Map.from_struct(map) + defp safe_map(map), do: map + + defp map_to_camel({k, v}), do: {Recase.to_camel(to_string(k)), v} + + @spec snake_map_key(map) :: map + def snake_map_key(map) do + map_list = + Enum.map(map, fn {k, v} -> + v = + cond do + is_datetime?(v) -> + DateTime.to_iso8601(v) + + is_map(v) -> + snake_map_key(safe_map(v)) + + true -> + v + end + + {Recase.to_snake(to_string(k)), v} + end) + + Enum.into(map_list, %{}) + end + + defp is_datetime?(%DateTime{}), do: true + defp is_datetime?(_), do: false + + def map_atom_value(attrs, :string) do + results = + Enum.map(attrs, fn {k, v} -> + cond do + v == true or v == false -> + {k, v} + + is_atom(v) -> + {k, v |> to_string() |> String.downcase()} + + true -> + {k, v} + end + end) + + results |> Enum.into(%{}) + end + + def deep_merge(left, right), do: Map.merge(left, right, &deep_resolve/3) + + # Key exists in both maps, and both values are maps as well. + # These can be merged recursively. + # defp deep_resolve(_key, left = %{},right = %{}) do + defp deep_resolve(_key, %{} = left, %{} = right), do: deep_merge(left, right) + + # Key exists in both maps, but at least one of the values is + # NOT a map. We fall back to standard merge behavior, preferring + # the value on the right. + defp deep_resolve(_key, _left, right), do: right +end diff --git a/lib/helper/utils.ex b/lib/helper/utils/utils.ex similarity index 59% rename from lib/helper/utils.ex rename to lib/helper/utils/utils.ex index 18d0c464c..987dd10d8 100644 --- a/lib/helper/utils.ex +++ b/lib/helper/utils/utils.ex @@ -8,7 +8,16 @@ defmodule Helper.Utils do import Helper.Validator.Guards, only: [g_none_empty_str: 1] - alias Helper.Cache + alias Helper.{Cache, Utils} + + defdelegate map_key_stringify(map), to: Utils.Map + defdelegate keys_to_atoms(map), to: Utils.Map + defdelegate keys_to_strings(map), to: Utils.Map + defdelegate camelize_map_key(map), to: Utils.Map + defdelegate camelize_map_key(map, opt), to: Utils.Map + defdelegate snake_map_key(map), to: Utils.Map + defdelegate deep_merge(left, right), to: Utils.Map + defdelegate map_atom_value(attrs, opt), to: Utils.Map def get_config(section, key, app \\ :groupher_server) @@ -87,117 +96,6 @@ defmodule Helper.Utils do |> Absinthe.Resolution.put_result({:error, message: err_msg, code: ecode()}) end - def map_key_stringify(%{__struct__: _} = map) when is_map(map) do - map = Map.from_struct(map) - map |> Enum.reduce(%{}, fn {key, val}, acc -> Map.put(acc, to_string(key), val) end) - end - - def map_key_stringify(map) when is_map(map) do - map |> Enum.reduce(%{}, fn {key, val}, acc -> Map.put(acc, to_string(key), val) end) - end - - @doc """ - see https://stackoverflow.com/a/61559842/4050784 - adjust it for map keys from atom to string - """ - def keys_to_atoms(json) when is_map(json) do - Map.new(json, &reduce_keys_to_atoms/1) - end - - def keys_to_atoms(string) when is_binary(string), do: string - - def reduce_keys_to_atoms({key, val}) when is_map(val), - # do: {String.to_existing_atom(key), keys_to_atoms(val)} - do: {String.to_atom(key), keys_to_atoms(val)} - - def reduce_keys_to_atoms({key, val}) when is_list(val), - do: {String.to_atom(key), Enum.map(val, &keys_to_atoms(&1))} - - def reduce_keys_to_atoms({key, val}), do: {String.to_atom(key), val} - - @doc """ - see https://stackoverflow.com/a/61559842/4050784 - adjust it for map keys from atom to string - """ - @spec keys_to_strings(map) :: map - def keys_to_strings(json) when is_map(json) do - Map.new(json, &reduce_keys_to_strings/1) - end - - defp reduce_keys_to_strings({key, val}) when is_map(val), - do: {Atom.to_string(key), keys_to_strings(val)} - - defp reduce_keys_to_strings({key, val}) when is_list(val), - do: {Atom.to_string(key), Enum.map(val, &keys_to_strings(&1))} - - defp reduce_keys_to_strings({key, val}), do: {Atom.to_string(key), val} - - @doc """ - Recursivly camelize the map keys - usage: convert factory attrs to used for simu Graphql parmas - """ - def camelize_map_key(map, v_trans \\ :ignore) do - map_list = - Enum.map(map, fn {k, v} -> - v = - cond do - is_datetime?(v) -> - DateTime.to_iso8601(v) - - is_map(v) -> - camelize_map_key(safe_map(v)) - - is_binary(v) -> - handle_camelize_value_trans(v, v_trans) - - true -> - v - end - - map_to_camel({k, v}) - end) - - Enum.into(map_list, %{}) - end - - defp handle_camelize_value_trans(v, :ignore), do: v - defp handle_camelize_value_trans(v, :downcase), do: String.downcase(v) - defp handle_camelize_value_trans(v, :upcase), do: String.upcase(v) - - defp safe_map(%{__struct__: _} = map), do: Map.from_struct(map) - defp safe_map(map), do: map - - defp map_to_camel({k, v}), do: {Recase.to_camel(to_string(k)), v} - - @spec snake_map_key(map) :: map - def snake_map_key(map) do - map_list = - Enum.map(map, fn {k, v} -> - v = - cond do - is_datetime?(v) -> - DateTime.to_iso8601(v) - - is_map(v) -> - snake_map_key(safe_map(v)) - - true -> - v - end - - {Recase.to_snake(to_string(k)), v} - end) - - Enum.into(map_list, %{}) - end - - def is_datetime?(%DateTime{}), do: true - def is_datetime?(_), do: false - - def deep_merge(left, right) do - Map.merge(left, right, &deep_resolve/3) - end - def integerfy(id) when is_binary(id), do: String.to_integer(id) def integerfy(id), do: id @@ -220,38 +118,10 @@ defmodule Helper.Utils do end) end - def map_atom_value(attrs, :string) do - results = - Enum.map(attrs, fn {k, v} -> - cond do - v == true or v == false -> - {k, v} - - is_atom(v) -> - {k, v |> to_string() |> String.downcase()} - - true -> - {k, v} - end - end) - - results |> Enum.into(%{}) - end - def empty_pagi_data do %{entries: [], total_count: 0, page_size: 0, total_pages: 1, page_number: 1} end - # Key exists in both maps, and both values are maps as well. - # These can be merged recursively. - # defp deep_resolve(_key, left = %{},right = %{}) do - defp deep_resolve(_key, %{} = left, %{} = right), do: deep_merge(left, right) - - # Key exists in both maps, but at least one of the values is - # NOT a map. We fall back to standard merge behavior, preferring - # the value on the right. - defp deep_resolve(_key, _left, right), do: right - @doc """ ["a", "b", "c", "c"] => %{"a" => 1, "b" => 1, "c" => 2} """ From 4529aef081d50f58a4551285c696fab0d5bc9679 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Tue, 30 Mar 2021 15:44:14 +0800 Subject: [PATCH 2/2] refactor(utils): re-org based on usage for string --- lib/helper/utils/map.ex | 8 -------- lib/helper/utils/string.ex | 30 ++++++++++++++++++++++++++++++ lib/helper/utils/utils.ex | 31 ++++++------------------------- 3 files changed, 36 insertions(+), 33 deletions(-) create mode 100644 lib/helper/utils/string.ex diff --git a/lib/helper/utils/map.ex b/lib/helper/utils/map.ex index 673cd125f..2287ae5b2 100644 --- a/lib/helper/utils/map.ex +++ b/lib/helper/utils/map.ex @@ -2,14 +2,6 @@ defmodule Helper.Utils.Map do @moduledoc """ unitil functions """ - import Ecto.Query, warn: false - import Helper.ErrorHandler - import Helper.ErrorCode - - import Helper.Validator.Guards, only: [g_none_empty_str: 1] - - alias Helper.Cache - def map_key_stringify(%{__struct__: _} = map) when is_map(map) do map = Map.from_struct(map) map |> Enum.reduce(%{}, fn {key, val}, acc -> Map.put(acc, to_string(key), val) end) diff --git a/lib/helper/utils/string.ex b/lib/helper/utils/string.ex new file mode 100644 index 000000000..341fc6242 --- /dev/null +++ b/lib/helper/utils/string.ex @@ -0,0 +1,30 @@ +defmodule Helper.Utils.String do + @moduledoc """ + string utils + """ + + def stringfy(v) when is_binary(v), do: v + def stringfy(v) when is_integer(v), do: to_string(v) + def stringfy(v) when is_atom(v), do: to_string(v) + def stringfy(v), do: v + + # see https://stackoverflow.com/a/49558074/4050784 + @spec str_occurence(String.t(), String.t()) :: Integer.t() + def str_occurence(string, substr) when is_binary(string) and is_binary(substr) do + len = string |> String.split(substr) |> length() + len - 1 + end + + def str_occurence(_, _), do: "must be strings" + + @doc """ + ["a", "b", "c", "c"] => %{"a" => 1, "b" => 1, "c" => 2} + """ + def count_words(words) when is_list(words) do + Enum.reduce(words, %{}, &update_word_count/2) + end + + defp update_word_count(word, acc) do + Map.update(acc, to_string(word), 1, &(&1 + 1)) + end +end diff --git a/lib/helper/utils/utils.ex b/lib/helper/utils/utils.ex index 987dd10d8..30aca4b0a 100644 --- a/lib/helper/utils/utils.ex +++ b/lib/helper/utils/utils.ex @@ -10,6 +10,7 @@ defmodule Helper.Utils do alias Helper.{Cache, Utils} + # Map utils defdelegate map_key_stringify(map), to: Utils.Map defdelegate keys_to_atoms(map), to: Utils.Map defdelegate keys_to_strings(map), to: Utils.Map @@ -19,6 +20,11 @@ defmodule Helper.Utils do defdelegate deep_merge(left, right), to: Utils.Map defdelegate map_atom_value(attrs, opt), to: Utils.Map + # String Utils + defdelegate stringfy(str), to: Utils.String + defdelegate count_words(str), to: Utils.String + defdelegate str_occurence(string, substr), to: Utils.String + def get_config(section, key, app \\ :groupher_server) def get_config(section, :all, app) do @@ -99,11 +105,6 @@ defmodule Helper.Utils do def integerfy(id) when is_binary(id), do: String.to_integer(id) def integerfy(id), do: id - def stringfy(v) when is_binary(v), do: v - def stringfy(v) when is_integer(v), do: to_string(v) - def stringfy(v) when is_atom(v), do: to_string(v) - def stringfy(v), do: v - # TODO: enhance, doc def repeat(times, [x]) when is_integer(x), do: to_string(for _ <- 1..times, do: x) def repeat(times, x), do: for(_ <- 1..times, do: x) @@ -122,26 +123,6 @@ defmodule Helper.Utils do %{entries: [], total_count: 0, page_size: 0, total_pages: 1, page_number: 1} end - @doc """ - ["a", "b", "c", "c"] => %{"a" => 1, "b" => 1, "c" => 2} - """ - def count_words(words) when is_list(words) do - Enum.reduce(words, %{}, &update_word_count/2) - end - - defp update_word_count(word, acc) do - Map.update(acc, to_string(word), 1, &(&1 + 1)) - end - - # see https://stackoverflow.com/a/49558074/4050784 - @spec str_occurence(String.t(), String.t()) :: Integer.t() - def str_occurence(string, substr) when is_binary(string) and is_binary(substr) do - len = string |> String.split(substr) |> length() - len - 1 - end - - def str_occurence(_, _), do: "must be strings" - @spec large_than(String.t() | Integer.t(), Integer.t()) :: true | false def large_than(value, target) when is_binary(value) and is_integer(target) do String.length(value) >= target