Current section

Files

Jump to
localize lib localize validity t.ex
Raw

lib/localize/validity/t.ex

defmodule Localize.Validity.T do
@moduledoc false
alias Localize.Validity.U
@field_mapping %{
"language" => :language,
"m0" => :m0,
"s0" => :s0,
"d0" => :d0,
"i0" => :i0,
"k0" => :k0,
"t0" => :t0,
"h0" => :h0,
"x0" => :x0
}
@fields Map.values(@field_mapping)
@inverse_field_mapping Enum.map(@field_mapping, fn {k, v} -> {v, k} end) |> Map.new()
@validity_data Localize.SupplementalData.validity(:t)
@dont_process_keys ["language"]
@valid_keys Map.keys(@validity_data) ++ @dont_process_keys
@process_keys @valid_keys -- @dont_process_keys
def fields do
@fields
end
def field_mapping do
@field_mapping
end
@doc """
Decodes and validates that a given value is valid
for a given key.
Returns both the canonical key
and the canonical value or an error.
"""
def decode("language" = key, {:ok, language_tag}) do
{:ok, {map(key), language_tag}}
end
def decode("language", {:error, error_language_tag}) do
{:error, error_language_tag}
end
def decode("x0" = key, private_use) do
{:ok, {map(key), private_use}}
end
def decode(key, value) do
with {:ok, value} <- valid(key, value) do
{:ok, {map(key), atomize(value)}}
else
{:error, _value} ->
{:error, U.invalid_value_error(key, value)}
end
end
@doc """
Encodes a key and value into the
form required for a string version
of a language tag.
"""
def encode(key, value) do
unmapped_key = unmap(key)
{unmapped_key, encode_key(unmapped_key, value)}
end
# Encode key functions take the form that is
# in the language tag locale struct and encodes it
# back to what is required in a textual form of
# the locale.
for {key, values} <- @validity_data, key in @process_keys do
inverted_values = Enum.map(values, &String.to_atom/1)
defp encode_key(unquote(key), value) when value in unquote(inverted_values) do
Atom.to_string(value)
end
end
defp encode_key("x0", private_use) when is_list(private_use) do
Enum.join(private_use, "-")
end
defp encode_key(key, values) when is_list(values) do
values
|> Enum.map(&encode_key(key, &1))
|> Enum.join("-")
end
defp encode_key(_key, {year}) do
to_string(year)
end
defp encode_key(_key, {year, month}) do
to_string(year) <> pad(month) <> to_string(month)
end
defp encode_key(_key, {year, month, day}) do
to_string(year) <> pad(month) <> to_string(month) <> pad(day) <> to_string(day)
end
defp encode_key("language", nil) do
nil
end
defp encode_key("language", %{canonical_locale_id: name}) when is_binary(name) do
String.downcase(name)
end
defp encode_key("language", %Localize.LanguageTag{} = language) do
String.downcase(Localize.LanguageTag.to_string(language))
end
defp encode_key("x0", private_use) do
private_use
end
# Check that the value provided
# is acceptable for the given key.
for {key, values} <- @validity_data, key in @process_keys do
defp valid(unquote(key), value) when value in unquote(values) do
{:ok, String.to_atom(value)}
end
end
defp valid(key, values) when is_list(values) do
case valid_list(key, values) do
{:ok, values} ->
# Dates sort to the end
Enum.sort(values, fn
<<digit_1::utf8, _rest_1::binary>>, <<digit_2::utf8, _rest_2::binary>>
when digit_1 >= ?0 and digit_1 <= ?9 and digit_2 >= ?0 and digit_2 <= ?9 ->
digit_1 < digit_2
<<digit_1::utf8, _rest::binary>>, _date_2 when digit_1 >= ?0 and digit_1 <= ?9 ->
false
_date_1, <<digit_1::utf8, _rest::binary>> when digit_1 >= ?0 and digit_1 <= ?9 ->
true
value_1, value_2 ->
value_1 < value_2
end)
|> wrap(:ok)
other ->
other
end
end
defp valid(key, value) when key in @valid_keys do
case Integer.parse(value) do
{_integer, ""} -> {:ok, make_date_tuple(value)}
_other -> {:error, U.invalid_value_error(key, value)}
end
end
defp valid("language", language) do
language
end
defp valid("x0", private_use) do
private_use
end
defp valid(key, _value) do
{:error, U.invalid_key_error(key)}
end
def valid_list(key, values) do
Enum.reduce_while(values, {:ok, []}, fn value, {:ok, acc} ->
# if we push a date tuple on a prior round then the
# date tuple isn't in last place which it is required to
# be so we return an error
if length(acc) > 0 && is_tuple(hd(acc)) do
{:halt, {:error, invalid_date_order(key, hd(acc))}}
else
case valid(key, value) do
{:ok, value} -> {:cont, {:ok, [value | acc]}}
{:error, _value} -> {:halt, {:error, U.invalid_value_error(key, value)}}
end
end
end)
end
def wrap(term, atom) do
{atom, term}
end
def pad(number) when number < 10, do: " "
def pad(_number), do: ""
defp make_date_tuple(<<year::binary-4, month::binary-2, day::binary-2>>) do
{String.to_integer(year), String.to_integer(month), String.to_integer(day)}
end
defp make_date_tuple(<<year::binary-4, month::binary-2>>) do
{String.to_integer(year), String.to_integer(month)}
end
defp make_date_tuple(<<year::binary-4>>) do
{String.to_integer(year)}
end
defp map(key) do
Map.fetch!(@field_mapping, key)
end
defp unmap(key) do
Map.fetch!(@inverse_field_mapping, key)
end
defp atomize(value) when is_atom(value) do
value
end
defp atomize(value) when is_binary(value) do
String.to_atom(value)
end
defp atomize(other) do
other
end
defp invalid_date_order(key, value) do
encoded = encode_key(key, value)
Localize.InvalidSubtagError.exception(
key: key,
value: encoded,
reason: :date_not_last
)
end
end