petrovich 0.2.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
metadata CHANGED
@@ -1,16 +1,44 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: petrovich
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
- - Andrew Kozloff
7
+ - Andrew Kozlov
8
8
  - Dmitry Ustalov
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-01-07 00:00:00.000000000 Z
12
+ date: 2016-03-16 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: commander
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - '='
19
+ - !ruby/object:Gem::Version
20
+ version: 4.3.5
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - '='
26
+ - !ruby/object:Gem::Version
27
+ version: 4.3.5
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '10.0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '10.0'
14
42
  - !ruby/object:Gem::Dependency
15
43
  name: minitest
16
44
  requirement: !ruby/object:Gem::Requirement
@@ -25,8 +53,22 @@ dependencies:
25
53
  - - ">="
26
54
  - !ruby/object:Gem::Version
27
55
  version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: minitest-reporters
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
28
70
  description: A library to inflect Russian anthroponyms such as first names, last names,
29
- and middle names.
71
+ and middle names. Also it has gender detection functionality.
30
72
  email:
31
73
  - demerest@gmail.com
32
74
  - dmitry@eveel.ru
@@ -38,9 +80,17 @@ files:
38
80
  - README.md
39
81
  - Rakefile
40
82
  - lib/petrovich.rb
41
- - lib/petrovich/extension.rb
42
- - lib/petrovich/rules.rb
83
+ - lib/petrovich/case/rule.rb
84
+ - lib/petrovich/case/rule/modifier.rb
85
+ - lib/petrovich/case/rule/test.rb
86
+ - lib/petrovich/gender.rb
87
+ - lib/petrovich/gender/rule.rb
88
+ - lib/petrovich/inflected.rb
89
+ - lib/petrovich/inflector.rb
90
+ - lib/petrovich/name.rb
91
+ - lib/petrovich/rule_set.rb
43
92
  - lib/petrovich/unicode.rb
93
+ - lib/petrovich/value.rb
44
94
  - lib/tasks/evaluate.rake
45
95
  - rules/rules.yml
46
96
  homepage: https://github.com/petrovich/petrovich-ruby
@@ -55,7 +105,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
55
105
  requirements:
56
106
  - - ">="
57
107
  - !ruby/object:Gem::Version
58
- version: 1.9.1
108
+ version: 1.9.3
59
109
  required_rubygems_version: !ruby/object:Gem::Requirement
60
110
  requirements:
61
111
  - - ">="
@@ -1,140 +0,0 @@
1
- # encoding: utf-8
2
-
3
- class Petrovich
4
- # Этот модуль разработан для возможности его подмешивания в класс Ruby.
5
- # Его можно подмешать в любой класс, например, в модель ActiveRecord.
6
- #
7
- # При помощи вызова метода +petrovich+ вы указываете, какие аттрибуты или методы класса
8
- # будут возвращать фамилию, имя и отчество.
9
- #
10
- # Опции:
11
- #
12
- # [:+firstname+]
13
- # Указывает метод, возвращающий имя
14
- #
15
- # [:+middlename+]
16
- # Указывает метод, возвращающий отчество
17
- #
18
- # [:+lastname+]
19
- # Указывает метод, возвращающий фамилию
20
- #
21
- # [:+gender+]
22
- # Указывает метод, возвращающий пол. Если пол не был указан, используется автоматическое определение
23
- # пола на основе отчества. Если отчество также не было указано, пытаемся определить правильное склонение
24
- # на основе файла правил.
25
- #
26
- # Пример использования
27
- #
28
- # class User
29
- # include Petrovich::Extension
30
- #
31
- # petrovich :firstname => :my_firstname,
32
- # :middlename => :my_middlename,
33
- # :lastname => :my_lastname,
34
- # :gender => :my_gender
35
- #
36
- # def my_firstname
37
- # 'Пётр'
38
- # end
39
- #
40
- # def my_middlename
41
- # 'Александрович'
42
- # end
43
- #
44
- # def my_lastname
45
- # 'Ларин'
46
- # end
47
- #
48
- # def my_gender
49
- # :male # :male, :female или :androgynous
50
- # end
51
- #
52
- # end
53
- #
54
- # Вы получите следующие методы
55
- #
56
- # user = User.new
57
- # user.my_lastname_dative # => Ларину
58
- # user.my_firstname_dative # => Петру
59
- # user.my_middlename_dative # => Александровичу
60
- #
61
- # Вышеперечисленные методы доступны и внутри класса User.
62
- #
63
- module Extension
64
- def self.included(base)
65
- base.extend ClassMethods
66
- end
67
-
68
- module ClassMethods
69
- def petrovich_configuration
70
- @petrovich_configuration ||= {
71
- :lastname => nil,
72
- :firstname => nil,
73
- :middlename => nil,
74
- :gender => nil
75
- }
76
- end
77
-
78
- def petrovich(options)
79
- self.petrovich_configuration.update(options)
80
- end
81
-
82
- def inherited(subclass)
83
- subclass.petrovich_configuration.update(self.petrovich_configuration)
84
- super
85
- end
86
- end
87
-
88
- def petrovich_create_getter(method_name, attribute, gcase)
89
- options = self.class.petrovich_configuration
90
- reflection = options.key(attribute.to_sym) or
91
- raise "No reflection for attribute '#{attribute}'!"
92
-
93
- self.class.send(:define_method, method_name) do
94
- # detect by gender attr if defined
95
- gender = options[:gender] && send(options[:gender])
96
- # detect by middlename attr if defined
97
- gender ||= begin
98
- middlename = options[:middlename] && send(options[:middlename])
99
- middlename && Petrovich.detect_gender(middlename)
100
- end
101
-
102
- rn = Petrovich.new gender
103
- rn.send reflection, send(attribute), gcase
104
- end
105
- end
106
-
107
- def method_missing(method_name, *args, &block)
108
- if match = method_name.to_s.match(petrovich_method_regex)
109
- attribute = match[1]
110
- gcase = match[2]
111
-
112
- petrovich_create_getter(method_name, attribute, gcase)
113
-
114
- if respond_to_without_petrovich?(method_name)
115
- send method_name
116
- else
117
- super
118
- end
119
- else
120
- super
121
- end
122
- end
123
-
124
- alias :respond_to_without_petrovich? :respond_to?
125
-
126
- def respond_to?(method_name, include_private = false)
127
- if match = method_name.to_s.match(petrovich_method_regex)
128
- true
129
- else
130
- super
131
- end
132
- end
133
-
134
- def petrovich_method_regex
135
- %r{(.+)_(#{Petrovich::CASES.join('|')})$}
136
- end
137
-
138
- protected :petrovich_method_regex
139
- end
140
- end
@@ -1,209 +0,0 @@
1
- # encoding: utf-8
2
-
3
- class Petrovich
4
- # Загрузка правил происходит один раз
5
- RULES = YAML.load_file(File.expand_path('../../../rules/rules.yml', __FILE__))
6
-
7
- class UnknownCaseException < Exception;;end
8
- class UnknownRuleException < Exception;;end
9
- class CantApplyRuleException < Exception;;end
10
-
11
- # Набор методов для нахождения и применения правил к имени, фамилии и отчеству.
12
- class Rules
13
- include Petrovich::Unicode
14
-
15
- attr_reader :gender
16
-
17
- Matchers = [
18
- proc {| x, y, i | y[ 0 ].size <=> x[ 0 ].size },
19
- proc {| x, y, i | x[ 1 ][ 'gender' ] != i.gender && 1 ||
20
- y[ 1 ][ 'gender' ] != i.gender && -1 || 0 },
21
- proc {| x, y, i | y[ 1 ][ 'test' ][ 0 ].size <=>
22
- x[ 1 ][ 'test' ][ 0 ].size },
23
- proc {| x, y, i | x[ 1 ][ 'test' ][ 0 ] <=> y[ 1 ][ 'test' ][ 0 ] } ]
24
-
25
- def initialize(gender = nil)
26
- @gender = gender
27
- end
28
-
29
- # Определяет методы +lastname_<i>case</i>+, +firstname_<i>case</i>+ и +middlename_<i>case</i>+
30
- # для получения имени, фамилии и отчества в нужном падеже.
31
- #
32
- # Использование:
33
- #
34
- # # Дательный падеж
35
- # lastname_dative('Комаров') # => Комарову
36
- #
37
- # # Винительный падеж
38
- # lastname_accusative('Комаров') # => Комарова
39
- #
40
- [:lastname, :firstname, :middlename].each do |method_name|
41
- define_method(method_name) do |name, gcase, scase|
42
- inflect(name, gcase, scase, Petrovich::RULES[method_name.to_s])
43
- end
44
- end
45
-
46
- protected
47
- # Известно несколько типов признаков, которые влияют на процесс поиска.
48
- #
49
- # Признак +first_word+ указывает, что данное слово является первым словом
50
- # в составном слове. Например, в двойной русской фамилии Иванов-Сидоров.
51
- #
52
- def match?(name, gcase, scase, rule, match_whole_word, tags)
53
- return false unless tags_allow? tags, rule['tags']
54
- return false if rule['gender'] == 'male' && female? ||
55
- rule['gender'] == 'female' && !female?
56
-
57
- rule['test'].each do |chars|
58
- begin
59
- chars = apply(chars, rule, scase, gcase) if scase != NOMINATIVE
60
- rescue CantApplyRuleException
61
- next
62
- end
63
-
64
- test = match_whole_word ? name : name.slice([name.size - chars.size, 0].max .. -1)
65
- return chars if test == chars
66
- end
67
-
68
- false
69
- end
70
-
71
- def male?
72
- @gender == 'male'
73
- end
74
-
75
- def female?
76
- @gender == 'female'
77
- end
78
-
79
- def inflect(name, gcase, scase, rules)
80
- i = 0
81
-
82
- parts = name.split('-')
83
-
84
- parts.map! do |part|
85
- first_word = (i += 1) == 1 && parts.size > 1
86
- find_and_apply(part, gcase, scase, rules, first_word: first_word)
87
- end
88
-
89
- parts.join('-')
90
- end
91
-
92
- # Применить правило
93
- def apply(name, rule, gcase, scase)
94
- mod = modificator_from(scase, rule) + modificator_for(gcase, rule)
95
- skip = 0
96
- mod.each_char do |char|
97
- case char
98
- when '.'
99
- when '-'
100
- raise CantApplyRuleException if name.empty?
101
- name = name.slice(0, name.size - 1)
102
- else
103
- name += char
104
- end
105
- end
106
-
107
- name
108
- end
109
-
110
- # Найти правило и применить к имени с учетом склонения
111
- def find_and_apply(name, gcase, scase, rules, features = {})
112
- rule = find_for(name, gcase, scase, rules, features)
113
- apply(name, rule, gcase, scase)
114
- rescue UnknownRuleException, CantApplyRuleException
115
- # Если не найдено правило для имени, или случилась ошибка применения
116
- # правила, возвращаем неизмененное имя.
117
- name
118
- end
119
-
120
- # Найти подходящее правило в исключениях или суффиксах
121
- def find_for(name, gcase, scase, rules, features = {})
122
- tags = extract_tags(features)
123
-
124
- # Сначала пытаемся найти исключения
125
- if rules.has_key?('exceptions')
126
- p = find(name, gcase, scase, rules['exceptions'], true, tags)
127
- return p if p
128
- end
129
-
130
- # Не получилось, ищем в суффиксах. Если не получилось найти и в них,
131
- # возвращаем неизмененное имя.
132
- find(name, gcase, scase, rules['suffixes'], false, tags) ||
133
- raise( UnknownRuleException, "Cannot find rule for #{name}" )
134
- end
135
-
136
- # Найти подходящее правило в конкретном списке правил
137
- def find(name, gcase, scase, rules, match_whole_word, tags)
138
- name = downcase(name)
139
- first =
140
- rules.map do| rule |
141
- score = match?(name, gcase, scase, rule, match_whole_word, tags)
142
- score && [ score, rule ] || nil
143
- end.compact.sort do| x, y |
144
- Matchers.reduce( 0 ) do| c, m |
145
- c = m.call( x, y, self )
146
- break c if c != 0
147
- end
148
- end.first
149
-
150
- first && first[ 1 ]
151
- end
152
-
153
- # Получить модификатор из указанного правиля для указанного склонения
154
- def modificator_for(gcase, rule)
155
- case gcase.to_sym
156
- when NOMINATIVE
157
- '.'
158
- when GENITIVE
159
- rule['mods'][0]
160
- when DATIVE
161
- rule['mods'][1]
162
- when ACCUSATIVE
163
- rule['mods'][2]
164
- when INSTRUMENTAL
165
- rule['mods'][3]
166
- when PREPOSITIONAL
167
- rule['mods'][4]
168
- else
169
- raise UnknownCaseException, "Unknown grammatic case: #{gcase}"
170
- end
171
- end
172
-
173
- # Получить модификатор из указанного правила для преобразования
174
- # из указанного склонения
175
- def modificator_from(scase, rule)
176
- return '.' if scase.to_sym == NOMINATIVE
177
-
178
- # TODO т.к. именительный падеж не может быть восстановлен
179
- # в некоторых случаях верно, используется первый попавшийся вариант
180
- # видимо нужно менять формат таблицы, или развязывать варианты,
181
- # находящиеся в поле test
182
- base = rule['test'][0].unpack('U*')
183
- mod = modificator_for(scase, rule).unpack('U*')
184
- mod.map do | char |
185
- case char
186
- when 46 # '.'
187
- 46
188
- when 45 # '-'
189
- base.pop
190
- else
191
- 45
192
- end
193
- end.reverse.pack('U*')
194
- end
195
-
196
- # Преобразование +{a: true, b: false, c: true}+ в +%w(a c)+.
197
- def extract_tags(features = {})
198
- features.keys.select { |k| features[k] == true }.map(&:to_s)
199
- end
200
-
201
- # Правило не подходит только в том случае, если оно содержит больше
202
- # тегов, чем требуется для данного слова.
203
- #
204
- def tags_allow?(tags, rule_tags)
205
- rule_tags ||= []
206
- (rule_tags - tags).empty?
207
- end
208
- end
209
- end