defmodule Cldr.List.Pattern do @moduledoc """ A list pattern drives list formatting and it defines how to combine list elements at the begging, in the middle and at the end of a list. It also has a special pattern used when the list contains only two elements. """ defstruct [:start, :middle, :end, :two] @type pattern :: [non_neg_integer() | String.t(), ...] @type t :: %__MODULE__{ start: pattern(), middle: pattern(), end: pattern(), two: pattern() } @valid_options [:start, :middle, :end, :two] @doc """ Creates a new list format. A list pattern consists of four string templates into which list elements are interpolated when formatting. The four templates are: * `:start` used to format the first two list elements. * `:middle` is used to format elements in the middle of the list. * `:end` is used to format the last two elements in the list. * `:two` is used to format a list if it contains only two elements. Only the `:start` option is required. It will be used as the default for any other option that is not provided. ## Arguments * `options` is a keyword list of options. ## Options * `:start` is a pattern template as a string. This option is required. * `:middle` is a pattern template as a string. * `:end` is a pattern template as a string. * `:two` is a pattern template as a string. ## Returns `{:ok, pattern}` or `{:error, reason}` ## Pattern template A pattern template is a string that contains two placeholders denoted by `{0}` and `{1}`. ## Example iex> Cldr.List.Pattern.new( ...> start: "{0}, {1}"), ...> middle: "{0}, {1}"), ...> end: "{0} and {1}"), ...> two: "{0} and {1}") ...> ) {:ok, _pattern} """ def new(options) when is_list(options) do with {:ok, options} <- validate_options(options) do start = Keyword.get(options, :start) struct(__MODULE__, options) |> put_new(:middle, start) |> put_new(:end, start) |> put_new(:two, start) |> wrap(:ok) end end defp validate_options(options) when is_list(options) do options = Enum.reduce_while options, [], fn {key, string}, acc when key in @valid_options and is_binary(string) -> case validate_pattern(string) do {:error, reason} -> {:halt, {:error, reason}} {:ok, string} -> pattern = Cldr.Substitution.parse(string) {:cont, [{key, pattern} | acc]} end {key, value}, _acc when key in @valid_options -> {:halt, {:error, "Invalid value #{inspect value} found. Option #{inspect key} should be a string"}} {key, _value}, _acc -> {:halt, {:error, "Invalid option #{inspect key} found. Valid options are #{inspect @valid_options}"}} end case options do {:error, reason} -> {:error, reason} options -> if Keyword.has_key?(options, :start), do: {:ok, options}, else: {:error, "Option :start must be provided"} end end defp validate_pattern(string) when is_binary(string) do if String.contains?(string, "{0}") && String.contains?(string, "{1}") do {:ok, string} else {:error, "Invalid pattern #{inspect string}. A pattern must have two placeholders {0} and {1}"} end end defp put_new(options, key, default) do if Map.get(options, key), do: options, else: Map.put(options, key, default) end defp wrap(term, atom) do {atom, term} end end