defmodule Digger.Core do @moduledoc false alias Digger.Opts.Atomizer alias Digger.Opts.CamelCaser alias Digger.Opts.Dasher alias Digger.Opts.LowerCaser alias Digger.Opts.SnakeCaser alias Digger.Opts.Stringifier alias Digger.Opts.UpperCaser @opts_modules %{ atomize: Atomizer, camel_case: CamelCaser, dasherize: Dasher, lowercase_first: LowerCaser, snake_case: SnakeCaser, stringify: Stringifier, upcase_first: UpperCaser } def resolve_opts(operation, opts), do: Map.fetch!(@opts_modules, operation).set_options(opts) # Map def map(map, operation, opts) do resolved = resolve_opts(operation, opts) key_transform = Keyword.get(resolved, :key_transform) value_transform = Keyword.get(resolved, :value_transform) extra = Keyword.take(resolved, [:existing]) Enum.reduce(map, %{}, fn {key, value}, acc -> new_key = apply(Digger, operation, [ key, [type: :key, key_transform: key_transform, value_transform: value_transform] ++ extra ]) new_value = apply(Digger, operation, [ value, [type: :value, key_transform: key_transform, value_transform: value_transform] ++ extra ]) Map.put(acc, new_key, new_value) end) end # List def list([], _operation, _opts), do: [] def list([head | tail], operation, opts) do resolved = resolve_opts(operation, opts) [apply(Digger, operation, [head, resolved])] ++ apply(Digger, operation, [tail, resolved]) end # Atom def atom(atom, :atomize, _opts), do: atom def atom(atom, :dasherize, _opts), do: atom def atom(atom, :camel_case, opts) do opts = CamelCaser.set_options(opts) atom |> to_string() |> Digger.camel_case(opts) |> String.to_atom() end def atom(atom, :lowercase_first, opts), do: atom_lower(atom, LowerCaser.set_options(opts)) def atom(atom, :snake_case, opts), do: atom_snake(atom, SnakeCaser.set_options(opts)) def atom(atom, :stringify, opts), do: atom_to_string(atom, Stringifier.set_options(opts)) def atom(atom, :upcase_first, opts), do: atom_upper(atom, UpperCaser.set_options(opts)) defp atom_upper(atom, type: :key, key_transform: :upper, value_transform: _) do atom |> to_string |> Digger.upcase_first() |> String.to_atom() end defp atom_upper(atom, type: :value, key_transform: _, value_transform: :upper) do atom |> to_string |> Digger.upcase_first() |> String.to_atom() end defp atom_upper(atom, _opts), do: atom defp atom_lower(atom, [type: :key, key_transform: :lower, value_transform: _] = opts), do: atom |> to_string |> Digger.lowercase_first(opts) |> String.to_atom() defp atom_lower(atom, [type: :value, key_transform: _, value_transform: :lower] = opts), do: atom |> to_string |> Digger.lowercase_first(opts) |> String.to_atom() defp atom_lower(atom, _), do: atom defp atom_snake(atom, [type: :key, key_transform: :snake, value_transform: _] = opts) do atom |> to_string |> Digger.snake_case(opts) |> String.to_atom() end defp atom_snake(atom, [type: :value, key_transform: _, value_transform: :snake] = opts) do atom |> to_string |> Digger.snake_case(opts) |> String.to_atom() end defp atom_snake(atom, _opts), do: atom defp atom_to_string(atom, type: :key, key_transform: :stringify, value_transform: _), do: atom |> Atom.to_string() defp atom_to_string(atom, _), do: atom # String def string(string, :atomize, opts), do: string_atomicize(string, Atomizer.set_options(opts)) def string(string, :camel_case, opts), do: string_camelize(string, CamelCaser.set_options(opts)) def string(string, :dasherize, opts), do: string_to_dash(string, Dasher.set_options(opts)) def string(string, :lowercase_first, opts), do: string_lower_first(string, LowerCaser.set_options(opts)) def string(string, :snake_case, opts), do: string_to_snake(string, SnakeCaser.set_options(opts)) def string(any, :stringify, _opts), do: any def string(string, :upcase_first, opts), do: string_uppercase_first_for_upcase(string, UpperCaser.set_options(opts)) defp string_atomicize(string, opts) do type = Keyword.get(opts, :type) key_transform = Keyword.get(opts, :key_transform) value_transform = Keyword.get(opts, :value_transform) existing = Keyword.get(opts, :existing, false) cond do type == :key and key_transform == :atomize -> to_atom(string, existing) type == :value and value_transform == :atomize -> to_atom(string, existing) true -> string end end defp to_atom(string, false), do: String.to_atom(string) defp to_atom(string, true) do String.to_existing_atom(string) rescue ArgumentError -> string end defp string_camelize(string, type: :key, key_transform: :none, value_transform: _), do: string defp string_camelize(string, type: :value, key_transform: _, value_transform: :none), do: string defp string_camelize(string, type: :key, key_transform: key_transform, value_transform: _) do string |> String.split(~r/[-_\/]+/, include_captures: false) |> Enum.map_join(&string_uppercase_first_for_camelize(&1, :upper)) |> string_uppercase_first_for_camelize(key_transform) end defp string_camelize(string, type: :value, key_transform: _, value_transform: value_transform) do string |> String.split(~r/[-_\/]+/, include_captures: false) |> Enum.map_join(&string_uppercase_first_for_camelize(&1, :upper)) |> string_uppercase_first_for_camelize(value_transform) end defp string_uppercase_first_for_camelize(string, :upper) do remainder = String.slice(string, 1..-1//1) string |> String.first() |> String.capitalize() |> Kernel.<>(remainder) end defp string_uppercase_first_for_camelize(string, _first_letter) do remainder = String.slice(string, 1..-1//1) string |> String.first() |> String.downcase() |> Kernel.<>(remainder) end defp string_to_dash(string, type: :key, key_transform: :dasherize, value_transform: _), do: string |> String.replace(~r/_/, "-") defp string_to_dash(string, type: :value, key_transform: _, value_transform: :dasherize), do: string |> String.replace(~r/_/, "-") defp string_to_dash(string, _), do: string defp string_lower_first(string, type: :key, key_transform: :lower, value_transform: _) do string |> string_to_lower() end defp string_lower_first(string, type: :value, key_transform: _, value_transform: :lower) do string |> string_to_lower() end defp string_lower_first(string, _), do: string defp string_to_lower(string) do remainder = String.slice(string, 1..-1//1) string |> String.first() |> String.downcase() |> Kernel.<>(remainder) end defp string_to_snake(string, type: :key, key_transform: :snake, value_transform: _) do string |> String.replace(~r/([A-Z\d]+)([A-Z][a-z])/, "\\1_\\2") |> String.replace(~r/([a-z\d])([A-Z])/, "\\1_\\2") |> String.replace(~r/-/, "_") |> String.downcase() end defp string_to_snake(string, type: :value, key_transform: _, value_transform: :snake) do string |> String.replace(~r/([A-Z\d]+)([A-Z][a-z])/, "\\1_\\2") |> String.replace(~r/([a-z\d])([A-Z])/, "\\1_\\2") |> String.replace(~r/-/, "_") |> String.downcase() end defp string_to_snake(string, _), do: string defp string_uppercase_first_for_upcase( string, type: :value, key_transform: _, value_transform: :upper ) do remainder = String.slice(string, 1..-1//1) string |> String.first() |> String.capitalize() |> Kernel.<>(remainder) end defp string_uppercase_first_for_upcase( string, type: :key, key_transform: :upper, value_transform: _ ) do remainder = String.slice(string, 1..-1//1) string |> String.first() |> String.capitalize() |> Kernel.<>(remainder) end defp string_uppercase_first_for_upcase(string, _), do: string # Integer & Float share identical numeric logic for atomize/stringify; the # other 5 ops are unconditional passthroughs for both types. @numeric_passthrough_ops [:camel_case, :dasherize, :lowercase_first, :snake_case, :upcase_first] def integer(integer, :atomize, opts), do: numeric_atomize(integer, Atomizer.set_options(opts)) def integer(any, op, _opts) when op in @numeric_passthrough_ops, do: any def integer(integer, :stringify, opts), do: numeric_stringify(integer, Stringifier.set_options(opts), &Integer.to_string/1) def float(float, :atomize, opts), do: numeric_atomize(float, Atomizer.set_options(opts)) def float(any, op, _opts) when op in @numeric_passthrough_ops, do: any def float(float, :stringify, opts), do: numeric_stringify(float, Stringifier.set_options(opts), &Float.to_string/1) defp numeric_atomize(number, opts) do type = Keyword.get(opts, :type) key_transform = Keyword.get(opts, :key_transform) value_transform = Keyword.get(opts, :value_transform) existing = Keyword.get(opts, :existing, false) cond do type == :key and key_transform == :atomize -> number |> to_string() |> to_atom(existing) type == :value and value_transform == :atomize -> number |> to_string() |> to_atom(existing) true -> number end end defp numeric_stringify( number, [type: :key, key_transform: :stringify, value_transform: _], to_string_fn ), do: to_string_fn.(number) defp numeric_stringify( number, [type: :value, key_transform: _, value_transform: :stringify], to_string_fn ), do: to_string_fn.(number) defp numeric_stringify(number, _opts, _to_string_fn), do: number end