From bb034f8edb41256d16c237cfa4b1b71e8075d64c Mon Sep 17 00:00:00 2001 From: James Harton Date: Wed, 20 Sep 2023 10:49:15 +1200 Subject: [PATCH 1/3] chore(CI): don't release on PR builds (WTF) --- .drone.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.drone.yml b/.drone.yml index ba118cf..522a0f3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -243,6 +243,9 @@ steps: when: branch: - main + event: + exclude: + - pull_request depends_on: - mix test - mix credo -- 2.45.2 From cba62306e345d26e5a0f0e6d01dbb48821f2c133 Mon Sep 17 00:00:00 2001 From: James Harton Date: Wed, 20 Sep 2023 12:22:06 +1200 Subject: [PATCH 2/3] docs: Improve docs and add spark DSL cheat sheets. --- .drone.yml | 15 +- documentation/dsls/DSL:-Smokestack.cheatmd | 153 +++++++++++++++++++++ lib/smokestack.ex | 53 +++++-- lib/smokestack/builder.ex | 11 +- lib/smokestack/builders/factory_builder.ex | 27 +++- lib/smokestack/builders/many_builder.ex | 28 +++- lib/smokestack/builders/param_builder.ex | 148 ++++++++++++++------ lib/smokestack/builders/record_builder.ex | 69 ++++++---- lib/smokestack/builders/related_builder.ex | 82 ++++++++--- mix.exs | 30 +++- mix.lock | 4 +- test/smokestack_test.exs | 4 - 12 files changed, 497 insertions(+), 127 deletions(-) create mode 100644 documentation/dsls/DSL:-Smokestack.cheatmd delete mode 100644 test/smokestack_test.exs diff --git a/.drone.yml b/.drone.yml index 522a0f3..869939f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -198,6 +198,19 @@ steps: commands: - asdf mix spark.formatter --check +- name: mix spark.cheat_sheets + image: code.harton.nz/james/asdf_container:latest + environment: + MIX_ENV: test + HEX_HOME: /drone/src/.hex + MIX_HOME: /drone/src/.mix + REBAR_BASE_DIR: /drone/src/.rebar3 + ASDF_DATA_DIR: /drone/src/.asdf + depends_on: + - mix compile + commands: + - asdf mix spark.cheat_sheets --check + - name: mix deps.unlock image: code.harton.nz/james/asdf_container:latest environment: @@ -241,7 +254,7 @@ steps: - name: mix git_ops.release image: code.harton.nz/james/asdf_container:latest when: - branch: + branch: - main event: exclude: diff --git a/documentation/dsls/DSL:-Smokestack.cheatmd b/documentation/dsls/DSL:-Smokestack.cheatmd new file mode 100644 index 0000000..c9ed0ae --- /dev/null +++ b/documentation/dsls/DSL:-Smokestack.cheatmd @@ -0,0 +1,153 @@ +# DSL: Smokestack.Dsl + +The DSL definition for the Smokestack DSL. + + + +## DSL Documentation + +### Index + + * smokestack + * factory + * attribute + +### Docs + +## smokestack + + + + * [factory](#module-factory) + * attribute + + + + + +--- + +* `:api` (`t:atom/0`) - The default Ash API to use when evaluating loads + + + +### factory + +Define factories for a resource + + * attribute + + + +* `:api` (`t:atom/0`) - The Ash API to use when evaluating loads + +* `:resource` (`t:atom/0`) - Required. An Ash Resource + +* `:variant` (`t:atom/0`) - The name of a factory variant The default value is `:default`. + + + +##### attribute + + + + + + + +* `:name` (`t:atom/0`) - Required. The name of the target attribute + +* `:generator` - Required. A function which can generate an appropriate value for the attribute.œ + + + + + + + + + + + + + +## smokestack + + +### Nested DSLs + * [factory](#smokestack-factory) + * attribute + + + + + +### Options +| Name | Type | Default | Docs | +| --- | --- | --- | --- | +| `api` | `module` | | The default Ash API to use when evaluating loads | + + + +## smokestack.factory +```elixir +factory resource, variant \ :default +``` + + +Define factories for a resource + +### Nested DSLs + * [attribute](#smokestack-factory-attribute) + + + + +### Arguments +| Name | Type | Default | Docs | +| --- | --- | --- | --- | +| `resource`* | `module` | | An Ash Resource | +| `variant` | `atom` | `:default` | The name of a factory variant | +### Options +| Name | Type | Default | Docs | +| --- | --- | --- | --- | +| `api` | `module` | | The Ash API to use when evaluating loads | + + +## smokestack.factory.attribute +```elixir +attribute name, generator +``` + + + + + + + + +### Arguments +| Name | Type | Default | Docs | +| --- | --- | --- | --- | +| `name`* | `atom` | | The name of the target attribute | +| `generator`* | `(-> any) \| mfa \| (any -> any) \| mfa \| (any, any -> any) \| mfa` | | A function which can generate an appropriate value for the attribute.œ | + + + + + + +### Introspection + +Target: `Smokestack.Dsl.Attribute` + + + + +### Introspection + +Target: `Smokestack.Dsl.Factory` + + + + diff --git a/lib/smokestack.ex b/lib/smokestack.ex index d55b306..8e45561 100644 --- a/lib/smokestack.ex +++ b/lib/smokestack.ex @@ -2,8 +2,8 @@ defmodule Smokestack do alias Spark.{Dsl, Dsl.Extension} @moduledoc """ - Smokestack provides a way to define test factories for your - [Ash Resources](https://ash-hq.org/docs/module/ash/latest/ash-resource) + Smokestack provides a way to define test factories for your + [Ash Resources](https://ash-hq.org/docs/module/ash/latest/ash-resource) using a convenient DSL: ``` @@ -28,7 +28,7 @@ defmodule Smokestack do ## Templates - Each attribute uses a template to generate a value when building a factory. + Each attribute uses a template to generate a value when building a factory. Templates can be anything that implements the `Smokestack.Template` protocol. This protocol is automatically implemented for functions with arities zero through two - meaning you can just drop in your own functions - or use one of @@ -37,7 +37,7 @@ defmodule Smokestack do ## Variants Sometimes you need to make slightly different factories to build a resource - in a specific state for your test scenario. + in a specific state for your test scenario. Here's an example defining an alternate `:trek` variant for the character factory defined above: @@ -57,7 +57,7 @@ defmodule Smokestack do ### Options - - `load`: an atom, list of atoms or keyword list of the same listing + - `load`: an atom, list of atoms or keyword list of the same listing relationships, calculations and aggregates that should be loaded after the record is created. - `count`: rather than inserting just a single record, you can specify a @@ -129,17 +129,21 @@ defmodule Smokestack do Automatically implemented by modules which `use Smokestack`. - See `Smokestack.ParamBuilder.build/2` for more information. + ## Options + + #{Builder.docs(ParamBuilder, nil)} """ @callback params(Resource.t(), [param_option]) :: {:ok, ParamBuilder.result()} | {:error, any} @doc """ - Raising version of `params/2`. + Raising version of `c:params/2`. Automatically implemented by modules which `use Smokestack`. - See `Smokestack.ParamBuilder.build/3` for more information. + ## Options + + #{Builder.docs(ParamBuilder, nil)} """ @callback params!(Resource.t(), [param_option]) :: ParamBuilder.result() | no_return @@ -148,17 +152,21 @@ defmodule Smokestack do Automatically implemented by modules which `use Smokestack`. - See `Smokestack.RecordBuilder.build/3` for more information. + ## Options + + #{Builder.docs(RecordBuilder, nil)} """ @callback insert(Resource.t(), [insert_option]) :: {:ok, RecordBuilder.result()} | {:error, any} @doc """ - Raising version of `insert/4`. + Raising version of `c:insert/2`. Automatically implemented by modules which `use Smokestack`. - See `Smokestack.RecordBuilder.build/3` for more information. + ## Options + + #{Builder.docs(RecordBuilder, nil)} """ @callback insert!(Resource.t(), [insert_option]) :: RecordBuilder.result() | no_return @@ -168,11 +176,20 @@ defmodule Smokestack do quote do @behaviour Smokestack + @moduledoc @moduledoc || + """ + A Smokestack factory. + + See `Smokestack` for more information. + """ + @doc """ Runs a factory and uses it to build a parameters suitable for simulating a request. - See `c:Smokestack.build/2` for more information. + ## Options + + #{Builder.docs(ParamBuilder, nil)} """ @spec params(Resource.t(), [Smokestack.param_option()]) :: {:ok, ParamBuilder.result()} | {:error, any} @@ -187,7 +204,9 @@ defmodule Smokestack do @doc """ Raising version of `params/2`. - See `c:Smokestack.build/3` for more information. + ## Options + + #{Builder.docs(ParamBuilder, nil)} """ @spec params!(Resource.t(), [Smokestack.param_option()]) :: ParamBuilder.result() | no_return @@ -201,7 +220,9 @@ defmodule Smokestack do @doc """ Execute the matching factory and return an inserted Ash Resource record. - See `c:Smokestack.insert/2` for more information. + ## Options + + #{Builder.docs(RecordBuilder, nil)} """ @spec insert(Resource.t(), [Smokestack.insert_option()]) :: {:ok, RecordBuilder.result()} | {:error, any} @@ -216,7 +237,9 @@ defmodule Smokestack do @doc """ Raising version of `insert/2`. - See `c:Smokestack.insert/2` for more information. + ## Options + + #{Builder.docs(RecordBuilder, nil)} """ @spec insert!(Resource.t(), [Smokestack.insert_option()]) :: RecordBuilder.result() | no_return diff --git a/lib/smokestack/builder.ex b/lib/smokestack/builder.ex index de7fd27..972f8a3 100644 --- a/lib/smokestack/builder.ex +++ b/lib/smokestack/builder.ex @@ -18,7 +18,7 @@ defmodule Smokestack.Builder do @doc """ Provide a schema for validating options. """ - @callback option_schema(Factory.t()) :: {:ok, OptionsHelpers.schema()} | {:error, any} + @callback option_schema(nil | Factory.t()) :: {:ok, OptionsHelpers.schema()} | {:error, any} @doc """ Given a builder and a factory, validate it's options and call the builder. @@ -30,4 +30,13 @@ defmodule Smokestack.Builder do builder.build(factory, options) end end + + @doc """ + Generate documentation for the available options. + """ + @spec docs(t, nil | Factory.t()) :: String.t() + def docs(builder, factory) do + {:ok, schema} = builder.option_schema(factory) + OptionsHelpers.docs(schema) + end end diff --git a/lib/smokestack/builders/factory_builder.ex b/lib/smokestack/builders/factory_builder.ex index aea0578..61c590d 100644 --- a/lib/smokestack/builders/factory_builder.ex +++ b/lib/smokestack/builders/factory_builder.ex @@ -42,13 +42,17 @@ defmodule Smokestack.FactoryBuilder do @doc false @impl true - @spec option_schema(Factory.t()) :: + @spec option_schema(nil | Factory.t()) :: {:ok, OptionsHelpers.schema()} | {:error, error} def option_schema(factory) do attr_keys = - factory.resource - |> Resource.Info.attributes() - |> Enum.map(&{&1.name, [type: :any, required: false]}) + if factory do + factory.resource + |> Resource.Info.attributes() + |> Enum.map(&{&1.name, [type: :any, required: false]}) + else + [{:*, [type: :any, required: false]}] + end {:ok, [ @@ -56,7 +60,20 @@ defmodule Smokestack.FactoryBuilder do type: :map, required: false, default: %{}, - keys: attr_keys + keys: attr_keys, + doc: """ + Attribute overrides. + + You can directly specify any overrides you would like set on the + resulting record without running their normal generator. + + For example: + + ```elixir + post = params!(Post, attrs: %{title: "What's wrong with Huntly?"}) + assert post.title == "What's wrong with Huntly?" + ``` + """ ] ]} end diff --git a/lib/smokestack/builders/many_builder.ex b/lib/smokestack/builders/many_builder.ex index 94a8909..d57d238 100644 --- a/lib/smokestack/builders/many_builder.ex +++ b/lib/smokestack/builders/many_builder.ex @@ -30,10 +30,32 @@ defmodule Smokestack.ManyBuilder do @doc false @impl true - @spec option_schema(Factory.t()) :: {:ok, OptionsHelpers.schema()} | {:error, error} + @spec option_schema(nil | Factory.t()) :: {:ok, OptionsHelpers.schema()} | {:error, error} def option_schema(factory) do - with {:ok, schema} <- RelatedBuilder.option_schema(factory) do - {:ok, Keyword.put(schema, :count, type: :pos_integer, required: true)} + with {:ok, related_schema} <- RelatedBuilder.option_schema(factory) do + schema = + [ + count: [ + type: :pos_integer, + required: true, + doc: """ + Specify the number of instances to build. + + Use this option to run the factory a number of times and return the + results as a list. + + For example: + + ```elixir + posts = params!(Post, count: 3) + assert length(posts) == 3 + ``` + """ + ] + ] + |> OptionsHelpers.merge_schemas(related_schema, "Options for building relationships") + + {:ok, schema} end end diff --git a/lib/smokestack/builders/param_builder.ex b/lib/smokestack/builders/param_builder.ex index 6978f27..32de588 100644 --- a/lib/smokestack/builders/param_builder.ex +++ b/lib/smokestack/builders/param_builder.ex @@ -69,59 +69,117 @@ defmodule Smokestack.ParamBuilder do @doc false @impl true - @spec option_schema(Factory.t()) :: {:ok, OptionsHelpers.schema()} | {:error, error} + @spec option_schema(nil | Factory.t()) :: {:ok, OptionsHelpers.schema()} | {:error, error} def option_schema(factory) do - with {:ok, schema0} <- RelatedBuilder.option_schema(factory), - {:ok, schema1} <- ManyBuilder.option_schema(factory) do - schema1 = - Keyword.update!(schema1, :count, fn current -> + with {:ok, related_schema} <- RelatedBuilder.option_schema(factory), + {:ok, many_schema} <- ManyBuilder.option_schema(factory) do + many_schema = + Keyword.update!(many_schema, :count, fn current -> current |> Keyword.update!(:type, &{:or, [&1, nil]}) |> Keyword.put(:default, nil) |> Keyword.put(:required, false) end) + our_schema = [ + encode: [ + type: {:or, [nil, :module]}, + required: false, + doc: """ + Provide an encoder module. + + If provided the result will be passed to an `encode/1` function + which should return an ok/error tuple with the encoded result. + + This is primarily for use with `Jason` or `Poison`, however you may + wish to define your own encoder for your tests. + + For example: + + ```elixir + post = params!(Post, encoder: Jason) + assert Jason.decode!(post) + ``` + """ + ], + nest: [ + type: {:or, [:atom, :string]}, + required: false, + doc: """ + Nest the result within an arbitrary map key. + + Mostly provided as a shorthand for building API requests where the + built instance may need to be nested inside a key such as `data`. + + Takes the result and nests it in a map under the provided key. + + For example: + + ```elixir + request = params!(Post, nest: :data) + assert is_binary(request.data.title) + ``` + + If you need a more complicated mangling of the result, I suggest + using the `encode` option. + """ + ], + key_case: [ + type: + {:or, + [ + {:tuple, [{:literal, :path}, :string]}, + {:in, + [ + :camel, + :constant, + :dot, + :header, + :kebab, + :name, + :pascal, + :path, + :sentence, + :snake, + :title + ]} + ]}, + required: false, + default: :snake, + doc: """ + Change the key case. + + Sometimes you will need to change the case of the keys before sending + to an API. Behind the scenes we use [recase](https://hexdocs.pm/recase) + to remap the keys as specified. + + For example: + + iex> params!(Post, key_case: :kebab) |> Map.keys() + [:title, :"sub-title"] + """ + ], + key_type: [ + type: {:in, [:string, :atom]}, + required: false, + default: :atom, + doc: """ + Specify string or atom keys. + + Allows you to specify the type of the returned keys. + + For example: + + iex> params!(Post, key_type: :string) |> Map.keys() + ["title", "sub_title"] + """ + ] + ] + schema = - schema0 - |> Keyword.merge(schema1) - |> Keyword.merge( - encode: [ - type: {:or, [nil, :module]}, - required: false - ], - nest: [ - type: {:or, [:atom, :string]}, - required: false - ], - key_case: [ - type: - {:or, - [ - {:tuple, [{:literal, :path}, :string]}, - {:in, - [ - :camel, - :constant, - :dot, - :header, - :kebab, - :name, - :pascal, - :path, - :sentence, - :snake, - :title - ]} - ]}, - required: false, - default: :snake - ], - key_type: [ - type: {:in, [:string, :atom]}, - required: false, - default: :atom - ] - ) + our_schema + |> OptionsHelpers.merge_schemas(many_schema, "Options for building multiple instances") + |> OptionsHelpers.merge_schemas(related_schema, "Options for building relationships") {:ok, schema} end diff --git a/lib/smokestack/builders/record_builder.ex b/lib/smokestack/builders/record_builder.ex index b4145e0..80eb578 100644 --- a/lib/smokestack/builders/record_builder.ex +++ b/lib/smokestack/builders/record_builder.ex @@ -31,20 +31,35 @@ defmodule Smokestack.RecordBuilder do @doc false @impl true - @spec option_schema(Factory.t()) :: {:ok, OptionsHelpers.schema()} | {:error, error} + @spec option_schema(nil | Factory.t()) :: {:ok, OptionsHelpers.schema()} | {:error, error} def option_schema(factory) do - with {:ok, schema0} <- RelatedBuilder.option_schema(factory), - {:ok, schema1} <- ManyBuilder.option_schema(factory) do - loadable_names = - factory.resource - |> Resource.Info.relationships() - |> Enum.concat(Resource.Info.aggregates(factory.resource)) - |> Enum.concat(Resource.Info.calculations(factory.resource)) - |> Enum.map(& &1.name) - |> Enum.uniq() + with {:ok, related_schema} <- RelatedBuilder.option_schema(factory), + {:ok, many_schema} <- ManyBuilder.option_schema(factory) do + load_type = + if factory do + loadable_names = + factory.resource + |> Resource.Info.relationships() + |> Enum.concat(Resource.Info.aggregates(factory.resource)) + |> Enum.concat(Resource.Info.calculations(factory.resource)) + |> Enum.map(& &1.name) + |> Enum.uniq() - schema1 = - Keyword.update!(schema1, :count, fn current -> + {:or, + [ + {:wrap_list, {:in, loadable_names}}, + {:keyword_list, + Enum.map( + loadable_names, + &{&1, type: {:or, [:atom, :keyword_list]}, required: false} + )} + ]} + else + {:or, [{:wrap_list, :atom}, :keyword_list]} + end + + many_schema = + Keyword.update!(many_schema, :count, fn current -> current |> Keyword.update!(:type, &{:or, [&1, nil]}) |> Keyword.put(:default, nil) @@ -54,22 +69,26 @@ defmodule Smokestack.RecordBuilder do schema = [ load: [ - type: - {:or, - [ - {:wrap_list, {:in, loadable_names}}, - {:keyword_list, - Enum.map( - loadable_names, - &{&1, type: {:or, [:atom, :keyword_list]}, required: false} - )} - ]}, + type: load_type, required: false, - default: [] + default: [], + doc: """ + An optional Ash load statement. + + You can request any calculations, aggregates or relationships you + would like loaded on the returned record. + + For example: + + ```elixir + insert!(Post, load: [:full_title]) + assert is_binary(post.full_title) + ``` + """ ] ] - |> Keyword.merge(schema0) - |> Keyword.merge(schema1) + |> OptionsHelpers.merge_schemas(many_schema, "Options for building multiple instances") + |> OptionsHelpers.merge_schemas(related_schema, "Options for building relationships") {:ok, schema} end diff --git a/lib/smokestack/builders/related_builder.ex b/lib/smokestack/builders/related_builder.ex index 29d7ea9..3f75c5b 100644 --- a/lib/smokestack/builders/related_builder.ex +++ b/lib/smokestack/builders/related_builder.ex @@ -32,32 +32,72 @@ defmodule Smokestack.RelatedBuilder do @doc false @impl true - @spec option_schema(Factory.t()) :: {:ok, OptionsHelpers.schema()} | {:error, error} + @spec option_schema(nil | Factory.t()) :: {:ok, OptionsHelpers.schema()} | {:error, error} def option_schema(factory) do with {:ok, factory_schema} <- FactoryBuilder.option_schema(factory) do - relationship_names = - factory.resource - |> Resource.Info.relationships() - |> Enum.map(& &1.name) + build_type = + if factory do + relationship_names = + factory.resource + |> Resource.Info.relationships() + |> Enum.map(& &1.name) - schema = [ - build: [ - type: - {:or, - [ - {:wrap_list, {:in, relationship_names}}, - {:keyword_list, - Enum.map( - relationship_names, - &{&1, type: {:or, [:atom, :keyword_list]}, required: false} - )} - ]}, - required: false, - default: [] + {:or, + [ + {:wrap_list, {:in, relationship_names}}, + {:keyword_list, + Enum.map( + relationship_names, + &{&1, type: {:or, [:atom, :keyword_list]}, required: false} + )} + ]} + else + {:or, [{:wrap_list, :atom}, :keyword_list]} + end + + schema = + [ + build: [ + type: build_type, + required: false, + default: [], + doc: """ + A (nested) list of relationships to build. + + A (possibly nested) list of Ash resource relationships which is + traversed building any instances as needed. + + For example: + + ```elixir + post = insert!(Post, build: Author) + assert is_struct(post.author, Author) + ``` + + Caveats: + - When building for a variant other than `:default` a matching + variant factory will be looked for and used if present, otherwise + it will build the default variant instead. + + - Note that for relationships whose cardinality is "many" we only + build one instance. + + If these caveats are an issue, then you can build them yourself and + pass them in using the `attrs` option. + + For example: + + ```elixir + posts = insert!(Post, count: 3) + author = insert(Author, posts: posts) + ``` + + """ + ] ] - ] + |> OptionsHelpers.merge_schemas(factory_schema, "Options for building instances") - {:ok, Keyword.merge(factory_schema, schema)} + {:ok, schema} end end diff --git a/mix.exs b/mix.exs index b42479c..2e6959a 100644 --- a/mix.exs +++ b/mix.exs @@ -23,18 +23,37 @@ defmodule Smokestack.MixProject do aliases: aliases(), dialyzer: [plt_add_apps: [:faker]], docs: [ - main: "Smokestack" + main: "Smokestack", + extra_section: "GUIDES", + formatters: ["html"], + filter_modules: ~r/^Elixir.Smokestack/, + source_url_pattern: + "https://code.harton.nz/james/smokestack/src/branch/main/%{path}#L%{line}", + spark: [ + extensions: [ + %{ + module: Smokestack.Dsl, + name: "Smokestack.Dsl", + target: "Smokestack", + type: "Smokestack" + } + ] + ] ] ] end def package do [ + name: :smokestack, + files: ~w[lib .formatter.exs mix.exs README* LICENSE* CHANGELOG* documentation], maintainers: ["James Harton "], licenses: ["HL3-FULL"], links: %{ - "Source" => "https://code.harton.nz/james/smokestack" - } + "Source" => "https://code.harton.nz/james/smokestack", + "Github Mirror" => "https://github.com/jimsynz/smokestack" + }, + source_url: "https://code.harton.nz/james/smokestack" ] end @@ -60,13 +79,14 @@ defmodule Smokestack.MixProject do {:git_ops, "~> 2.6", opts}, {:mix_audit, "~> 2.1", opts}, {:recase, "~> 0.7"}, - {:spark, "~> 1.1"} + {:spark, "~> 1.1 and >= 1.1.39"} ] end defp aliases do [ - "spark.formatter": "spark.formatter --extensions=Smokestack.Dsl" + "spark.formatter": "spark.formatter --extensions=Smokestack.Dsl", + "spark.cheat_sheets": "spark.cheat_sheets --extensions=Smokestack.Dsl" ] end diff --git a/mix.lock b/mix.lock index 7ce14b0..2ae4ca1 100644 --- a/mix.lock +++ b/mix.lock @@ -27,8 +27,8 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "recase": {:hex, :recase, "0.7.0", "3f2f719f0886c7a3b7fe469058ec539cb7bbe0023604ae3bce920e186305e5ae", [:mix], [], "hexpm", "36f5756a9f552f4a94b54a695870e32f4e72d5fad9c25e61bc4a3151c08a4e0c"}, - "sourceror": {:hex, :sourceror, "0.12.3", "a2ad3a1a4554b486d8a113ae7adad5646f938cad99bf8bfcef26dc0c88e8fade", [:mix], [], "hexpm", "4d4e78010ca046524e8194ffc4683422f34a96f6b82901abbb45acc79ace0316"}, - "spark": {:hex, :spark, "1.1.22", "68ba00f9acb4c8bc2c93ef82249493687ddf0f0a4f7e79c3c0e22b06719add56", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "b798b95990eed8f2409df47b818b5dbcd00e9b5c30d0355465d0b04bbf9b5c4c"}, + "sourceror": {:hex, :sourceror, "0.14.0", "b6b8552d0240400d66b6f107c1bab7ac1726e998efc797f178b7b517e928e314", [:mix], [], "hexpm", "809c71270ad48092d40bbe251a133e49ae229433ce103f762a2373b7a10a8d8b"}, + "spark": {:hex, :spark, "1.1.39", "f143b84a5b796bf2d83ec8fb4793ee9e66e67510c40d785f9a67050bb88e7677", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "d71bc26014c7e7abcdcf553f4cf7c5a5ff96f8365b1e20be3768ce503aafb203"}, "stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"}, diff --git a/test/smokestack_test.exs b/test/smokestack_test.exs deleted file mode 100644 index 7dc7600..0000000 --- a/test/smokestack_test.exs +++ /dev/null @@ -1,4 +0,0 @@ -defmodule SmokestackTest do - use ExUnit.Case - doctest Smokestack -end -- 2.45.2 From a1b229d1a913336058426680b316f2c5890ed133 Mon Sep 17 00:00:00 2001 From: James Harton Date: Wed, 20 Sep 2023 12:50:41 +1200 Subject: [PATCH 3/3] fix: bug in constant mapper. --- lib/smokestack/template/constant.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/smokestack/template/constant.ex b/lib/smokestack/template/constant.ex index ee3e8df..1396bbf 100644 --- a/lib/smokestack/template/constant.ex +++ b/lib/smokestack/template/constant.ex @@ -8,7 +8,7 @@ defmodule Smokestack.Template.Constant do def init(constant), do: constant def generate(constant, _, _) when is_function(constant.mapper, 1), - do: constant.mapper(constant.value) + do: constant.mapper.(constant.value) def generate(constant, _, _), do: constant.value -- 2.45.2