defmodule AntlPhonenumber.Range do
@moduledoc """
Defines a range of phone numbers.
"""
defstruct first: nil, last: nil, iso_country_code: nil
@type t :: %__MODULE__{
first: String.t(),
last: String.t(),
iso_country_code: String.t()
}
@doc """
Creates a new range.
## Examples
iex> AntlPhonenumber.Range.new "0548451840", "0548451845", "IL"
%AntlPhonenumber.Range{
first: "972548451840",
last: "972548451845",
iso_country_code: "IL"
}
"""
@spec new(String.t(), String.t(), String.t()) :: t
def new(first, last, iso_country_code) when is_binary(first) and is_binary(last) do
first = AntlPhonenumber.to_plus_e164!(first, iso_country_code)
last = AntlPhonenumber.to_plus_e164!(last, iso_country_code)
if AntlPhonenumber.get_iso_country_code!(first) !=
AntlPhonenumber.get_iso_country_code!(last) do
raise ArgumentError,
"ranges (first..last) expect both sides to have the same contry_code, " <>
"got: #{inspect(first)}..#{inspect(last)}"
end
%__MODULE__{first: first, last: last, iso_country_code: iso_country_code}
end
defimpl Enumerable, for: AntlPhonenumber.Range do
import AntlPhonenumber, only: [to_integer: 1, next: 1, previous: 1, move: 2]
def reduce(%AntlPhonenumber.Range{first: first, last: last} = range, acc, fun),
do: reduce(first, last, acc, fun, _asc? = asc?(range))
defp reduce(_first, _last, {:halt, acc}, _fun, _asc?), do: {:halted, acc}
defp reduce(first, last, {:suspend, acc}, fun, asc?),
do: {:suspended, acc, &reduce(first, last, &1, fun, asc?)}
defp reduce(first, last, {:cont, acc}, fun, _asc? = true) when first <= last,
do: reduce(next(first), last, fun.(first, acc), fun, _asc? = true)
defp reduce(first, last, {:cont, acc}, fun, _asc? = false) when first >= last,
do: reduce(previous(first), last, fun.(first, acc), fun, _asc? = false)
defp reduce(_, _, {:cont, acc}, _fun, _asc?), do: {:done, acc}
def member?(%AntlPhonenumber.Range{} = range, number) do
case AntlPhonenumber.to_plus_e164(number, range.iso_country_code) do
{:ok, plus_e164} -> {:ok, member_as_plus_e164?(range, plus_e164)}
_ -> {:ok, false}
end
end
defp member_as_plus_e164?(%AntlPhonenumber.Range{} = range, plus_e164) do
if asc?(range),
do: range.first <= plus_e164 and plus_e164 <= range.last,
else: range.last <= plus_e164 and plus_e164 <= range.first
end
def count(%AntlPhonenumber.Range{} = range),
do: {:ok, abs(to_integer(range.last) - to_integer(range.first)) + 1}
def slice(%AntlPhonenumber.Range{} = range) do
if asc?(range),
do: {:ok, Enum.count(range), &slice_asc(move(range.first, &1), &2, &3)},
else: {:ok, Enum.count(range), &slice_desc(move(range.first, -&1), &2, &3)}
end
defp slice_asc(current, 1, _step), do: [current]
defp slice_asc(current, remaining, step) do
[current | slice_asc(move(current, step), remaining - 1, step)]
end
defp slice_desc(current, 1, _step), do: [current]
defp slice_desc(current, remaining, step) do
[current | slice_desc(move(current, -step), remaining - 1, step)]
end
defp asc?(%AntlPhonenumber.Range{} = range), do: range.first <= range.last
end
defimpl Inspect do
def inspect(%AntlPhonenumber.Range{first: first, last: last}, _), do: "#{first}..#{last}"
end
end