inflecto 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/.travis.yml +24 -0
- data/Changelog.md +7 -0
- data/Gemfile +8 -0
- data/Gemfile.devtools +66 -0
- data/Guardfile +18 -0
- data/LICENSE +20 -0
- data/README.md +46 -0
- data/Rakefile +5 -0
- data/TODO +1 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +3 -0
- data/config/roodi.yml +20 -0
- data/config/site.reek +95 -0
- data/config/yardstick.yml +2 -0
- data/inflecto.gemspec +16 -0
- data/lib/inflecto.rb +307 -0
- data/lib/inflecto/defaults.rb +58 -0
- data/lib/inflecto/inflections.rb +206 -0
- data/spec/integration/inflector_spec.rb +7 -0
- data/spec/rcov.opts +7 -0
- data/spec/shared/command_method_behavior.rb +7 -0
- data/spec/shared/each_method_behaviour.rb +15 -0
- data/spec/shared/hash_method_behavior.rb +17 -0
- data/spec/shared/idempotent_method_behavior.rb +7 -0
- data/spec/shared/invertible_method_behaviour.rb +9 -0
- data/spec/shared/mutator_behavior.rb +44 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/unit/inflector/class_methods/camelize_spec.rb +21 -0
- data/spec/unit/inflector/class_methods/classify_spec.rb +15 -0
- data/spec/unit/inflector/class_methods/demodulize_spec.rb +13 -0
- data/spec/unit/inflector/class_methods/foreign_key_spec.rb +13 -0
- data/spec/unit/inflector/class_methods/humanize_spec.rb +14 -0
- data/spec/unit/inflector/class_methods/pluralize_spec.rb +194 -0
- data/spec/unit/inflector/class_methods/singularize_spec.rb +153 -0
- data/spec/unit/inflector/class_methods/tabelize_spec.rb +27 -0
- data/spec/unit/inflector/class_methods/underscore_spec.rb +21 -0
- metadata +99 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
Inflecto.inflections do |inflect|
|
2
|
+
inflect.plural(/\z/, 's')
|
3
|
+
inflect.plural(/s\z/i, 's')
|
4
|
+
inflect.plural(/(ax|test)is\z/i, '\1es')
|
5
|
+
inflect.plural(/(octop|vir)us\z/i, '\1i')
|
6
|
+
inflect.plural(/(octop|vir)i\z/i, '\1i')
|
7
|
+
inflect.plural(/(alias|status)\z/i, '\1es')
|
8
|
+
inflect.plural(/(bu)s\z/i, '\1ses')
|
9
|
+
inflect.plural(/(buffal|tomat)o\z/i, '\1oes')
|
10
|
+
inflect.plural(/([ti])um\z/i, '\1a')
|
11
|
+
inflect.plural(/([ti])a\z/i, '\1a')
|
12
|
+
inflect.plural(/sis\z/i, 'ses')
|
13
|
+
inflect.plural(/(?:([^f])fe|([lr])f)\z/i, '\1\2ves')
|
14
|
+
inflect.plural(/(hive)\z/i, '\1s')
|
15
|
+
inflect.plural(/([^aeiouy]|qu)y\z/i, '\1ies')
|
16
|
+
inflect.plural(/(x|ch|ss|sh)\z/i, '\1es')
|
17
|
+
inflect.plural(/(matr|vert|ind)(?:ix|ex)\z/i, '\1ices')
|
18
|
+
inflect.plural(/([m|l])ouse\z/i, '\1ice')
|
19
|
+
inflect.plural(/([m|l])ice\z/i, '\1ice')
|
20
|
+
inflect.plural(/^(ox)\z/i, '\1en')
|
21
|
+
inflect.plural(/^(oxen)\z/i, '\1')
|
22
|
+
inflect.plural(/(quiz)\z/i, '\1zes')
|
23
|
+
|
24
|
+
inflect.singular(/s\z/i, '')
|
25
|
+
inflect.singular(/(n)ews\z/i, '\1ews')
|
26
|
+
inflect.singular(/([ti])a\z/i, '\1um')
|
27
|
+
inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses\z/i, '\1\2sis')
|
28
|
+
inflect.singular(/(^analy)ses\z/i, '\1sis')
|
29
|
+
inflect.singular(/([^f])ves\z/i, '\1fe')
|
30
|
+
inflect.singular(/(hive)s\z/i, '\1')
|
31
|
+
inflect.singular(/(tive)s\z/i, '\1')
|
32
|
+
inflect.singular(/([lr])ves\z/i, '\1f')
|
33
|
+
inflect.singular(/([^aeiouy]|qu)ies\z/i, '\1y')
|
34
|
+
inflect.singular(/(s)eries\z/i, '\1eries')
|
35
|
+
inflect.singular(/(m)ovies\z/i, '\1ovie')
|
36
|
+
inflect.singular(/(x|ch|ss|sh)es\z/i, '\1')
|
37
|
+
inflect.singular(/([m|l])ice\z/i, '\1ouse')
|
38
|
+
inflect.singular(/(bus)es\z/i, '\1')
|
39
|
+
inflect.singular(/(o)es\z/i, '\1')
|
40
|
+
inflect.singular(/(shoe)s\z/i, '\1')
|
41
|
+
inflect.singular(/(cris|ax|test)es\z/i, '\1is')
|
42
|
+
inflect.singular(/(octop|vir)i\z/i, '\1us')
|
43
|
+
inflect.singular(/(alias|status)es\z/i, '\1')
|
44
|
+
inflect.singular(/^(ox)en/i, '\1')
|
45
|
+
inflect.singular(/(vert|ind)ices\z/i, '\1ex')
|
46
|
+
inflect.singular(/(matr)ices\z/i, '\1ix')
|
47
|
+
inflect.singular(/(quiz)zes\z/i, '\1')
|
48
|
+
inflect.singular(/(database)s\z/i, '\1')
|
49
|
+
|
50
|
+
inflect.irregular('person', 'people')
|
51
|
+
inflect.irregular('man', 'men')
|
52
|
+
inflect.irregular('child', 'children')
|
53
|
+
inflect.irregular('sex', 'sexes')
|
54
|
+
inflect.irregular('move', 'moves')
|
55
|
+
inflect.irregular('cow', 'kine')
|
56
|
+
|
57
|
+
inflect.uncountable(%w(hovercraft moose milk rain Swiss grass equipment information rice money species series fish sheep jeans))
|
58
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
module Inflecto
|
2
|
+
# A singleton instance of this class is yielded by Inflecto.inflections, which can then be used to specify additional
|
3
|
+
# inflection rules. Examples:
|
4
|
+
#
|
5
|
+
# Inflecto.inflections do |inflect|
|
6
|
+
# inflect.plural /^(ox)$/i, '\1\2en'
|
7
|
+
# inflect.singular /^(ox)en/i, '\1'
|
8
|
+
#
|
9
|
+
# inflect.irregular 'octopus', 'octopi'
|
10
|
+
#
|
11
|
+
# inflect.uncountable "equipment"
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
|
15
|
+
# pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
|
16
|
+
# already have been loaded.
|
17
|
+
#
|
18
|
+
class Inflections
|
19
|
+
|
20
|
+
# Return instance
|
21
|
+
#
|
22
|
+
# @return [Inflections]
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
#
|
26
|
+
def self.instance
|
27
|
+
@__instance__ ||= new
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return plurals
|
31
|
+
#
|
32
|
+
# @return [Array]
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
#
|
36
|
+
attr_reader :plurals
|
37
|
+
|
38
|
+
# Return singulars
|
39
|
+
#
|
40
|
+
# @return [Array]
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
#
|
44
|
+
attr_reader :singulars
|
45
|
+
|
46
|
+
# Return uncountables
|
47
|
+
#
|
48
|
+
# @return [Array]
|
49
|
+
#
|
50
|
+
# @api private
|
51
|
+
#
|
52
|
+
attr_reader :uncountables
|
53
|
+
|
54
|
+
# Return humans
|
55
|
+
#
|
56
|
+
# @return [Array]
|
57
|
+
#
|
58
|
+
# @api private
|
59
|
+
#
|
60
|
+
#
|
61
|
+
attr_reader :humans
|
62
|
+
|
63
|
+
# Initialize object
|
64
|
+
#
|
65
|
+
# @return [undefined]
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
#
|
69
|
+
def initialize
|
70
|
+
@plurals, @singulars, @uncountables, @humans = [], [], [], []
|
71
|
+
end
|
72
|
+
|
73
|
+
# Add a new plural role
|
74
|
+
#
|
75
|
+
# Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
|
76
|
+
# The replacement should always be a string that may include references to the matched data from the rule.
|
77
|
+
#
|
78
|
+
# @param [String, Regexp] rule
|
79
|
+
# @param [String, Regexp] replacement
|
80
|
+
#
|
81
|
+
# @return [self]
|
82
|
+
#
|
83
|
+
# @api private
|
84
|
+
#
|
85
|
+
def plural(rule, replacement)
|
86
|
+
@uncountables.delete(rule) if rule.is_a?(String)
|
87
|
+
@uncountables.delete(replacement)
|
88
|
+
@plurals.insert(0, [rule, replacement])
|
89
|
+
self
|
90
|
+
end
|
91
|
+
|
92
|
+
# Add a new singular rule
|
93
|
+
#
|
94
|
+
# Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
|
95
|
+
# The replacement should always be a string that may include references to the matched data from the rule.
|
96
|
+
#
|
97
|
+
# @param [String, Regexp] rule
|
98
|
+
# @param [String, Regexp] replacement
|
99
|
+
#
|
100
|
+
# @return [self]
|
101
|
+
#
|
102
|
+
# @api private
|
103
|
+
#
|
104
|
+
def singular(rule, replacement)
|
105
|
+
@uncountables.delete(rule) if rule.is_a?(String)
|
106
|
+
@uncountables.delete(replacement)
|
107
|
+
@singulars.insert(0, [rule, replacement])
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
# Add a new irregular pluralization
|
112
|
+
#
|
113
|
+
# Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
|
114
|
+
# for strings, not regular expressions. You simply pass the irregular in singular and plural form.
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
#
|
118
|
+
# Inflecto.irregular('octopus', 'octopi')
|
119
|
+
# Inflecto.irregular('person', 'people')
|
120
|
+
#
|
121
|
+
# @param [String] singular
|
122
|
+
# @param [String] plural
|
123
|
+
#
|
124
|
+
# @return [self]
|
125
|
+
#
|
126
|
+
# @api private
|
127
|
+
#
|
128
|
+
def irregular(singular, plural)
|
129
|
+
@uncountables.delete(singular)
|
130
|
+
@uncountables.delete(plural)
|
131
|
+
|
132
|
+
if singular[0,1].upcase == plural[0,1].upcase
|
133
|
+
plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
|
134
|
+
plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1])
|
135
|
+
singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
|
136
|
+
else
|
137
|
+
plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
|
138
|
+
plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
|
139
|
+
plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
|
140
|
+
plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
|
141
|
+
singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1])
|
142
|
+
singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1])
|
143
|
+
end
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
147
|
+
# Add uncountable words
|
148
|
+
#
|
149
|
+
# Uncountable will not be inflected
|
150
|
+
#
|
151
|
+
# @example
|
152
|
+
#
|
153
|
+
# Inflecto.uncountable "money"
|
154
|
+
# Inflecto.uncountable "money", "information"
|
155
|
+
# Inflecto.uncountable %w( money information rice )
|
156
|
+
#
|
157
|
+
# @param [Enumerable<String>] words
|
158
|
+
#
|
159
|
+
# @return [self]
|
160
|
+
#
|
161
|
+
# @api private
|
162
|
+
#
|
163
|
+
def uncountable(words)
|
164
|
+
@uncountables.concat(words)
|
165
|
+
self
|
166
|
+
end
|
167
|
+
|
168
|
+
# Add humanize rule
|
169
|
+
#
|
170
|
+
# Specifies a humanized form of a string by a regular expression rule or by a string mapping.
|
171
|
+
# When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
|
172
|
+
# When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
|
173
|
+
#
|
174
|
+
# @example
|
175
|
+
# Inflecto.human(/_cnt$/i, '\1_count')
|
176
|
+
# Inflecto.human("legacy_col_person_name", "Name")
|
177
|
+
#
|
178
|
+
# @param [String, Regexp] rule
|
179
|
+
# @param [String, Regexp] replacement
|
180
|
+
#
|
181
|
+
# @api private
|
182
|
+
#
|
183
|
+
# @return [self]
|
184
|
+
#
|
185
|
+
def human(rule, replacement)
|
186
|
+
@humans.insert(0, [rule, replacement])
|
187
|
+
self
|
188
|
+
end
|
189
|
+
|
190
|
+
# Clear all inflection rules
|
191
|
+
#
|
192
|
+
# @example
|
193
|
+
#
|
194
|
+
# Inflecto.clear
|
195
|
+
#
|
196
|
+
# @return [self]
|
197
|
+
#
|
198
|
+
# @api private
|
199
|
+
#
|
200
|
+
def clear
|
201
|
+
initialize
|
202
|
+
self
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
end
|
data/spec/rcov.opts
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
shared_examples_for 'an #each method' do
|
4
|
+
it_should_behave_like 'a command method'
|
5
|
+
|
6
|
+
context 'with no block' do
|
7
|
+
subject { object.each }
|
8
|
+
|
9
|
+
it { should be_instance_of(to_enum.class) }
|
10
|
+
|
11
|
+
it 'yields the expected values' do
|
12
|
+
subject.to_a.should eql(object.to_a)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
shared_examples_for 'a hash method' do
|
4
|
+
it_should_behave_like 'an idempotent method'
|
5
|
+
|
6
|
+
specification = proc do
|
7
|
+
should be_instance_of(Fixnum)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'is a fixnum' do
|
11
|
+
instance_eval(&specification)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'memoizes the hash code' do
|
15
|
+
subject.should eql(object.memoized(:hash))
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
shared_examples_for 'a mutator' do
|
2
|
+
subject { object.each(node) { |item| yields << item } }
|
3
|
+
|
4
|
+
let(:yields) { [] }
|
5
|
+
let(:object) { described_class }
|
6
|
+
|
7
|
+
unless instance_methods.map(&:to_s).include?('node')
|
8
|
+
let(:node) { source.to_ast }
|
9
|
+
end
|
10
|
+
|
11
|
+
it_should_behave_like 'a command method'
|
12
|
+
|
13
|
+
context 'with no block' do
|
14
|
+
subject { object.each(node) }
|
15
|
+
|
16
|
+
it { should be_instance_of(to_enum.class) }
|
17
|
+
|
18
|
+
let(:expected_mutations) do
|
19
|
+
mutations.map do |mutation|
|
20
|
+
if mutation.respond_to?(:to_ast)
|
21
|
+
mutation.to_ast.to_sexp
|
22
|
+
else
|
23
|
+
mutation
|
24
|
+
end
|
25
|
+
end.to_set
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'generates the expected mutations' do
|
29
|
+
subject = self.subject.map(&:to_sexp).to_set
|
30
|
+
|
31
|
+
unless subject == expected_mutations
|
32
|
+
message = "Missing mutations: %s\nUnexpected mutations: %s" %
|
33
|
+
[expected_mutations - subject, subject - expected_mutations ].map(&:to_a).map(&:inspect)
|
34
|
+
fail message
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
shared_examples_for 'a noop mutator' do
|
41
|
+
let(:mutations) { [] }
|
42
|
+
|
43
|
+
it_should_behave_like 'a mutator'
|
44
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Inflecto do
|
4
|
+
describe '.camelize' do
|
5
|
+
it 'camelizes data_mapper as DataMapper' do
|
6
|
+
Inflecto.camelize('data_mapper').should == 'DataMapper'
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'camelizes merb as Merb' do
|
10
|
+
Inflecto.camelize('merb').should == 'Merb'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'camelizes data_mapper/resource as DataMapper::Resource' do
|
14
|
+
Inflecto.camelize('data_mapper/resource').should == 'DataMapper::Resource'
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'camelizes data_mapper/associations/one_to_many as DataMapper::Associations::OneToMany' do
|
18
|
+
Inflecto.camelize('data_mapper/associations/one_to_many').should == 'DataMapper::Associations::OneToMany'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Inflecto, '.classify' do
|
4
|
+
it 'classifies data_mapper as DataMapper' do
|
5
|
+
Inflecto.classify('data_mapper').should == 'DataMapper'
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'classifies enlarged_testes as EnlargedTestis' do
|
9
|
+
Inflecto.classify('enlarged_testes').should == 'EnlargedTestis'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'singularizes string first: classifies data_mappers as egg_and_hams as EggAndHam' do
|
13
|
+
Inflecto.classify('egg_and_hams').should == 'EggAndHam'
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Inflecto do
|
4
|
+
describe '.demodulize' do
|
5
|
+
it 'demodulizes module name: DataMapper::Inflecto => Inflecto' do
|
6
|
+
Inflecto.demodulize('DataMapper::Inflecto').should == 'Inflecto'
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'demodulizes module name: A::B::C::D::E => E' do
|
10
|
+
Inflecto.demodulize('A::B::C::D::E').should == 'E'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|