Skip to content

Commit

Permalink
Version 0.1.1 release
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn committed Jun 29, 2017
1 parent 7716873 commit 88a6150
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 37 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ erl_crash.dump

#### custom ####
/doc
*.tar
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Changelog

## Version 0.1.1

### Bugfixes

- Fixes bug when `Detector` was showing wrong values for complex names with `-` inside

### Documentation

- Adds more `@doc` and `@module` to the code
- Adds little fixes to the existing docs
- Adds `CHANGELOG.md`
- Adds `CONTRIBUTING.md`

## Version 0.1.0

- Initial release
23 changes: 23 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Contributing to Petrovich

## Pull Requests Welcome

1. Fork `petrovich_elixir`
2. Create a topic branch
3. Make logically-grouped commits with clear commit messages
4. Push commits to your fork
5. Open a pull request against `petrovich_elixir/master`

## Development

Please, make sure that all these commands succeed before pushing anything:

1. `mix test`
2. `mix credo --strict`
3. `mix dialyzer` (it may take long on the first run)

## Issues

If you believe there to be a bug, please provide the maintainers with enough
detail to reproduce or a link to an app exhibiting unexpected behavior. For
help, please start with Stack Overflow.
74 changes: 74 additions & 0 deletions lib/petrovich.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ defmodule Petrovich do
Public interface to all the functions.
It can inflect first, middle, and last names.
It also can detect gender by name.
## List of cases
Here's a quick reminder:
> nomenative: именительный
> genitive: родительный
> dative: дательный
> accusative: винительный
> instrumental: творительный
> prepositional: предложный
"""

use Application
Expand All @@ -26,31 +37,94 @@ defmodule Petrovich do

@default_gender nil

@doc """
Inflects first name.
This function is used to inflect a first name.
It accepts three arguments:
1. `name` a person's first name
2. `case_` atom, one of the atoms with the case name
3. optional `gender`, since it could be detected from name
It returns `{:ok, inflected_name}` or `:error`
## Examples
iex> Petrovich.firstname("Кира", :dative)
{:ok, "Кире"}
"""
@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

@doc """
The same as `firstname/3`, but raises `ParseException` on errors.
"""
@spec firstname!(String.t, atom(), atom() | none()) :: String.t
def firstname!(name, case_, gender \\ @default_gender) do
Parser.parse!(name, :firstname, case_, gender)
end

@doc """
Inflects middle name.
This function is used to inflect a middle name.
It accepts three arguments:
1. `name` a person's middle name
2. `case_` atom, one of the atoms with the case name
3. optional `gender`, since it could be detected from name
It returns `{:ok, inflected_name}` or `:error`
## Examples
iex> Petrovich.firstname("Викторовна", :dative)
{:ok, "Викторовне"}
"""
@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

@doc """
The same as `middlename/3`, but raises `ParseException` on errors.
"""
@spec middlename!(String.t, atom(), atom() | none()) :: String.t
def middlename!(name, case_, gender \\ @default_gender) do
Parser.parse!(name, :middlename, case_, gender)
end

@doc """
Inflects last name.
This function is used to inflect a last name.
It accepts three arguments:
1. `name` a person's last name
2. `case_` atom, one of the atoms with the case name
3. optional `gender`, since it could be detected from name
It returns `{:ok, inflected_name}` or `:error`
## Examples
iex> Petrovich.firstname("Горева", :dative)
{:ok, "Гореве"}
"""
@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

@doc """
The same as `lastname/3`, but raises `ParseException` on errors.
"""
@spec lastname!(String.t, atom(), atom() | none()) :: String.t
def lastname!(name, case_, gender \\ @default_gender) do
Parser.parse!(name, :lastname, case_, gender)
Expand Down
38 changes: 30 additions & 8 deletions lib/petrovich/detector.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ defmodule Petrovich.Detector do
"""

alias Petrovich.GenderStore
alias Petrovich.Utils.ResultJoiner

@doc """
Detects a gender by name.
Expand All @@ -13,33 +14,54 @@ defmodule Petrovich.Detector do
1. `name` raw person's name in nomenative case
2. `type` which shows what part of person's name it is
You can use any of these values as `type`:
[:firstname, :middlename, :lastname]
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, "male"}
iex> Detector.detect_gender("Саша", :firstname)
{:ok, "androgynous"}
iex> Detector.detect_gender("Саша", :firstname)
{:ok, "androgynous"}
iex> Detector.detect_gender("123", :firstname)
:error
iex> Detector.detect_gender("123", :firstname)
:error
"""
@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))

name
|> String.downcase
|> String.split("-")
|> Enum.map(fn(item) -> prepare_value(item, exceptions, suffixes) end)
|> ResultJoiner.join_result(&join_result/1)
end

defp prepare_value(name, exceptions, suffixes) do
name
|> maybe_exception(exceptions)
|> maybe_rule(suffixes)
end

defp join_result(result) do
result
|> Enum.find(fn (item) ->
case item do
{:ok, _} -> true
_ -> false
end
end)
|> elem(1)
end

defp maybe_exception(name, exceptions) do
exceptions
|> Enum.find(fn({_, names}) -> name in names end)
Expand Down
8 changes: 8 additions & 0 deletions lib/petrovich/exceptions.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
defmodule Petrovich.Exceptions.ParseException do
@moduledoc """
This exception is used when it is impossible for some reason
to parse and inflect a person's name.
"""
defexception message: "Name is not parsed"
end

defmodule Petrovich.Exceptions.RulesFileException do
@moduledoc """
This exception is raised when wrong setting
with wrong path for a `Store` is supplied.
"""
defexception message: "Rules file is missing"
end
52 changes: 24 additions & 28 deletions lib/petrovich/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule Petrovich.Parser do
"""

alias Petrovich.{NameStore, Applier, Detector}
alias Petrovich.Utils.ResultJoiner
alias Petrovich.Exceptions.ParseException

@doc """
Expand All @@ -18,11 +19,12 @@ defmodule Petrovich.Parser do
## Examples
iex> Parser.parse("Николай", :firstname, :dative, :male)
{:ok, "Николаю"}
iex> Parser.parse("Николай", :firstname, :dative, :male)
{:ok, "Николаю"}
iex> Parser.parse("Пирогов", :lastname, :instrumental, :male)
{:ok, "Пироговым"}
iex> Parser.parse("Пирогов", :lastname, :instrumental, :male)
{:ok, "Пироговым"}
"""
@spec parse(String.t, atom(),
atom(), atom() | nil) :: {:ok, String.t} | :error
Expand All @@ -37,13 +39,14 @@ defmodule Petrovich.Parser do
## Examples
iex> Parser.parse!("Николай", :firstname, :dative, :male)
"Николаю"
iex> Parser.parse!("Николай", :firstname, :dative, :male)
"Николаю"
iex> Parser.parse!("Пирогов", :lastname, :instrumental, :male)
"Пироговым"
iex> Parser.parse!("Пирогов", :lastname, :instrumental, :male)
"Пироговым"
"""
@spec parse!(String.t, atom(), atom(), atom() | none) :: String.t
@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
Expand All @@ -62,39 +65,32 @@ defmodule Petrovich.Parser do
end

defp apply_rule(values, type, case_, gender) do
# NameStore.start_link()
%{"exceptions" => exceptions,
"suffixes" => suffixes} = NameStore.get(type)

values
|> String.split("-")
|> Enum.map(fn(item) -> prepare_value(item, type, case_, gender) end)
|> join_result
|> Enum.map(fn(item) ->
prepare_value(item, case_, gender, exceptions, suffixes)
end)
|> ResultJoiner.join_result(&join_callback/1)
end

defp prepare_value(value, type, case_, gender) do
%{"exceptions" => exceptions,
"suffixes" => suffixes} = NameStore.get(type)

defp prepare_value(value, case_, gender, exceptions, suffixes) do
value
|> String.downcase
|> maybe_exception(gender, exceptions)
|> maybe_rule(gender, suffixes)
|> case do
{:error, _} -> {:error, value}
{:error, _} -> :error
{:ok, res} -> {:ok, Applier.apply(value, case_, res)}
end
end

defp join_result(results) do
success = Enum.all?(results, fn({status, _}) -> status == :ok end)
joined = results
|> Enum.map(fn({_, item}) -> item end)
|> Enum.join("-")

if success do
{:ok, joined}
else
:error
end
defp join_callback(results) do
results
|> Enum.map(fn({_, item}) -> item end)
|> Enum.join("-")
end

defp maybe_exception(name, gender, exceptions) do
Expand Down
16 changes: 16 additions & 0 deletions lib/petrovich/utils/result_joiner.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
defmodule Petrovich.Utils.ResultJoiner do
@moduledoc false

def join_result(results, callback) do
success = Enum.any?(results, &compare_status/1)

if success do
{:ok, callback.(results)}
else
:error
end
end

defp compare_status({:ok, _}), do: true
defp compare_status(_), do: false
end
2 changes: 1 addition & 1 deletion 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.1.0"
@version "0.1.1"
@url "https://github.com/petrovich/petrovich_elixir"

def project do
Expand Down
Binary file removed petrovich_elixir-0.0.1.tar
Binary file not shown.
Binary file removed petrovich_elixir-0.1.0.tar
Binary file not shown.
6 changes: 6 additions & 0 deletions test/petrovich_test/detector_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ defmodule PetrovichTest.DetectorTest do
assert Detector.detect_gender("Саша", :firstname) == {:ok, "androgynous"}
end

test "detects complex names" do
assert Detector.detect_gender(
"Соколов-Саенко", :lastname
) == {:ok, "male"}
end

test "fails to detect gender" do
assert Detector.detect_gender("123", :lastname) == :error
end
Expand Down
2 changes: 2 additions & 0 deletions test/petrovich_test/public_api_test.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
defmodule PetrovichTest.PublicAPITest do
use ExUnit.Case

doctest Petrovich

test "public firstname function works" do
assert Petrovich.firstname(
"Александр", :accusative, :male) == {:ok, "Александра"}
Expand Down

0 comments on commit 88a6150

Please sign in to comment.