Skip to content

Commit

Permalink
Add ability to assign sub-maps to a variable based on key
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewtimberlake committed Oct 22, 2024
1 parent c8d42f3 commit 42c7fc8
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 10 deletions.
59 changes: 49 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

![](https://github.com/andrewtimberlake/shorthand/actions/workflows/elixir.yml/badge.svg)

Convenience macros to eliminate laborious typing. Provides macros for short map, string keyed map, keyword lists, and structs (ES6 like style)
Shorthand provides macros to create or match against maps and keyword lists with atom or string-based keys.

## Installation

Expand All @@ -18,16 +18,55 @@ end

## Usage

Wherever you would use a map literal, you can use the shorthand macros instead, whether in assignment or as a pattern.

`%{conn: conn}` can become `m(conn)`.

`%{params: %{"email" => email, "password" => password}}` can become `m(params: sm(email, password))`.

`%{foo: _foo}` can become `m(_foo)`.

`%{foo: ^foo}` can become `m(^foo)`.

You can specify the variable name for the key you are destructuring along with shorthand keys, but like normal function calls, they work as keyword lists at the end.

`%{foo: foo, bar: bor, baz: qux}` can become `m(foo, bar, baz: qux)`

`%{foo: bar, baz: baz, qux: qux}` would need to become `m(baz, qux, foo: bar)`

See the [docs](https://hexdocs.pm/shorthand) for more examples

```elixir
defmodule MyModule do
import Shorthand
## Atom keyed maps

defstruct name: nil, age: nil
| Shorthand | Equivalent Elixir |
| ------------------------------------ | --------------------------------------------------------- |
| `m(foo, bar)` | `%{foo: foo, bar: bar}` |
| `m(foo, _bar, ^baz)` | `%{foo: foo, bar: _bar, baz: ^baz}` |
| `m(foo, bar, baz: m(qux))` | `%{foo: foo, bar: bar, baz: %{qux: qux}}` |
| `m(foo, m(baz) = bar, qux: m(quux))` | `%{foo: foo, bar: %{baz: baz} = bar, qux: %{quux: quux}}` |
| `m(foo, bar = m(baz), qux: m(quux))` | `%{foo: foo, bar: %{baz: baz} = bar, qux: %{quux: quux}}` |

def my_func(m(name, age, _height)) do
st(MyModule, name, age)
end
end
```
## String keyed maps
| Shorthand | Equivalent Elixir |
| --------------------------------------- | ----------------------------------------------------------------------------- |
| `sm(foo, bar)` | `%{"foo" => foo, "bar" => bar}` |
| `sm(foo, _bar, ^baz)` | `%{"foo" => foo, "bar" => _bar, "baz" => ^baz}` |
| `sm(foo, bar, baz: sm(qux))` | `%{"foo" => foo, "bar" => bar, "baz" => %{"qux" => qux}}` |
| `sm(foo, sm(baz) = bar, qux: sm(quux))` | `%{"foo" => foo, "bar" => %{"baz" => baz} = bar, "qux" => %{"quux" => quux}}` |
| `sm(foo, bar = sm(baz), qux: sm(quux))` | `%{"foo" => foo, "bar" => %{"baz" => baz} = bar, "qux" => %{"quux" => quux}}` |

## Keyword lists

| Shorthand | Equivalent Elixir |
| --------------------------------------- | ------------------------------------------------------ |
| `kw(foo, bar)` | `[foo: foo, bar: bar]` |
| `kw(foo, _bar, ^baz)` | `[foo: foo, bar: _bar, baz: ^baz]` |
| `kw(foo, bar, baz: kw(qux))` | `[foo: foo, bar: bar, baz: [qux: qux]]` |
| `kw(foo, kw(baz) = bar, qux: kw(quux))` | `[foo: foo, bar: [baz: baz] = bar, qux: [quux: quux]]` |
| `kw(foo, bar = kw(baz), qux: kw(quux))` | `[foo: foo, bar: [baz: baz] = bar, qux: [quux: quux]]` |

## Structs

| Shorthand | Equivalent Elixir |
| ------------------------ | ------------------------------- |
| `st(MyStruct, foo, bar)` | `%MyStruct{foo: foo, bar: bar}` |
32 changes: 32 additions & 0 deletions lib/shorthand.ex
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ defmodule Shorthand do
1
iex> b
2
## Example:
iex> m(m(a, b) = model) = %{model: %{a: 1, b: 2, c: 3, d: 4}}
iex> model
%{a: 1, b: 2, c: 3, d: 4}
iex> a
1
iex> b
2
"""
defmacro m([_ | _] = args) do
build_map(args, :atom)
Expand Down Expand Up @@ -142,6 +152,16 @@ defmodule Shorthand do
1
iex> b
2
## Example:
iex> sm(sm(a, b) = model) = %{"model" => %{"a" => 1, "b" => 2, "c" => 3, "d" => 4}}
iex> model
%{"a" => 1, "b" => 2, "c" => 3, "d" => 4}
iex> a
1
iex> b
2
"""
defmacro sm([_ | _] = args) do
build_map(args, :string)
Expand Down Expand Up @@ -226,19 +246,31 @@ defmodule Shorthand do

args
|> Enum.map(fn
# m(a: 1, ...)
{name, value} ->
{map_key(name, type), value}

# m(^a)
{:^, context1, [{name, context2, nil}]} ->
{map_key(name, type), {:^, context1, [{name, context2, nil}]}}

# m(a)
{name, context, nil} ->
{map_key(variable_name(name), type), {name, context, nil}}

# m(a, b: m(c))
keyword_list when is_list(keyword_list) ->
keyword_list
|> Enum.map(fn {key, value} -> {map_key(key, type), value} end)

# m(m(b) = bar)
{:=, context, [left, {name, _context2, nil} = right]} ->
[{map_key(name, type), {:=, context, [left, right]}}]

# m(bar = m(b))
{:=, context, [{name, _context2, nil} = left, right]} ->
[{map_key(name, type), {:=, context, [left, right]}}]

# other ->
# IO.inspect(other, label: "other")
end)
Expand Down
27 changes: 27 additions & 0 deletions test/shorthand_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,33 @@ defmodule ShorthandTest do
assert m(a: nil) == %{a: nil}
assert m(a: 1) == %{a: 1}
end

test "with nested maps" do
assert m(a: m(b: 1)) == %{a: %{b: 1}}
end

test "with assigned map key" do
assert m(foo, m(b) = bar) = %{foo: :foo, bar: %{b: 1}}
assert foo == :foo
assert bar == %{b: 1}
assert b == 1
end

test "with assigned map key and other keys" do
assert m(foo, m(b) = bar, baz: bz) = %{foo: :foo, bar: %{b: 1}, baz: 3}
assert foo == :foo
assert bar == %{b: 1}
assert b == 1
assert bz == 3
end

test "with assigned map key and other keys (opposite way around)" do
assert m(foo, bar = m(b), baz: bz) = %{foo: :foo, bar: %{b: 1}, baz: 3}
assert foo == :foo
assert bar == %{b: 1}
assert b == 1
assert bz == 3
end
end

describe "sm" do
Expand Down

0 comments on commit 42c7fc8

Please sign in to comment.