├── test ├── test_helper.exs └── shorthand_test.exs ├── .formatter.exs ├── config └── config.exs ├── .gitignore ├── mix.exs ├── .github └── workflows │ └── elixir.yml ├── mix.lock ├── README.md └── lib └── shorthand.ex /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | if Mix.env() == :docs do 4 | config :shorthand, variable_args: false 5 | end 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | shorthand-*.tar 24 | 25 | .vscode -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Shorthand.MixProject do 2 | use Mix.Project 3 | 4 | @version "1.2.0" 5 | @github_url "https://github.com/andrewtimberlake/shorthand" 6 | 7 | def project do 8 | [ 9 | app: :shorthand, 10 | name: "Shorthand", 11 | description: """ 12 | Convenience macros to eliminate laborious typing. Provides macros for short map, string keyed map, keyword lists, and structs (ES6 like style) 13 | """, 14 | license: "MIT", 15 | version: @version, 16 | elixir: "~> 1.10", 17 | start_permanent: Mix.env() == :prod, 18 | deps: deps(), 19 | source_url: @github_url, 20 | docs: fn -> 21 | [ 22 | source_ref: @version, 23 | canonical: "https://hexdocs.pm/shorthand", 24 | main: "Shorthand", 25 | source_url: @github_url, 26 | extras: ["README.md"] 27 | ] 28 | end, 29 | package: [ 30 | maintainers: ["Andrew Timberlake"], 31 | contributors: ["Andrew Timberlake"], 32 | licenses: ["MIT"], 33 | links: %{"GitHub" => @github_url} 34 | ] 35 | ] 36 | end 37 | 38 | def application do 39 | [ 40 | # extra_applications: [:logger] 41 | ] 42 | end 43 | 44 | defp deps do 45 | [{:ex_doc, ">= 0.0.0", only: :dev, runtime: false}] 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /.github/workflows/elixir.yml: -------------------------------------------------------------------------------- 1 | name: Elixir CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | env: 10 | MIX_ENV: test 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | name: Build and test 18 | runs-on: ubuntu-latest 19 | 20 | strategy: 21 | matrix: 22 | elixir: ['1.14.5', '1.15.4', '1.16.3', '1.17.3'] 23 | erlang: ['24.3', '25.3', '26.0', '27.1'] 24 | exclude: 25 | - elixir: '1.14.5' 26 | erlang: '27.1' 27 | - elixir: '1.15.4' 28 | erlang: '27.1' 29 | - elixir: '1.16.3' 30 | erlang: '27.1' 31 | - elixir: '1.17.3' 32 | erlang: '24.3' 33 | steps: 34 | - uses: actions/checkout@v3 35 | - name: Set up Elixir 36 | uses: erlef/setup-beam@v1 37 | with: 38 | version-type: 'loose' 39 | elixir-version: ${{ matrix.elixir }} 40 | otp-version: ${{ matrix.erlang }} 41 | - name: Restore dependencies cache 42 | uses: actions/cache@v3 43 | with: 44 | path: deps 45 | key: ${{ runner.os }}-${{ matrix.erlang }}-${{ matrix.elixir }}-mix-${{ hashFiles('**/mix.lock') }} 46 | restore-keys: ${{ runner.os }}-${{ matrix.erlang }}-${{ matrix.elixir }}-mix- 47 | - name: Install dependencies 48 | run: mix deps.get 49 | - name: Run tests 50 | run: mix test 51 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm", "c57508ddad47dfb8038ca6de1e616e66e9b87313220ac5d9817bc4a4dc2257b9"}, 3 | "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, 4 | "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"}, 5 | "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, 6 | "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, 7 | "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, 8 | "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, 9 | } 10 | -------------------------------------------------------------------------------- /test/shorthand_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ShorthandTest do 2 | use ExUnit.Case 3 | import Shorthand 4 | doctest Shorthand 5 | 6 | defmodule TestModule do 7 | def test_map_func(m(a, b)) do 8 | kw(a, b) 9 | end 10 | 11 | def test_string_map_func(sm(a, b)) do 12 | kw(a, b) 13 | end 14 | 15 | def index(_conn, sm(model: sm(a, b) = params)) do 16 | kw(params, a, b) 17 | end 18 | end 19 | 20 | describe "function arguments" do 21 | test "function with map arguments" do 22 | assert TestModule.test_map_func(%{a: 1, b: 2}) == [a: 1, b: 2] 23 | end 24 | 25 | test "function with string key map arguments" do 26 | assert TestModule.test_string_map_func(%{"a" => 1, "b" => 2}) == [a: 1, b: 2] 27 | end 28 | 29 | test "phoenix style action functions" do 30 | assert TestModule.index(nil, %{"model" => %{"a" => 1, "b" => 2, "c" => 3}}) == [ 31 | params: %{"a" => 1, "b" => 2, "c" => 3}, 32 | a: 1, 33 | b: 2 34 | ] 35 | end 36 | end 37 | 38 | describe "m" do 39 | test "with a single keyword argument" do 40 | assert m(a: nil) == %{a: nil} 41 | assert m(a: 1) == %{a: 1} 42 | end 43 | 44 | test "with nested maps" do 45 | assert m(a: m(b: 1)) == %{a: %{b: 1}} 46 | end 47 | 48 | test "with assigned map key" do 49 | assert m(foo, m(b) = bar) = %{foo: :foo, bar: %{b: 1}} 50 | assert foo == :foo 51 | assert bar == %{b: 1} 52 | assert b == 1 53 | end 54 | 55 | test "with assigned map key and other keys" do 56 | assert m(foo, m(b) = bar, baz: bz) = %{foo: :foo, bar: %{b: 1}, baz: 3} 57 | assert foo == :foo 58 | assert bar == %{b: 1} 59 | assert b == 1 60 | assert bz == 3 61 | end 62 | 63 | test "with assigned map key and other keys (opposite way around)" do 64 | assert m(foo, bar = m(b), baz: bz) = %{foo: :foo, bar: %{b: 1}, baz: 3} 65 | assert foo == :foo 66 | assert bar == %{b: 1} 67 | assert b == 1 68 | assert bz == 3 69 | end 70 | end 71 | 72 | describe "sm" do 73 | test "with a single keyword argument" do 74 | assert sm(a: nil) == %{"a" => nil} 75 | assert sm(a: 1) == %{"a" => 1} 76 | end 77 | end 78 | 79 | describe "st" do 80 | defmodule TestStruct do 81 | defstruct a: 1, b: 2, foo: nil, bar: nil, baz: nil 82 | end 83 | 84 | test "with no argument" do 85 | assert st(TestStruct) == %TestStruct{} 86 | end 87 | 88 | test "with a single keyword argument" do 89 | assert st(TestStruct, a: nil) == %TestStruct{a: nil} 90 | assert st(TestStruct, a: 1) == %TestStruct{a: 1} 91 | end 92 | 93 | test "with nested maps" do 94 | assert st(TestStruct, a: st(TestStruct, b: 1)) == %TestStruct{a: %TestStruct{b: 1}} 95 | end 96 | 97 | test "with assigned map key" do 98 | assert st(TestStruct, foo, st(TestStruct, b) = bar) = %TestStruct{ 99 | foo: :foo, 100 | bar: %TestStruct{b: 1} 101 | } 102 | 103 | assert foo == :foo 104 | assert bar == %TestStruct{b: 1} 105 | assert b == 1 106 | end 107 | 108 | test "with assigned map key and other keys" do 109 | assert st(TestStruct, foo, st(TestStruct, b) = bar, baz: bz) = %TestStruct{ 110 | foo: :foo, 111 | bar: %TestStruct{b: 1}, 112 | baz: 3 113 | } 114 | 115 | assert foo == :foo 116 | assert bar == %TestStruct{b: 1} 117 | assert b == 1 118 | assert bz == 3 119 | end 120 | 121 | test "with assigned map key and other keys (opposite way around)" do 122 | assert st(TestStruct, foo, bar = st(TestStruct, b), baz: bz) = %TestStruct{ 123 | foo: :foo, 124 | bar: %TestStruct{b: 1}, 125 | baz: 3 126 | } 127 | 128 | assert foo == :foo 129 | assert bar == %TestStruct{b: 1} 130 | assert b == 1 131 | assert bz == 3 132 | end 133 | 134 | test "with matching" do 135 | a = 4 136 | foo = :bar 137 | assert st(TestStruct, ^a, ^foo) = %TestStruct{a: 4, foo: :bar} 138 | assert st(TestStruct, ^a, _b, ^foo) = %TestStruct{a: 4, foo: :bar} 139 | assert st(TestStruct, ^a, _b, ^foo, baz: bz) = %TestStruct{a: 4, foo: :bar, baz: 42} 140 | assert bz == 42 141 | end 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shorthand 2 | 3 | ![](https://github.com/andrewtimberlake/shorthand/actions/workflows/elixir.yml/badge.svg) 4 | 5 | Shorthand provides macros to create or match against maps and keyword lists with atom or string-based keys. 6 | 7 | ## Installation 8 | 9 | Add `shorthand` as a dependency in your project in your `mix.exs` file: 10 | 11 | ```elixir 12 | def deps do 13 | [ 14 | {:shorthand, "~> 1.0"} 15 | ] 16 | end 17 | ``` 18 | 19 | ## Usage 20 | 21 | Wherever you would use a map literal, you can use the shorthand macros instead, whether in assignment or as a pattern. 22 | 23 | `%{conn: conn}` can become `m(conn)`. 24 | 25 | `%{params: %{"email" => email, "password" => password}}` can become `m(params: sm(email, password))`. 26 | 27 | `%{foo: _foo}` can become `m(_foo)`. 28 | 29 | `%{foo: ^foo}` can become `m(^foo)`. 30 | 31 | 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. 32 | 33 | `%{foo: foo, bar: bor, baz: qux}` can become `m(foo, bar, baz: qux)` 34 | 35 | `%{foo: bar, baz: baz, qux: qux}` would need to become `m(baz, qux, foo: bar)` 36 | 37 | See the [docs](https://hexdocs.pm/shorthand) for more examples 38 | 39 | ## Atom keyed maps 40 | 41 | | Shorthand | Equivalent Elixir | 42 | | ------------------------------------ | --------------------------------------------------------- | 43 | | `m(foo, bar)` | `%{foo: foo, bar: bar}` | 44 | | `m(foo, _bar, ^baz)` | `%{foo: foo, bar: _bar, baz: ^baz}` | 45 | | `m(foo, bar, baz: m(qux))` | `%{foo: foo, bar: bar, baz: %{qux: qux}}` | 46 | | `m(foo, m(baz) = bar, qux: m(quux))` | `%{foo: foo, bar: %{baz: baz} = bar, qux: %{quux: quux}}` | 47 | | `m(foo, bar = m(baz), qux: m(quux))` | `%{foo: foo, bar: %{baz: baz} = bar, qux: %{quux: quux}}` | 48 | 49 | ## String keyed maps 50 | | Shorthand | Equivalent Elixir | 51 | | --------------------------------------- | ----------------------------------------------------------------------------- | 52 | | `sm(foo, bar)` | `%{"foo" => foo, "bar" => bar}` | 53 | | `sm(foo, _bar, ^baz)` | `%{"foo" => foo, "bar" => _bar, "baz" => ^baz}` | 54 | | `sm(foo, bar, baz: sm(qux))` | `%{"foo" => foo, "bar" => bar, "baz" => %{"qux" => qux}}` | 55 | | `sm(foo, sm(baz) = bar, qux: sm(quux))` | `%{"foo" => foo, "bar" => %{"baz" => baz} = bar, "qux" => %{"quux" => quux}}` | 56 | | `sm(foo, bar = sm(baz), qux: sm(quux))` | `%{"foo" => foo, "bar" => %{"baz" => baz} = bar, "qux" => %{"quux" => quux}}` | 57 | 58 | ## Keyword lists 59 | 60 | | Shorthand | Equivalent Elixir | 61 | | --------------------------------------- | ------------------------------------------------------ | 62 | | `kw(foo, bar)` | `[foo: foo, bar: bar]` | 63 | | `kw(foo, _bar, ^baz)` | `[foo: foo, bar: _bar, baz: ^baz]` | 64 | | `kw(foo, bar, baz: kw(qux))` | `[foo: foo, bar: bar, baz: [qux: qux]]` | 65 | | `kw(foo, kw(baz) = bar, qux: kw(quux))` | `[foo: foo, bar: [baz: baz] = bar, qux: [quux: quux]]` | 66 | | `kw(foo, bar = kw(baz), qux: kw(quux))` | `[foo: foo, bar: [baz: baz] = bar, qux: [quux: quux]]` | 67 | 68 | ## Structs 69 | 70 | | Shorthand | Equivalent Elixir | 71 | | ------------------------------------------------ | ----------------------------------------------------------------- | 72 | | `st(MyStruct, foo, bar)` | `%MyStruct{foo: foo, bar: bar}` | 73 | | `st(MyStruct, foo, _bar, ^baz)` | `%MyStruct{foo: foo, bar: _bar, baz: ^baz}` | 74 | | `st(MyStruct, foo, bar, baz: st(MyStruct, qux))` | `%MyStruct{foo: foo, bar: bar, baz: %MyStruct{qux: qux}}` | 75 | | `st(MyStruct, foo, m(baz) = bar, qux: m(quux))` | `%MyStruct{foo: foo, bar: %{baz: baz} = bar, qux: %{quux: quux}}` | 76 | | `st(MyStruct, foo, bar = m(baz), qux: m(quux))` | `%MyStruct{foo: foo, bar: %{baz: baz} = bar, qux: %{quux: quux}}` | 77 | -------------------------------------------------------------------------------- /lib/shorthand.ex: -------------------------------------------------------------------------------- 1 | defmodule Shorthand do 2 | @moduledoc """ 3 | Convenience macros to eliminate laborious typing. Provides macros for short map, string keyed map, keyword lists, and structs (ES6 like style) 4 | 5 | ## Examples: 6 | These examples use variable arguments (default is 10, see configuration below) 7 | 8 | Instead of `%{one: one, two: two, three: three}`, you can type `m(one, two, three)` 9 | 10 | Instead of `my_func(one: one, two: two, three: three)`, you can type `my_func(kw(one, two, three))` 11 | 12 | Instead of `%MyStruct(one: one, two: two, three: three)`, you can type `st(MyStruct, one, two, three)` 13 | 14 | ### Without variable arguments, 15 | Instead of `%{one: one, two: two, three: three}`, you can type `m([one, two, three])` 16 | 17 | Instead of `my_func(one: one, two: two, three: three)`, you can type `my_func(kw([one, two, three]))` 18 | 19 | Instead of `%MyStruct(one: one, two: two, three: three)`, you can type `bulid_struct(MyStruct, [one, two, three])` 20 | 21 | ## Configuration 22 | 23 | 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. 24 | 25 | config :shorthand, 26 | variable_args: 10 # false to remove variable arguemnts 27 | 28 | 29 | ## Usage 30 | 31 | You can import the `Shorthand` module and call each macro 32 | 33 | defmodule MyModule do 34 | import Shorthand 35 | 36 | def my_func(m(a, b)) do 37 | kw(a, b) 38 | end 39 | end 40 | 41 | or you can require the module and prefix calls with the module name (example uses a module alias to keep the typing low 42 | 43 | defmodule MyModule do 44 | require Shorthand, as: S 45 | 46 | def my_func(S.m(a, b)) do 47 | S.kw(a, b) 48 | end 49 | end 50 | 51 | ## Phoenix Examples 52 | defmodule MyController do 53 | # ... 54 | 55 | def index(conn, sm(id)) do 56 | model = Repo.get(MyModel, id) 57 | # ... 58 | end 59 | 60 | def create(conn, sm(my_model: sm(first_name, last_name) = params)) do 61 | changeset = MyModel.changeset(%MyModel{}, params) # params contains all form fields, not just first_name and last_name 62 | # ... 63 | conn 64 | |> put_flash(:notice, "User \#{first_name} \#{last_name} was created successfully") 65 | # ... 66 | end 67 | end 68 | """ 69 | 70 | @doc ~S""" 71 | Builds a map where the keys and values have the same name 72 | 73 | ## Example: 74 | 75 | iex> a = 1 76 | iex> b = 2 77 | iex> m(a, b) 78 | %{a: 1, b: 2} 79 | 80 | ## Example: 81 | 82 | iex> a = 1 83 | iex> c = 2 84 | iex> m(a, b: m(c), d: nil) 85 | %{a: 1, b: %{c: 2}, d: nil} 86 | 87 | ## Example: 88 | 89 | iex> a = 1 90 | iex> m(^a, _b, c) = %{a: 1, b: 3, c: 2} 91 | iex> c 92 | 2 93 | iex> match?(m(^a), %{a: 2}) 94 | false 95 | 96 | ## Example: 97 | 98 | iex> m(model: m(a, b) = params) = %{model: %{a: 1, b: 2, c: 3, d: 4}} 99 | iex> params 100 | %{a: 1, b: 2, c: 3, d: 4} 101 | iex> a 102 | 1 103 | iex> b 104 | 2 105 | 106 | ## Example: 107 | 108 | iex> m(m(a, b) = model) = %{model: %{a: 1, b: 2, c: 3, d: 4}} 109 | iex> model 110 | %{a: 1, b: 2, c: 3, d: 4} 111 | iex> a 112 | 1 113 | iex> b 114 | 2 115 | """ 116 | defmacro m([_ | _] = args) do 117 | build_map(args, :atom) 118 | end 119 | 120 | @doc ~S""" 121 | Builds a map where the string keys and values have the same name 122 | 123 | ## Example: 124 | 125 | iex> a = 1 126 | iex> b = 2 127 | iex> sm(a, b) 128 | %{"a" => 1, "b" => 2} 129 | 130 | ## Example: 131 | 132 | iex> a = 1 133 | iex> b = 2 134 | iex> sm(a, other: sm(b)) 135 | %{"a" => 1, "other" => %{"b" => 2}} 136 | 137 | ## Example: 138 | 139 | iex> a = 1 140 | iex> sm(^a, _b, c) = %{"a" => 1, "b" => 3, "c" => 2} 141 | iex> c 142 | 2 143 | iex> match?(sm(^a), %{"a" => 2}) 144 | false 145 | 146 | ## Example: 147 | 148 | iex> sm(model: sm(a, b) = params) = %{"model" => %{"a" => 1, "b" => 2, "c" => 3, "d" => 4}} 149 | iex> params 150 | %{"a" => 1, "b" => 2, "c" => 3, "d" => 4} 151 | iex> a 152 | 1 153 | iex> b 154 | 2 155 | 156 | ## Example: 157 | 158 | iex> sm(sm(a, b) = model) = %{"model" => %{"a" => 1, "b" => 2, "c" => 3, "d" => 4}} 159 | iex> model 160 | %{"a" => 1, "b" => 2, "c" => 3, "d" => 4} 161 | iex> a 162 | 1 163 | iex> b 164 | 2 165 | """ 166 | defmacro sm([_ | _] = args) do 167 | build_map(args, :string) 168 | end 169 | 170 | @doc ~S""" 171 | Builds a keyword list where the keys and value arguments are the same name 172 | 173 | ## Example: 174 | 175 | iex> a = 1 176 | iex> b = 2 177 | iex> c = 3 178 | iex> kw(a, b, c) 179 | [a: 1, b: 2, c: 3] 180 | 181 | ## Examples 182 | 183 | iex> c = 3 184 | iex> kw(a, _b, ^c) = [a: 1, b: 3, c: 3] 185 | iex> a 186 | 1 187 | iex> match?(kw(^a), [a: 1]) 188 | true 189 | """ 190 | defmacro kw([_ | _] = args) do 191 | build_keywords(args) 192 | end 193 | 194 | @doc ~S""" 195 | Builds a struct where the field names are the same as the arguments supplied 196 | 197 | ## Example: 198 | 199 | iex> scheme = "https" 200 | iex> host = "elixir-lang.org" 201 | iex> path = "/docs.html" 202 | iex> st(URI, scheme, host, path) 203 | %URI{scheme: "https", host: "elixir-lang.org", path: "/docs.html"} 204 | """ 205 | defmacro st(module) do 206 | {:%, [], [module, {:%{}, [], []}]} 207 | end 208 | 209 | variable_args = Application.compile_env(:shorthand, :variable_args, 10) 210 | 211 | if variable_args do 212 | 1..variable_args 213 | |> Enum.each(fn i -> 214 | args = 1..i |> Enum.map(fn i -> {:"arg#{i}", [], nil} end) 215 | 216 | defmacro m(unquote_splicing(args)) do 217 | build_map(unquote(args), :atom) 218 | end 219 | 220 | defmacro sm(unquote_splicing(args)) do 221 | build_map(unquote(args), :string) 222 | end 223 | 224 | defmacro kw(unquote_splicing(args)) do 225 | build_keywords(unquote(args)) 226 | end 227 | 228 | defmacro st(module, unquote_splicing(args)) do 229 | map = build_map(unquote(args), :atom) 230 | {:%, [], [module, map]} 231 | end 232 | end) 233 | end 234 | 235 | defp build_map(args, type) do 236 | {:%{}, [], parse_args(args, type)} 237 | end 238 | 239 | defp build_keywords(args) do 240 | quote do 241 | unquote(parse_args(args, :atom)) 242 | end 243 | end 244 | 245 | defp parse_args(args, type) do 246 | # IO.inspect(args, label: "args") 247 | 248 | args 249 | |> Enum.map(fn 250 | # m(a: 1, ...) 251 | {name, value} -> 252 | {map_key(name, type), value} 253 | 254 | # m(^a) 255 | {:^, context1, [{name, context2, nil}]} -> 256 | {map_key(name, type), {:^, context1, [{name, context2, nil}]}} 257 | 258 | # m(a) 259 | {name, context, nil} -> 260 | {map_key(variable_name(name), type), {name, context, nil}} 261 | 262 | # m(a, b: m(c)) 263 | keyword_list when is_list(keyword_list) -> 264 | keyword_list 265 | |> Enum.map(fn {key, value} -> {map_key(key, type), value} end) 266 | 267 | # m(m(b) = bar) 268 | {:=, context, [left, {name, _context2, nil} = right]} -> 269 | [{map_key(name, type), {:=, context, [left, right]}}] 270 | 271 | # m(bar = m(b)) 272 | {:=, context, [{name, _context2, nil} = left, right]} -> 273 | [{map_key(name, type), {:=, context, [left, right]}}] 274 | 275 | # other -> 276 | # IO.inspect(other, label: "other") 277 | end) 278 | |> List.flatten() 279 | end 280 | 281 | defp map_key(key, :atom) when is_atom(key), do: key 282 | defp map_key(key, :string) when is_atom(key), do: to_string(key) 283 | 284 | defp variable_name(name) when is_atom(name), do: variable_name(Atom.to_string(name)) 285 | defp variable_name(<<"_", name::binary>>), do: variable_name(name) 286 | defp variable_name(name), do: String.to_atom(name) 287 | end 288 | --------------------------------------------------------------------------------