Current section
Files
Jump to
Current section
Files
lib/petrovich/parser.ex
defmodule Petrovich.Parser do
@moduledoc """
Parser receives the name and case to infect it to.
Then it parses the rules to find the appropriate modifications.
It then calls `Applier` to modify the value.
This module should not be used directly. Use `Petrovich` module instead.
"""
alias Petrovich.{Applier, Detector, NameStore}
alias Petrovich.Exceptions.ParseException
alias Petrovich.Utils.ResultJoiner
@doc """
Parses name and gets modifiers for the given case.
Then it passes the name and modification rules to the `Applier`.
## Examples
iex> Parser.parse("Николай", :firstname, :dative, :male)
{:ok, "Николаю"}
iex> Parser.parse("Пирогов", :lastname, :instrumental, :male)
{:ok, "Пироговым"}
"""
@spec parse(String.t(), atom(), atom(), atom() | nil) ::
{:ok, String.t()} | :error
def parse(data, _, :nomenative, _), do: {:ok, data}
def parse(data, type, case_, gender) do
gender = maybe_detect_gender(gender, data, type)
apply_rule(data, to_string(type), case_, to_string(gender))
end
@doc """
Pretty much the same as `parse/4`, but raises exception instead.
## Examples
iex> Parser.parse!("Николай", :firstname, :dative, :male)
"Николаю"
iex> Parser.parse!("Пирогов", :lastname, :instrumental, :male)
"Пироговым"
"""
@spec parse!(String.t(), atom(), atom(), atom() | nil) :: String.t()
def parse!(data, type, case_, gender) do
case parse(data, type, case_, gender) do
{:ok, value} -> value
:error -> raise ParseException
end
end
defp maybe_detect_gender(gender, data, type) do
with nil <- gender,
{:ok, parsed_gender} <- Detector.detect_gender(data, type) do
parsed_gender
else
:error -> :androgynous
_ -> gender
end
end
defp apply_rule(values, type, case_, gender) do
%{"exceptions" => exceptions, "suffixes" => suffixes} = NameStore.get(type)
values
|> String.split("-")
|> Enum.map(fn item ->
prepare_value(item, case_, gender, exceptions, suffixes)
end)
|> ResultJoiner.join_all_results(&join_callback/1)
end
defp prepare_value(value, case_, gender, exceptions, suffixes) do
value
|> String.downcase()
|> maybe_exception(gender, exceptions)
|> maybe_rule(gender, suffixes)
|> case do
{:error, _} -> :error
{:ok, res} -> {:ok, Applier.apply(value, case_, res)}
end
end
defp join_callback(results) do
results
|> Enum.map(fn {_, item} -> item end)
|> Enum.join("-")
end
defp maybe_exception(name, gender, exceptions) do
exceptions
|> Enum.filter(fn item -> fits?(:e, name, gender, item) end)
|> pick_one(name)
end
defp maybe_rule({:ok, rule}, _, _), do: {:ok, rule}
defp maybe_rule({:error, name}, gender, suffixes) do
suffixes
|> Enum.filter(fn item -> fits?(:r, name, gender, item) end)
|> pick_one(name)
end
defp fits?(:e, name, gender, rule) do
gender?(gender, rule) and name in rule["test"]
end
defp fits?(:r, name, gender, rule) do
name_len = String.length(name)
gender?(gender, rule) and
Enum.any?(rule["test"], fn test ->
test_len = String.length(test)
test == String.slice(name, name_len - test_len, test_len)
end)
end
defp gender?(gender, rule) do
gender == rule["gender"] or rule["gender"] == "androgynous"
end
defp pick_one(items, name) do
items
|> Enum.at(0)
|> case do
nil -> {:error, name}
rule -> {:ok, rule}
end
end
end