defmodule IbanEx.Country.Template do @moduledoc false alias IbanEx.Iban @type size() :: non_neg_integer() @type rule() :: Regex.t() @type country_code() :: <<_::16>> | atom() @type joiner() :: String.t() @callback size() :: size() @callback rule() :: rule() @callback rules() :: [] @callback rules_map() :: %{} @callback bban_fields() :: [atom()] @callback bban_size() :: non_neg_integer() @callback to_string(Iban.t(), joiner()) :: String.t() @callback to_string(Iban.t()) :: String.t() defmacro __using__(_opts) do quote do alias IbanEx.Iban @behaviour IbanEx.Country.Template @impl IbanEx.Country.Template @spec to_string(Iban.t()) :: binary() @spec to_string(Iban.t(), binary()) :: binary() def to_string( %Iban{ country_code: country_code, check_digits: check_digits, bank_code: bank_code, branch_code: _branch_code, national_check: _national_check, account_number: account_number } = _iban, joiner \\ " " ) do [country_code, check_digits, bank_code, account_number] |> Enum.join(joiner) end @impl IbanEx.Country.Template @spec size() :: integer() def size(), do: @size @doc """ Return Regex for parsing complete BBAN (part of IBAN string) """ @impl IbanEx.Country.Template @spec rule() :: Regex.t() def rule(), do: @rule @impl IbanEx.Country.Template @spec bban_size() :: integer() def bban_size() do {_rules, bban_size} = calculate_rules() bban_size end @impl IbanEx.Country.Template @spec bban_fields() :: [] def bban_fields(), do: rules_map() |> Map.keys() @impl IbanEx.Country.Template @spec rules_map() :: %{} def rules_map(), do: rules() |> Map.new() @impl IbanEx.Country.Template @spec rules() :: [] def rules() do {rules, _bban_size} = calculate_rules() rules end defp calculate_rules() do scanner = ~r/\(\?\<([\w_]+)\>(([^{]+)\{(\d+)\})\)/i source = @rule |> Regex.source() {list, bban_length} = Regex.scan(scanner, source) |> Enum.reduce({[], 0}, fn [_part, k, r, _syms, l], {list, position} = acc -> key = String.to_atom(k) {:ok, regex} = Regex.compile(r, "i") length = String.to_integer(l) left = position right = left + length - 1 {[{key, %{regex: regex, range: left..right}} | list], right + 1} end) {Enum.reverse(list), bban_length} end defoverridable to_string: 1, to_string: 2, size: 0, rule: 0 end end end