dry-inflector 0.1.0

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.
@@ -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: []