Current section
Files
Jump to
Current section
Files
lib/cozy_case.ex
defmodule CozyCase do
@moduledoc ~S"""
Convert data between common naming conventions, such as snake case, kebab case, camel case and
pascal case.
Currently, this module provides these main functions:
+ `snake_case/1`
+ `kebab_case/1`
+ `camel_case/1`
+ `pascal_case/1`
## Common naming conventions
| name | example |
| ----------- | ----------------- |
| snake case | `welcome_message` |
| kebab case | `welcome-message` |
| camel case | `welcomeMessage` |
| pascal case | `WelcomeMessage` |
Read [Naming convention - Examples of multiple-word identifier formats](https://en.wikipedia.org/w/index.php?title=Naming_convention_(programming)&oldid=1126175049#Examples_of_multiple-word_identifier_formats) for more multiple-word identifier formats.
## Examples
All these functions have the same intefaces.
For strings or atoms, these functions convert them directly:
iex> CozyCase.snake_case("HelloWorld")
"hello_world"
iex> CozyCase.snake_case(HelloWorld)
"hello_world"
For maps, there functions convert the keys of maps recursively, without touching the values
of maps:
iex> CozyCase.snake_case(%{
iex> "FamilyMembers" => [
iex> %{
iex> "Name" => "Lily",
iex> "Age" => 50,
iex> "Hobbies" => ["Dreaming", "Singing"]
iex> },
iex> %{
iex> "Name" => "Charlie",
iex> "Age" => 55,
iex> "Hobbies" => ["Dreaming", "Singing"]
iex> }
iex> ]
iex> })
%{
"family_members" => [
%{"name" => "Lily", "age" => 50, "hobbies" => ["Dreaming", "Singing"]},
%{"name" => "Charlie", "age" => 55, "hobbies" => ["Dreaming", "Singing"]}
]
}
For lists, these functions convert the keys of maps in lists recursively:
iex> CozyCase.snake_case([
iex> %{
iex> "Name" => "Lily",
iex> "Age" => 50,
iex> "Hobbies" => ["Dreaming", "Singing"]
iex> },
iex> %{
iex> "Name" => "Charlie",
iex> "Age" => 55,
iex> "Hobbies" => ["Dreaming", "Singing"]
iex> }
iex> ])
[
%{"name" => "Lily", "age" => 50, "hobbies" => ["Dreaming", "Singing"]},
%{"name" => "Charlie", "age" => 55, "hobbies" => ["Dreaming", "Singing"]}
]
## Integrate with other packages
`CozyCase` doesn't provide higher-level wrappers. Because everything is a function, it's very
easy to integrate `CozyCase` with other packages.
### [`plug`](https://hexdocs.pm/plug)
Converts params in `%Plug.Conn{}` to snake case:
defmodule DemoWeb.Plug.SnakeCaseParams do
@behaviour Plug
@impl true
def init(opts), do: opts
@impl true
def call(%{params: params} = conn, _opts) do
%{conn | params: CozyCase.snake_case(params)}
end
end
### [`jason`](https://hexdocs.pm/jason)
Decode a JSON string:
iex> json = "{\"Age\":23,\"FamilyMembers\":[],\"Name\":\"Lenna\"}"
iex> Jason.decode!(json, keys: &CozyCase.snake_case/1)
%{"name" => "Lenna", "age" => 23, "family_members" => []}
Encode a map as a JSON string:
iex> map = %{"name" => "Lenna", "age" => 23, "family_members" => []}
iex> map |> CozyCase.camel_case() |> Jason.encode!()
"{\"age\":23,\"familyMembers\":[],\"name\":\"Lenna\"}"
"""
alias CozyCase.SnakeCase
alias CozyCase.KebabCase
alias CozyCase.CamelCase
alias CozyCase.PascalCase
@type accepted_data_types() :: String.t() | atom() | map() | list()
@spec snake_case(accepted_data_types()) :: String.t()
def snake_case(term) when is_binary(term) or is_atom(term), do: convert_plain(term, &SnakeCase.convert/1)
def snake_case(term) when is_map(term) or is_list(term), do: convert_nest(term, &SnakeCase.convert/1)
@spec kebab_case(accepted_data_types()) :: String.t()
def kebab_case(term) when is_binary(term) or is_atom(term), do: convert_plain(term, &KebabCase.convert/1)
def kebab_case(term) when is_map(term) or is_list(term), do: convert_nest(term, &KebabCase.convert/1)
@spec camel_case(accepted_data_types()) :: String.t()
def camel_case(term) when is_binary(term) or is_atom(term), do: convert_plain(term, &CamelCase.convert/1)
def camel_case(term) when is_map(term) or is_list(term), do: convert_nest(term, &CamelCase.convert/1)
@spec pascal_case(accepted_data_types()) :: String.t()
def pascal_case(term) when is_binary(term) or is_atom(term), do: convert_plain(term, &PascalCase.convert/1)
def pascal_case(term) when is_map(term) or is_list(term), do: convert_nest(term, &PascalCase.convert/1)
defp convert_plain(string, fun) when is_binary(string) do
fun.(string)
end
defp convert_plain(atom, fun) when is_atom(atom) do
Atom.to_string(atom)
|> case do
"Elixir." <> rest -> rest
string -> string
end
|> fun.()
end
defp convert_plain(any, _fun), do: any
defp convert_nest(map, fun) when is_map(map) do
try do
for {k, v} <- map,
into: %{},
do: {convert_plain(k, fun), convert_nest(v, fun)}
rescue
# not Enumerable
Protocol.UndefinedError -> map
end
end
defp convert_nest(list, fun) when is_list(list) do
Enum.map(list, &convert_nest(&1, fun))
end
defp convert_nest(any, _fun), do: any
end