diff --git a/.gitignore b/.gitignore index 5bbdeed..e0eae69 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ erl_crash.dump # Ignore package tarball (built via "mix hex.build"). mentat-*.tar +.expert diff --git a/lib/mentat.ex b/lib/mentat.ex index 9f36e8d..17a776c 100644 --- a/lib/mentat.ex +++ b/lib/mentat.ex @@ -8,7 +8,55 @@ defmodule Mentat do use Supervisor use Oath - @type cache_opts() :: Keyword.t() + @cache_opts NimbleOptions.new!([ + name: [ + type: :atom, + doc: "The cache name as an atom", + required: true + ], + cleanup_interval: [ + type: :pos_integer, + doc: "How often the janitor process will remove old keys.", + default: 5_000 + ], + ets_args: [ + type: {:list, :any}, + doc: "Additional arguments to pass to `:ets.new/2`", + default: [] + ], + ttl: [ + type: :timeout, + doc: "The default ttl for all keys. Defaults to `:infinity`.", + default: :infinity + ], + limit: [ + type: :keyword_list, + doc: "Limits to the number of keys a cache will store.", + keys: [ + size: [ + type: :pos_integer, + required: true, + doc: "The maximum number of values to store in the cache" + ], + reclaim: [ + type: :float, + default: 0.1, + doc: "The percentage of keys to reclaim if the limit is exceeded." + ] + ] + ], + clock: [ + type: :any, + doc: false, + type_doc: false, + default: System + ] + ]) + + @default_limit %{reclaim: 0.1} + + @type cache_opt() :: unquote(NimbleOptions.option_typespec(@cache_opts)) + @type cache_opts() :: [cache_opt()] @type name :: atom() @type key :: term() @type value :: term() @@ -16,28 +64,8 @@ defmodule Mentat do {:ttl, pos_integer() | :infinity}, ] - @default_limit %{reclaim: 0.1} - alias Mentat.Janitor - defp cache_opts do - import Norm - - coll_of( - one_of([ - {:name, spec(is_atom)}, - {:cleanup_interval, spec(is_integer and & &1 > 0)}, - {:ets_args, spec(is_list)}, - {:ttl, one_of([spec(is_integer and & &1 > 0), :infinity])}, - {:clock, spec(is_atom)}, - {:limit, coll_of(one_of([ - {:size, spec(is_integer and & &1 > 0)}, - {:reclaim, spec(is_float)}, - ]))} - ]) - ) - end - @doc false def child_spec(opts) do name = opts[:name] || raise ArgumentError, ":name is required" @@ -53,17 +81,11 @@ defmodule Mentat do Starts a new cache. Options: - * `:name` - the cache name as an atom. required. - * `:cleanup_interval` - How often the janitor process will remove old keys. Defaults to 5_000. - * `:ets_args` - Additional arguments to pass to `:ets.new/2`. - * `:ttl` - The default ttl for all keys. Default `:infinity`. - * `:limit` - Limits to the number of keys a cache will store. Defaults to `:none`. - * `:size` - The maximum number of values to store in the cache. - * `:reclaim` - The percentage of keys to reclaim if the limit is exceeded. Defaults to 0.1. + #{NimbleOptions.docs(@cache_opts)} """ @spec start_link(cache_opts()) :: Supervisor.on_start() def start_link(args) do - args = Norm.conform!(args, cache_opts()) + args = NimbleOptions.validate!(args, @cache_opts) name = args[:name] Supervisor.start_link(__MODULE__, args, name: name) end @@ -245,12 +267,12 @@ defmodule Mentat do def init(args) do name = args[:name] - interval = args[:cleanup_interval] || 5_000 + interval = args[:cleanup_interval] limit = args[:limit] || :none limit = if limit != :none, do: Map.merge(@default_limit, Map.new(limit)), else: limit ets_args = args[:ets_args] || [] clock = args[:clock] || System - ttl = args[:ttl] || :infinity + ttl = args[:ttl] ^name = :ets.new(name, [:set, :named_table, :public] ++ ets_args) put_config(name, %{limit: limit, ttl: ttl, clock: clock}) diff --git a/mix.exs b/mix.exs index 08660a6..b97b8f6 100644 --- a/mix.exs +++ b/mix.exs @@ -33,7 +33,7 @@ defmodule Mentat.MixProject do defp deps do [ {:telemetry, "~> 0.4 or ~> 1.0"}, - {:norm, "~> 0.12"}, + {:nimble_options, "~> 1.1"}, {:oath, "~> 0.1"}, {:credo, "~> 1.5", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 3abab90..51ffc49 100644 --- a/mix.lock +++ b/mix.lock @@ -12,8 +12,8 @@ "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [: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", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.4", "29563475afa9b8a2add1b7a9c8fb68d06ca7737648f28398e04461f008b69521", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f4ed47ecda66de70dd817698a703f8816daa91272e7e45812469498614ae8b29"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, - "norm": {:hex, :norm, "0.13.0", "2c562113f3205e3f195ee288d3bd1ab903743e7e9f3282562c56c61c4d95dec4", [:mix], [{:stream_data, "~> 0.5", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "447cc96dd2d0e19dcb37c84b5fc0d6842aad69386e846af048046f95561d46d7"}, "oath": {:hex, :oath, "0.1.1", "9e96fc0bc94c8e2fe2deb84641f8b4533b7753575ac1829e1db4180f2b5656c4", [:mix], [{:decorator, "~> 1.3", [hex: :decorator, repo: "hexpm", optional: false]}], "hexpm", "d7e32cf0555905d422e5a90e8a7ce49c123710caa0fa2f7840bddbc1a0e93103"}, "propcheck": {:hex, :propcheck, "1.4.1", "c12908dbe6f572032928548089b34ff9d40672d5d70f1562e3a9e9058d226cc9", [:mix], [{:libgraph, "~> 0.13", [hex: :libgraph, repo: "hexpm", optional: false]}, {:proper, "~> 1.4", [hex: :proper, repo: "hexpm", optional: false]}], "hexpm", "e1b088f574785c3c7e864da16f39082d5599b3aaf89086d3f9be6adb54464b19"}, "proper": {:hex, :proper, "1.4.0", "89a44b8c39d28bb9b4be8e4d715d534905b325470f2e0ec5e004d12484a79434", [:rebar3], [], "hexpm", "18285842185bd33efbda97d134a5cb5a0884384db36119fee0e3cfa488568cbb"},