Skip to content

Commit

Permalink
Version 0.1.0 release
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn committed Jun 29, 2017
1 parent 053f388 commit 7716873
Show file tree
Hide file tree
Showing 14 changed files with 161 additions and 37 deletions.
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,33 @@ end

## Usage

### Do I need to know russian to use it?
### Do I need to know Russian to use it?

Yes, you will need some basic russian knowledge to work with this library.
You need to understand how [grammatical cases](https://en.wikipedia.org/wiki/Grammatical_case) work in russian language.
Yes, you will need some basic Russian knowledge to work with this library.
You need to understand how [grammatical cases](https://en.wikipedia.org/wiki/Grammatical_case) work in Russian language.

### API
### Inflection

```elixir
Petrovich.firstname("Александр", :accusative, :male)
Petrovich.firstname!("Александр", :accusative)
# => Александра

Petrovich.middlename("Сергеевич", :accusative, :male)
Petrovich.middlename!("Сергеевич", :accusative)
# => Сергеевича

Petrovich.lastname("Пушкин", :accusative, :male)
Petrovich.lastname!("Пушкин", :accusative, :male)
# => Пушкина
```

### Gender detection

```elixir
alias Petrovich.Detector

Detector.detect_gender("Александр", :firstname)
# => {:ok, "male"}
```


## Configuration

Expand Down
26 changes: 25 additions & 1 deletion lib/petrovich.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,51 @@ defmodule Petrovich do
It also can detect gender by name.
"""

use Application

alias Petrovich.Parser

@default_gender :androgynous
def start(_type, _args) do
import Supervisor.Spec, warn: false

children =
[
worker(Petrovich.NameStore, []),
worker(Petrovich.GenderStore, []),
]

opts = [strategy: :one_for_one, name: Petrovich.Supervisor]
Supervisor.start_link(children, opts)
end

@default_gender nil

@spec firstname(String.t, atom(), atom() | none()) :: {atom(), String.t}
def firstname(name, case_, gender \\ @default_gender) do
Parser.parse(name, :firstname, case_, gender)
end

@spec firstname!(String.t, atom(), atom() | none()) :: String.t
def firstname!(name, case_, gender \\ @default_gender) do
Parser.parse!(name, :firstname, case_, gender)
end

@spec middlename(String.t, atom(), atom() | none()) :: {atom(), String.t}
def middlename(name, case_, gender \\ @default_gender) do
Parser.parse(name, :middlename, case_, gender)
end

@spec middlename!(String.t, atom(), atom() | none()) :: String.t
def middlename!(name, case_, gender \\ @default_gender) do
Parser.parse!(name, :middlename, case_, gender)
end

@spec lastname(String.t, atom(), atom() | none()) :: {atom(), String.t}
def lastname(name, case_, gender \\ @default_gender) do
Parser.parse(name, :lastname, case_, gender)
end

@spec lastname!(String.t, atom(), atom() | none()) :: String.t
def lastname!(name, case_, gender \\ @default_gender) do
Parser.parse!(name, :lastname, case_, gender)
end
Expand Down
9 changes: 9 additions & 0 deletions lib/petrovich/applier.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ defmodule Petrovich.Applier do
This module applies inflection rules on raw values.
It is tightly coupled with `Parser`, which supplies the rules.
This function should not be used directly. Use `Petrovich` module instead.
"""

@rules %{
Expand All @@ -14,6 +16,13 @@ defmodule Petrovich.Applier do
prepositional: 4,
}

@doc """
This function applies one of the rule based on case to the name,
making the inflection itself.
These rules should be in format: `%{"mods" => list(5)}`.
"""
@spec apply(String.t, atom(), map()) :: {atom(), String.t}
def apply(data, case_, %{"mods" => mods}) do
mod = Enum.at(mods, @rules[case_])
apply_mod(mod, data)
Expand Down
3 changes: 1 addition & 2 deletions lib/petrovich/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ defmodule Petrovich.Config do
"""

@doc """
Returns the requested variable
Returns the requested variable from application's environment.
"""

@spec get_env(atom, atom, atom | map) :: term
def get_env(application, key, default \\ nil) do
application
Expand Down
30 changes: 25 additions & 5 deletions lib/petrovich/detector.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,35 @@ defmodule Petrovich.Detector do
alias Petrovich.GenderStore

@doc """
Returns gender from person's name
Detects a gender by name.
This function receives two arguments:
1. `name` raw person's name in nomenative case
2. `type` which shows what part of person's name it is
On success it returns a tuple `{:ok, "detected_gender"}`.
Or `:error` in cases when it is impossible to detect gender.
## Examples
iex> Detector.detect_gender("Игорь", :firstname)
{:ok, "male"}
iex> Detector.detect_gender("Саша", :firstname)
{:ok, "androgynous"}
iex> Detector.detect_gender("123", :firstname)
:error
"""
def detect_gender(value, type) do
GenderStore.start_link()
@spec detect_gender(String.t, atom()) :: {:ok, String.t} | :error
def detect_gender(name, type) do
# GenderStore.start_link()

%{"exceptions" => exceptions,
"suffixes" => suffixes} = GenderStore.get("gender", to_string(type))

value
name
|> String.downcase
|> maybe_exception(exceptions)
|> maybe_rule(suffixes)
Expand All @@ -35,7 +55,7 @@ defmodule Petrovich.Detector do
|> Enum.find(fn({_, rule}) -> fits?(name, rule) end)
|> case do
{gender, _} -> {:ok, gender}
nil -> {:error, name}
nil -> :error
end
end

Expand Down
51 changes: 44 additions & 7 deletions lib/petrovich/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,65 @@ defmodule Petrovich.Parser do
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.{NameStore, Applier}
alias Petrovich.{NameStore, Applier, Detector}
alias Petrovich.Exceptions.ParseException

@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() | none) :: String.t
def parse!(data, type, case_, gender) do
{status, data} = parse(data, type, case_, gender)
case parse(data, type, case_, gender) do
{:ok, value} -> value
:error -> raise ParseException
end
end

if status == :ok do
data
defp maybe_detect_gender(gender, data, type) do
with nil <- gender,
{:ok, parsed_gender} <- Detector.detect_gender(data, type) do
parsed_gender
else
raise ParseException
:error -> :androgynous
_ -> gender
end
end

defp apply_rule(values, type, case_, gender) do
NameStore.start_link()
# NameStore.start_link()

values
|> String.split("-")
Expand Down Expand Up @@ -56,7 +93,7 @@ defmodule Petrovich.Parser do
if success do
{:ok, joined}
else
{:error, joined}
:error
end
end

Expand Down
7 changes: 4 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Petrovich.Mixfile do
use Mix.Project

@version "0.0.1"
@version "0.1.0"
@url "https://github.com/petrovich/petrovich_elixir"

def project do
Expand Down Expand Up @@ -33,7 +33,7 @@ defmodule Petrovich.Mixfile do

def application do
# Specify extra applications you'll use from Erlang/Elixir
[extra_applications: [:logger]]
[mod: {Petrovich, []}, extra_applications: [:logger]]
end

defp deps do
Expand All @@ -58,6 +58,7 @@ defmodule Petrovich.Mixfile do
[maintainers: ["Nikita Sobolev"],
licenses: ["MIT"],
links: %{"GitHub" => @url},
files: ~w(mix.exs README.md lib)]
files: ~w(mix.exs README.md lib config rules/*.json)
]
end
end
Binary file added petrovich_elixir-0.0.1.tar
Binary file not shown.
Binary file added petrovich_elixir-0.1.0.tar
Binary file not shown.
25 changes: 25 additions & 0 deletions test/petrovich_test/applier_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule PetrovichTest.ApplierTest do
use ExUnit.Case

alias Petrovich.Applier

setup do
# Just some fake modifications:
values = %{"mods" => [
"--1", # genitive
"-2", # dative
"3", # accusative
"44", # instrumental
"." # prepositional
]}
{:ok, rules: values}
end

test "applies modifications to the value", %{rules: rules} do
assert Applier.apply("123", :genitive, rules) == "11"
assert Applier.apply("123", :dative, rules) == "122"
assert Applier.apply("123", :accusative, rules) == "1233"
assert Applier.apply("123", :instrumental, rules) == "12344"
assert Applier.apply("123", :prepositional, rules) == "123"
end
end
10 changes: 10 additions & 0 deletions test/petrovich_test/detector_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ defmodule PetrovichTest.DetectorTest do

alias Petrovich.Detector

doctest Detector

test "detects exception genders" do
assert Detector.detect_gender("Никита", :firstname) == {:ok, "male"}
assert Detector.detect_gender("Женя", :firstname) == {:ok, "androgynous"}
Expand All @@ -13,4 +15,12 @@ defmodule PetrovichTest.DetectorTest do
assert Detector.detect_gender("Айгуль", :firstname) == {:ok, "female"}
assert Detector.detect_gender("Ибрагим", :firstname) == {:ok, "male"}
end

test "detects androgynous names" do
assert Detector.detect_gender("Саша", :firstname) == {:ok, "androgynous"}
end

test "fails to detect gender" do
assert Detector.detect_gender("123", :lastname) == :error
end
end
2 changes: 2 additions & 0 deletions test/petrovich_test/parser_test/firstname_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ defmodule PetrovichTest.ParserTest.FirstName.Normal do

alias Petrovich.Parser

doctest Parser # only in this module

setup do
values = %{
nomenative: [
Expand Down
6 changes: 0 additions & 6 deletions test/petrovich_test/stores_test/gender_store_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@ defmodule PetrovichTest.GenderStoreTest do

alias Petrovich.GenderStore

setup do
{status, _pid} = GenderStore.start_link()

status
end

test "agent has all end values" do
values = GenderStore.all()
assert map_size(values) == 1
Expand Down
6 changes: 0 additions & 6 deletions test/petrovich_test/stores_test/name_store_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@ defmodule PetrovichTest.NameStoreTest do

alias Petrovich.NameStore

setup do
{status, _pid} = NameStore.start_link()

status
end

test "agent has all end values" do
values = NameStore.all()
assert map_size(values) == 3
Expand Down

0 comments on commit 7716873

Please sign in to comment.