inflecto 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +1 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +24 -0
  4. data/Changelog.md +7 -0
  5. data/Gemfile +8 -0
  6. data/Gemfile.devtools +66 -0
  7. data/Guardfile +18 -0
  8. data/LICENSE +20 -0
  9. data/README.md +46 -0
  10. data/Rakefile +5 -0
  11. data/TODO +1 -0
  12. data/config/flay.yml +3 -0
  13. data/config/flog.yml +2 -0
  14. data/config/mutant.yml +3 -0
  15. data/config/roodi.yml +20 -0
  16. data/config/site.reek +95 -0
  17. data/config/yardstick.yml +2 -0
  18. data/inflecto.gemspec +16 -0
  19. data/lib/inflecto.rb +307 -0
  20. data/lib/inflecto/defaults.rb +58 -0
  21. data/lib/inflecto/inflections.rb +206 -0
  22. data/spec/integration/inflector_spec.rb +7 -0
  23. data/spec/rcov.opts +7 -0
  24. data/spec/shared/command_method_behavior.rb +7 -0
  25. data/spec/shared/each_method_behaviour.rb +15 -0
  26. data/spec/shared/hash_method_behavior.rb +17 -0
  27. data/spec/shared/idempotent_method_behavior.rb +7 -0
  28. data/spec/shared/invertible_method_behaviour.rb +9 -0
  29. data/spec/shared/mutator_behavior.rb +44 -0
  30. data/spec/spec_helper.rb +10 -0
  31. data/spec/unit/inflector/class_methods/camelize_spec.rb +21 -0
  32. data/spec/unit/inflector/class_methods/classify_spec.rb +15 -0
  33. data/spec/unit/inflector/class_methods/demodulize_spec.rb +13 -0
  34. data/spec/unit/inflector/class_methods/foreign_key_spec.rb +13 -0
  35. data/spec/unit/inflector/class_methods/humanize_spec.rb +14 -0
  36. data/spec/unit/inflector/class_methods/pluralize_spec.rb +194 -0
  37. data/spec/unit/inflector/class_methods/singularize_spec.rb +153 -0
  38. data/spec/unit/inflector/class_methods/tabelize_spec.rb +27 -0
  39. data/spec/unit/inflector/class_methods/underscore_spec.rb +21 -0
  40. 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
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Inflecto, 'underscore' do
4
+ specify 'allows to create snake_case from CamelCase' do
5
+ Inflecto.underscore('CamelCase').should eql('camel_case')
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ --exclude-only "spec/,^/"
2
+ --sort coverage
3
+ --callsites
4
+ --xrefs
5
+ --profile
6
+ --text-summary
7
+ --failure-threshold 100
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'a command method' do
4
+ it 'returns self' do
5
+ should equal(object)
6
+ end
7
+ end
@@ -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,7 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an idempotent method' do
4
+ it 'is idempotent' do
5
+ should equal(instance_eval(&self.class.subject))
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an invertible method' do
4
+ it_should_behave_like 'an idempotent method'
5
+
6
+ it 'is invertible' do
7
+ subject.inverse.should equal(object)
8
+ end
9
+ 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
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rspec'
4
+ require 'inflecto'
5
+
6
+ # require spec support files and shared behavior
7
+ Dir[File.expand_path('../{support,shared}/**/*.rb', __FILE__)].each { |f| require(f) }
8
+
9
+ RSpec.configure do |config|
10
+ end
@@ -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