From 99a9a3007cd2e1b2b521e0ea88a308742e1d6542 Mon Sep 17 00:00:00 2001 From: Kip Cole Date: Mon, 27 Dec 2021 10:26:31 +1100 Subject: [PATCH] Add tests for :only and :except and bump version for release --- CHANGELOG.md | 12 +++++++++ lib/cldr/unit.ex | 41 +++++++++++++++++++++++++++++-- test/unit_parse_test.exs | 53 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 test/unit_parse_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index a38ed99..49baac0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## Cldr_Units v3.10.0 + +This is the changelog for Cldr_units v3.10.0 released on December 27th, 2021. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_units/tags) + +### Bug Fixes + +* Further refinement to `Cldr.Unit.unit_category/1` to return a result in a broader range of cases. + +### Enhancements + +* Adds `:only` and `:except` options to `Cldr.Unit.parse/2`. These options provide a mechanism to disambiguate the unit when a unit string could refer to more than one unit. For example, "2w" could refer to either "2 weeks" or "2 watts". If neither option is provided then the result is the same as in prior releases: the unit with the lexically shorter and alphabetically earlier unit is returned. + ## Cldr_Units v3.9.2 This is the changelog for Cldr_units v3.9.2 released on December 26th, 2021. For older changelogs please consult the release tag on [GitHub](https://github.com/elixir-cldr/cldr_units/tags) diff --git a/lib/cldr/unit.ex b/lib/cldr/unit.ex index 56153b9..7e0b35a 100644 --- a/lib/cldr/unit.ex +++ b/lib/cldr/unit.ex @@ -440,17 +440,38 @@ defmodule Cldr.Unit do * `:backend` is any module that includes `use Cldr` and therefore is a `Cldr` backend module. The default is `Cldr.default_backend!/0`. + * `:only` is a unit category or list of unit categories. The parsed + unit must match one of the categories in order to be valid. This is + helpful when disambiguating parsed units. For example, parsing "2w" + could be either "2 watts" or "2 weeks". Specifying `only: :duration` + would return "2 weeks". Specifiying `only: :power` would return + "2 watts" + + * `:except` is the oppostte of `:only`. The parsed unit must *not* + match the specified unit category or unit categories. + ## Returns * `{:ok, unit}` or * `{:error, {exception, reason}}` + ## Notes + + When both `:only` and `:except` options are passed, both + conditions must be true in order to return a parsed result. + ## Examples iex> Cldr.Unit.parse "1kg" Cldr.Unit.new(1, :kilogram) + iex> Cldr.Unit.parse "1w" + Cldr.Unit.new(1, :watt) + + iex> Cldr.Unit.parse "1w", only: :duration + Cldr.Unit.new(1, :week) + iex> Cldr.Unit.parse "1 tages", locale: "de" Cldr.Unit.new(1, :day) @@ -511,7 +532,7 @@ defmodule Cldr.Unit do defp unit_matching_filter(_unit, units, [] = _only, [] = _except) do units |> Enum.map(&Kernel.to_string/1) - |> Enum.sort(&(String.length(&1) <= String.length(&2))) + |> Enum.sort(&(String.length(&1) <= String.length(&2) && &1 < &2)) |> hd |> wrap(:ok) end @@ -2519,10 +2540,26 @@ defmodule Cldr.Unit do end @doc false + defp category_unit_match_error(unit, only, []) do + { + Cldr.Unit.CategoryMatchError, + "None of the units #{inspect Enum.sort(unit)} belong to a unit category matching " <> + "only: #{inspect only}" + } + end + + defp category_unit_match_error(unit, [], except) do + { + Cldr.Unit.CategoryMatchError, + "None of the units #{inspect Enum.sort(unit)} belong to a unit category matching " <> + "except: #{inspect except}" + } + end + defp category_unit_match_error(unit, only, except) do { Cldr.Unit.CategoryMatchError, - "None of the units #{inspect unit} belong to a unit category matching " <> + "None of the units #{inspect Enum.sort(unit)} belong to a unit category matching " <> "only: #{inspect only} except: #{inspect except}" } end diff --git a/test/unit_parse_test.exs b/test/unit_parse_test.exs new file mode 100644 index 0000000..1b75151 --- /dev/null +++ b/test/unit_parse_test.exs @@ -0,0 +1,53 @@ +defmodule Cldr.Unit.Parse.Test do + use ExUnit.Case, async: true + + test "Parse a simple unit" do + assert MyApp.Cldr.Unit.parse("1 week") == Cldr.Unit.new(1, :week) + assert MyApp.Cldr.Unit.parse("2 weeks") == Cldr.Unit.new(2, :week) + end + + test "Parse an ambiguous unit with no filter" do + assert MyApp.Cldr.Unit.parse("2 w") == Cldr.Unit.new(2, :watt) + end + + test "Parse an ambiguous unit with :only filter" do + assert MyApp.Cldr.Unit.parse("2 w", only: :duration) == Cldr.Unit.new(2, :week) + assert MyApp.Cldr.Unit.parse("2 w", only: [:duration, :length]) == Cldr.Unit.new(2, :week) + assert MyApp.Cldr.Unit.parse("2 w", only: :power) == Cldr.Unit.new(2, :watt) + end + + test "Parse an ambiguous unit with :except filter" do + assert MyApp.Cldr.Unit.parse("2 w", except: :duration) == Cldr.Unit.new(2, :watt) + assert MyApp.Cldr.Unit.parse("2 w", except: :power) == Cldr.Unit.new(2, :week) + end + + test "Parse with a filter that doesn't match" do + assert MyApp.Cldr.Unit.parse("2 w", only: :energy) == + {:error, + {Cldr.Unit.CategoryMatchError, + "None of the units [:watt, :week] belong to a unit category matching only: [:energy]"}} + end + + test "Parse with an invalid :only or :except" do + assert MyApp.Cldr.Unit.parse("2w", only: :invalid) == + {:error, + {Cldr.Unit.UnknownUnitCategoryError, + "The unit category :invalid is not known."}} + + assert MyApp.Cldr.Unit.parse("2w", only: [:invalid, :also_invalid]) == + {:error, + {Cldr.Unit.UnknownUnitCategoryError, + "The unit categories [:invalid, :also_invalid] are not known."}} + + assert MyApp.Cldr.Unit.parse("2w", except: :invalid) == + {:error, + {Cldr.Unit.UnknownUnitCategoryError, + "The unit category :invalid is not known."}} + + assert MyApp.Cldr.Unit.parse("2w", except: [:invalid, :also_invalid]) == + {:error, + {Cldr.Unit.UnknownUnitCategoryError, + "The unit categories [:invalid, :also_invalid] are not known."}} + end + +end \ No newline at end of file