From 4dec259f482b1c92993bb8a0484682aec4a963de Mon Sep 17 00:00:00 2001 From: Diederick Lawson Date: Thu, 24 Oct 2024 09:22:15 +0200 Subject: [PATCH 01/14] Shorten map function names --- README.md | 3 +- lib/wuunder_utils/maps.ex | 304 +++++++++++++++++++------------------- mix.exs | 2 +- 3 files changed, 154 insertions(+), 155 deletions(-) diff --git a/README.md b/README.md index bbc597e..0cc50cb 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ by adding `wuunder_utils` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:wuunder_utils, "~> 0.1.0"} + {:wuunder_utils, "~> 0.9.0"} ] end ``` @@ -18,4 +18,3 @@ end Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) and published on [HexDocs](https://hexdocs.pm). Once published, the docs can be found at . - diff --git a/lib/wuunder_utils/maps.ex b/lib/wuunder_utils/maps.ex index ca05145..a027df3 100644 --- a/lib/wuunder_utils/maps.ex +++ b/lib/wuunder_utils/maps.ex @@ -16,51 +16,51 @@ defmodule WuunderUtils.Maps do ## Examples - iex> WuunderUtils.Maps.get_field(%{value: 20}, :value) + iex> WuunderUtils.Maps.get(%{value: 20}, :value) 20 - iex> WuunderUtils.Maps.get_field(%{"value" => 20}, :value) + iex> WuunderUtils.Maps.get(%{"value" => 20}, :value) 20 - iex> WuunderUtils.Maps.get_field(%{value: 20}, "value") + iex> WuunderUtils.Maps.get(%{value: 20}, "value") 20 - iex> WuunderUtils.Maps.get_field(%{value: 20}, "non-existent") + iex> WuunderUtils.Maps.get(%{value: 20}, "non-existent") nil - iex> WuunderUtils.Maps.get_field(%{value: 20}, :weight) + iex> WuunderUtils.Maps.get(%{value: 20}, :weight) nil - iex> WuunderUtils.Maps.get_field(%{value: 20}, :weight, 350) + iex> WuunderUtils.Maps.get(%{value: 20}, :weight, 350) 350 - iex> WuunderUtils.Maps.get_field(%{value: 20}, "currency", "EUR") + iex> WuunderUtils.Maps.get(%{value: 20}, "currency", "EUR") "EUR" - iex> WuunderUtils.Maps.get_field([name: "Henk", name: "Kees", last_name: "Jansen"], "name") + iex> WuunderUtils.Maps.get([name: "Henk", name: "Kees", last_name: "Jansen"], "name") "Henk" - iex> WuunderUtils.Maps.get_field(["a", "b", "c"], 1) + iex> WuunderUtils.Maps.get(["a", "b", "c"], 1) "b" - iex> WuunderUtils.Maps.get_field(["a", "b", "c"], 3, "d") + iex> WuunderUtils.Maps.get(["a", "b", "c"], 3, "d") "d" - iex> WuunderUtils.Maps.get_field({"a", "b", "c"}, 1) + iex> WuunderUtils.Maps.get({"a", "b", "c"}, 1) "b" - iex> WuunderUtils.Maps.get_field({"a", "b", "c"}, 3, "d") + iex> WuunderUtils.Maps.get({"a", "b", "c"}, 3, "d") "d" """ - @spec get_field(any(), map_key() | non_neg_integer(), any()) :: any() - def get_field(map, key, default \\ nil) + @spec get(any(), map_key() | non_neg_integer(), any()) :: any() + def get(map, key, default \\ nil) - def get_field(list, index, default) when is_list(list) and is_number(index), + def get(list, index, default) when is_list(list) and is_number(index), do: Enum.at(list, index, default) - def get_field(list, key, default) when is_list(list) and is_binary(key) do + def get(list, key, default) when is_list(list) and is_binary(key) do atom_key = get_safe_key(key) if is_atom(atom_key) && Keyword.keyword?(list) do @@ -70,15 +70,15 @@ defmodule WuunderUtils.Maps do end end - def get_field(tuple, index, _default) + def get(tuple, index, _default) when is_tuple(tuple) and is_number(index) and index < tuple_size(tuple), do: elem(tuple, index) - def get_field(tuple, index, default) + def get(tuple, index, default) when is_tuple(tuple) and is_number(index) and index >= tuple_size(tuple), do: default - def get_field(map, key, default) + def get(map, key, default) when is_map(map) and is_valid_map_atom_key(key) do if Map.has_key?(map, key) do Map.get(map, key) @@ -87,7 +87,7 @@ defmodule WuunderUtils.Maps do end end - def get_field(map, key, default) when is_map(map) and is_valid_map_binary_key(key) do + def get(map, key, default) when is_map(map) and is_valid_map_binary_key(key) do atom_key = get_safe_key(key) if is_atom(atom_key) && Map.has_key?(map, atom_key) do @@ -121,31 +121,31 @@ defmodule WuunderUtils.Maps do ...> } ...> } ...> - ...> WuunderUtils.Maps.get_field_in(person, [:country, :code]) + ...> WuunderUtils.Maps.deep_get(person, [:country, :code]) "NL" - iex> WuunderUtils.Maps.get_field_in(person, "country.code") + iex> WuunderUtils.Maps.deep_get(person, "country.code") "NL" - iex> WuunderUtils.Maps.get_field_in(person, [:address, :company]) + iex> WuunderUtils.Maps.deep_get(person, [:address, :company]) %Company{name: "Wuunder"} - iex> WuunderUtils.Maps.get_field_in(person, [:address, :company, :name]) + iex> WuunderUtils.Maps.deep_get(person, [:address, :company, :name]) "Wuunder" - iex> WuunderUtils.Maps.get_field_in(person, [:meta, :skills]) + iex> WuunderUtils.Maps.deep_get(person, [:meta, :skills]) ["programmer", "manager", %{name: "painting", type: "hobby", grades: {"A+", "C"}}] - iex> WuunderUtils.Maps.get_field_in(person, [:meta, :skills, 1]) + iex> WuunderUtils.Maps.deep_get(person, [:meta, :skills, 1]) "manager" - iex> WuunderUtils.Maps.get_field_in(person, "meta.skills.1") + iex> WuunderUtils.Maps.deep_get(person, "meta.skills.1") "manager" - iex> WuunderUtils.Maps.get_field_in(person, [:meta, :skills, 2, :type]) + iex> WuunderUtils.Maps.deep_get(person, [:meta, :skills, 2, :type]) "hobby" - iex> WuunderUtils.Maps.get_field_in(person, "meta.skills.2.type") + iex> WuunderUtils.Maps.deep_get(person, "meta.skills.2.type") "hobby" - iex> WuunderUtils.Maps.get_field_in(person, "meta.skills.2.non_existent") + iex> WuunderUtils.Maps.deep_get(person, "meta.skills.2.non_existent") nil - iex> WuunderUtils.Maps.get_field_in(person, "meta.skills.2.non_existent", "default") + iex> WuunderUtils.Maps.deep_get(person, "meta.skills.2.non_existent", "default") "default" - iex> WuunderUtils.Maps.get_field_in(person, "meta.skills.2.grades.0") + iex> WuunderUtils.Maps.deep_get(person, "meta.skills.2.grades.0") "A+" - iex> WuunderUtils.Maps.get_field_in(person, "meta.skills.2.grades.2", "none") + iex> WuunderUtils.Maps.deep_get(person, "meta.skills.2.grades.2", "none") "none" iex> keyword_list = [ @@ -157,45 +157,45 @@ defmodule WuunderUtils.Maps do ...> ] ...> ] ...> - iex> WuunderUtils.Maps.get_field_in(keyword_list, "name") + iex> WuunderUtils.Maps.deep_get(keyword_list, "name") "Henk" - iex> WuunderUtils.Maps.get_field_in(keyword_list, "addresses") + iex> WuunderUtils.Maps.deep_get(keyword_list, "addresses") [%{"number" => 1, "street" => "Laan"}, %{"number" => 1337, "street" => "Straat"}] - iex> WuunderUtils.Maps.get_field_in(keyword_list, "addresses.0") + iex> WuunderUtils.Maps.deep_get(keyword_list, "addresses.0") %{"number" => 1, "street" => "Laan"} - iex> WuunderUtils.Maps.get_field_in(keyword_list, "addresses.1.street") + iex> WuunderUtils.Maps.deep_get(keyword_list, "addresses.1.street") "Straat" - iex> WuunderUtils.Maps.get_field_in(keyword_list, "addresses.1.other_field", "none") + iex> WuunderUtils.Maps.deep_get(keyword_list, "addresses.1.other_field", "none") "none" - iex> WuunderUtils.Maps.get_field_in(keyword_list, "addresses.2.other_field", "none") + iex> WuunderUtils.Maps.deep_get(keyword_list, "addresses.2.other_field", "none") nil """ - @spec get_field_in(any(), list(atom()) | String.t()) :: any() - def get_field_in(value, path, default \\ nil) + @spec deep_get(any(), list(atom()) | String.t()) :: any() + def deep_get(value, path, default \\ nil) - def get_field_in(value, path, default) when is_binary(path) do + def deep_get(value, path, default) when is_binary(path) do keys = keys_from_path(path) - get_field_in(value, keys, default) + deep_get(value, keys, default) end - def get_field_in(nil, _keys, _default), do: nil + def deep_get(nil, _keys, _default), do: nil - def get_field_in(value, [], _default), do: value + def deep_get(value, [], _default), do: value - def get_field_in(value, _keys, _default) + def deep_get(value, _keys, _default) when not is_map(value) and not is_list(value) and not is_tuple(value), do: nil - def get_field_in(map_list_or_tuple, [key | rest], default) + def deep_get(map_list_or_tuple, [key | rest], default) when is_map(map_list_or_tuple) or is_list(map_list_or_tuple) or is_tuple(map_list_or_tuple) do map_list_or_tuple - |> get_field(key, default) - |> get_field_in(rest, default) + |> get(key, default) + |> deep_get(rest, default) end - def get_field_in(nil, keys, _default) when is_list(keys), do: nil + def deep_get(nil, keys, _default) when is_list(keys), do: nil @doc """ Creates a map from a given set of fields. The output will always be a string. @@ -217,7 +217,7 @@ defmodule WuunderUtils.Maps do ...> } ...> } ...> - ...> WuunderUtils.Maps.get_fields_in( + ...> WuunderUtils.Maps.deep_get_values( ...> person, ...> [ ...> [:country, :code], @@ -236,8 +236,8 @@ defmodule WuunderUtils.Maps do } """ - @spec get_fields_in(map() | struct() | list(), list()) :: map() - def get_fields_in(value, fields) do + @spec deep_get_values(map() | struct() | list(), list()) :: map() + def deep_get_values(value, fields) do initial_map = Enum.reduce(fields, %{}, fn field, initial_map -> keys = @@ -245,18 +245,18 @@ defmodule WuunderUtils.Maps do |> get_keys() |> ensure_zero_index() - Map.merge(initial_map, empty_map(keys)) + Map.merge(initial_map, new(keys)) end) Enum.reduce(fields, initial_map, fn field, final_map -> - value = get_field_in(value, field) + value = deep_get(value, field) keys = field |> get_keys() |> ensure_zero_index() - put_field_in(final_map, keys, value) + deep_put(final_map, keys, value) end) end @@ -265,40 +265,40 @@ defmodule WuunderUtils.Maps do ## Examples - iex> WuunderUtils.Maps.empty_map([:person, :name, :meta, 0, :hobby, :type]) + iex> WuunderUtils.Maps.new([:person, :name, :meta, 0, :hobby, :type]) %{"person" => %{"name" => %{"meta" => [%{"hobby" => %{"type" => %{}}}]}}} - iex> WuunderUtils.Maps.empty_map([:person, :name, :meta, 0, :hobbies, 0, :type]) + iex> WuunderUtils.Maps.new([:person, :name, :meta, 0, :hobbies, 0, :type]) %{"person" => %{"name" => %{"meta" => [%{"hobbies" => [%{"type" => %{}}]}]}}} """ - @spec empty_map(String.t() | list()) :: map() - def empty_map(path) when is_binary(path) do + @spec new(String.t() | list()) :: map() + def new(path) when is_binary(path) do path |> keys_from_path() - |> empty_map() + |> new() end - def empty_map(keys) when is_list(keys), do: empty_map(%{}, keys) + def new(keys) when is_list(keys), do: new(%{}, keys) - @spec empty_map(map() | list(), list()) :: map() | list() - def empty_map(list, [key | rest]) when is_list(list) do + @spec new(map() | list(), list()) :: map() | list() + def new(list, [key | rest]) when is_list(list) do if is_integer(key) do - [empty_map(rest)] + [new(rest)] else - [%{"key" => empty_map(rest)}] + [%{"key" => new(rest)}] end end - def empty_map(map, [key | rest]) when is_map(map) do + def new(map, [key | rest]) when is_map(map) do if is_integer(key) do - [empty_map(rest)] + [new(rest)] else - Map.put(map, "#{key}", empty_map(rest)) + Map.put(map, "#{key}", new(rest)) end end - def empty_map(map_or_list, []), do: map_or_list + def new(map_or_list, []), do: map_or_list @doc """ Acts as an IndifferentMap. Put a key/value regardless of the key type. If the map @@ -307,43 +307,43 @@ defmodule WuunderUtils.Maps do ## Examples - iex> WuunderUtils.Maps.put_field(%{value: 20}, :weight, 350) + iex> WuunderUtils.Maps.put(%{value: 20}, :weight, 350) %{value: 20, weight: 350} - iex> WuunderUtils.Maps.put_field(["a", "b", "c"], 1, "d") + iex> WuunderUtils.Maps.put(["a", "b", "c"], 1, "d") ["a", "d", "c"] - iex> WuunderUtils.Maps.put_field(["a", "b", "c"], 4, "d") + iex> WuunderUtils.Maps.put(["a", "b", "c"], 4, "d") ["a", "b", "c"] - iex> WuunderUtils.Maps.put_field(%{value: 20, weight: 200}, "weight", 350) + iex> WuunderUtils.Maps.put(%{value: 20, weight: 200}, "weight", 350) %{value: 20, weight: 350} - iex> WuunderUtils.Maps.put_field(%{value: 20}, "weight", 350) + iex> WuunderUtils.Maps.put(%{value: 20}, "weight", 350) %{:value => 20, "weight" => 350} - iex> WuunderUtils.Maps.put_field(%{"weight" => 350}, :value, 25) + iex> WuunderUtils.Maps.put(%{"weight" => 350}, :value, 25) %{"weight" => 350, "value" => 25} - iex> WuunderUtils.Maps.put_field(%{"weight" => 350}, "value", 25) + iex> WuunderUtils.Maps.put(%{"weight" => 350}, "value", 25) %{"weight" => 350, "value" => 25} """ - @spec put_field(map() | struct() | list() | nil, String.t() | atom(), any()) :: + @spec put(map() | struct() | list() | nil, String.t() | atom(), any()) :: map() | struct() | list() - def put_field(map, key, value) + def put(map, key, value) when is_map(map) and is_valid_map_atom_key(key) do - if Map.has_key?(map, key) || has_only_atom_keys?(map) do + if Map.has_key?(map, key) || only_atom_keys?(map) do Map.put(map, key, value) else Map.put(map, "#{key}", value) end end - def put_field(list, index, value) when is_list(list) and is_integer(index), + def put(list, index, value) when is_list(list) and is_integer(index), do: List.replace_at(list, index, value) - def put_field(map, key, value) when is_map(map) and is_valid_map_binary_key(key) do + def put(map, key, value) when is_map(map) and is_valid_map_binary_key(key) do atom_key = get_safe_key(key) if Map.has_key?(map, atom_key) do @@ -376,122 +376,122 @@ defmodule WuunderUtils.Maps do ...> ] ...> } ...> } - iex> WuunderUtils.Maps.put_field_in(person, [:first_name], "Piet") + iex> WuunderUtils.Maps.deep_put(person, [:first_name], "Piet") %Person{person | first_name: "Piet"} - iex> WuunderUtils.Maps.put_field_in(person, [:country, :code], "US") + iex> WuunderUtils.Maps.deep_put(person, [:country, :code], "US") %Person{person | country: %Country{code: "US"}} - iex> WuunderUtils.Maps.put_field_in(person, [:meta, :skills, 1], "vaultdweller") + iex> WuunderUtils.Maps.deep_put(person, [:meta, :skills, 1], "vaultdweller") %Person{person | meta: %{skills: ["programmer", "vaultdweller", %{name: "painting", type: "hobby"}]}} - iex> WuunderUtils.Maps.put_field_in(person, [:meta, :skills, 2, :name], "walking") + iex> WuunderUtils.Maps.deep_put(person, [:meta, :skills, 2, :name], "walking") %Person{person | meta: %{skills: ["programmer", "manager", %{name: "walking", type: "hobby"}]}} - iex> WuunderUtils.Maps.put_field_in(person, "meta.skills.2.name", "walking") + iex> WuunderUtils.Maps.deep_put(person, "meta.skills.2.name", "walking") %Person{person | meta: %{skills: ["programmer", "manager", %{name: "walking", type: "hobby"}]}} """ - @spec put_field_in( + @spec deep_put( map() | struct() | list() | nil, list(atom() | String.t()) | String.t(), any() ) :: any() - def put_field_in(value, path, value_to_set) + def deep_put(value, path, value_to_set) when (is_map(value) or is_list(value)) and is_binary(path) do keys = keys_from_path(path) - put_field_in(value, keys, value_to_set) + deep_put(value, keys, value_to_set) end - def put_field_in(map_or_list, [key | rest], value_to_set) + def deep_put(map_or_list, [key | rest], value_to_set) when is_map(map_or_list) or is_list(map_or_list) do - current = get_field(map_or_list, key) - put_field(map_or_list, key, put_field_in(current, rest, value_to_set)) + current = get(map_or_list, key) + put(map_or_list, key, deep_put(current, rest, value_to_set)) end - def put_field_in(_map_or_list, [], value_to_set), do: value_to_set + def deep_put(_map_or_list, [], value_to_set), do: value_to_set @doc """ Removes a key from a map. Doesn't matter if the key is an atom or string ## Examples - iex> WuunderUtils.Maps.delete_field(%{length: 255, weight: 100}, :length) + iex> WuunderUtils.Maps.delete(%{length: 255, weight: 100}, :length) %{weight: 100} - iex> WuunderUtils.Maps.delete_field(%{length: 255, weight: 100}, "length") + iex> WuunderUtils.Maps.delete(%{length: 255, weight: 100}, "length") %{weight: 100} - iex> WuunderUtils.Maps.delete_field(%{"value" => 50, "currency" => "EUR"}, "currency") + iex> WuunderUtils.Maps.delete(%{"value" => 50, "currency" => "EUR"}, "currency") %{"value" => 50} - iex> WuunderUtils.Maps.delete_field(%{"value" => 50, "currency" => "EUR"}, :currency) + iex> WuunderUtils.Maps.delete(%{"value" => 50, "currency" => "EUR"}, :currency) %{"value" => 50} - iex> WuunderUtils.Maps.delete_field(["a", "b", "c"], 1) + iex> WuunderUtils.Maps.delete(["a", "b", "c"], 1) ["a", "c"] - iex> WuunderUtils.Maps.delete_field({"a", "b", "c"}, 1) + iex> WuunderUtils.Maps.delete({"a", "b", "c"}, 1) {"a", "c"} iex> country = %Country{code: "NL"} ...> - ...> WuunderUtils.Maps.delete_field(country, :code) + ...> WuunderUtils.Maps.delete(country, :code) %Country{code: ""} iex> country = %Country{code: "NL"} ...> - ...> WuunderUtils.Maps.delete_field(country, "code") + ...> WuunderUtils.Maps.delete(country, "code") %Country{code: ""} iex> country = %Country{code: "NL"} ...> - ...> WuunderUtils.Maps.delete_field(country, "does_not_exist") + ...> WuunderUtils.Maps.delete(country, "does_not_exist") %Country{code: "NL"} """ - @spec delete_field(any(), map_key() | non_neg_integer()) :: any() - def delete_field(%module{} = struct, key) + @spec delete(any(), map_key() | non_neg_integer()) :: any() + def delete(%module{} = struct, key) when is_struct(struct) and is_valid_map_atom_key(key) do default = struct(module, %{}) if Map.has_key?(default, key) do - put_field(struct, key, Map.get(default, key)) + put(struct, key, Map.get(default, key)) else struct end end - def delete_field(struct, key) when is_struct(struct) and is_valid_map_binary_key(key) do + def delete(struct, key) when is_struct(struct) and is_valid_map_binary_key(key) do atom_key = get_safe_key(key) if Map.has_key?(struct, atom_key) do - delete_field(struct, atom_key) + delete(struct, atom_key) else struct end end - def delete_field(map, key) when is_map(map) and is_valid_map_atom_key(key) do - if has_only_atom_keys?(map) do + def delete(map, key) when is_map(map) and is_valid_map_atom_key(key) do + if only_atom_keys?(map) do Map.delete(map, key) else Map.delete(map, "#{key}") end end - def delete_field(tuple, index) + def delete(tuple, index) when is_tuple(tuple) and is_number(index) and index < tuple_size(tuple), do: Tuple.delete_at(tuple, index) - def delete_field(tuple, index) + def delete(tuple, index) when is_tuple(tuple) and is_number(index) and index >= tuple_size(tuple), do: tuple - def delete_field(list, index) when is_list(list) and is_number(index) and index < length(list), + def delete(list, index) when is_list(list) and is_number(index) and index < length(list), do: List.delete_at(list, index) - def delete_field(list, index) when is_list(list) and is_number(index) and index >= length(list), + def delete(list, index) when is_list(list) and is_number(index) and index >= length(list), do: list - def delete_field(map, key) when is_map(map) and is_valid_map_binary_key(key) do + def delete(map, key) when is_map(map) and is_valid_map_binary_key(key) do atom_key = get_safe_key(key) if Map.has_key?(map, atom_key) do @@ -506,7 +506,7 @@ defmodule WuunderUtils.Maps do ## Examples - iex> WuunderUtils.Maps.delete_field_in(%{"data" => [%{"name" => "Piet"}, %{"name" => "Henk"}]}, "data.0.name") + iex> WuunderUtils.Maps.deep_delete(%{"data" => [%{"name" => "Piet"}, %{"name" => "Henk"}]}, "data.0.name") %{"data" => [%{}, %{"name" => "Henk"}]} iex> person = %Person{ @@ -524,7 +524,7 @@ defmodule WuunderUtils.Maps do ...> } ...> } ...> - ...> WuunderUtils.Maps.delete_field_in(person, [:country, :code]) + ...> WuunderUtils.Maps.deep_delete(person, [:country, :code]) %Person{ country: %Country{code: ""}, address: %Address{ @@ -539,7 +539,7 @@ defmodule WuunderUtils.Maps do ] } } - iex> WuunderUtils.Maps.delete_field_in(person, "meta.skills.1") + iex> WuunderUtils.Maps.deep_delete(person, "meta.skills.1") %Person{ country: %Country{code: "NL"}, address: %Address{ @@ -554,58 +554,58 @@ defmodule WuunderUtils.Maps do } } """ - @spec delete_field_in(any(), list(atom()) | String.t()) :: any() - def delete_field_in(value, path) when is_binary(path) do + @spec deep_delete(any(), list(atom()) | String.t()) :: any() + def deep_delete(value, path) when is_binary(path) do keys = keys_from_path(path) - delete_field_in(value, keys) + deep_delete(value, keys) end - def delete_field_in(nil, _keys), do: nil + def deep_delete(nil, _keys), do: nil - def delete_field_in(value, []), do: value + def deep_delete(value, []), do: value - def delete_field_in(value, _keys) + def deep_delete(value, _keys) when not is_map(value) and not is_list(value) and not is_tuple(value), do: nil - def delete_field_in(map_list_or_tuple, [key]) + def deep_delete(map_list_or_tuple, [key]) when is_map(map_list_or_tuple) or is_list(map_list_or_tuple) or is_tuple(map_list_or_tuple), - do: delete_field(map_list_or_tuple, key) + do: delete(map_list_or_tuple, key) - def delete_field_in(map_list_or_tuple, [_head | _tail] = keys) + def deep_delete(map_list_or_tuple, [_head | _tail] = keys) when is_map(map_list_or_tuple) or is_list(map_list_or_tuple) or is_tuple(map_list_or_tuple) do before_last_key = Enum.slice(keys, 0..-2//1) last_key = List.last(keys) new_value = map_list_or_tuple - |> get_field_in(before_last_key) - |> delete_field(last_key) + |> deep_get(before_last_key) + |> delete(last_key) - put_field_in(map_list_or_tuple, before_last_key, new_value) + deep_put(map_list_or_tuple, before_last_key, new_value) end - def delete_field_in(nil, keys) when is_list(keys), do: nil + def deep_delete(nil, keys) when is_list(keys), do: nil @doc """ Tests if the given map only consists of atom keys ## Examples - iex> WuunderUtils.Maps.has_only_atom_keys?(%{a: 1, b: 2}) + iex> WuunderUtils.Maps.only_atom_keys?(%{a: 1, b: 2}) true - iex> WuunderUtils.Maps.has_only_atom_keys?(%{:a => 1, "b" => 2}) + iex> WuunderUtils.Maps.only_atom_keys?(%{:a => 1, "b" => 2}) false - iex> WuunderUtils.Maps.has_only_atom_keys?(%{"a" => 1, "b" => 2}) + iex> WuunderUtils.Maps.only_atom_keys?(%{"a" => 1, "b" => 2}) false """ - @spec has_only_atom_keys?(map() | struct()) :: boolean() - def has_only_atom_keys?(struct) when is_struct(struct), do: true + @spec only_atom_keys?(map() | struct()) :: boolean() + def only_atom_keys?(struct) when is_struct(struct), do: true - def has_only_atom_keys?(map) when is_map(map) do + def only_atom_keys?(map) when is_map(map) do map |> Map.keys() |> Enum.all?(&is_atom/1) @@ -616,21 +616,21 @@ defmodule WuunderUtils.Maps do ## Examples - iex> WuunderUtils.Maps.alias_field(%{country: "NL"}, :country, :country_code) + iex> WuunderUtils.Maps.rename_key(%{country: "NL"}, :country, :country_code) %{country_code: "NL"} - iex> WuunderUtils.Maps.alias_field(%{"country" => "NL"}, :country, :country_code) + iex> WuunderUtils.Maps.rename_key(%{"country" => "NL"}, :country, :country_code) %{"country_code" => "NL"} - iex> WuunderUtils.Maps.alias_field(%{street_name: "Straatnaam"}, :street, :street_address) + iex> WuunderUtils.Maps.rename_key(%{street_name: "Straatnaam"}, :street, :street_address) %{street_name: "Straatnaam"} """ - @spec alias_field(map(), atom(), atom()) :: map() - def alias_field(map, from, to) + @spec rename_key(map(), atom(), atom()) :: map() + def rename_key(map, from, to) when is_map(map) and is_valid_map_atom_key(from) and is_valid_map_atom_key(to) do - from_key = if Enum.empty?(map) || has_only_atom_keys?(map), do: from, else: "#{from}" - to_key = if Enum.empty?(map) || has_only_atom_keys?(map), do: to, else: "#{to}" + from_key = if Enum.empty?(map) || only_atom_keys?(map), do: from, else: "#{from}" + to_key = if Enum.empty?(map) || only_atom_keys?(map), do: to, else: "#{to}" if is_nil(Map.get(map, from_key)) == false && is_nil(Map.get(map, to_key)) do map @@ -646,15 +646,15 @@ defmodule WuunderUtils.Maps do ## Examples - iex> WuunderUtils.Maps.alias_fields(%{country: "NL", street: "Straat", number: 666}, %{country: :country_code, street: :street_name, number: :house_number}) + iex> WuunderUtils.Maps.rename_keys(%{country: "NL", street: "Straat", number: 666}, %{country: :country_code, street: :street_name, number: :house_number}) %{country_code: "NL", house_number: 666, street_name: "Straat"} """ - @spec alias_fields(map(), map()) :: map() - def alias_fields(map, aliasses), + @spec rename_keys(map(), map()) :: map() + def rename_keys(map, aliasses), do: Enum.reduce(Map.keys(aliasses), map, fn key, alias_params -> - alias_field(alias_params, key, Map.get(aliasses, key)) + rename_key(alias_params, key, Map.get(aliasses, key)) end) @doc """ @@ -788,7 +788,7 @@ defmodule WuunderUtils.Maps do do: put_when(map, !!condition.(), key, value) def put_when(map, true, key, value) when is_map(map) and is_valid_map_key(key), - do: put_field(map, key, value) + do: put(map, key, value) def put_when(map, false, key, _value) when is_map(map) and is_valid_map_key(key), do: map @@ -1080,9 +1080,9 @@ defmodule WuunderUtils.Maps do new_value = delete_empty(value) if is_nil(new_value) do - delete_field(new_map, key) + delete(new_map, key) else - put_field(new_map, key, new_value) + put(new_map, key, new_value) end end) @@ -1138,7 +1138,7 @@ defmodule WuunderUtils.Maps do value = Map.get(new_map, key) new_value = map_all(value, map_fn) - put_field(new_map, key, new_value) + put(new_map, key, new_value) end) end diff --git a/mix.exs b/mix.exs index 6f04afe..4e7b8e8 100644 --- a/mix.exs +++ b/mix.exs @@ -16,7 +16,7 @@ defmodule WuunderUtils.MixProject do start_permanent: Mix.env() == :prod, test_coverage: [tool: ExCoveralls], preferred_cli_env: [coveralls: :test, "coveralls.html": :test], - version: "0.8.1" + version: "0.9.0" ] end From 322a7f90b26c77a59ce9103c478cea07132684a9 Mon Sep 17 00:00:00 2001 From: Diederick Lawson Date: Thu, 24 Oct 2024 09:37:58 +0200 Subject: [PATCH 02/14] Renamed map_all to map and added stringify_keys helper --- lib/wuunder_utils/maps.ex | 74 ++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/lib/wuunder_utils/maps.ex b/lib/wuunder_utils/maps.ex index a027df3..e6c1b7f 100644 --- a/lib/wuunder_utils/maps.ex +++ b/lib/wuunder_utils/maps.ex @@ -1127,32 +1127,88 @@ defmodule WuunderUtils.Maps do ## Examples - iex> WuunderUtils.Maps.map_all(%{name: " test ", data: ["some item", "other item ", %{x: " value"}]}, &WuunderUtils.Presence.trim/1) + iex> WuunderUtils.Maps.map(%{name: " test ", data: ["some item", "other item ", %{x: " value"}]}, &WuunderUtils.Presence.trim/1) %{data: ["some item", "other item", %{x: "value"}], name: "test"} """ - @spec map_all(any(), function()) :: any() - def map_all(value, map_fn) when is_map(value) and is_function(map_fn) do + @spec map(any(), function()) :: any() + def map(value, map_fn) when is_map(value) and is_function(map_fn) do value |> Map.keys() |> Enum.reduce(value, fn key, new_map -> value = Map.get(new_map, key) - new_value = map_all(value, map_fn) + new_value = map(value, map_fn) put(new_map, key, new_value) end) end - def map_all(values, map_fn) when is_list(values) and is_function(map_fn), - do: Enum.map(values, &map_all(&1, map_fn)) + def map(values, map_fn) when is_list(values) and is_function(map_fn), + do: Enum.map(values, &map(&1, map_fn)) - def map_all(values, map_fn) when is_tuple(values) and is_function(map_fn) do + def map(values, map_fn) when is_tuple(values) and is_function(map_fn) do values |> Tuple.to_list() - |> map_all(map_fn) + |> map(map_fn) |> List.to_tuple() end - def map_all(value, map_fn), do: map_fn.(value) + def map(value, map_fn), do: map_fn.(value) + + @doc """ + Converts keys in maps to strings. Note: skips structs. So you need to convert these to maps first. + + ## Examples + + iex> WuunderUtils.Maps.stringify_keys(%{ + ...> first_name: "Peter", + ...> last_name: "Griffin", + ...> skills: [%{code: "A", title: "Talking"}, %{code: "B", title: "Moving"}] + ...> }) + %{ + "first_name" => "Peter", + "last_name" => "Griffin", + "skills" => [%{"code" => "A", "title" => "Talking"}, %{"code" => "B", "title" => "Moving"}] + } + + iex> WuunderUtils.Maps.stringify_keys(%Person{ + ...> first_name: "Peter", + ...> last_name: "Pan", + ...> date_of_birth: ~D[1980-01-02], + ...> weight: Decimal.new("81.5"), + ...> country: %Country{code: "UK"}, + ...> time_of_death: ~T[13:37:37], + ...> meta: %{skills: [%{code: "A", title: "Talking"}, %{code: "B", title: "Moving"}]} + ...> }) + %Person{ + first_name: "Peter", + last_name: "Pan", + date_of_birth: ~D[1980-01-02], + weight: Decimal.new("81.5"), + country: %Country{code: "UK"}, + time_of_death: ~T[13:37:37], + meta: %{"skills" => [%{"code" => "A", "title" => "Talking"}, %{"code" => "B", "title" => "Moving"}]} + } + """ + @spec stringify_keys(any()) :: any() + def stringify_keys(struct) when is_struct(struct) do + struct + |> Map.keys() + |> Enum.reduce(struct, fn key, new_struct -> + value = Map.get(struct, key) + Map.put(new_struct, key, stringify_keys(value)) + end) + end + + def stringify_keys(map) when is_non_struct_map(map) do + map + |> Enum.reduce(%{}, fn {key, value}, new_map -> + Map.put(new_map, "#{key}", stringify_keys(value)) + end) + end + + def stringify_keys(list) when is_list(list), do: Enum.map(list, &stringify_keys/1) + + def stringify_keys(value), do: value defp transform_struct(module, struct, transform) do if has_ecto_schema?(module) do From 82ad1666733b2987394e1cca8b19ebfa90071737 Mon Sep 17 00:00:00 2001 From: Diederick Lawson Date: Thu, 24 Oct 2024 09:45:37 +0200 Subject: [PATCH 03/14] Fix dialyzer errors and upgrade hex packages --- .dialyzer_ignore.exs | 3 ++- .tool-versions | 4 ++-- lib/wuunder_utils/numbers.ex | 6 +++--- mix.exs | 2 +- mix.lock | 24 ++++++++++++------------ 5 files changed, 20 insertions(+), 19 deletions(-) diff --git a/.dialyzer_ignore.exs b/.dialyzer_ignore.exs index 1df7743..2191a10 100644 --- a/.dialyzer_ignore.exs +++ b/.dialyzer_ignore.exs @@ -1,2 +1,3 @@ [{"lib/wuunder_utils/maps.ex", :contract_supertype}, -{"lib/wuunder_utils/strings.ex", :contract_supertype},] +{"lib/wuunder_utils/strings.ex", :contract_supertype}, +{"lib/wuunder_utils/numbers.ex", :contract_supertype}] diff --git a/.tool-versions b/.tool-versions index b7c4c5b..9c71c0d 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -elixir 1.17.2-otp-27 -erlang 27.0.1 +elixir 1.17.3-otp-27 +erlang 27.1 diff --git a/lib/wuunder_utils/numbers.ex b/lib/wuunder_utils/numbers.ex index 54040c7..ed8e590 100644 --- a/lib/wuunder_utils/numbers.ex +++ b/lib/wuunder_utils/numbers.ex @@ -122,7 +122,9 @@ defmodule WuunderUtils.Numbers do "1.0" """ - @spec as_string(any()) :: String.t() | nil + @spec as_string(term()) :: nil | String.t() + def as_string(nil), do: nil + def as_string(value) when is_decimal(value), do: Decimal.to_string(value) def as_string(value) when is_float(value), do: Float.to_string(value) def as_string(value) when is_integer(value), do: Integer.to_string(value) @@ -133,8 +135,6 @@ defmodule WuunderUtils.Numbers do |> as_string() end - def as_string(nil), do: nil - @doc """ Adds two Decimal's together. Defaults back to 0. diff --git a/mix.exs b/mix.exs index 4e7b8e8..07b5dc2 100644 --- a/mix.exs +++ b/mix.exs @@ -8,7 +8,7 @@ defmodule WuunderUtils.MixProject do description: "Set of helper modules", dialyzer: dialyzer_config(), docs: docs(), - elixir: "~> 1.14", + elixir: "~> 1.15", name: "Wuunder Utils", organization: "wuunder", package: package(), diff --git a/mix.lock b/mix.lock index b70ad65..3ac0e92 100644 --- a/mix.lock +++ b/mix.lock @@ -1,18 +1,18 @@ %{ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "credo": {:hex, :credo, "1.7.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"}, + "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, - "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.32.1", "21e40f939515373bcdc9cffe65f3b3543f05015ac6c3d01d991874129d173420", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5142c9db521f106d61ff33250f779807ed2a88620e472ac95dc7d59c380113da"}, - "excoveralls": {:hex, :excoveralls, "0.18.1", "a6f547570c6b24ec13f122a5634833a063aec49218f6fff27de9df693a15588c", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d65f79db146bb20399f23046015974de0079668b9abb2f5aac074d078da60b8d"}, - "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, + "dialyxir": {:hex, :dialyxir, "1.4.4", "fb3ce8741edeaea59c9ae84d5cec75da00fa89fe401c72d6e047d11a61f65f70", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "cd6111e8017ccd563e65621a4d9a4a1c5cd333df30cebc7face8029cacb4eff6"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, + "ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, + "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"}, + "excoveralls": {:hex, :excoveralls, "0.18.3", "bca47a24d69a3179951f51f1db6d3ed63bca9017f476fe520eb78602d45f7756", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "746f404fcd09d5029f1b211739afb8fb8575d775b21f6a3908e7ce3e640724c6"}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, } From 55ae132a3886a0e062ef95a29bf9b15a8304e3e6 Mon Sep 17 00:00:00 2001 From: Diederick Lawson Date: Thu, 24 Oct 2024 09:48:27 +0200 Subject: [PATCH 04/14] Add license file --- LICENSE.md | 9 +++++++++ mix.exs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..1a6b396 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2024 Wuunder + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/mix.exs b/mix.exs index 07b5dc2..7ac4238 100644 --- a/mix.exs +++ b/mix.exs @@ -42,7 +42,7 @@ defmodule WuunderUtils.MixProject do [ name: "wuunder_utils", files: ~w(lib .formatter.exs mix.exs README*), - licenses: ["Apache-2.0"], + licenses: ["MIT"], links: %{ "GitHub" => "https://github.com/wuunder/wuunder_utils", "Docs" => "https://hexdocs.pm/wuunder_utils" From ff9c27d48927cb08ff8fa3e7ab57f1ca4abd5a52 Mon Sep 17 00:00:00 2001 From: Diederick Lawson Date: Thu, 24 Oct 2024 09:51:46 +0200 Subject: [PATCH 05/14] Moved requirement for Elixir back. Replaced guard to make it incompatible. --- lib/wuunder_utils/maps.ex | 2 +- mix.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/wuunder_utils/maps.ex b/lib/wuunder_utils/maps.ex index e6c1b7f..074ea3e 100644 --- a/lib/wuunder_utils/maps.ex +++ b/lib/wuunder_utils/maps.ex @@ -1199,7 +1199,7 @@ defmodule WuunderUtils.Maps do end) end - def stringify_keys(map) when is_non_struct_map(map) do + def stringify_keys(map) when is_map(map) do map |> Enum.reduce(%{}, fn {key, value}, new_map -> Map.put(new_map, "#{key}", stringify_keys(value)) diff --git a/mix.exs b/mix.exs index 7ac4238..5cf6832 100644 --- a/mix.exs +++ b/mix.exs @@ -8,7 +8,7 @@ defmodule WuunderUtils.MixProject do description: "Set of helper modules", dialyzer: dialyzer_config(), docs: docs(), - elixir: "~> 1.15", + elixir: "~> 1.14", name: "Wuunder Utils", organization: "wuunder", package: package(), From 582a62496b803f3986ef47728b9e1609c44ae477 Mon Sep 17 00:00:00 2001 From: Diederick Lawson Date: Thu, 24 Oct 2024 10:09:45 +0200 Subject: [PATCH 06/14] Add atomize_keys helper --- lib/wuunder_utils/maps.ex | 67 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/lib/wuunder_utils/maps.ex b/lib/wuunder_utils/maps.ex index 074ea3e..6296a77 100644 --- a/lib/wuunder_utils/maps.ex +++ b/lib/wuunder_utils/maps.ex @@ -1210,6 +1210,73 @@ defmodule WuunderUtils.Maps do def stringify_keys(value), do: value + @doc """ + Converts keys in maps to atoms. By default, this function will only atomize strings that already exist. + Note: skips structs. + + ## Examples + + iex> WuunderUtils.Maps.atomize_keys(%{ + ...> "first_name" => "Peter", + ...> "last_name" => "Griffin", + ...> "skills" => [%{"code" => "A", "title" => "Talking"}, %{"code" => "B", "title" => "Moving"}] + ...> }) + %{ + first_name: "Peter", + last_name: "Griffin", + skills: [%{code: "A", title: "Talking"}, %{code: "B", title: "Moving"}] + } + + iex> WuunderUtils.Maps.atomize_keys(%Person{ + ...> first_name: "Peter", + ...> last_name: "Pan", + ...> date_of_birth: ~D[1980-01-02], + ...> weight: Decimal.new("81.5"), + ...> country: %Country{code: "UK"}, + ...> time_of_death: ~T[13:37:37], + ...> meta: %{"skills" => [%{"code" => "A", "title" => "Talking"}, %{"code" => "B", "title" => "Moving"}]} + ...> }) + %Person{ + first_name: "Peter", + last_name: "Pan", + date_of_birth: ~D[1980-01-02], + weight: Decimal.new("81.5"), + country: %Country{code: "UK"}, + time_of_death: ~T[13:37:37], + meta: %{skills: [%{code: "A", title: "Talking"}, %{code: "B", title: "Moving"}]} + } + """ + @spec atomize_keys(any(), Keyword.t()) :: any() + def atomize_keys(value, options \\ [existing_atoms: true]) + + def atomize_keys(struct, options) when is_struct(struct) and is_list(options) do + struct + |> Map.keys() + |> Enum.reduce(struct, fn key, new_struct -> + value = Map.get(struct, key) + Map.put(new_struct, key, atomize_keys(value, options)) + end) + end + + def atomize_keys(map, options) when is_map(map) and is_list(options) do + map + |> Enum.reduce(%{}, fn {key, value}, new_map -> + new_key = + cond do + is_binary(key) and options[:existing_atoms] -> get_safe_key(key) + is_binary(key) -> String.to_atom(key) + is_atom(key) -> key + end + + Map.put(new_map, new_key, atomize_keys(value, options)) + end) + end + + def atomize_keys(list, options) when is_list(list) and is_list(options), + do: Enum.map(list, &atomize_keys(&1, options)) + + def atomize_keys(value, _options), do: value + defp transform_struct(module, struct, transform) do if has_ecto_schema?(module) do module From 913adc15f8798780fb7225a9b73701bad0b50721 Mon Sep 17 00:00:00 2001 From: Diederick Lawson Date: Thu, 24 Oct 2024 10:15:00 +0200 Subject: [PATCH 07/14] Bump to rc.0 temporary --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 5cf6832..7c3d16f 100644 --- a/mix.exs +++ b/mix.exs @@ -16,7 +16,7 @@ defmodule WuunderUtils.MixProject do start_permanent: Mix.env() == :prod, test_coverage: [tool: ExCoveralls], preferred_cli_env: [coveralls: :test, "coveralls.html": :test], - version: "0.9.0" + version: "0.9.0-rc.0" ] end From 6892566a1105efb8ff1952c7303eec578158f489 Mon Sep 17 00:00:00 2001 From: Diederick Lawson Date: Thu, 24 Oct 2024 10:21:10 +0200 Subject: [PATCH 08/14] Include CHANGELOG --- CHANGELOG.md | 22 ++++++++++++++++++++++ mix.exs | 5 +++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1b843ae --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +## v0.9 (2024-10-24) + + +### Backwards incompatible changes + + * Renamed `WuunderUtils.Maps.delete_field` to `WuunderUtils.Maps.delete` + * Renamed `WuunderUtils.Maps.delete_field_in` to `WuunderUtils.Maps.deep_delete` + * Renamed `WuunderUtils.Maps.get_field` to `WuunderUtils.Maps.get` + * Renamed `WuunderUtils.Maps.get_field_in` to `WuunderUtils.Maps.deep_get` + * Renamed `WuunderUtils.Maps.get_fields_in` to `WuunderUtils.Maps.deep_get_values` + * Renamed `WuunderUtils.Maps.put_field` to `WuunderUtils.Maps.put` + * Renamed `WuunderUtils.Maps.put_field_in` to `WuunderUtils.Maps.deep_put` + * Renamed `WuunderUtils.Maps.has_only_atom_keys?` to `WuunderUtils.Maps.only_atom_keys?` + * Renamed `WuunderUtils.Maps.alias_fields` to `WuunderUtils.Maps.rename_keys` + * Renamed `WuunderUtils.Maps.map_all` to `WuunderUtils.Maps.map` + +### Enhancements + + * Added `strinigy_keys` helper in `WuunderUtils.Maps` + * Added `atomize_keys` helper in `WuunderUtils.Maps` diff --git a/mix.exs b/mix.exs index 7c3d16f..52ba2b9 100644 --- a/mix.exs +++ b/mix.exs @@ -41,9 +41,10 @@ defmodule WuunderUtils.MixProject do defp package do [ name: "wuunder_utils", - files: ~w(lib .formatter.exs mix.exs README*), + files: ~w(lib .formatter.exs mix.exs CHANGELOG.md LICENSE.md README*), licenses: ["MIT"], links: %{ + "Changelog" => "https://hexdocs.pm/wuunder_utils/changelog.html", "GitHub" => "https://github.com/wuunder/wuunder_utils", "Docs" => "https://hexdocs.pm/wuunder_utils" } @@ -53,7 +54,7 @@ defmodule WuunderUtils.MixProject do defp docs do [ main: "WuunderUtils", - extras: ["README.md"] + extras: ["README.md", "CHANGELOG.md"] ] end From accf071bef679c447a31d0a191227197bec045b2 Mon Sep 17 00:00:00 2001 From: Diederick Lawson Date: Thu, 24 Oct 2024 10:21:34 +0200 Subject: [PATCH 09/14] Bump back to 0.9.0 --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 52ba2b9..1c07761 100644 --- a/mix.exs +++ b/mix.exs @@ -16,7 +16,7 @@ defmodule WuunderUtils.MixProject do start_permanent: Mix.env() == :prod, test_coverage: [tool: ExCoveralls], preferred_cli_env: [coveralls: :test, "coveralls.html": :test], - version: "0.9.0-rc.0" + version: "0.9.0" ] end From 92dff2ca725e24d3d129e6c4bf17f4cff6e617d6 Mon Sep 17 00:00:00 2001 From: Diederick Lawson Date: Thu, 24 Oct 2024 10:22:45 +0200 Subject: [PATCH 10/14] Formatting of changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b843ae..88c4e7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ ## v0.9 (2024-10-24) - ### Backwards incompatible changes * Renamed `WuunderUtils.Maps.delete_field` to `WuunderUtils.Maps.delete` From 3a15f162418a5ca61d1e8fe23bf069446498e37e Mon Sep 17 00:00:00 2001 From: Diederick Lawson Date: Thu, 24 Oct 2024 10:24:08 +0200 Subject: [PATCH 11/14] Fix CI --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6642354..5c80a99 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,7 @@ name: Test and build on: pull_request: + push: branches: - master @@ -35,8 +36,8 @@ jobs: restore-keys: ${{ runner.os }}-mixv2- - name: Setup deps - run: | - mix deps.get + run: | + mix deps.get mix deps.unlock --check-unused - name: Compile @@ -53,7 +54,6 @@ jobs: - name: Cleanup run: mix clean - dialyzer: name: "Run Dialyzer" runs-on: ubuntu-latest @@ -110,8 +110,8 @@ jobs: priv/plts - name: Setup deps - run: | - mix deps.get + run: | + mix deps.get mix deps.unlock --check-unused - name: Compile From d2773cbe256530cc200c73f8c13158e5ccedec7a Mon Sep 17 00:00:00 2001 From: Diederick Lawson Date: Thu, 24 Oct 2024 10:27:10 +0200 Subject: [PATCH 12/14] Only run test (no coveralls) and added Hex docs badge and CI badge --- .github/workflows/ci.yml | 4 +--- README.md | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c80a99..68881db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,9 +47,7 @@ jobs: run: mix format --check-formatted - name: Run Tests - run: mix coveralls.github --warnings-as-errors - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: mix test - name: Cleanup run: mix clean diff --git a/README.md b/README.md index 0cc50cb..fb70987 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Contains a set of modules that makes Elixir development more fun and productive. +[![CI](https://github.com/wuunder/wuunder_utils/actions/workflows/ci.yml/badge.svg)](https://github.com/wuunder/wuunder_utils/actions/workflows/ci.yml)[![Hex.pm](https://img.shields.io/hexpm/v/wuunder_utils.svg)](https://hex.pm/packages/wuunder_utils) + ## Installation If [available in Hex](https://hex.pm/docs/publish), the package can be installed From aedda5f0c3c0f89a4fb4cefcdd08b019a1000051 Mon Sep 17 00:00:00 2001 From: Diederick Lawson Date: Thu, 24 Oct 2024 10:28:30 +0200 Subject: [PATCH 13/14] Fix build badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fb70987..9d15e2c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ Contains a set of modules that makes Elixir development more fun and productive. -[![CI](https://github.com/wuunder/wuunder_utils/actions/workflows/ci.yml/badge.svg)](https://github.com/wuunder/wuunder_utils/actions/workflows/ci.yml)[![Hex.pm](https://img.shields.io/hexpm/v/wuunder_utils.svg)](https://hex.pm/packages/wuunder_utils) +![Build status](https://github.com/wuunder/wuunder_utils/actions/workflows/ci.yml/badge.svg) +[![Hex.pm](https://img.shields.io/hexpm/v/wuunder_utils.svg)](https://hex.pm/packages/wuunder_utils) ## Installation From 706bac7f6602eab7d3cd3476ad301f11cd3ecb04 Mon Sep 17 00:00:00 2001 From: Diederick Lawson Date: Thu, 24 Oct 2024 10:31:14 +0200 Subject: [PATCH 14/14] Update CI --- .github/workflows/ci.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68881db..10136d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,17 @@ name: Test and build on: - pull_request: push: - branches: - - master + branches: ["main"] + pull_request: + branches: ["main"] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: test: