diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d35c1e..841ae2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,16 @@ -# Changelog for Cldr_Currencies v2.9.0 +# Changelog + +## Cldr_Currencies v2.10.0 + +This is the changelog for Cldr_Currencies v2.10.0 released on June 17th, 2021. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) + +### Enhancements + +* Allow `%Currency{}` structs in `currency_for_code/3`. Thanks to @jeroenvisser101 for the PR. [Improves the performance](https://github.com/elixir-cldr/cldr_currencies/pull/4) by up to 40x when validating currencies when the currency has already been pre-constructed. + +* Add implementation of the `Inspect` protocol for `t:Cldr.Currency` structs. + +## Cldr_Currencies v2.9.0 This is the changelog for Cldr_Currencies v2.9.0 released on April 8th, 2021. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -10,7 +22,7 @@ This is the changelog for Cldr_Currencies v2.9.0 released on April 8th, 2021. F * Add implementation of `String.Chars` and `Cldr.Chars` protocols for `t:Cldr.Currency` structs. -# Changelog for Cldr_Currencies v2.9.0-rc.1 +## Cldr_Currencies v2.9.0-rc.1 This is the changelog for Cldr_Currencies v2.9.0-rc.1 released on March 24th, 2021. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -20,7 +32,7 @@ This is the changelog for Cldr_Currencies v2.9.0-rc.1 released on March 24th, 20 * Add implementation of `String.Chars` and `Cldr.Chars` protocols for `t:Cldr.Currency` structs. -# Changelog for Cldr_Currencies v2.9.0-rc.0 +## Cldr_Currencies v2.9.0-rc.0 This is the changelog for Cldr_Currencies v2.9.0-rc.0 released on March 19th, 2021. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -28,7 +40,7 @@ This is the changelog for Cldr_Currencies v2.9.0-rc.0 released on March 19th, 20 * Depends upon `ex_cldr` version 2.20 which embodies CLDR39 data. -# Changelog for Cldr_Currencies v2.8.0 +## Cldr_Currencies v2.8.0 This is the changelog for Cldr_Currencies v2.8.0 released on November 1st, 2020. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -36,7 +48,7 @@ This is the changelog for Cldr_Currencies v2.8.0 released on November 1st, 2020. * Add support for [CLDR 38](http://cldr.unicode.org/index/downloads/cldr-38) -# Changelog for Cldr_Currencies v2.7.0 +## Cldr_Currencies v2.7.0 This is the changelog for Cldr_Currencies v2.7.0 released on September 25th, 2020. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -44,7 +56,7 @@ This is the changelog for Cldr_Currencies v2.7.0 released on September 25th, 202 * Use `Cldr.default_backend!/0` when available since `Cldr.default_backend/0` is deprecated as of `ex_cldr` version `2.18.0` -# Changelog for Cldr_Currencies v2.6.2 +## Cldr_Currencies v2.6.2 This is the changelog for Cldr_Currencies v2.6.2 released on August 31st, 2020. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -52,7 +64,7 @@ This is the changelog for Cldr_Currencies v2.6.2 released on August 31st, 2020. * Uses `Supervisor.child_spec/2` for `eternal` workers to remove deprecation warning on Elixir 1.11 -# Changelog for Cldr_Currencies v2.6.1 +## Cldr_Currencies v2.6.1 This is the changelog for Cldr_Currencies v2.6.1 released on July 19th, 2020. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -60,7 +72,7 @@ This is the changelog for Cldr_Currencies v2.6.1 released on July 19th, 2020. F * Make dialyzer happy. The `@spec`s for `Cldr.Eternal.start_link/1` and `Cldr.Eternal.start_link/0` however remain a mystery and are commented out for now - success typing seems happy nevertheless. -# Changelog for Cldr_Currencies v2.6.0 +## Cldr_Currencies v2.6.0 This is the changelog for Cldr_Currencies v2.6.0 released on July 18th, 2020. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -80,7 +92,7 @@ This is the changelog for Cldr_Currencies v2.6.0 released on July 18th, 2020. F * Add `:alt_code` to the `Cldr.Currency` struct. When creating a new currency, the currency code must conform to ISO4217 meaning that any new code must be in the "private use" range. This in turn means that the currency code must start with "X" and be three characters long. Many crypto currencies have either conflicting currency codes (do not comply with ISO4217 private use) or are invalid codes (longer than three characters). The `:alt_code` can be used to store an arbitrary alternative currency code than can be used to identify cryptocurrencies by a more familiar code. -# Changelog for Cldr_Currencies v2.5.0 +## Cldr_Currencies v2.5.0 This is the changelog for Cldr_Currencies v2.5.0 released on May 2nd, 2020. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -96,7 +108,7 @@ This is the changelog for Cldr_Currencies v2.5.0 released on May 2nd, 2020. For * Add `.currency_from_locale/{1, 2}` -# Changelog for Cldr_Currencies v2.4,1 +## Cldr_Currencies v2.4,1 This is the changelog for Cldr_Currencies v2.4.1 released on November 7th, 2019. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -104,7 +116,7 @@ This is the changelog for Cldr_Currencies v2.4.1 released on November 7th, 2019. * In some rare cases, currency strings have a trailing `.`. These are now removed when producing currency strings that are used for parsing money amounts in `ex_money`. -# Changelog for Cldr_Currencies v2.4.0 +## Cldr_Currencies v2.4.0 This is the changelog for Cldr_Currencies v2.4.0 released on November 6th, 2019. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -112,7 +124,7 @@ This is the changelog for Cldr_Currencies v2.4.0 released on November 6th, 2019. * Adds the options `:only` and `:except` to `Cldr.Currency.filter_currencies/3`. These options are exercised in [ex_money](https://hex.pm/pacakges/ex_money) in the `Money.parse/2` function to limited parsed user input to a particular set of currencies. -# Changelog for Cldr_Currencies v2.3.0 +## Cldr_Currencies v2.3.0 This is the changelog for Cldr_Currencies v2.3.0 released on March 28th, 2019. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -120,7 +132,7 @@ This is the changelog for Cldr_Currencies v2.3.0 released on March 28th, 2019. * Updates to [CLDR version 35.0.0](http://cldr.unicode.org/index/downloads/cldr-35) released on March 27th 2019. -# Changelog for Cldr_Currencies v2.2.5 +## Cldr_Currencies v2.2.5 This is the changelog for Cldr_Currencies v2.2.5 released on March 15th, 2019. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -128,7 +140,7 @@ This is the changelog for Cldr_Currencies v2.2.5 released on March 15th, 2019. * Fix dialyzer warnings -# Changelog for Cldr_Currencies v2.2.4 +## Cldr_Currencies v2.2.4 This is the changelog for Cldr_Currencies v2.2.4 released on March 15th, 2019. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -145,7 +157,7 @@ defmodule MyApp.Cldr do generate_docs: false end -# Changelog for Cldr_Currencies v2.2.3 +## Cldr_Currencies v2.2.3 This is the changelog for Cldr_Currencies v2.2.3 released on March 7th, 2019. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -153,7 +165,7 @@ This is the changelog for Cldr_Currencies v2.2.3 released on March 7th, 2019. F * Fix or silence all remaining dialyzer warnings for real this time -# Changelog for Cldr_Currencies v2.2.2 +## Cldr_Currencies v2.2.2 This is the changelog for Cldr_Currencies v2.2.2 released on March 7th, 2019. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -161,7 +173,7 @@ This is the changelog for Cldr_Currencies v2.2.2 released on March 7th, 2019. F * Fix or silence all remaining dialyzer warnings -# Changelog for Cldr_Currencies v2.2.1 +## Cldr_Currencies v2.2.1 This is the changelog for Cldr_Currencies v2.2.1 released on March 6th, 2019. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -169,7 +181,7 @@ This is the changelog for Cldr_Currencies v2.2.1 released on March 6th, 2019. F * Fix or silence dialyzer warnings -# Changelog for Cldr_Currencies v2.2.0 +## Cldr_Currencies v2.2.0 This is the changelog for Cldr_Currencies v2.2.0 released on February 23nd, 2019. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -205,7 +217,7 @@ iex> Cldr.Currency.current_currency_for_locale "en-AU", MyApp.Cldr :AUD ``` -# Changelog for Cldr_Currencies v2.1.4 +## Cldr_Currencies v2.1.4 This is the changelog for Cldr_Currencies v2.1.4 released on February 22nd, 2019. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -213,7 +225,7 @@ This is the changelog for Cldr_Currencies v2.1.4 released on February 22nd, 2019 * Fixes significant performance regression in `Cldr.Currency.currencies_for_locale/2`. Thanks to @doughsay for the issue. Closes #98 in [money](https://github.com/elixir-cldr/money). -# Changelog for Cldr_Currencies v2.1.3 +## Cldr_Currencies v2.1.3 This is the changelog for Cldr_Currencies v2.1.3 released on February 18th, 2019. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -221,7 +233,7 @@ This is the changelog for Cldr_Currencies v2.1.3 released on February 18th, 2019 * Updates `ex_cldr` to fix the regex that parses currency names used for money parsing -# Changelog for Cldr_Currencies v2.1.2 +## Cldr_Currencies v2.1.2 This is the changelog for Cldr_Currencies v2.1.2 released on February 13th, 2019. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -229,7 +241,7 @@ This is the changelog for Cldr_Currencies v2.1.2 released on February 13th, 2019 * Some different currencies may have the same currency name. This most commonly happens when there are historic currencies with the same name as a current currency. Afghan Afghanis, for example, has the code `:AFA` until 2002 when it was replaced by the currency code `:AFN`. Now when extracting currency strings, the currency names map only to the current currency and the duplicated are therefore removed. -# Changelog for Cldr_Currencies v2.1.1 +## Cldr_Currencies v2.1.1 This is the changelog for Cldr_Currencies v2.1.1 released on February 10th, 2019. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -245,7 +257,7 @@ This is the changelog for Cldr_Currencies v2.1.1 released on February 10th, 2019 * Added `:unannotated` to `Cldr.Currency.currency_filter/2`. It omits currency names that have a "(...)" in then since these are typically financial instruments. -# Changelog for Cldr_Currencies v2.1.0 +## Cldr_Currencies v2.1.0 This is the changelog for Cldr_Currencies v2.1.0 released on February 9th, 2019. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) @@ -263,7 +275,7 @@ In addition the `Cldr.Currency.t` structure has changed: * The `Cldr.Currency.t` struct now includes effective dates `:to` and `:from`. These were previously encoded in the currency name. The currency name no longer includes these dates. -# Changelog for Cldr_Currencies v2.0.0 +## Cldr_Currencies v2.0.0 This is the changelog for Cldr_Currencies v2.0.0 released on November 22nd, 2018. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_currencies/tags) diff --git a/bench/currency_for_code.exs b/bench/currency_for_code.exs new file mode 100644 index 0000000..33856ae --- /dev/null +++ b/bench/currency_for_code.exs @@ -0,0 +1,16 @@ +{:ok, currency} = Cldr.Currency.currency_for_code(:AUD, MyApp.Cldr) + +Benchee.run( + %{ + "with precompiled currency" => fn -> + Cldr.Currency.currency_for_code(currency, MyApp.Cldr) + end, + + "without precompiled currency" => fn -> + Cldr.Currency.currency_for_code(:AUD, MyApp.Cldr) + end, + + }, + time: 10, + memory_time: 2 +) \ No newline at end of file diff --git a/lib/cldr/backend.ex b/lib/cldr/backend.ex index c0aaaa0..f6bd693 100644 --- a/lib/cldr/backend.ex +++ b/lib/cldr/backend.ex @@ -47,34 +47,32 @@ defmodule Cldr.Currency.Backend do ## Example - iex> #{inspect(__MODULE__)}.new(:XAA, name: "Custom Name", digits: 0) - {:ok, - %Cldr.Currency{ - alt_code: :XAA, - cash_digits: 0, - cash_rounding: nil, - code: :XAA, - count: %{other: "Custom Name"}, - digits: 0, - from: nil, - iso_digits: 0, - name: "Custom Name", - narrow_symbol: nil, - rounding: 0, - symbol: "XAA", - tender: false, - to: nil - }} - - iex> MyApp.Cldr.Currency.new(:XAA, name: "Custom Name") - {:error, "Required options are missing. Required options are [:name, :digits]"} - - iex> #{inspect(__MODULE__)}.new(:XBC) - {:error, {Cldr.CurrencyAlreadyDefined, "Currency :XBC is already defined."}} + iex> #{inspect(__MODULE__)}.new(:XAE, name: "Custom Name", digits: 0) + {:ok, + %Cldr.Currency{ + alt_code: :XAE, + cash_digits: 0, + cash_rounding: nil, + code: :XAE, + count: %{other: "Custom Name"}, + digits: 0, + from: nil, + iso_digits: 0, + name: "Custom Name", + narrow_symbol: nil, + rounding: 0, + symbol: "XAE", + tender: false, + to: nil + }} + iex> MyApp.Cldr.Currency.new(:XAH, name: "Custom Name") + {:error, "Required options are missing. Required options are [:name, :digits]"} + iex> #{inspect(__MODULE__)}.new(:XAE, name: "XAE", digits: 0) + {:error, {Cldr.CurrencyAlreadyDefined, "Currency :XAE is already defined."}} """ - @spec new(Cldr.Currency.code(), map() | Keyword.t) :: - {:ok, Cldr.Currency.t} | {:error, {module(), String.t}} + @spec new(Cldr.Currency.code(), map() | Keyword.t()) :: + {:ok, Cldr.Currency.t()} | {:error, {module(), String.t()}} def new(currency, options \\ []) @@ -98,7 +96,7 @@ defmodule Cldr.Currency.Backend do * `locale` is any valid locale name returned by `MyApp.Cldr.known_locale_names/0` or a `Cldr.LanguageTag` struct returned by `MyApp.Cldr.Locale.new!/1`. The - default is `#{inspect backend}.get_locale/0` + default is `#{inspect(backend)}.get_locale/0` ## Returns @@ -136,8 +134,7 @@ defmodule Cldr.Currency.Backend do ## Example - iex> #{inspect(__MODULE__)}.known_currency_codes |> Enum.count - 303 + iex> #{inspect(__MODULE__)}.known_currency_codes """ @spec known_currency_codes() :: list(atom) @@ -201,7 +198,7 @@ defmodule Cldr.Currency.Backend do """ @spec known_currency_code(Cldr.Currency.code()) :: - {:ok, Cldr.Currency.code} | {:error, {module, String.t}} + {:ok, Cldr.Currency.code()} | {:error, {module, String.t()}} def known_currency_code(currency_code) do Cldr.Currency.known_currency_code(currency_code) @@ -224,15 +221,15 @@ defmodule Cldr.Currency.Backend do ## Examples - iex> {:ok, locale} = #{inspect backend}.validate_locale "en" - iex> #{inspect __MODULE__}.currency_from_locale locale + iex> {:ok, locale} = #{inspect(backend)}.validate_locale "en" + iex> #{inspect(__MODULE__)}.currency_from_locale locale :USD - iex> {:ok, locale} = #{inspect backend}.validate_locale "en-AU" - iex> #{inspect __MODULE__}.currency_from_locale locale + iex> {:ok, locale} = #{inspect(backend)}.validate_locale "en-AU" + iex> #{inspect(__MODULE__)}.currency_from_locale locale :AUD - iex> #{inspect __MODULE__}.currency_from_locale "en-GB" + iex> #{inspect(__MODULE__)}.currency_from_locale "en-GB" :GBP """ @@ -250,9 +247,14 @@ defmodule Cldr.Currency.Backend do ## Arguments - * `currency_or_currency_code` is a `binary` or `atom` representation + * `currency_or_currency_code` is a `binary` or `atom` representation of an ISO 4217 currency code, or a `%Cldr.Currency{}` struct. + ## Options + + * `:locale` is any valid locale name returned by `Cldr.known_locale_names/1` + or a `Cldr.LanguageTag` struct returned by `Cldr.Locale.new!/2` + ## Examples iex> #{inspect(__MODULE__)}.currency_for_code("AUD") @@ -424,9 +426,11 @@ defmodule Cldr.Currency.Backend do }} """ - @spec currency_strings(Cldr.LanguageTag.t() | Cldr.Locale.locale_name(), + @spec currency_strings( + Cldr.LanguageTag.t() | Cldr.Locale.locale_name(), only :: Cldr.Currency.filter(), - except :: Cldr.Currency.filter()) :: + except :: Cldr.Currency.filter() + ) :: {:ok, map()} | {:error, {module(), String.t()}} @dialyzer {:nowarn_function, currency_strings: 3} @@ -455,11 +459,12 @@ defmodule Cldr.Currency.Backend do inverted_currency_strings = Cldr.Currency.invert_currency_strings(currency_strings) |> Cldr.Currency.remove_duplicate_strings(currencies) - |> Map.new + |> Map.new() def currencies_for_locale( %LanguageTag{cldr_locale_name: unquote(locale_name)}, - only, except + only, + except ) do filtered_currencies = unquote(Macro.escape(currencies)) @@ -571,9 +576,12 @@ defmodule Cldr.Currency.Backend do } """ - @spec currencies_for_locale(Cldr.Locale.locale_name() | LanguageTag.t(), - only :: Cldr.Currency.filter(), except :: Cldr.Currency.filter()) :: - map() | no_return() + @spec currencies_for_locale( + Cldr.Locale.locale_name() | LanguageTag.t(), + only :: Cldr.Currency.filter(), + except :: Cldr.Currency.filter() + ) :: + map() | no_return() def currencies_for_locale!(locale, only \\ :all, except \\ nil) do case currencies_for_locale(locale, only, except) do @@ -620,8 +628,11 @@ defmodule Cldr.Currency.Backend do } """ - @spec currency_strings!(Cldr.LanguageTag.t() | Cldr.Locale.locale_name(), - only :: Cldr.Currency.filter(), except :: Cldr.Currency.filter()) :: + @spec currency_strings!( + Cldr.LanguageTag.t() | Cldr.Locale.locale_name(), + only :: Cldr.Currency.filter(), + except :: Cldr.Currency.filter() + ) :: map() | no_return() def currency_strings!(locale_name, only \\ :all, except \\ nil) do @@ -679,8 +690,8 @@ defmodule Cldr.Currency.Backend do } """ - @spec currency_history_for_locale(LanguageTag.t | Cldr.Locale.locale_name) :: - map() | {:error, {module(), String.t}} + @spec currency_history_for_locale(LanguageTag.t() | Cldr.Locale.locale_name()) :: + map() | {:error, {module(), String.t()}} def currency_history_for_locale(%LanguageTag{} = language_tag) do Cldr.Currency.currency_history_for_locale(language_tag) diff --git a/lib/cldr/currency.ex b/lib/cldr/currency.ex index 303ac1c..f60e284 100644 --- a/lib/cldr/currency.ex +++ b/lib/cldr/currency.ex @@ -64,7 +64,7 @@ defmodule Cldr.Currency do # possible @doc false - @spec start_link(Keyword.t) :: Cldr.Eternal.on_start() + @spec start_link(Keyword.t()) :: Cldr.Eternal.on_start() def start_link(options) when is_list(options) do options = Keyword.merge(@default_options, options) Cldr.Eternal.start_link(__MODULE__, @table_options, options) @@ -119,36 +119,33 @@ defmodule Cldr.Currency do ## Example - iex> Cldr.Currency.new(:XAA, name: "XAA currency", digits: 0) + iex> Cldr.Currency.new(:XAC, name: "XAC currency", digits: 0) {:ok, %Cldr.Currency{ - alt_code: :XAA, + alt_code: :XAC, cash_digits: 0, cash_rounding: nil, - code: :XAA, - count: %{other: "XAA currency"}, + code: :XAC, + count: %{other: "XAC currency"}, digits: 0, from: nil, iso_digits: 0, - name: "XAA currency", + name: "XAC currency", narrow_symbol: nil, rounding: 0, - symbol: "XAA", + symbol: "XAC", tender: false, to: nil }} - iex> Cldr.Currency.new(:XBC) {:error, {Cldr.CurrencyAlreadyDefined, "Currency :XBC is already defined."}} - - iex> MyApp.Cldr.Currency.new(:XAA, name: "Private Use Name") + iex> MyApp.Cldr.Currency.new(:XAB, name: "Private Use Name") {:error, "Required options are missing. Required options are [:name, :digits]"} - iex> Cldr.Currency.new(:ZAA, name: "Invalid Private Use Name", digits: 0) {:error, {Cldr.UnknownCurrencyError, "The currency :ZAA is invalid"}} """ - @spec new(binary | atom, map | list) :: {:ok, t} | {:error, {module(), String.t}} + @spec new(binary | atom, map | list) :: {:ok, t} | {:error, {module(), String.t()}} def new(currency, options \\ []) def new(currency, options) when is_list(options) do @@ -185,7 +182,7 @@ defmodule Cldr.Currency do if Enum.all?(keys, &options[&1]) do {:ok, options} else - {:error, "Required options are missing. Required options are #{inspect keys}"} + {:error, "Required options are missing. Required options are #{inspect(keys)}"} end end @@ -195,14 +192,14 @@ defmodule Cldr.Currency do ## Example - iex> Cldr.Currency.validate_new_currency :XAC - {:ok, :XAC} + iex> Cldr.Currency.validate_new_currency :XAD + {:ok, :XAD} iex> Cldr.Currency.validate_new_currency :USD {:error, {Cldr.CurrencyAlreadyDefined, "Currency :USD is already defined."}} """ - @spec validate_new_currency(code) :: {:ok, code} | {:error, {module, String.t}} + @spec validate_new_currency(code) :: {:ok, code} | {:error, {module, String.t()}} def validate_new_currency(code) do if code in known_currency_codes() do {:error, {Cldr.CurrencyAlreadyDefined, currency_already_defined_error(code)}} @@ -211,11 +208,12 @@ defmodule Cldr.Currency do end end - def store_currency(%Cldr.Currency{code: code} = currency) do + defp store_currency(%Cldr.Currency{code: code} = currency) do :ets.insert_new(__MODULE__, {code, currency}) {:ok, currency} - rescue ArgumentError -> - {:error, {Cldr.CurrencyNotSavedError, currency_not_saved_error(code)}} + rescue + ArgumentError -> + {:error, {Cldr.CurrencyNotSavedError, currency_not_saved_error(code)}} end @doc """ @@ -401,8 +399,7 @@ defmodule Cldr.Currency do ## Example - iex> Cldr.Currency.known_currency_codes |> Enum.count - 303 + iex> Cldr.Currency.known_currency_codes """ @spec known_currency_codes() :: list(atom) @@ -472,7 +469,7 @@ defmodule Cldr.Currency do {:error, {Cldr.UnknownCurrencyError, "The currency \\"GGG\\" is invalid"}} """ - @spec known_currency_code(code()) :: {:ok, code} | {:error, {module, String.t}} + @spec known_currency_code(code()) :: {:ok, code} | {:error, {module, String.t()}} def known_currency_code(currency_code) do with {:ok, currency_code} <- Cldr.validate_currency(currency_code) do if currency_code in known_currency_codes() do @@ -502,10 +499,11 @@ defmodule Cldr.Currency do @spec private_currencies :: %{code => t} def private_currencies do __MODULE__ - |> :ets.tab2list - |> Map.new - rescue ArgumentError -> - %{} + |> :ets.tab2list() + |> Map.new() + rescue + ArgumentError -> + %{} end @doc """ @@ -639,9 +637,12 @@ defmodule Cldr.Currency do {:ok, currencies} <- Map.fetch(territory_currencies(), territory) do {:ok, currencies} else - :error -> {:error, {Cldr.UnknownCurrencyError, - "No currencies for #{inspect territory} were found"}} - other -> other + :error -> + {:error, + {Cldr.UnknownCurrencyError, "No currencies for #{inspect(territory)} were found"}} + + other -> + other end end @@ -676,8 +677,8 @@ defmodule Cldr.Currency do } """ - @spec currency_history_for_locale(LanguageTag.t) :: - {:ok, map()} | {:error, {atom, binary}} + @spec currency_history_for_locale(LanguageTag.t()) :: + {:ok, map()} | {:error, {atom, binary}} def currency_history_for_locale(%LanguageTag{} = locale) do locale @@ -685,8 +686,8 @@ defmodule Cldr.Currency do |> territory_currencies() end - @spec currency_history_for_locale(Locale.locale_name, Cldr.backend) :: - {:ok, map()} | {:error, {module(), String.t}} + @spec currency_history_for_locale(Locale.locale_name(), Cldr.backend()) :: + {:ok, map()} | {:error, {module(), String.t()}} def currency_history_for_locale(locale_name, backend) when is_binary(locale_name) do with {:ok, locale} <- Cldr.validate_locale(locale_name, backend) do @@ -728,7 +729,7 @@ defmodule Cldr.Currency do end @spec current_currency_from_locale(Cldr.Locale.locale_name(), Cldr.backend()) :: - code() | nil | {:error, {module(), String.t()}} + code() | nil | {:error, {module(), String.t()}} def current_currency_from_locale(locale_name, backend) when is_binary(locale_name) do with {:ok, locale} <- Cldr.validate_locale(locale_name, backend) do @@ -754,12 +755,11 @@ defmodule Cldr.Currency do """ @spec current_currency_for_territory(Cldr.territory()) :: - code() | nil | {:error, {module(), String.t()}} + code() | nil | {:error, {module(), String.t()}} def current_currency_for_territory(territory) do with {:ok, territory} <- Cldr.validate_territory(territory), {:ok, history} <- territory_currencies(territory) do - history |> Enum.find(fn {_currency, dates} -> Map.has_key?(dates, :to) && is_nil(dates.to) end) |> elem(0) @@ -824,12 +824,15 @@ defmodule Cldr.Currency do {:ok, t()} | {:error, {module(), String.t()}} def currency_for_code(currency_or_currency_code, backend, options \\ []) - def currency_for_code(%__MODULE__{} = currency, _backend, _options), do: {:ok, currency} + + def currency_for_code(%__MODULE__{} = currency, _backend, _options) do + {:ok, currency} + end def currency_for_code(currency_code, backend, options) do {locale, backend} = Cldr.locale_and_backend_from(options[:locale], backend) - with {:ok, code} <- known_currency_code(currency_code), + with {:ok, code} <- Cldr.validate_currency(currency_code), {:ok, locale} <- Cldr.validate_locale(locale, backend), {:ok, currencies} <- currencies_for_locale(locale, backend) do {:ok, Map.get_lazy(currencies, code, fn -> Map.get(private_currencies(), code) end)} @@ -897,8 +900,12 @@ defmodule Cldr.Currency do }} """ - @spec currencies_for_locale(Locale.locale_name() | LanguageTag.t(), Cldr.backend(), - only :: filter(), except :: filter()) :: + @spec currencies_for_locale( + Locale.locale_name() | LanguageTag.t(), + Cldr.backend(), + only :: filter(), + except :: filter() + ) :: {:ok, map()} | {:error, {module(), String.t()}} def currencies_for_locale(locale, backend, only \\ :all, except \\ nil) do @@ -965,10 +972,12 @@ defmodule Cldr.Currency do } """ - @spec currencies_for_locale!(Locale.locale_name() | LanguageTag.t(), + @spec currencies_for_locale!( + Locale.locale_name() | LanguageTag.t(), Cldr.backend(), only :: filter(), - except :: filter()) :: + except :: filter() + ) :: map() | no_return() def currencies_for_locale!(locale, backend, only \\ :all, except \\ nil) do @@ -1029,8 +1038,11 @@ defmodule Cldr.Currency do }} """ - @spec currency_strings(Cldr.LanguageTag.t() | Cldr.Locale.locale_name(), - only :: filter(), except :: filter()) :: + @spec currency_strings( + Cldr.LanguageTag.t() | Cldr.Locale.locale_name(), + only :: filter(), + except :: filter() + ) :: {:ok, map()} | {:error, {module(), String.t()}} def currency_strings(locale, backend, only \\ :all, except \\ nil) do @@ -1075,8 +1087,11 @@ defmodule Cldr.Currency do } """ - @spec currency_strings!(Cldr.LanguageTag.t() | Cldr.Locale.locale_name(), - only :: filter(), except :: filter()) :: + @spec currency_strings!( + Cldr.LanguageTag.t() | Cldr.Locale.locale_name(), + only :: filter(), + except :: filter() + ) :: map() | no_return def currency_strings!(locale, backend, only \\ :all, except \\ nil) do @@ -1108,14 +1123,14 @@ defmodule Cldr.Currency do ## Example - iex> Cldr.Currency.strings_for_currency :AUD, "en", MyApp.Cldr - ["a$", "australian dollars", "aud", "australian dollar"] + iex> Cldr.Currency.strings_for_currency(:AUD, "en", MyApp.Cldr) |> Enum.sort + ["a$", "aud", "australian dollar", "australian dollars"] - iex> Cldr.Currency.strings_for_currency :AUD, "de", MyApp.Cldr - ["australische dollar", "australischer dollar", "au$", "aud"] + iex> Cldr.Currency.strings_for_currency(:AUD, "de", MyApp.Cldr) |> Enum.sort + ["au$", "aud", "australische dollar", "australischer dollar"] - iex> Cldr.Currency.strings_for_currency :AUD, "zh", MyApp.Cldr - ["澳大利亚元", "au$", "aud"] + iex> Cldr.Currency.strings_for_currency(:AUD, "zh", MyApp.Cldr) |> Enum.sort + ["au$", "aud", "澳大利亚元"] """ def strings_for_currency(currency, locale, backend) do @@ -1169,10 +1184,14 @@ defmodule Cldr.Currency do @spec currency_filter( Cldr.Currency.t() | [Cldr.Currency.t()] | map(), Cldr.Currency.currency_status() - ) :: list(Cldr.Currency.t) + ) :: list(Cldr.Currency.t()) def currency_filter(currencies, only \\ :all, except \\ nil) + def currency_filter(currencies, :all, nil) do + currencies + end + def currency_filter(currencies, only, except) when not is_list(only) do currency_filter(currencies, [only], except) end @@ -1181,22 +1200,18 @@ defmodule Cldr.Currency do currency_filter(currencies, only, [except]) end - def currency_filter(%Cldr.Currency{} = currency, only, except) do + def currency_filter(%Cldr.Currency{} = currency, only, except) do currency_filter([currency], only, except) end def currency_filter(currencies, only, except) when is_map(currencies) do currencies - |> Map.values + |> Map.values() |> currency_filter(only, except) |> Map.new(fn currency -> {String.to_atom(currency.code), currency} end) end - def currency_filter(currencies, [:all], [nil]) do - currencies - end - - def currency_filter(currencies, only, except) do + def currency_filter(currencies, only, except) do expand_filter(currencies, :only, only) -- expand_filter(currencies, :except, except) end @@ -1213,33 +1228,41 @@ defmodule Cldr.Currency do case filter do :historic -> Enum.filter(currencies, &historic?/1) + :tender -> Enum.filter(currencies, &tender?/1) + :current -> Enum.filter(currencies, ¤t?/1) + :annotated -> Enum.filter(currencies, &annotated?/1) + :unannotated -> Enum.filter(currencies, &unannotated?/1) + :private -> private_currencies() + code when is_binary(code) -> Enum.filter(currencies, fn currency -> currency.code == code end) + code when is_atom(code) -> code = to_string(code) + Enum.filter(currencies, fn currency -> currency.code == code end) end end) - |> Enum.uniq + |> Enum.uniq() end def historic?(%Cldr.Currency{} = currency) do is_nil(currency.iso_digits) || - (is_integer(currency.to) && currency.to < Date.utc_today().year) + (is_integer(currency.to) && currency.to < Date.utc_today().year) end def tender?(%Cldr.Currency{} = currency) do @@ -1263,10 +1286,15 @@ defmodule Cldr.Currency do @doc false def string_comparator({k, v1}, {k, v2}, currencies) do cond do - historic?(currencies[v1]) -> false - historic?(currencies[v2]) -> true - true -> raise "String #{inspect k} has two current currencies of #{inspect v1} and " <> - "#{inspect v2}." + historic?(currencies[v1]) -> + false + + historic?(currencies[v2]) -> + true + + true -> + raise "String #{inspect(k)} has two current currencies of #{inspect(v1)} and " <> + "#{inspect(v2)}." end end @@ -1308,7 +1336,7 @@ defmodule Cldr.Currency do Enum.reduce(currency_strings, [], fn {code, strings}, acc -> [Enum.map(strings, fn string -> {string, code} end) | acc] end) - |> List.flatten + |> List.flatten() end defp currency_already_defined_error(code) do @@ -1317,7 +1345,7 @@ defmodule Cldr.Currency do defp currency_not_saved_error(code) do """ - The currency #{inspect code} could not be defined. + The currency #{inspect(code)} could not be defined. This is probably because the table is not defined in which the new currency information is saved. @@ -1337,5 +1365,4 @@ defmodule Cldr.Currency do Cldr.default_backend() end end - end diff --git a/lib/cldr/eternal.ex b/lib/cldr/eternal.ex index 154794e..9d74875 100644 --- a/lib/cldr/eternal.ex +++ b/lib/cldr/eternal.ex @@ -1,169 +1,168 @@ -defmodule Cldr.Eternal do - @moduledoc """ - This module implements bindings around what should be an eternal ETS table, - or at least until you decide to terminate it. It works by using "bouncing" - GenServers which come up as needed to provide an heir for the ETS table. It - operates as follows: - - 1. An ETS table is created with the provided name and options. - 2. Two GenServers are started, an `owner` and an `heir`. The ETS table is gifted - to the `owner`, and has the `heir` set as the heir. - 3. If the `owner` crashes, the `heir` becomes the owner, and a new GenServer - is started and assigned the role of `heir`. - 4. If an `heir` dies, we attempt to start a new GenServer and notify the `owner` - so that they may change the assigned `heir`. - - This means that there should always be an `heir` to your table, which should - ensure that you don't lose anything inside ETS. - """ - - # import guards - import Cldr.Eternal.Table - import Cldr.Eternal.Priv - - # alias while we're at it - alias Cldr.Eternal.Priv - alias Cldr.Eternal.Table - alias Cldr.Eternal.Supervisor, as: Sup - - # Return values of `start_link` functions - @type on_start :: { :ok, pid } | :ignore | - { :error, { :already_started, pid } | { :shutdown, term } | term } - - @doc """ - Creates a new ETS table using the provided `ets_opts`. - - These options are passed through as-is, with the exception of prepending the - `:public` and `:named_table` options. Seeing as you can't execute inside the - GenServers, your table will have to be public to be interacted with. - - ## Options - - You may provide a third parameter containing Eternal options: - - - `:name` - override the default naming scheme and use a custom name for this - table. Remember to use this name when calling `stop/1`. - - `:quiet` - by default, Eternal logs debug messages. Setting this to true will - disable this logging. - - ## Examples - - iex> Cldr.Eternal.start_link(:table1, [ ], [ quiet: true ]) - - iex> Cldr.Eternal.start_link(:table2, [ :compressed ], [ quiet: true ]) - - iex> Cldr.Eternal.start_link(:table3, [ ], [ quiet: true ]) - - """ - # @spec start_link(name :: atom, ets_opts :: Keyword.t, opts :: Keyword.t) :: on_start - @dialyzer {:nowarn_function, {:start_link, 3}} - def start_link(name, ets_opts \\ [], opts \\ []) when is_opts(name, ets_opts, opts) do - with { :ok, pid, _table } <- create(name, [ :named_table ] ++ ets_opts, opts) do - {:ok, pid} - end - end - - @doc """ - Functionally equivalent to `start_link/3`, except that the link to the starting - process is removed after the table is started. - - ## Examples - - iex> Cldr.Eternal.start(:table1, [ ], [ quiet: true ]) - - iex> Cldr.Eternal.start(:table2, [ :compressed ], [ quiet: true ]) - - iex> Cldr.Eternal.start(:table3, [ ], [ quiet: true ]) - - """ - # @spec start(name :: atom, ets_opts :: Keyword.t, opts :: Keyword.t) :: on_start - @dialyzer {:nowarn_function, {:start, 3}} - def start(name, ets_opts \\ [], opts \\ []) when is_opts(name, ets_opts, opts) do - with {:ok, pid} <- start_link(name, ets_opts, opts) do - :erlang.unlink(pid) - {:ok, pid} - end - end - - @doc """ - Returns the heir of a given ETS table. - - ## Examples - - iex> Cldr.Eternal.heir(:my_table) - - """ - @spec heir(table :: Table.t) :: any() - def heir(table) when is_table(table), - do: :ets.info(table, :heir) - - @doc """ - Returns the owner of a given ETS table. - - ## Examples - - iex> Cldr.Eternal.owner(:my_table) - - """ - @spec owner(table :: Table.t) :: any() - def owner(table) when is_table(table), - do: :ets.info(table, :owner) - - @doc """ - Terminates both servers in charge of a given ETS table. - - Note: this will terminate your ETS table. - - ## Examples - - iex> Cldr.Eternal.stop(:my_table) - :ok - - """ - @spec stop(table :: Table.t) :: :ok - def stop(table) when is_table(table) do - name = Table.to_name(table) - proc = GenServer.whereis(name) - - if proc && Process.alive?(proc) do - Supervisor.stop(proc) - end - - :ok - end - - # Creates a table supervisor with the provided options and nominates the children - # as owner/heir of the ETS table immediately afterwards. We do this by fetching - # the children of the supervisor and using the process id to nominate. - @dialyzer {:nowarn_function, {:create, 3}} - defp create(name, ets_opts, opts) do - with { :ok, pid, table } <- Sup.start_link(name, ets_opts, opts), - [proc1, proc2] = Supervisor.which_children(pid), - {_id1, pid1, :worker, [__MODULE__.Server]} = proc1, - {_id2, pid2, :worker, [__MODULE__.Server]} = proc2 do - - Priv.heir(table, pid2) - Priv.gift(table, pid1) - - maybe_process_callback(opts[:callback], pid, table) - {:ok, pid, table} - end - end - - # Callback function when the :ets table - # is created and the supervisor process - # is up and running. - @dialyzer {:nowarn_function, {:maybe_process_callback, 3}} - defp maybe_process_callback(nil, _pid, _table) do - nil - end - - defp maybe_process_callback(fun, pid, table) when is_function(fun, 2) do - fun.(pid, table) - end - - defp maybe_process_callback({module, function, args}, pid, table) - when is_mfa(module, function, args) do - :erlang.apply(module, function, [pid, table | args]) - end -end +defmodule Cldr.Eternal do + @moduledoc """ + This module implements bindings around what should be an eternal ETS table, + or at least until you decide to terminate it. It works by using "bouncing" + GenServers which come up as needed to provide an heir for the ETS table. It + operates as follows: + + 1. An ETS table is created with the provided name and options. + 2. Two GenServers are started, an `owner` and an `heir`. The ETS table is gifted + to the `owner`, and has the `heir` set as the heir. + 3. If the `owner` crashes, the `heir` becomes the owner, and a new GenServer + is started and assigned the role of `heir`. + 4. If an `heir` dies, we attempt to start a new GenServer and notify the `owner` + so that they may change the assigned `heir`. + + This means that there should always be an `heir` to your table, which should + ensure that you don't lose anything inside ETS. + """ + + # import guards + import Cldr.Eternal.Table + import Cldr.Eternal.Priv + + # alias while we're at it + alias Cldr.Eternal.Priv + alias Cldr.Eternal.Table + alias Cldr.Eternal.Supervisor, as: Sup + + # Return values of `start_link` functions + @type on_start :: + {:ok, pid} | :ignore | {:error, {:already_started, pid} | {:shutdown, term} | term} + + @doc """ + Creates a new ETS table using the provided `ets_opts`. + + These options are passed through as-is, with the exception of prepending the + `:public` and `:named_table` options. Seeing as you can't execute inside the + GenServers, your table will have to be public to be interacted with. + + ## Options + + You may provide a third parameter containing Eternal options: + + - `:name` - override the default naming scheme and use a custom name for this + table. Remember to use this name when calling `stop/1`. + - `:quiet` - by default, Eternal logs debug messages. Setting this to true will + disable this logging. + + ## Examples + + iex> Cldr.Eternal.start_link(:table1, [], [quiet: true]) + + iex> Cldr.Eternal.start_link(:table2, [:compressed], [quiet: true]) + + iex> Cldr.Eternal.start_link(:table3, [], [quiet: true]) + + """ + # @spec start_link(name :: atom, ets_opts :: Keyword.t, opts :: Keyword.t) :: on_start + @dialyzer {:nowarn_function, {:start_link, 3}} + def start_link(name, ets_opts \\ [], opts \\ []) when is_opts(name, ets_opts, opts) do + with {:ok, pid, _table} <- create(name, [:named_table] ++ ets_opts, opts) do + {:ok, pid} + end + end + + @doc """ + Functionally equivalent to `start_link/3`, except that the link to the starting + process is removed after the table is started. + + ## Examples + + iex> Cldr.Eternal.start(:table1, [], [quiet: true]) + + iex> Cldr.Eternal.start(:table2, [:compressed], [quiet: true]) + + iex> Cldr.Eternal.start(:table3, [], [quiet: true]) + + """ + # @spec start(name :: atom, ets_opts :: Keyword.t, opts :: Keyword.t) :: on_start + @dialyzer {:nowarn_function, {:start, 3}} + def start(name, ets_opts \\ [], opts \\ []) when is_opts(name, ets_opts, opts) do + with {:ok, pid} <- start_link(name, ets_opts, opts) do + :erlang.unlink(pid) + {:ok, pid} + end + end + + @doc """ + Returns the heir of a given ETS table. + + ## Examples + + iex> Cldr.Eternal.heir(:my_table) + + """ + @spec heir(table :: Table.t()) :: any() + def heir(table) when is_table(table), + do: :ets.info(table, :heir) + + @doc """ + Returns the owner of a given ETS table. + + ## Examples + + iex> Cldr.Eternal.owner(:my_table) + + """ + @spec owner(table :: Table.t()) :: any() + def owner(table) when is_table(table), + do: :ets.info(table, :owner) + + @doc """ + Terminates both servers in charge of a given ETS table. + + Note: this will terminate your ETS table. + + ## Examples + + iex> Cldr.Eternal.stop(:my_table) + :ok + + """ + @spec stop(table :: Table.t()) :: :ok + def stop(table) when is_table(table) do + name = Table.to_name(table) + proc = GenServer.whereis(name) + + if proc && Process.alive?(proc) do + Supervisor.stop(proc) + end + + :ok + end + + # Creates a table supervisor with the provided options and nominates the children + # as owner/heir of the ETS table immediately afterwards. We do this by fetching + # the children of the supervisor and using the process id to nominate. + @dialyzer {:nowarn_function, {:create, 3}} + defp create(name, ets_opts, opts) do + with {:ok, pid, table} <- Sup.start_link(name, ets_opts, opts), + [proc1, proc2] = Supervisor.which_children(pid), + {_id1, pid1, :worker, [__MODULE__.Server]} = proc1, + {_id2, pid2, :worker, [__MODULE__.Server]} = proc2 do + Priv.heir(table, pid2) + Priv.gift(table, pid1) + + maybe_process_callback(opts[:callback], pid, table) + {:ok, pid, table} + end + end + + # Callback function when the :ets table + # is created and the supervisor process + # is up and running. + @dialyzer {:nowarn_function, {:maybe_process_callback, 3}} + defp maybe_process_callback(nil, _pid, _table) do + nil + end + + defp maybe_process_callback(fun, pid, table) when is_function(fun, 2) do + fun.(pid, table) + end + + defp maybe_process_callback({module, function, args}, pid, table) + when is_mfa(module, function, args) do + :erlang.apply(module, function, [pid, table | args]) + end +end diff --git a/lib/cldr/eternal/priv.ex b/lib/cldr/eternal/priv.ex index f890afd..5c5c252 100644 --- a/lib/cldr/eternal/priv.ex +++ b/lib/cldr/eternal/priv.ex @@ -1,97 +1,97 @@ -defmodule Cldr.Eternal.Priv do - @moduledoc false - # This module contains code private to the Eternal project, basically just - # providing utility functions and macros. Nothing too interesting to see here - # beyond shorthands for common blocks. - - # we need is_table/1 - import Cldr.Eternal.Table - alias Cldr.Eternal.Table - - # we also need logging - require Logger - - @doc """ +defmodule Cldr.Eternal.Priv do + @moduledoc false + # This module contains code private to the Eternal project, basically just + # providing utility functions and macros. Nothing too interesting to see here + # beyond shorthands for common blocks. + + # we need is_table/1 + import Cldr.Eternal.Table + alias Cldr.Eternal.Table + + # we also need logging + require Logger + + @doc """ Provides a safe execution environment for ETS actions. - + If any errors occur inside ETS, we simply return a false value. It should be noted that the table is passed through purely as sugar so we can use inline anonymous functions. - """ - @spec ets_try(table :: Table.t, fun :: function) :: any - def ets_try(table, fun) when is_table(table) and is_function(fun, 1) do - fun.(table) - rescue - _ -> false - end - - @doc """ + """ + @spec ets_try(table :: Table.t(), fun :: function) :: any + def ets_try(table, fun) when is_table(table) and is_function(fun, 1) do + fun.(table) + rescue + _ -> false + end + + @doc """ Gifts away an ETS table to another process. - + This must be called from within the owning process. - """ - @spec gift(table :: Table.t, pid :: pid) :: any | false - def gift(table, pid) when is_table(table) and is_pid(pid), - do: ets_try(table, &:ets.give_away(&1, pid, :gift)) - - @doc """ + """ + @spec gift(table :: Table.t(), pid :: pid) :: any | false + def gift(table, pid) when is_table(table) and is_pid(pid), + do: ets_try(table, &:ets.give_away(&1, pid, :gift)) + + @doc """ Sets the Heir of an ETS table to a given process. - + This must be called from within the owning process. - """ - @spec heir(table :: Table.t, pid :: pid) :: any | false - def heir(table, pid) when is_table(table) and is_pid(pid), - do: ets_try(table, &:ets.setopts(&1, { :heir, pid, :heir })) - - @doc """ + """ + @spec heir(table :: Table.t(), pid :: pid) :: any | false + def heir(table, pid) when is_table(table) and is_pid(pid), + do: ets_try(table, &:ets.setopts(&1, {:heir, pid, :heir})) + + @doc """ Logs a message inside a noisy environment. - + If the options contains a truthy quiet flag, no logging occurs. - """ - @spec log(msg :: any, opts :: Keyword.t) :: :ok - def log(msg, opts) when is_list(opts) do - noisy(opts, fn -> - Logger.debug(fn -> "[eternal] #{msg}" end) - end) - end - - @doc """ + """ + @spec log(msg :: any, opts :: Keyword.t()) :: :ok + def log(msg, opts) when is_list(opts) do + noisy(opts, fn -> + Logger.debug(fn -> "[eternal] #{msg}" end) + end) + end + + @doc """ Executes a function only in a noisy environment. - + Noisy environments are determined by the opts having a falsy quiet flag. - """ - @spec noisy(opts :: Keyword.t, fun :: function) :: :ok - def noisy(opts, fun) when is_list(opts) and is_function(fun, 0) do - !Keyword.get(opts, :quiet) && fun.() - :ok - end - - @doc """ + """ + @spec noisy(opts :: Keyword.t(), fun :: function) :: :ok + def noisy(opts, fun) when is_list(opts) and is_function(fun, 0) do + !Keyword.get(opts, :quiet) && fun.() + :ok + end + + @doc """ Converts a PID to a Binary using `inspect/1`. - """ - @spec spid(pid :: pid) :: spid :: binary - def spid(pid), - do: inspect(pid) - - @doc """ + """ + @spec spid(pid :: pid) :: spid :: binary + def spid(pid), + do: inspect(pid) + + @doc """ Determines if a list of arguments are correctly formed. - """ - defmacro is_opts(name, ets_opts, opts) do - quote do - is_atom(unquote(name)) and - is_list(unquote(ets_opts)) and - is_list(unquote(opts)) - end - end - - @doc """ + """ + defmacro is_opts(name, ets_opts, opts) do + quote do + is_atom(unquote(name)) and + is_list(unquote(ets_opts)) and + is_list(unquote(opts)) + end + end + + @doc """ Determines if a 3-tuple is a {module, function, args} - """ - defmacro is_mfa(module, function, args) do - quote do - is_atom(unquote(module)) and - is_atom(unquote(function)) and - is_list(unquote(args)) - end - end -end + """ + defmacro is_mfa(module, function, args) do + quote do + is_atom(unquote(module)) and + is_atom(unquote(function)) and + is_list(unquote(args)) + end + end +end diff --git a/lib/cldr/eternal/server.ex b/lib/cldr/eternal/server.ex index e2a2f9f..b4f8625 100644 --- a/lib/cldr/eternal/server.ex +++ b/lib/cldr/eternal/server.ex @@ -1,63 +1,63 @@ -defmodule Cldr.Eternal.Server do - @moduledoc false - # This module remains internal to Eternal and should not be manually created - # by anyone external so it shall remain undocumented for now. - # - # Basically, this module implements an extremely base GenServer which listens - # on two messages - the first is that of ETS to trigger a log that the table - # owner has changed, and the other is a recognisation that a new heir has been - # attached and needs assigning to ETS (as only an owner can set the heir). - - # use default server behaviour - use GenServer - - # add a Priv alias - alias Cldr.Eternal.Priv - - @doc """ +defmodule Cldr.Eternal.Server do + @moduledoc false + # This module remains internal to Eternal and should not be manually created + # by anyone external so it shall remain undocumented for now. + # + # Basically, this module implements an extremely base GenServer which listens + # on two messages - the first is that of ETS to trigger a log that the table + # owner has changed, and the other is a recognisation that a new heir has been + # attached and needs assigning to ETS (as only an owner can set the heir). + + # use default server behaviour + use GenServer + + # add a Priv alias + alias Cldr.Eternal.Priv + + @doc """ Simply performs a base validation and passes through the arguments to the server. - """ - def start_link({ _table, _opts, _base } = args), - do: GenServer.start_link(__MODULE__, args) - - @doc """ + """ + def start_link({_table, _opts, _base} = args), + do: GenServer.start_link(__MODULE__, args) + + @doc """ Initialization phase of an Eternal server. - + If the server process is intended to be a new heir, we message the owner in order to let it know we need adding as an heir. We don't do this is there is no owner or the owner is the base process creating the Eternal table, as this would result in no heir being assigned. - """ - def init({ table, opts, base }) do - owner = Cldr.Eternal.owner(table) - - unless owner in [ :undefined, base ] do - send(owner, { :"ETS-HEIR-UP", table, self() }) - end - - { :ok, { table, opts } } - end - - # Handles the transfer of an ETS table from an owner to an heir, with the server - # receiving this message being the heir. We log the change before starting a new - # heir and triggering a monitor to occur against this server (the new owner). - def handle_info({ :"ETS-TRANSFER", table, from, reason }, { table, opts } = state) do - Priv.log("Table '#{table}' #{reason}ed to #{Priv.spid(self())} via #{Priv.spid(from)}", opts) - { :noreply, state } - end - - # Handles the termination of an heir, through the usual GenServer stop functions. - # In this scenario the heir will attempt to message through to the owner in order - # to inform it to create a new heir, which it will then carry out. - def handle_info({ :"ETS-HEIR-UP", table, pid }, { table, opts } = state) do - Priv.heir(table, pid) - Priv.log("Assigned new heir #{Priv.spid(pid)}", opts) - { :noreply, state } - end - - # Catch all info handler to ensure that we don't crash for whatever reason when - # an unrecognised message is sent. In theory, a crash shouldn't be an issue, but - # it's better logically to avoid doing so here. - def handle_info(_msg, state), - do: { :noreply, state } -end + """ + def init({table, opts, base}) do + owner = Cldr.Eternal.owner(table) + + unless owner in [:undefined, base] do + send(owner, {:"ETS-HEIR-UP", table, self()}) + end + + {:ok, {table, opts}} + end + + # Handles the transfer of an ETS table from an owner to an heir, with the server + # receiving this message being the heir. We log the change before starting a new + # heir and triggering a monitor to occur against this server (the new owner). + def handle_info({:"ETS-TRANSFER", table, from, reason}, {table, opts} = state) do + Priv.log("Table '#{table}' #{reason}ed to #{Priv.spid(self())} via #{Priv.spid(from)}", opts) + {:noreply, state} + end + + # Handles the termination of an heir, through the usual GenServer stop functions. + # In this scenario the heir will attempt to message through to the owner in order + # to inform it to create a new heir, which it will then carry out. + def handle_info({:"ETS-HEIR-UP", table, pid}, {table, opts} = state) do + Priv.heir(table, pid) + Priv.log("Assigned new heir #{Priv.spid(pid)}", opts) + {:noreply, state} + end + + # Catch all info handler to ensure that we don't crash for whatever reason when + # an unrecognised message is sent. In theory, a crash shouldn't be an issue, but + # it's better logically to avoid doing so here. + def handle_info(_msg, state), + do: {:noreply, state} +end diff --git a/lib/cldr/eternal/supervisor.ex b/lib/cldr/eternal/supervisor.ex index c6fb153..a3b0967 100644 --- a/lib/cldr/eternal/supervisor.ex +++ b/lib/cldr/eternal/supervisor.ex @@ -1,92 +1,92 @@ -defmodule Cldr.Eternal.Supervisor do - @moduledoc false - # This module contains the main Eternal Supervisor which is used to manage the - # two internal GenServers which act as owner/heir to the ETS table. There's little - # in here beyond setting up a Supervision tree, as we want to keep the code simple - # to make it pretty bulletproof as an implementation. - - # this is a supervisor - use Supervisor - - # we need some guards - import Cldr.Eternal.Priv - - # add alias for convenience - alias Application, as: App - alias Cldr.Eternal.Priv - alias Cldr.Eternal.Table - - @doc """ +defmodule Cldr.Eternal.Supervisor do + @moduledoc false + # This module contains the main Eternal Supervisor which is used to manage the + # two internal GenServers which act as owner/heir to the ETS table. There's little + # in here beyond setting up a Supervision tree, as we want to keep the code simple + # to make it pretty bulletproof as an implementation. + + # this is a supervisor + use Supervisor + + # we need some guards + import Cldr.Eternal.Priv + + # add alias for convenience + alias Application, as: App + alias Cldr.Eternal.Priv + alias Cldr.Eternal.Table + + @doc """ Starts an Eternal Supervision tree which manages the two internal servers. - + This returns a Tuple containing the table name, so it cannot be used inside a Supervision tree directly. If you want to use this Supervisor, you should go via the main Eternal module. - """ - # @spec start_link(name :: atom, ets_opts :: Keyword.t, opts :: Keyword.t) :: - # { :ok, pid, Table.t } | :ignore | - # { :error, { :already_started, pid } | { :shutdown, term } | term } - @dialyzer {:nowarn_function, {:start_link, 3}} - def start_link(name, ets_opts \\ [], opts \\ []) when is_opts(name, ets_opts, opts) do - detect_clash(name, ets_opts, fn -> - super_tab = :ets.new(name, [ :public ] ++ ets_opts) - super_args = { super_tab, opts, self() } - super_opts = [ name: gen_name(opts, super_tab) ] - super_proc = Supervisor.start_link(__MODULE__, super_args, super_opts) - - with { :ok, pid } <- super_proc do - { :ok, pid, super_tab } - end - end) - end - - @doc false - # Main initialization phase which takes a table and options as an argument and - # sets up a child spec containing the two GenServers, passing through arguments - # as necessary. We also ensure that the Logger application is started at this - # point, just in case the user has been unable to start it for some reason. - # @spec init({ table :: Table.t, opts :: Keyword.t }) :: { :ok, tuple } - def init({ table, opts, base }) do - flags = Keyword.take(opts, [ :monitor, :quiet ]) - - Priv.noisy(flags, fn -> - App.ensure_all_started(:logger) - end) - - children = [ - Supervisor.child_spec({Cldr.Eternal.Server, { table, flags, base }}, id: Server.One), - Supervisor.child_spec({Cldr.Eternal.Server, { table, flags, base }}, id: Server.Two) - ] - - Supervisor.init(children, strategy: :one_for_one) - end - - # Detects a potential name clash inside ETS. If we have a named table and the - # table is already in use, we return a link to the existing Supervisor. This - # means we can be transparent to any crashes caused by starting the same ETS - # table twice. Otherwise, we execute the callback which will create the table. - defp detect_clash(name, ets_opts, fun) do - if exists?(name, ets_opts) do - { :error, { :already_started, Process.whereis(name) } } - else - fun.() - end - end - - # Shorthand function to determine if an ETS table exists or not. We calculate - # this by looking for the name inside the list of ETS tables, but only if the - # options specify that we should name the ETS table. If it's not named, there - # won't be a table clash when starting a new table, so we're safe to continue. - defp exists?(name, ets_opts) do - Enum.member?(ets_opts, :named_table) and - Enum.member?(:ets.all(), name) - end - - # Generates the name to use for the Supervisor. If the name is provided, we use - # that, otherwise we generates a default table name from the table identifier. - defp gen_name(opts, super_tab) do - Keyword.get_lazy(opts, :name, fn -> - Table.to_name(super_tab, true) - end) - end -end + """ + # @spec start_link(name :: atom, ets_opts :: Keyword.t, opts :: Keyword.t) :: + # { :ok, pid, Table.t } | :ignore | + # { :error, { :already_started, pid } | { :shutdown, term } | term } + @dialyzer {:nowarn_function, {:start_link, 3}} + def start_link(name, ets_opts \\ [], opts \\ []) when is_opts(name, ets_opts, opts) do + detect_clash(name, ets_opts, fn -> + super_tab = :ets.new(name, [:public] ++ ets_opts) + super_args = {super_tab, opts, self()} + super_opts = [name: gen_name(opts, super_tab)] + super_proc = Supervisor.start_link(__MODULE__, super_args, super_opts) + + with {:ok, pid} <- super_proc do + {:ok, pid, super_tab} + end + end) + end + + @doc false + # Main initialization phase which takes a table and options as an argument and + # sets up a child spec containing the two GenServers, passing through arguments + # as necessary. We also ensure that the Logger application is started at this + # point, just in case the user has been unable to start it for some reason. + # @spec init({ table :: Table.t, opts :: Keyword.t }) :: { :ok, tuple } + def init({table, opts, base}) do + flags = Keyword.take(opts, [:monitor, :quiet]) + + Priv.noisy(flags, fn -> + App.ensure_all_started(:logger) + end) + + children = [ + Supervisor.child_spec({Cldr.Eternal.Server, {table, flags, base}}, id: Server.One), + Supervisor.child_spec({Cldr.Eternal.Server, {table, flags, base}}, id: Server.Two) + ] + + Supervisor.init(children, strategy: :one_for_one) + end + + # Detects a potential name clash inside ETS. If we have a named table and the + # table is already in use, we return a link to the existing Supervisor. This + # means we can be transparent to any crashes caused by starting the same ETS + # table twice. Otherwise, we execute the callback which will create the table. + defp detect_clash(name, ets_opts, fun) do + if exists?(name, ets_opts) do + {:error, {:already_started, Process.whereis(name)}} + else + fun.() + end + end + + # Shorthand function to determine if an ETS table exists or not. We calculate + # this by looking for the name inside the list of ETS tables, but only if the + # options specify that we should name the ETS table. If it's not named, there + # won't be a table clash when starting a new table, so we're safe to continue. + defp exists?(name, ets_opts) do + Enum.member?(ets_opts, :named_table) and + Enum.member?(:ets.all(), name) + end + + # Generates the name to use for the Supervisor. If the name is provided, we use + # that, otherwise we generates a default table name from the table identifier. + defp gen_name(opts, super_tab) do + Keyword.get_lazy(opts, :name, fn -> + Table.to_name(super_tab, true) + end) + end +end diff --git a/lib/cldr/eternal/table.ex b/lib/cldr/eternal/table.ex index 662cf41..0f7871f 100644 --- a/lib/cldr/eternal/table.ex +++ b/lib/cldr/eternal/table.ex @@ -1,44 +1,47 @@ -defmodule Cldr.Eternal.Table do - @moduledoc false - # This module contains functions related to interactions with tables. At the moment - # this just consists of guard expressions to determine valid tables, and the ability - # to convert a table identifier to a valid Supervisor name. - - # define a table typespec - @type t :: number | atom - - @doc """ +defmodule Cldr.Eternal.Table do + @moduledoc false + # This module contains functions related to interactions with tables. At the moment + # this just consists of guard expressions to determine valid tables, and the ability + # to convert a table identifier to a valid Supervisor name. + + # define a table typespec + @type t :: number | atom + + @doc """ Converts a table name to a valid Supervisor name. - + Because tables can be integer references, we convert this to an atom only if the `create` flag is set to true. Otherwise, we attempt to convert to an existing name (as it should have already been created). - """ - @spec to_name(name :: (number | atom), create :: true | false) :: name :: atom | nil - def to_name(name, create \\ false) - def to_name(name, _create) when is_atom(name), - do: name - def to_name(name, true) when is_number(name) do - name - |> Kernel.to_string - |> String.to_atom - end - def to_name(name, false) when is_number(name) do - name - |> Kernel.to_string - |> String.to_existing_atom - rescue - _ -> nil - end - - @doc """ + """ + @spec to_name(name :: number | atom, create :: true | false) :: name :: atom | nil + def to_name(name, create \\ false) + + def to_name(name, _create) when is_atom(name), + do: name + + def to_name(name, true) when is_number(name) do + name + |> Kernel.to_string() + |> String.to_atom() + end + + def to_name(name, false) when is_number(name) do + name + |> Kernel.to_string() + |> String.to_existing_atom() + rescue + _ -> nil + end + + @doc """ Determines whether a value is a table or not. Tables can be either atoms or integer values. - """ - defmacro is_table(val) do - quote do - is_atom(unquote(val)) or - is_integer(unquote(val)) - end - end -end + """ + defmacro is_table(val) do + quote do + is_atom(unquote(val)) or + is_integer(unquote(val)) + end + end +end diff --git a/lib/cldr/exception.ex b/lib/cldr/exception.ex index b87a3c2..6b1e457 100644 --- a/lib/cldr/exception.ex +++ b/lib/cldr/exception.ex @@ -8,4 +8,4 @@ defmodule Cldr.CurencyNotSavedError do def exception(message) do %__MODULE__{message: message} end -end \ No newline at end of file +end diff --git a/lib/cldr/protocol/inspect.ex b/lib/cldr/protocol/inspect.ex new file mode 100644 index 0000000..1208d4e --- /dev/null +++ b/lib/cldr/protocol/inspect.ex @@ -0,0 +1,5 @@ +defimpl Inspect, for: Cldr.Currency do + def inspect(currency, _options) do + "#Cldr.Currency<" <> inspect(currency.code) <> ">" + end +end diff --git a/mix.exs b/mix.exs index 8206d1d..d21b52e 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule Cldr.Currencies.MixProject do use Mix.Project - @version "2.9.0" + @version "2.10.0" def project do [ @@ -41,7 +41,8 @@ defmodule Cldr.Currencies.MixProject do {:ex_cldr, "~> 2.20"}, {:jason, "~> 1.0", optional: true}, {:ex_doc, "~> 0.18", runtime: false}, - {:dialyxir, "~> 1.0", only: [:dev], runtime: false} + {:dialyxir, "~> 1.0", only: [:dev], runtime: false}, + {:benchee, "~> 1.0", only: :dev, optional: true} ] end diff --git a/mix.lock b/mix.lock index e238ab7..99e42c6 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,8 @@ %{ + "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"}, "cldr_utils": {:hex, :cldr_utils, "2.15.1", "1136987437ac4821f3ec7d46ff150f1c020a1f79730ff1252bfa7681ed577f31", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "ca3c8da0aab18b4d944541392e9548422e30854e3a9d7768dc629c8123f8efb8"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, + "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, "earmark": {:hex, :earmark, "1.4.9", "837e4c1c5302b3135e9955f2bbf52c6c52e950c383983942b68b03909356c0d9", [:mix], [{:earmark_parser, ">= 1.4.9", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "0d72df7d13a3dc8422882bed5263fdec5a773f56f7baeb02379361cb9e5b0d8e"}, "earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"}, diff --git a/mix/backend.ex b/mix/backend.ex index b54dd41..75ee780 100644 --- a/mix/backend.ex +++ b/mix/backend.ex @@ -7,4 +7,4 @@ if Mix.env() in [:dev] do default_locale: "en", providers: [Cldr.Currency] end -end \ No newline at end of file +end diff --git a/test/cldr_currencies_test.exs b/test/cldr_currencies_test.exs index 2a20b83..6d5812c 100644 --- a/test/cldr_currencies_test.exs +++ b/test/cldr_currencies_test.exs @@ -41,7 +41,7 @@ defmodule Cldr.Currency.Test do test "names with annotations are intact" do assert Cldr.Currency.strings_for_currency(:USN, "en", MyApp.Cldr) == - ["us dollar (next day)", "usn", "us dollars (next day)"] + ["us dollars (next day)", "us dollar (next day)", "usn"] end test "currency strings is a map" do diff --git a/test/cldr_eteneral_test.exs b/test/cldr_eteneral_test.exs index 0c1fa37..4d37910 100644 --- a/test/cldr_eteneral_test.exs +++ b/test/cldr_eteneral_test.exs @@ -6,24 +6,34 @@ defmodule Cldr.Cldr.EternalTest do doctest Cldr.Eternal test "starting a table successfully" do - assert(match?({ :ok, _pid }, Cldr.Eternal.start_link(:table_no_options, [], [ quiet: true ]))) + assert(match?({:ok, _pid}, Cldr.Eternal.start_link(:table_no_options, [], quiet: true))) end test "starting a table with options" do - assert(match?({ :ok, _pid }, Cldr.Eternal.start_link(:table_with_options, [ :compressed ], [ quiet: true ]))) + assert( + match?( + {:ok, _pid}, + Cldr.Eternal.start_link(:table_with_options, [:compressed], quiet: true) + ) + ) + assert(:ets.info(:table_with_options, :compressed) == true) end def callback_fun(_pid, table) do require Logger - Logger.debug to_string(table) + Logger.debug(to_string(table)) end test "starting with an MFA callback" do - msg = capture_log(fn -> - Cldr.Eternal.start_link Cldr.Eternal, [], callback: {__MODULE__, :callback_fun, []}, quiet: true - end) + msg = + capture_log(fn -> + Cldr.Eternal.start_link(Cldr.Eternal, [], + callback: {__MODULE__, :callback_fun, []}, + quiet: true + ) + end) assert msg =~ "Cldr.Eternal" end @@ -31,16 +41,20 @@ defmodule Cldr.Cldr.EternalTest do test "starting with a function capture callback" do require Logger - msg = capture_log(fn -> - Cldr.Eternal.start_link Cldr.Eternal, [], callback: fn pid, table -> callback_fun(pid, table) end, quiet: true - end) + msg = + capture_log(fn -> + Cldr.Eternal.start_link(Cldr.Eternal, [], + callback: fn pid, table -> callback_fun(pid, table) end, + quiet: true + ) + end) assert msg =~ "Cldr.Eternal" end test "starting a table with no link" do spawn(fn -> - Cldr.Eternal.start(:unlinked, [], [ quiet: true ]) + Cldr.Eternal.start(:unlinked, [], quiet: true) end) :timer.sleep(25) @@ -98,23 +112,29 @@ defmodule Cldr.Cldr.EternalTest do end test "logging output when creating a table" do - msg = capture_log(fn -> - Cldr.Eternal.start_link(:logging_output) - :timer.sleep(25) - Cldr.Eternal.stop(:logging_output) - end) - - assert(Regex.match?(~r/\[debug\] \[eternal\] Table 'logging_output' gifted to #PID<\d\.\d+\.\d> via #PID<\d\.\d+\.\d>/, msg)) + msg = + capture_log(fn -> + Cldr.Eternal.start_link(:logging_output) + :timer.sleep(25) + Cldr.Eternal.stop(:logging_output) + end) + + assert( + Regex.match?( + ~r/\[debug\] \[eternal\] Table 'logging_output' gifted to #PID<\d\.\d+\.\d> via #PID<\d\.\d+\.\d>/, + msg + ) + ) end test "starting a table twice finds the previous owner" do - { :ok, pid } = Cldr.Eternal.start_link(:existing_table, [], [ quiet: true ]) - result2 = Cldr.Eternal.start_link(:existing_table, [], [ quiet: true ]) - assert(result2 == { :error, { :already_started, pid } }) + {:ok, pid} = Cldr.Eternal.start_link(:existing_table, [], quiet: true) + result2 = Cldr.Eternal.start_link(:existing_table, [], quiet: true) + assert(result2 == {:error, {:already_started, pid}}) end defp create(name, tab_opts \\ [], opts \\ []) do - { :ok, _pid } = Cldr.Eternal.start_link(name, tab_opts, opts ++ [ quiet: true ]) + {:ok, _pid} = Cldr.Eternal.start_link(name, tab_opts, opts ++ [quiet: true]) name end end diff --git a/test/support/test_backend.ex b/test/support/test_backend.ex index 2c02a78..1fcf9b6 100644 --- a/test/support/test_backend.ex +++ b/test/support/test_backend.ex @@ -10,5 +10,4 @@ end defmodule NoDoc.Cldr do use Cldr, generate_docs: false, providers: [Cldr.Currency] - end diff --git a/test/test_helper.exs b/test/test_helper.exs index 869559e..3085a5f 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1 +1,2 @@ ExUnit.start() +Cldr.Currency.start_link()