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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +16 -0
- data/LICENSE.md +22 -0
- data/README.md +109 -0
- data/dry-inflector.gemspec +32 -0
- data/lib/dry/inflector.rb +292 -0
- data/lib/dry/inflector/inflections.rb +217 -0
- data/lib/dry/inflector/inflections/defaults.rb +110 -0
- data/lib/dry/inflector/rules.rb +37 -0
- data/lib/dry/inflector/version.rb +8 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -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
|
data/CHANGELOG.md
ADDED
@@ -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`
|
data/LICENSE.md
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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 [](https://gitter.im/dry-rb/chat)
|
10
|
+
|
11
|
+
[][gem]
|
12
|
+
[][travis]
|
13
|
+
[][gemnasium]
|
14
|
+
[][codeclimate]
|
15
|
+
[][codeclimate]
|
16
|
+
[][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
|
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: []
|