Packages

Convenience macros to eliminate laborious typing. Provides macros for short map, string keyed map, keyword lists, and structs (ES6 like style)

Current section

Files

Jump to
shorthand lib shorthand.ex
Raw

lib/shorthand.ex

defmodule Shorthand do
@moduledoc """
Convenience macros to eliminate laborious typing. Provides macros for short map, string key'd map, keyword lists, and structs (ES6 like style)
## Examples:
These examples use variable arguments (default is 10, see configuration below)
Instead of `%{one: one, two: two, three: three}`, you can type `m(one, two, three)`
Instead of `my_func(one: one, two: two, three: three)`, you can type `my_func(kw(one, two, three))`
Instead of `%MyStruct(one: one, two: two, three: three)`, you can type `st(MyStruct, one, two, three)`
### Without variable arguemnts,
Instead of `%{one: one, two: two, three: three}`, you can type `m([one, two, three])`
Instead of `my_func(one: one, two: two, three: three)`, you can type `my_func(kw([one, two, three]))`
Instead of `%MyStruct(one: one, two: two, three: three)`, you can type `bulid_struct(MyStruct, [one, two, three])`
## Configuration
For the convenience of `m(a, b, c, d, e, f, g, h, i, j)` instead of `m([a, b, c, d, e, f, g, h, i, j])` `Shorthand` generates multiple copies of each macro like m/1, m/2, m/3, …, m/10. You can configure how many of these "variable argument" macros are generated.
config :shorthand,
variable_args: 10 # false to remove variable arguemnts
## Usage
You can import the `Shorthand` module and call each macro
defmodule MyModule do
import Shorthand
def my_func(m(a, b)) do
kw(a, b)
end
end
or you can require the module and prefix calls with the module name (example uses a module alias to keep the typing low
defmodule MyModule do
require Shorthand, as: S
def my_func(S.m(a, b)) do
S.kw(a, b)
end
end
## Phoenix Examples
defmodule MyController do
# ...
def index(conn, sm(id)) do
model = Repo.get(MyModel, id)
# ...
end
def create(conn, sm(my_model: sm(first_name, last_name) = params)) do
changeset = MyModel.changeset(%MyModel{}, params) # params contains all form fields, not just first_name and last_name
# ...
conn
|> put_flash(:notice, "User \#{first_name} \#{last_name} was created successfully")
# ...
end
end
"""
@doc ~S"""
Builds a map where the keys and values have the same name
## Example:
iex> a = 1
iex> b = 2
iex> m(a, b)
%{a: 1, b: 2}
## Example:
iex> a = 1
iex> c = 2
iex> m(a, b: m(c), d: nil)
%{a: 1, b: %{c: 2}, d: nil}
## Example:
iex> a = 1
iex> m(^a, _b, c) = %{a: 1, b: 3, c: 2}
iex> c
2
iex> match?(m(^a), %{a: 2})
false
## Example:
iex> m(model: m(a, b) = params) = %{model: %{a: 1, b: 2, c: 3, d: 4}}
iex> params
%{a: 1, b: 2, c: 3, d: 4}
iex> a
1
iex> b
2
"""
defmacro m([_ | _] = args) do
build_map(args, :atom)
end
@doc ~S"""
Builds a map where the string keys and values have the same name
## Example:
iex> a = 1
iex> b = 2
iex> sm(a, b)
%{"a" => 1, "b" => 2}
## Example:
iex> a = 1
iex> b = 2
iex> sm(a, other: sm(b))
%{"a" => 1, "other" => %{"b" => 2}}
## Example:
iex> a = 1
iex> sm(^a, _b, c) = %{"a" => 1, "b" => 3, "c" => 2}
iex> c
2
iex> match?(sm(^a), %{"a" => 2})
false
## Example:
iex> sm(model: sm(a, b) = params) = %{"model" => %{"a" => 1, "b" => 2, "c" => 3, "d" => 4}}
iex> params
%{"a" => 1, "b" => 2, "c" => 3, "d" => 4}
iex> a
1
iex> b
2
"""
defmacro sm([_ | _] = args) do
build_map(args, :string)
end
@doc ~S"""
Builds a keyword list where the keys and value arguments are the same name
## Example:
iex> a = 1
iex> b = 2
iex> c = 3
iex> kw(a, b, c)
[a: 1, b: 2, c: 3]
## Examples
iex> c = 3
iex> kw(a, _b, ^c) = [a: 1, b: 3, c: 3]
iex> a
1
iex> match?(kw(^a), [a: 2])
false
"""
defmacro kw([_ | _] = args) do
build_keywords(args)
end
@doc ~S"""
Builds a struct where the field names are the same as the arguments supplied
## Example:
iex> scheme = "https"
iex> host = "elixir-lang.org"
iex> path = "/docs.html"
iex> st(URI, scheme, host, path)
%URI{scheme: "https", host: "elixir-lang.org", path: "/docs.html"}
"""
defmacro st(module, [_ | _] = args) do
build_struct_from_list(module, args)
end
variable_args = Application.get_env(:shorthand, :variable_args, 10)
if variable_args do
1..variable_args
|> Enum.each(fn i ->
args = 1..i |> Enum.map(fn i -> {:"arg#{i}", [], nil} end)
defmacro m(unquote_splicing(args)) do
build_map(unquote(args), :atom)
end
defmacro sm(unquote_splicing(args)) do
build_map(unquote(args), :string)
end
defmacro kw(unquote_splicing(args)) do
build_keywords(unquote(args))
end
defmacro st(module, unquote_splicing(args)) do
build_struct_from_list(module, unquote(args))
end
end)
end
defp build_map(args, type) do
{:%{}, [], parse_args(args, type)}
end
defp build_keywords(args) do
quote do
unquote(parse_args(args, :atom))
end
end
defp parse_args(args, type) do
# IO.inspect(args, label: "args")
args
|> Enum.map(fn
{name, value} ->
{map_key(name, type), value}
{:^, context, [{name, context, nil}]} ->
{map_key(name, type), {:^, context, [{name, context, nil}]}}
{name, context, nil} ->
{map_key(variable_name(name), type), {name, context, nil}}
keyword_list when is_list(keyword_list) ->
keyword_list
|> Enum.map(fn {key, value} -> {map_key(key, type), value} end)
# other ->
# IO.inspect(other, label: "other")
end)
|> List.flatten()
end
defp build_struct_from_list(module, args) do
quote do
struct(unquote(module), unquote(build_keywords(args)))
end
end
defp map_key(key, :atom) when is_atom(key), do: key
defp map_key(key, :string) when is_atom(key), do: to_string(key)
defp variable_name(name) when is_atom(name), do: variable_name(Atom.to_string(name))
defp variable_name(<<"_", name::binary>>), do: variable_name(name)
defp variable_name(name), do: String.to_atom(name)
end