dry-inflector 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c6a797f23150eb58f07e63794a61f5e2e904b6b51e7029678e2a6fd01e7413d2
4
+ data.tar.gz: 338a9cf61f97e6afe9d298e70017575b89cea25dfeca4159dc97763f51efaecb
5
+ SHA512:
6
+ metadata.gz: 50f87e79a468bd3001605e7aa9598d4f0b519e88f3fa0b0b9567e88dc2ca583f7dfe56e1dfe59caf8bc77e00cb6e5f73703e2d6fc5742dd7512bbb9fdd578d12
7
+ data.tar.gz: b3de9ce00c62bbe31aaeef50bd54b2fe30f0a7a37e5850ac4e44a0c6b57333d6528828735289336a4cb658c685f7a9c81e2ef1b429b2a3455bad857330894130
@@ -0,0 +1,16 @@
1
+ # Dry::Inflector
2
+
3
+ Inflector for Ruby
4
+
5
+ ## v0.1.0 (unreleased)
6
+ ### Added
7
+ - [Luca Guidi] Introduced `Dry::Inflector#pluralize`
8
+ - [Luca Guidi] Introduced `Dry::Inflector#singularize`
9
+ - [Luca Guidi] Introduced `Dry::Inflector#camelize`
10
+ - [Luca Guidi] Introduced `Dry::Inflector#classify`
11
+ - [Luca Guidi] Introduced `Dry::Inflector#tableize`
12
+ - [Luca Guidi] Introduced `Dry::Inflector#dasherize`
13
+ - [Luca Guidi] Introduced `Dry::Inflector#underscore`
14
+ - [Luca Guidi] Introduced `Dry::Inflector#demodulize`
15
+ - [Luca Guidi] Introduced `Dry::Inflector#humanize`
16
+ - [Luca Guidi] Introduced `Dry::Inflector#ordinalize`
@@ -0,0 +1,22 @@
1
+ Copyright © The Dry, Rails, Merb, Datamapper, Inflecto, Flexus, and Hanami teams
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,109 @@
1
+ [gitter]: https://gitter.im/dry-rb/chat
2
+ [gem]: https://img.shields.io/gem/v/dry-inflector.svg]
3
+ [travis]: https://travis-ci.org/dry-rb/dry-inflector
4
+ [gemnasium]: https://gemnasium.com/dry-rb/dry-inflector
5
+ [codeclimate]: https://codeclimate.com/github/dry-rb/dry-inflector
6
+ [coveralls]: https://coveralls.io/r/dry-rb/dry-inflector
7
+ [inchpages]: http://inch-ci.org/github/dry-rb/dry-inflector
8
+
9
+ # dry-inflector [![Join the chat at https://gitter.im/dry-rb/chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dry-rb/chat)
10
+
11
+ [![Gem Version](https://badge.fury.io/rb/dry-inflector.svg)][gem]
12
+ [![Build Status](https://travis-ci.org/dry-rb/dry-inflector.svg?branch=master)][travis]
13
+ [![Dependency Status](https://gemnasium.com/dry-rb/dry-inflector.svg)][gemnasium]
14
+ [![Code Climate](https://codeclimate.com/github/dry-rb/dry-inflector/badges/gpa.svg)][codeclimate]
15
+ [![Test Coverage](https://codeclimate.com/github/dry-rb/dry-inflector/badges/coverage.svg)][codeclimate]
16
+ [![Inline docs](http://inch-ci.org/github/dry-rb/dry-inflector.svg?branch=master)][inchpages]
17
+
18
+ dry-inflector is an inflector gem for Ruby.
19
+
20
+ ## Installation
21
+
22
+ Add this line to your application's `Gemfile`:
23
+
24
+ ```ruby
25
+ gem 'dry-inflector'
26
+ ```
27
+
28
+ And then execute:
29
+
30
+ ```shell
31
+ $ bundle
32
+ ```
33
+
34
+ Or install it yourself as:
35
+
36
+ ```shell
37
+ $ gem install dry-inflector
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ### Basic usage
43
+
44
+ ```ruby
45
+ require "dry/inflector"
46
+
47
+ inflector = Dry::Inflector.new
48
+
49
+ inflector.pluralize("book") # => "books"
50
+ inflector.singularize("books") # => "book"
51
+
52
+ inflector.camelize("dry/inflector") # => "Dry::Inflector"
53
+ inflector.classify("books") # => "Book"
54
+ inflector.tableize("Book") # => "books"
55
+
56
+ inflector.dasherize("dry_inflector") # => "dry-inflector"
57
+ inflector.underscore("dry-inflector") # => "dry_inflector"
58
+
59
+ inflector.demodulize("Dry::Inflector") # => "Inflector"
60
+
61
+ inflector.humanize("dry_inflector") # => "Dry inflector"
62
+ inflector.humanize("author_id") # => "Author"
63
+
64
+ inflector.ordinalize(1) # => "1st"
65
+ inflector.ordinalize(2) # => "2nd"
66
+ inflector.ordinalize(3) # => "3rd"
67
+ inflector.ordinalize(10) # => "10th"
68
+ inflector.ordinalize(23) # => "23rd"
69
+ ```
70
+
71
+ ### Custom inflection rules
72
+
73
+ ```ruby
74
+ require "dry/inflector"
75
+
76
+ inflector = Dry::Inflector.new do |inflections|
77
+ inflections.plural "virus", "viruses" # specify a rule for #pluralize
78
+ inflections.singular "thieves", "thief" # specify a rule for #singularize
79
+ inflections.uncountable "dry-inflector" # add an exception for an uncountable word
80
+ end
81
+
82
+ inflector.pluralize("virus") # => "viruses"
83
+ inflector.singularize("thieves") # => "thief"
84
+
85
+ inflector.pluralize("dry-inflector") # => "dry-inflector"
86
+ ```
87
+
88
+ ## Credits
89
+
90
+ This gem is the cumulative effort of the Ruby community.
91
+ It started with the extlib inflecto originated from [active_support](https://github.com/rails/rails), then dm-core inflector originated from [extlib](https://github.com/datamapper/extlib).
92
+ Later, [`inflecto`](https://github.com/mbj/inflecto) was extracted from [dm-core](https://github.com/datamapper/dm-core) as a standalone inflector.
93
+ Now, we resurrect `inflecto` and merged [`flexus`](https://github.com/Ptico/flexus), with some inflection rules from [`hanami-utils`](https://github.com/hanami/utils).
94
+
95
+ This is `dry-inflector`.
96
+
97
+ ## Development
98
+
99
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
100
+
101
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
102
+
103
+ ## Contributing
104
+
105
+ Bug reports and pull requests are welcome on GitHub at https://github.com/dry-rb/dry-inflector.
106
+
107
+ ## Copyright
108
+
109
+ Copyright © The Dry, Rails, Merb, Datamapper, Inflecto, Flexus, and Hanami teams - Released under the MIT License
@@ -0,0 +1,32 @@
1
+
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path("../lib", __FILE__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require "dry/inflector/version"
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = "dry-inflector"
10
+ spec.version = Dry::Inflector::VERSION
11
+ spec.authors = ["Luca Guidi", "Andrii Savchenko", "Abinoam P. Marques Jr."]
12
+ spec.email = ["me@lucaguidi.com", "andrey@aejis.eu", "abinoam@gmail.com"]
13
+
14
+ spec.summary = "DRY Inflector"
15
+ spec.description = "String inflections for dry-rb"
16
+ spec.homepage = "http://dry-rb.org"
17
+ spec.license = "MIT"
18
+
19
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
20
+
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.files = `git ls-files -- lib/* CHANGELOG.md LICENSE.md README.md dry-inflector.gemspec`.split($INPUT_RECORD_SEPARATOR)
26
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.15"
29
+ spec.add_development_dependency "rake", "~> 12.0"
30
+ spec.add_development_dependency "rspec", "~> 3.6"
31
+ spec.add_development_dependency "rubocop", "~> 0.50.0"
32
+ end
@@ -0,0 +1,292 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module Dry
6
+ # dry-inflector
7
+ #
8
+ # @since 0.1.0
9
+ class Inflector
10
+ require "dry/inflector/version"
11
+ require "dry/inflector/inflections"
12
+
13
+ # Instantiate the inflector
14
+ #
15
+ # @param blk [Proc] an optional block to specify custom inflection rules
16
+ # @yieldparam [Dry::Inflector::Inflections] the inflection rules
17
+ #
18
+ # @return [Dry::Inflector] the inflector
19
+ #
20
+ # @since 0.1.0
21
+ #
22
+ # @example Basic usage
23
+ # require "dry/inflector"
24
+ #
25
+ # inflector = Dry::Inflector.new
26
+ #
27
+ # @example Custom inflection rules
28
+ # require "dry/inflector"
29
+ #
30
+ # inflector = Dry::Inflector.new do |inflections|
31
+ # inflections.plural "virus", "viruses" # specify a rule for #pluralize
32
+ # inflections.singular "thieves", "thief" # specify a rule for #singularize
33
+ # inflections.uncountable "dry-inflector" # add an exception for an uncountable word
34
+ # end
35
+ def initialize(&blk)
36
+ @inflections = Inflections.build(&blk)
37
+ end
38
+
39
+ # Camelize a string
40
+ #
41
+ # @param input [String,Symbol] the input
42
+ # @return [String] the camelized string
43
+ #
44
+ # @since 0.1.0
45
+ #
46
+ # @example
47
+ # require "dry/inflector"
48
+ #
49
+ # inflector = Dry::Inflector.new
50
+ # inflector.camelize("dry/inflector") # => "Dry::Inflector"
51
+ def camelize(input)
52
+ input.to_s.gsub(/\/(.?)/) { "::#{Regexp.last_match(1).upcase}" }.gsub(/(?:\A|_)(.)/) { Regexp.last_match(1).upcase }
53
+ end
54
+
55
+ # Find a constant with the name specified in the argument string
56
+ #
57
+ # The name is assumed to be the one of a top-level constant,
58
+ # constant scope of caller is ignored
59
+ #
60
+ # @param input [String,Symbol] the input
61
+ # @return [Class, Module] the class or module
62
+ #
63
+ # @since 0.1.0
64
+ #
65
+ # @example
66
+ # require "dry/inflector"
67
+ #
68
+ # inflector = Dry::Inflector.new
69
+ # inflector.constantize("Module") # => Module
70
+ # inflector.constantize("Dry::Inflector") # => Dry::Inflector
71
+ def constantize(input)
72
+ Object.const_get(input)
73
+ end
74
+
75
+ # Classify a string
76
+ #
77
+ # @param input [String,Symbol] the input
78
+ # @return [String] the classified string
79
+ #
80
+ # @since 0.1.0
81
+ #
82
+ # @example
83
+ # require "dry/inflector"
84
+ #
85
+ # inflector = Dry::Inflector.new
86
+ # inflector.classify("books") # => "Book"
87
+ def classify(input)
88
+ camelize(singularize(input.to_s.sub(/.*\./, "")))
89
+ end
90
+
91
+ # Dasherize a string
92
+ #
93
+ # @param input [String,Symbol] the input
94
+ # @return [String] the dasherized string
95
+ #
96
+ # @since 0.1.0
97
+ #
98
+ # @example
99
+ # require "dry/inflector"
100
+ #
101
+ # inflector = Dry::Inflector.new
102
+ # inflector.dasherize("dry_inflector") # => "dry-inflector"
103
+ def dasherize(input)
104
+ input.to_s.tr("_", "-")
105
+ end
106
+
107
+ # Demodulize a string
108
+ #
109
+ # @param input [String,Symbol] the input
110
+ # @return [String] the demodulized string
111
+ #
112
+ # @since 0.1.0
113
+ #
114
+ # @example
115
+ # require "dry/inflector"
116
+ #
117
+ # inflector = Dry::Inflector.new
118
+ # inflector.demodulize("Dry::Inflector") # => "Inflector"
119
+ def demodulize(input)
120
+ input.to_s.split("::").last
121
+ end
122
+
123
+ # Humanize a string
124
+ #
125
+ # @param input [String,Symbol] the input
126
+ # @return [String] the humanized string
127
+ #
128
+ # @since 0.1.0
129
+ #
130
+ # @example
131
+ # require "dry/inflector"
132
+ #
133
+ # inflector = Dry::Inflector.new
134
+ # inflector.humanize("dry_inflector") # => "Dry inflector"
135
+ # inflector.humanize("author_id") # => "Author"
136
+ def humanize(input)
137
+ input = input.to_s
138
+ result = inflections.humans.apply_to(input)
139
+ result.gsub!(/_id\z/, "")
140
+ result.tr!("_", " ")
141
+ result.capitalize!
142
+ result
143
+ end
144
+
145
+ # Creates a foreign key name
146
+ #
147
+ # @param input [String, Symbol] the input
148
+ # @return [String] foreign key
149
+ #
150
+ # @example
151
+ # require "dry/inflector"
152
+ #
153
+ # inflector = Dry::Inflector.new
154
+ # inflector.foreign_key("Message") => "message_id"
155
+ def foreign_key(input)
156
+ "#{underscorize(demodulize(input))}_id"
157
+ end
158
+
159
+ # Ordinalize a number
160
+ #
161
+ # @param number [Integer] the input
162
+ # @return [String] the ordinalized number
163
+ #
164
+ # @since 0.1.0
165
+ #
166
+ # @example
167
+ # require "dry/inflector"
168
+ #
169
+ # inflector = Dry::Inflector.new
170
+ # inflector.ordinalize(1) # => "1st"
171
+ # inflector.ordinalize(2) # => "2nd"
172
+ # inflector.ordinalize(3) # => "3rd"
173
+ # inflector.ordinalize(10) # => "10th"
174
+ # inflector.ordinalize(23) # => "23rd"
175
+ def ordinalize(number)
176
+ abs_value = number.abs
177
+
178
+ if ORDINALIZE_TH.include?(abs_value % 100)
179
+ "#{number}th"
180
+ else
181
+ case abs_value % 10
182
+ when 1 then "#{number}st"
183
+ when 2 then "#{number}nd"
184
+ when 3 then "#{number}rd"
185
+ end
186
+ end
187
+ end
188
+
189
+ # Pluralize a string
190
+ #
191
+ # @param input [String,Symbol] the input
192
+ # @return [String] the pluralized string
193
+ #
194
+ # @since 0.1.0
195
+ #
196
+ # @example
197
+ # require "dry/inflector"
198
+ #
199
+ # inflector = Dry::Inflector.new
200
+ # inflector.pluralize("book") # => "books"
201
+ # inflector.pluralize("money") # => "money"
202
+ def pluralize(input)
203
+ input = input.to_s
204
+ return input if uncountable?(input)
205
+ inflections.plurals.apply_to(input)
206
+ end
207
+
208
+ # Singularize a string
209
+ #
210
+ # @param input [String] the input
211
+ # @return [String] the singularized string
212
+ #
213
+ # @since 0.1.0
214
+ #
215
+ # @example
216
+ # require "dry/inflector"
217
+ #
218
+ # inflector = Dry::Inflector.new
219
+ # inflector.singularize("books") # => "book"
220
+ # inflector.singularize("money") # => "money"
221
+ def singularize(input)
222
+ input = input.to_s
223
+ return input if uncountable?(input)
224
+ inflections.singulars.apply_to(input)
225
+ end
226
+
227
+ # Tableize a string
228
+ #
229
+ # @param input [String,Symbol] the input
230
+ # @return [String] the tableized string
231
+ #
232
+ # @since 0.1.0
233
+ #
234
+ # @example
235
+ # require "dry/inflector"
236
+ #
237
+ # inflector = Dry::Inflector.new
238
+ # inflector.tableize("Book") # => "books"
239
+ def tableize(input)
240
+ input = input.to_s.gsub(/::/, "_")
241
+ pluralize(underscorize(input))
242
+ end
243
+
244
+ # Underscore a string
245
+ #
246
+ # @param input [String,Symbol] the input
247
+ # @return [String] the underscored string
248
+ #
249
+ # @since 0.1.0
250
+ #
251
+ # @example
252
+ # require "dry/inflector"
253
+ #
254
+ # inflector = Dry::Inflector.new
255
+ # inflector.underscore("dry-inflector") # => "dry_inflector"
256
+ def underscore(input)
257
+ input = input.to_s.gsub(/::/, "/")
258
+ underscorize(input)
259
+ end
260
+
261
+ # Check if the input is an uncountable word
262
+ #
263
+ # @param input [String] the input
264
+ # @return [TrueClass,FalseClass] the result of the check
265
+ #
266
+ # @since 0.1.0
267
+ # @api private
268
+ def uncountable?(input)
269
+ !(input =~ /\A[[:space:]]*\z/).nil? || inflections.uncountables.include?(input.downcase)
270
+ end
271
+
272
+ private
273
+
274
+ # @since 0.1.0
275
+ # @api private
276
+ ORDINALIZE_TH = (4..16).to_set.freeze
277
+
278
+ # @since 0.1.0
279
+ # @api private
280
+ attr_reader :inflections
281
+
282
+ # @since 0.1.0
283
+ # @api private
284
+ def underscorize(input)
285
+ input.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
286
+ input.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
287
+ input.tr!("-", "_")
288
+ input.downcase!
289
+ input
290
+ end
291
+ end
292
+ end
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "dry/inflector/rules"
5
+
6
+ module Dry
7
+ class Inflector
8
+ # Inflections
9
+ #
10
+ # @since 0.1.0
11
+ class Inflections
12
+ require "dry/inflector/inflections/defaults"
13
+
14
+ # Instantiate a set of inflection rules.
15
+ # It adds the default rules and the optional customizations, passed as a block.
16
+ #
17
+ # @param blk [Proc] the optional, custom rules
18
+ #
19
+ # @since 0.1.0
20
+ # @api private
21
+ def self.build(&blk)
22
+ new do |inflect|
23
+ Defaults.call(inflect)
24
+ blk.call(inflect) if block_given?
25
+ end
26
+ end
27
+
28
+ # Pluralization rules
29
+ #
30
+ # @return [Dry::Inflector::Rules]
31
+ #
32
+ # @since 0.1.0
33
+ # @api private
34
+ attr_reader :plurals
35
+
36
+ # Singularization rules
37
+ #
38
+ # @return [Dry::Inflector::Rules]
39
+ #
40
+ # @since 0.1.0
41
+ # @api private
42
+ attr_reader :singulars
43
+
44
+ # Uncountable rules
45
+ #
46
+ # @return [Set]
47
+ #
48
+ # @since 0.1.0
49
+ # @api private
50
+ attr_reader :uncountables
51
+
52
+ # Human rules
53
+ #
54
+ # @return [Dry::Inflector::Rules]
55
+ #
56
+ # @since 0.1.0
57
+ # @api private
58
+ attr_reader :humans
59
+
60
+ # Instantiate the rules
61
+ #
62
+ # @return [Dry::Inflector::Inflections]
63
+ # @yieldparam [self]
64
+ #
65
+ # @since 0.1.0
66
+ # @api private
67
+ def initialize
68
+ @plurals = Rules.new
69
+ @singulars = Rules.new
70
+ @humans = Rules.new
71
+ @uncountables = Set[]
72
+
73
+ yield(self) if block_given?
74
+ end
75
+
76
+ # Add a custom pluralization rule
77
+ #
78
+ # Specifies a new pluralization rule and its replacement.
79
+ # The rule can either be a string or a regular expression.
80
+ # The replacement should always be a string that may include references to the matched data from the rule.
81
+ #
82
+ # @param rule [String, Regexp] the rule
83
+ # @param replacement [String] the replacement
84
+ #
85
+ # @since 0.1.0
86
+ #
87
+ # @example
88
+ # require "dry/inflector"
89
+ #
90
+ # inflector = Dry::Inflector.new do |inflections|
91
+ # inflections.plural "virus", "viruses"
92
+ # end
93
+ def plural(rule, replacement)
94
+ rule(rule, replacement, plurals)
95
+ end
96
+
97
+ # Add a custom singularization rule
98
+ #
99
+ # Specifies a new singularization rule and its replacement.
100
+ # The rule can either be a string or a regular expression.
101
+ # The replacement should always be a string that may include references to the matched data from the rule.
102
+ #
103
+ # @param rule [String, Regexp] the rule
104
+ # @param replacement [String] the replacement
105
+ #
106
+ # @since 0.1.0
107
+ #
108
+ # @example
109
+ # require "dry/inflector"
110
+ #
111
+ # inflector = Dry::Inflector.new do |inflections|
112
+ # inflections.singular "thieves", "thief"
113
+ # end
114
+ def singular(rule, replacement)
115
+ rule(rule, replacement, singulars)
116
+ end
117
+
118
+ # Add a custom pluralization rule
119
+ #
120
+ # Specifies a new irregular that applies to both pluralization and singularization at the same time.
121
+ # This can only be used for strings, not regular expressions.
122
+ # You simply pass the irregular in singular and plural form.
123
+ #
124
+ # @param singular [String] the singular
125
+ # @param plural [String] the plural
126
+ #
127
+ # @since 0.1.0
128
+ #
129
+ # @example
130
+ # require "dry/inflector"
131
+ #
132
+ # inflector = Dry::Inflector.new do |inflections|
133
+ # inflections.singular "octopus", "octopi"
134
+ # end
135
+ def irregular(singular, plural)
136
+ uncountables.delete(singular)
137
+ uncountables.delete(plural)
138
+
139
+ add_irregular(singular, plural, plurals)
140
+ add_irregular(plural, singular, singulars)
141
+ end
142
+
143
+ # Add a custom rule for uncountable words
144
+ #
145
+ # Uncountable will not be inflected
146
+ #
147
+ # @param [Enumerable<String>] words
148
+ #
149
+ # @since 0.1.0
150
+ #
151
+ # @example
152
+ # require "dry/inflector"
153
+ #
154
+ # inflector = Dry::Inflector.new do |inflections|
155
+ # inflections.uncountable "money"
156
+ # inflections.uncountable "money", "information"
157
+ # inflections.uncountable %w(money information rice)
158
+ # end
159
+ def uncountable(*words)
160
+ uncountables.merge(words.flatten)
161
+ end
162
+
163
+ # Add a custom humanize rule
164
+ #
165
+ # Specifies a humanized form of a string by a regular expression rule or by a string mapping.
166
+ # When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
167
+ # When a string is used, the human form should be specified as desired (example: `"The name"`, not `"the_name"`)
168
+ #
169
+ # @param rule [String, Regexp] the rule
170
+ # @param replacement [String] the replacement
171
+ #
172
+ # @since 0.1.0
173
+ #
174
+ # @example
175
+ # require "dry/inflector"
176
+ #
177
+ # inflector = Dry::Inflector.new do |inflections|
178
+ # inflections.human(/_cnt$/i, '\1_count')
179
+ # inflections.human("legacy_col_person_name", "Name")
180
+ # end
181
+ def human(rule, replacement)
182
+ humans.insert(0, [rule, replacement])
183
+ end
184
+
185
+ private
186
+
187
+ # Add irregular inflection
188
+ #
189
+ # @param rule [String] the rule
190
+ # @param replacement [String] the replacement
191
+ #
192
+ # @return [undefined]
193
+ #
194
+ # @since 0.1.0
195
+ # @api private
196
+ def add_irregular(rule, replacement, target)
197
+ head, *tail = rule.chars.to_a
198
+ rule(/(#{head})#{tail.join}\z/i, '\1' + replacement[1..-1], target)
199
+ end
200
+
201
+ # Add a new rule
202
+ #
203
+ # @param rule [String, Regexp] the rule
204
+ # @param replacement [String, Regexp] the replacement
205
+ # @param target [Dry::Inflector::Rules] the target
206
+ #
207
+ # @since 0.1.0
208
+ # @api private
209
+ def rule(rule, replacement, target)
210
+ uncountables.delete(rule)
211
+ uncountables.delete(replacement)
212
+
213
+ target.insert(0, [rule, replacement])
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ class Inflector
5
+ class Inflections
6
+ # Default inflections
7
+ #
8
+ # @since 0.1.0
9
+ # @api private
10
+ #
11
+ # rubocop:disable Metrics/AbcSize
12
+ # rubocop:disable Metrics/MethodLength
13
+ module Defaults
14
+ # @since 0.1.0
15
+ # @api private
16
+ def self.call(inflect)
17
+ plural(inflect)
18
+ singular(inflect)
19
+ irregular(inflect)
20
+ uncountable(inflect)
21
+ end
22
+
23
+ # @since 0.1.0
24
+ # @api private
25
+ def self.plural(inflect)
26
+ inflect.plural(/\z/, "s")
27
+ inflect.plural(/s\z/i, "s")
28
+ inflect.plural(/(ax|test)is\z/i, '\1es')
29
+ inflect.plural(/(.*)us\z/i, '\1uses')
30
+ inflect.plural(/(octop|vir|cact)us\z/i, '\1i')
31
+ inflect.plural(/(octop|vir)i\z/i, '\1i')
32
+ inflect.plural(/(alias|status)\z/i, '\1es')
33
+ inflect.plural(/(buffal|domin|ech|embarg|her|mosquit|potat|tomat)o\z/i, '\1oes')
34
+ inflect.plural(/(?<!b)um\z/i, '\1a')
35
+ inflect.plural(/([ti])a\z/i, '\1a')
36
+ inflect.plural(/sis\z/i, "ses")
37
+ inflect.plural(/(.*)(?:([^f]))f[e]*\z/i, '\1\2ves')
38
+ inflect.plural(/(hive|proof)\z/i, '\1s') # TODO: proof can be moved in the above regexp
39
+ inflect.plural(/([^aeiouy]|qu)y\z/i, '\1ies')
40
+ inflect.plural(/(x|ch|ss|sh)\z/i, '\1es')
41
+ inflect.plural(/(stoma|epo)ch\z/i, '\1chs')
42
+ inflect.plural(/(matr|vert|ind)(?:ix|ex)\z/i, '\1ices')
43
+ inflect.plural(/([m|l])ouse\z/i, '\1ice')
44
+ inflect.plural(/([m|l])ice\z/i, '\1ice')
45
+ inflect.plural(/^(ox)\z/i, '\1en')
46
+ inflect.plural(/^(oxen)\z/i, '\1')
47
+ inflect.plural(/(quiz)\z/i, '\1zes')
48
+ inflect.plural(/(.*)non\z/i, '\1na')
49
+ inflect.plural(/(.*)ma\z/i, '\1mata')
50
+ inflect.plural(/(.*)(eau|eaux)\z/, '\1eaux')
51
+ end
52
+
53
+ # @since 0.1.0
54
+ # @api private
55
+ def self.singular(inflect)
56
+ inflect.singular(/s\z/i, "")
57
+ inflect.singular(/(n)ews\z/i, '\1ews')
58
+ inflect.singular(/([ti])a\z/i, '\1um')
59
+ inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses\z/i, '\1\2sis')
60
+ inflect.singular(/(^analy)ses\z/i, '\1sis')
61
+ inflect.singular(/([^f])ves\z/i, '\1fe')
62
+ inflect.singular(/(hive)s\z/i, '\1')
63
+ inflect.singular(/(tive)s\z/i, '\1')
64
+ inflect.singular(/([lr])ves\z/i, '\1f')
65
+ inflect.singular(/([^aeiouy]|qu)ies\z/i, '\1y')
66
+ inflect.singular(/(s)eries\z/i, '\1eries')
67
+ inflect.singular(/(m)ovies\z/i, '\1ovie')
68
+ inflect.singular(/(ss)\z/i, '\1')
69
+ inflect.singular(/(x|ch|ss|sh)es\z/i, '\1')
70
+ inflect.singular(/([m|l])ice\z/i, '\1ouse')
71
+ inflect.singular(/(bus)es\z/i, '\1')
72
+ inflect.singular(/(o)es\z/i, '\1')
73
+ inflect.singular(/(shoe)s\z/i, '\1')
74
+ inflect.singular(/(cris|ax|test)es\z/i, '\1is')
75
+ inflect.singular(/(octop|vir)i\z/i, '\1us')
76
+ inflect.singular(/(alias|status)es\z/i, '\1')
77
+ inflect.singular(/^(ox)en/i, '\1')
78
+ inflect.singular(/(vert|ind)ices\z/i, '\1ex')
79
+ inflect.singular(/(matr)ices\z/i, '\1ix')
80
+ inflect.singular(/(quiz)zes\z/i, '\1')
81
+ inflect.singular(/(database)s\z/i, '\1')
82
+ end
83
+
84
+ # @since 0.1.0
85
+ # @api private
86
+ def self.irregular(inflect)
87
+ inflect.irregular("person", "people")
88
+ inflect.irregular("man", "men")
89
+ inflect.irregular("human", "humans") # NOTE: this is here only to override the previous rule
90
+ inflect.irregular("child", "children")
91
+ inflect.irregular("sex", "sexes")
92
+ inflect.irregular("foot", "feet")
93
+ inflect.irregular("tooth", "teeth")
94
+ inflect.irregular("goose", "geese")
95
+ inflect.irregular("forum", "forums") # FIXME: this is here because I need to fix the "um" regexp
96
+ end
97
+
98
+ # @since 0.1.0
99
+ # @api private
100
+ def self.uncountable(inflect)
101
+ inflect.uncountable(%w[hovercraft moose deer milk rain Swiss grass equipment information rice money species series fish sheep jeans])
102
+ end
103
+
104
+ private_class_method :plural, :singular, :irregular, :uncountable
105
+ end
106
+ # rubocop:enable Metrics/MethodLength
107
+ # rubocop:enable Metrics/AbcSize
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ class Inflector
5
+ # A set of inflection rules
6
+ #
7
+ # @since 0.1.0
8
+ # @api private
9
+ class Rules
10
+ # @since 0.1.0
11
+ # @api private
12
+ def initialize
13
+ @rules = []
14
+ end
15
+
16
+ # @since 0.1.0
17
+ # @api private
18
+ def apply_to(word)
19
+ result = word.dup
20
+ each { |rule, replacement| break if result.gsub!(rule, replacement) }
21
+ result
22
+ end
23
+
24
+ # @since 0.1.0
25
+ # @api private
26
+ def insert(index, array)
27
+ @rules.insert(index, array)
28
+ end
29
+
30
+ # @since 0.1.0
31
+ # @api private
32
+ def each(&blk)
33
+ @rules.each(&blk)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ class Inflector
5
+ # @since 0.1.0
6
+ VERSION = "0.1.0"
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dry-inflector
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Luca Guidi
8
+ - Andrii Savchenko
9
+ - Abinoam P. Marques Jr.
10
+ autorequire:
11
+ bindir: exe
12
+ cert_chain: []
13
+ date: 2017-11-17 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bundler
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.15'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1.15'
29
+ - !ruby/object:Gem::Dependency
30
+ name: rake
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '12.0'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '12.0'
43
+ - !ruby/object:Gem::Dependency
44
+ name: rspec
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '3.6'
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '3.6'
57
+ - !ruby/object:Gem::Dependency
58
+ name: rubocop
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: 0.50.0
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: 0.50.0
71
+ description: String inflections for dry-rb
72
+ email:
73
+ - me@lucaguidi.com
74
+ - andrey@aejis.eu
75
+ - abinoam@gmail.com
76
+ executables: []
77
+ extensions: []
78
+ extra_rdoc_files: []
79
+ files:
80
+ - CHANGELOG.md
81
+ - LICENSE.md
82
+ - README.md
83
+ - dry-inflector.gemspec
84
+ - lib/dry/inflector.rb
85
+ - lib/dry/inflector/inflections.rb
86
+ - lib/dry/inflector/inflections/defaults.rb
87
+ - lib/dry/inflector/rules.rb
88
+ - lib/dry/inflector/version.rb
89
+ homepage: http://dry-rb.org
90
+ licenses:
91
+ - MIT
92
+ metadata:
93
+ allowed_push_host: https://rubygems.org
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 2.7.1
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: DRY Inflector
114
+ test_files: []