From 42c7fc8eab4d6ab04c6e1597ee83304c88a27dd3 Mon Sep 17 00:00:00 2001 From: Andrew Timberlake Date: Tue, 22 Oct 2024 08:38:33 +0200 Subject: [PATCH] Add ability to assign sub-maps to a variable based on key --- README.md | 59 ++++++++++++++++++++++++++++++++++------- lib/shorthand.ex | 32 ++++++++++++++++++++++ test/shorthand_test.exs | 27 +++++++++++++++++++ 3 files changed, 108 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 75cb109..26c670d 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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}` | diff --git a/lib/shorthand.ex b/lib/shorthand.ex index 0d5dea2..837a4c1 100644 --- a/lib/shorthand.ex +++ b/lib/shorthand.ex @@ -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) @@ -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) @@ -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) diff --git a/test/shorthand_test.exs b/test/shorthand_test.exs index 9d7e220..23eb134 100644 --- a/test/shorthand_test.exs +++ b/test/shorthand_test.exs @@ -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