Current section
Files
Jump to
Current section
Files
lib/map_rewire.ex
defmodule MapRewire do
@moduledoc """
MapRewire provides functions and operators to bulk rekey maps.
```
iex> %{"id" => "234923409", "title" => "asdf"} <~> ~w(title=>name id=>shopify_id)
{:ok, %{"id" => "234923409", "title" => "asdf"}, %{"shopify_id" => "234923409", "name" => "asdf"}}
```
"""
@typedoc """
The shape of MapRewire transformation rules. These rules may be specified in
one of several ways. Transforms specified as strings are in the form
`left=>right` (note that there are no spaces around the arrow).
1. As a string with multiple transforms separated by whitespace:
```
"title=>name id=>shopify_id"
```
2. As a list of strings with one transform per string:
```
["title=>name", "id=>shopify_id"]
```
3. As any enumerable that iterates as tuples:
```
[title: :name, id: :shopify_id]
[{"title", "name"}, {"id", "shopify_id"}]
%{"title" => :name, "id" => :shopify_id}
```
"""
@type transform_rules ::
String.t()
| list(String.t())
| keyword
| map
| list({String.t() | atom, String.t() | atom})
@debug Application.get_env(:map_rewire, :debug?)
@transform_to "=>"
@no_match "<~>NoMatch<~>" <> Base.encode16(:crypto.strong_rand_bytes(32))
require Logger
defmacro __using__(_) do
quote do
import MapRewire
end
end
@doc """
Remaps the map `content` and replaces the key if it matches with an item in
`list`. This makes `MapRewire.rewire/2` act as an operator.
"""
def data <~> transforms do
rewire(data, transforms, debug: false)
end
@doc """
Remaps the map `content` and replaces the key if it matches with an item in
`list`.
```
iex> MapRewire.rewrite(%{"id"=>"234923409", "title"=>"asdf"}, ~w(title=>name id=>shopify_id))
{:ok, %{"id" => "234923409", "title" => "asdf"}, %{"shopify_id" => "234923409", "name" => "asdf"}}
```
"""
@spec rewire(map, transform_rules, keyword) :: {:ok, old :: map, new :: map}
def rewire(content, rules, options \\ [])
def rewire(content, rules, options)
when is_map(content) and (is_list(rules) or is_binary(rules) or is_map(rules)) do
debug = Keyword.get(options, :debug, @debug)
if debug do
Logger.info("[MapRewire:arg1]rewire#content: #{inspect(content)}")
Logger.info("[MapRewire:arg2]rewire#rules: #{inspect(rules)}")
end
new =
rules
|> normalize_rules(debug)
|> Enum.map(&rewire_entry(&1, content, debug))
|> Enum.reject(&match?({_, @no_match}, &1))
|> Enum.into(%{})
{content, new}
end
def rewire(content, rules, _options) when is_map(content) do
raise ArgumentError,
"[MapRewire:content<~>rules] expected rules to be a list, map, or string." <>
" content: `#{inspect(content)}`, rules: `#{inspect(rules)}`"
end
def rewire(content, _rules, _options) when not is_map(content) do
raise ArgumentError,
"[MapRewire:content<~>rules] expected content to be a map, got `#{inspect(content)}`"
end
def rewire(content, rules, _options) do
raise ArgumentError,
"[MapRewire:content<~>rules] bad arguments. Error reason not known. " <>
"Please check inspections: content: `#{inspect(content)}`, rules: `#{inspect(rules)}`"
end
defp normalize_rules(rules, debug) when is_binary(rules) do
if(debug, do: Logger.info("[MapRewire]normalize_rules#rules (String): #{inspect(rules)}"))
rules
|> String.split(~r/\s/)
|> Enum.map(&normalize_rule(&1, debug))
end
defp normalize_rules(rules, debug) when is_list(rules) do
if(debug, do: Logger.info("[MapRewire]normalize_rules#rules (List): #{inspect(rules)}"))
Enum.map(rules, &normalize_rule(&1, debug))
end
defp normalize_rules(rules, debug) when is_map(rules) do
if(debug, do: Logger.info("[MapRewire]normalize_rules#rules (Map): #{inspect(rules)}"))
Enum.to_list(rules)
end
defp normalize_rule({_old, _new} = rule, debug) do
if(debug, do: Logger.info("[MapRewire]normalize_rule#rule (Tuple): #{inspect(rule)}"))
rule
end
defp normalize_rule(rule, debug) when is_binary(rule) do
if(debug, do: Logger.info("[MapRewire]normalize_rule#rule (String): #{inspect(rule)}"))
List.to_tuple(String.split(rule, @transform_to))
end
defp normalize_rule(rule, _) when is_list(rule) and length(rule) != 2 do
raise ArgumentError,
"[MapRewire:content<~>rules] bad argument: invalid rule format #{inspect(rule)}"
end
defp normalize_rule(rule, debug) when is_list(rule) do
if(debug, do: Logger.info("[MapRewire]normalize_rule#rule (List-2): #{inspect(rule)}"))
List.to_tuple(rule)
end
defp normalize_rule(rule, _) do
raise ArgumentError,
"[MapRewire:content<~>rules] bad argument: invalid rule format #{inspect(rule)}"
end
defp rewire_entry({old, new}, map, debug) do
if(debug, do: Logger.info("[MapRewire]rewire_entry: from #{old} to #{new}"))
{new, Map.get(map, old, @no_match)}
end
end