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.
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