flexus 1.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +37 -0
- data/.rspec +1 -0
- data/.travis.yml +37 -0
- data/Changelog.md +7 -0
- data/Gemfile +9 -0
- data/Guardfile +18 -0
- data/LICENSE.txt +22 -0
- data/README.md +78 -0
- data/Rakefile +12 -0
- data/TODO +1 -0
- data/flexus.gemspec +33 -0
- data/lib/flexus/defaults.rb +59 -0
- data/lib/flexus/inflections.rb +232 -0
- data/lib/flexus/rules_collection.rb +38 -0
- data/lib/flexus/version.rb +3 -0
- data/lib/flexus.rb +331 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d4fb081e1ddb588f626630927563e06b07bf29d2
|
4
|
+
data.tar.gz: 9a17ef6e6ec631441073dc6b018617d7677fe9f6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8e17f42debb31c4392d530067560de527d8ee7d842aad65fd4400d5884e168332331fa953993f1f6479bdf759f87a4481a89e8160489e66b5ebeffb7ef4c5778
|
7
|
+
data.tar.gz: 7d2f42bec41028173062ef36fd3dcba84f923f98cd4c283de691024b235ff2ab169f7d5e361a9135f7240c0980c0ba60183e43f75fc450c58b7b8ada738e597c
|
data/.gitignore
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
## MAC OS
|
2
|
+
.DS_Store
|
3
|
+
|
4
|
+
## TEXTMATE
|
5
|
+
*.tmproj
|
6
|
+
tmtags
|
7
|
+
|
8
|
+
## EMACS
|
9
|
+
*~
|
10
|
+
\#*
|
11
|
+
.\#*
|
12
|
+
|
13
|
+
## VIM
|
14
|
+
*.swp
|
15
|
+
|
16
|
+
## Rubinius
|
17
|
+
*.rbc
|
18
|
+
.rbx
|
19
|
+
|
20
|
+
## PROJECT::GENERAL
|
21
|
+
*.gem
|
22
|
+
coverage
|
23
|
+
profiling
|
24
|
+
turbulence
|
25
|
+
rdoc
|
26
|
+
pkg
|
27
|
+
tmp
|
28
|
+
doc
|
29
|
+
log
|
30
|
+
.yardoc
|
31
|
+
measurements
|
32
|
+
|
33
|
+
## BUNDLER
|
34
|
+
.bundle
|
35
|
+
Gemfile.lock
|
36
|
+
|
37
|
+
## PROJECT::SPECIFIC
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
language: ruby
|
2
|
+
cache: bundler
|
3
|
+
before_install: gem install bundler
|
4
|
+
bundler_args: --without yard guard benchmarks
|
5
|
+
script: "bundle exec rake ci:metrics"
|
6
|
+
rvm:
|
7
|
+
- 2.1.8
|
8
|
+
- 2.2.6
|
9
|
+
- 2.3.3
|
10
|
+
- 2.4.0
|
11
|
+
- ruby-head
|
12
|
+
- rbx-3.81
|
13
|
+
notifications:
|
14
|
+
irc:
|
15
|
+
channels:
|
16
|
+
- "irc.freenode.org#rom-rb"
|
17
|
+
on_success: never
|
18
|
+
on_failure: change
|
19
|
+
email:
|
20
|
+
on_success: never
|
21
|
+
on_failure: change
|
22
|
+
matrix:
|
23
|
+
include:
|
24
|
+
- rvm: jruby-19mode
|
25
|
+
env: JRUBY_OPTS="$JRUBY_OPTS --debug"
|
26
|
+
- rvm: jruby-head
|
27
|
+
env: JRUBY_OPTS="$JRUBY_OPTS --debug"
|
28
|
+
- rvm: 2.4.0
|
29
|
+
env: WITH_MUTANT=true
|
30
|
+
script: "bundle exec rake ci" # include mutant
|
31
|
+
allow_failures:
|
32
|
+
- rvm: ruby-head
|
33
|
+
- rvm: rbx-3.81
|
34
|
+
- rvm: jruby-19mode
|
35
|
+
- rvm: jruby-head
|
36
|
+
- env: WITH_MUTANT=true
|
37
|
+
fast_finish: true
|
data/Changelog.md
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
guard :bundler do
|
4
|
+
watch('Gemfile')
|
5
|
+
end
|
6
|
+
|
7
|
+
guard :rspec do
|
8
|
+
# run all specs if the spec_helper or supporting files files are modified
|
9
|
+
watch('spec/spec_helper.rb') { 'spec' }
|
10
|
+
watch(%r{\Aspec/(?:lib|support|shared)/.+\.rb\z}) { 'spec' }
|
11
|
+
|
12
|
+
# run unit specs if associated lib code is modified
|
13
|
+
watch(%r{\Alib/(.+)\.rb\z}) { |m| Dir["spec/unit/#{m[1]}"] }
|
14
|
+
watch("lib/#{File.basename(File.expand_path('../', __FILE__))}.rb") { 'spec' }
|
15
|
+
|
16
|
+
# run a spec if it is modified
|
17
|
+
watch(%r{\Aspec/(?:unit|integration)/.+_spec\.rb\z})
|
18
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) The rails, merb & datamapper team
|
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,78 @@
|
|
1
|
+
flexus
|
2
|
+
========
|
3
|
+
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/flexus.png)][gem]
|
5
|
+
[![Build Status](https://secure.travis-ci.org/Ptico/flexus.png?branch=master)][travis]
|
6
|
+
[![Dependency Status](https://gemnasium.com/badges/github.com/Ptico/flexus.svg)][gemnasium]
|
7
|
+
[![Code Climate](https://codeclimate.com/github/Ptico/flexus.png)][codeclimate]
|
8
|
+
[![Coverage Status](https://coveralls.io/repos/Ptico/flexus/badge.png?branch=master)][coveralls]
|
9
|
+
|
10
|
+
[gem]: https://rubygems.org/gems/flexus
|
11
|
+
[travis]: https://travis-ci.org/Ptico/flexus
|
12
|
+
[gemnasium]: https://gemnasium.com/Ptico/flexus
|
13
|
+
[codeclimate]: https://codeclimate.com/github/Ptico/flexus
|
14
|
+
[coveralls]: https://coveralls.io/r/Ptico/flexus
|
15
|
+
|
16
|
+
This gem is a resurrection of [inflecto](https://github.com/mbj/inflecto)
|
17
|
+
|
18
|
+
Inflecto is a standalone inflector ripped out from [dm-core](https://github.com/datamapper/dm-core)
|
19
|
+
|
20
|
+
The dm-core inflector originated from [extlib](https://github.com/datamapper/extlib)
|
21
|
+
|
22
|
+
The extlib inflecto originated from [active_support](https://github.com/rails/rails)
|
23
|
+
|
24
|
+
Migration from `inflecto`
|
25
|
+
-------------------------
|
26
|
+
|
27
|
+
Remove inflecto and add flexus to your Gemfile.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
gem 'flexus', '1.0.0'
|
31
|
+
```
|
32
|
+
|
33
|
+
At your code, change all `Inflecto` references to `Flexus` and it should work out of the box for flexus version 1.0.0. Please report any issues.
|
34
|
+
|
35
|
+
Flexus will try the best to respect semantic versioning as http://semver.org/
|
36
|
+
So, expect api breaking changes on major version releases.
|
37
|
+
|
38
|
+
We'll make pre release gems for 2.0.0 version. To experiment with that you should add this to your Gemfile.
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
gem 'flexus', '>= 2.0.0.beta'
|
42
|
+
```
|
43
|
+
|
44
|
+
Examples
|
45
|
+
--------
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
require 'flexus'
|
49
|
+
|
50
|
+
Flexus.underscore("CamelCase")
|
51
|
+
# => "camel_case"
|
52
|
+
```
|
53
|
+
|
54
|
+
Credits
|
55
|
+
-------
|
56
|
+
|
57
|
+
The rails, merb & datamapper team
|
58
|
+
|
59
|
+
The original gem was extracted and maintained by Markus Schirp ([mbj](https://github.com/mbj)) with help from [indrekj](https://github.com/indrekj).
|
60
|
+
|
61
|
+
Current maintainers: Andrii Savchenko [Ptico](https://github.com/Ptico) and Abinoam P. Marques Jr. [abinoam](https://github.com/abinoam)
|
62
|
+
|
63
|
+
Contributing
|
64
|
+
-------------
|
65
|
+
|
66
|
+
* If you want your code merged into the mainline, please discuss the proposed changes with me before doing any work on it. This library is still in early development, and the direction it is going may not always be clear. Some features may not be appropriate yet, may need to be deferred until later when the foundation for them is laid, or may be more applicable in a plugin.
|
67
|
+
* Fork the project.
|
68
|
+
* Make your feature addition or bug fix.
|
69
|
+
* Follow this [style guide](https://github.com/dkubb/styleguide).
|
70
|
+
* Add specs for it. This is important so I don't break it in a future version unintentionally. Tests must cover all branches within the code, and code must be fully covered.
|
71
|
+
* Commit, do not mess with Rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
72
|
+
* Run "rake ci". This must pass and not show any regressions in the metrics for the code to be merged.
|
73
|
+
* Send me a pull request. Bonus points for topic branches.
|
74
|
+
|
75
|
+
License
|
76
|
+
-------
|
77
|
+
|
78
|
+
See LICENSE.txt for details
|
data/Rakefile
ADDED
data/flexus.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "flexus/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "flexus"
|
8
|
+
spec.version = Flexus::VERSION
|
9
|
+
spec.authors =
|
10
|
+
[ "The rails, merb & datamapper team",
|
11
|
+
"Markus Schirp",
|
12
|
+
"Andrii Savchenko",
|
13
|
+
"Abinoam P. Marques Jr." ]
|
14
|
+
spec.email = ["andrey@aejis.eu", "abinoam@gmail.com"]
|
15
|
+
spec.description = "Inflector for strings"
|
16
|
+
spec.summary = spec.description
|
17
|
+
spec.homepage = "https://github.com/Ptico/flexus"
|
18
|
+
spec.license = "MIT"
|
19
|
+
|
20
|
+
spec.require_paths = %w[lib]
|
21
|
+
|
22
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
23
|
+
f.match(%r{^(test|spec|features|config)/})
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.extra_rdoc_files = %w[LICENSE.txt README.md TODO]
|
27
|
+
|
28
|
+
spec.required_ruby_version = ">= 2.1"
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler"
|
31
|
+
spec.add_development_dependency "rake"
|
32
|
+
spec.add_development_dependency "rspec"
|
33
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
Flexus.inflections do |inflect|
|
2
|
+
inflect.plural(/\z/, 's')
|
3
|
+
inflect.plural(/s\z/i, 's')
|
4
|
+
inflect.plural(/(ax|test)is\z/i, '\1es')
|
5
|
+
inflect.plural(/(octop|vir)us\z/i, '\1i')
|
6
|
+
inflect.plural(/(octop|vir)i\z/i, '\1i')
|
7
|
+
inflect.plural(/(alias|status)\z/i, '\1es')
|
8
|
+
inflect.plural(/(bu)s\z/i, '\1ses')
|
9
|
+
inflect.plural(/(buffal|tomat)o\z/i, '\1oes')
|
10
|
+
inflect.plural(/([ti])um\z/i, '\1a')
|
11
|
+
inflect.plural(/([ti])a\z/i, '\1a')
|
12
|
+
inflect.plural(/sis\z/i, 'ses')
|
13
|
+
inflect.plural(/(?:([^f])fe|([lr])f)\z/i, '\1\2ves')
|
14
|
+
inflect.plural(/(hive)\z/i, '\1s')
|
15
|
+
inflect.plural(/([^aeiouy]|qu)y\z/i, '\1ies')
|
16
|
+
inflect.plural(/(x|ch|ss|sh)\z/i, '\1es')
|
17
|
+
inflect.plural(/(matr|vert|ind)(?:ix|ex)\z/i, '\1ices')
|
18
|
+
inflect.plural(/([m|l])ouse\z/i, '\1ice')
|
19
|
+
inflect.plural(/([m|l])ice\z/i, '\1ice')
|
20
|
+
inflect.plural(/^(ox)\z/i, '\1en')
|
21
|
+
inflect.plural(/^(oxen)\z/i, '\1')
|
22
|
+
inflect.plural(/(quiz)\z/i, '\1zes')
|
23
|
+
|
24
|
+
inflect.singular(/s\z/i, '')
|
25
|
+
inflect.singular(/(n)ews\z/i, '\1ews')
|
26
|
+
inflect.singular(/([ti])a\z/i, '\1um')
|
27
|
+
inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses\z/i, '\1\2sis')
|
28
|
+
inflect.singular(/(^analy)ses\z/i, '\1sis')
|
29
|
+
inflect.singular(/([^f])ves\z/i, '\1fe')
|
30
|
+
inflect.singular(/(hive)s\z/i, '\1')
|
31
|
+
inflect.singular(/(tive)s\z/i, '\1')
|
32
|
+
inflect.singular(/([lr])ves\z/i, '\1f')
|
33
|
+
inflect.singular(/([^aeiouy]|qu)ies\z/i, '\1y')
|
34
|
+
inflect.singular(/(s)eries\z/i, '\1eries')
|
35
|
+
inflect.singular(/(m)ovies\z/i, '\1ovie')
|
36
|
+
inflect.singular(/(ss)\z/i, '\1')
|
37
|
+
inflect.singular(/(x|ch|ss|sh)es\z/i, '\1')
|
38
|
+
inflect.singular(/([m|l])ice\z/i, '\1ouse')
|
39
|
+
inflect.singular(/(bus)es\z/i, '\1')
|
40
|
+
inflect.singular(/(o)es\z/i, '\1')
|
41
|
+
inflect.singular(/(shoe)s\z/i, '\1')
|
42
|
+
inflect.singular(/(cris|ax|test)es\z/i, '\1is')
|
43
|
+
inflect.singular(/(octop|vir)i\z/i, '\1us')
|
44
|
+
inflect.singular(/(alias|status)es\z/i, '\1')
|
45
|
+
inflect.singular(/^(ox)en/i, '\1')
|
46
|
+
inflect.singular(/(vert|ind)ices\z/i, '\1ex')
|
47
|
+
inflect.singular(/(matr)ices\z/i, '\1ix')
|
48
|
+
inflect.singular(/(quiz)zes\z/i, '\1')
|
49
|
+
inflect.singular(/(database)s\z/i, '\1')
|
50
|
+
|
51
|
+
inflect.irregular('person', 'people')
|
52
|
+
inflect.irregular('man', 'men')
|
53
|
+
inflect.irregular('child', 'children')
|
54
|
+
inflect.irregular('sex', 'sexes')
|
55
|
+
inflect.irregular('move', 'moves')
|
56
|
+
inflect.irregular('cow', 'cows')
|
57
|
+
|
58
|
+
inflect.uncountable(%w(hovercraft moose milk rain Swiss grass equipment information rice money species series fish sheep jeans))
|
59
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
module Flexus
|
2
|
+
# A singleton instance of this class is yielded by Flexus.inflections, which can then be used to specify additional
|
3
|
+
# inflection rules. Examples:
|
4
|
+
#
|
5
|
+
# Flexus.inflections do |inflect|
|
6
|
+
# inflect.plural /^(ox)$/i, '\1\2en'
|
7
|
+
# inflect.singular /^(ox)en/i, '\1'
|
8
|
+
#
|
9
|
+
# inflect.irregular 'octopus', 'octopi'
|
10
|
+
#
|
11
|
+
# inflect.uncountable "equipment"
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
|
15
|
+
# pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
|
16
|
+
# already have been loaded.
|
17
|
+
#
|
18
|
+
class Inflections
|
19
|
+
|
20
|
+
# Return instance
|
21
|
+
#
|
22
|
+
# @return [Inflections]
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
#
|
26
|
+
def self.instance
|
27
|
+
@__instance__ ||= new
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return plurals
|
31
|
+
#
|
32
|
+
# @return [Array]
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
#
|
36
|
+
attr_reader :plurals
|
37
|
+
|
38
|
+
# Return singulars
|
39
|
+
#
|
40
|
+
# @return [Array]
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
#
|
44
|
+
attr_reader :singulars
|
45
|
+
|
46
|
+
# Return uncountables
|
47
|
+
#
|
48
|
+
# @return [Array]
|
49
|
+
#
|
50
|
+
# @api private
|
51
|
+
#
|
52
|
+
attr_reader :uncountables
|
53
|
+
|
54
|
+
# Return humans
|
55
|
+
#
|
56
|
+
# @return [Array]
|
57
|
+
#
|
58
|
+
# @api private
|
59
|
+
#
|
60
|
+
#
|
61
|
+
attr_reader :humans
|
62
|
+
|
63
|
+
# Initialize object
|
64
|
+
#
|
65
|
+
# @return [undefined]
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
#
|
69
|
+
def initialize
|
70
|
+
@plurals = RulesCollection.new
|
71
|
+
@singulars = RulesCollection.new
|
72
|
+
@humans = RulesCollection.new
|
73
|
+
@uncountables = Set[]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Add a new plural role
|
77
|
+
#
|
78
|
+
# Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
|
79
|
+
# The replacement should always be a string that may include references to the matched data from the rule.
|
80
|
+
#
|
81
|
+
# @param [String, Regexp] rule
|
82
|
+
# @param [String, Regexp] replacement
|
83
|
+
#
|
84
|
+
# @return [self]
|
85
|
+
#
|
86
|
+
# @api private
|
87
|
+
#
|
88
|
+
def plural(rule, replacement)
|
89
|
+
rule(rule, replacement, plurals)
|
90
|
+
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
# Add a new singular rule
|
95
|
+
#
|
96
|
+
# Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
|
97
|
+
# The replacement should always be a string that may include references to the matched data from the rule.
|
98
|
+
#
|
99
|
+
# @param [String, Regexp] rule
|
100
|
+
# @param [String, Regexp] replacement
|
101
|
+
#
|
102
|
+
# @return [self]
|
103
|
+
#
|
104
|
+
# @api private
|
105
|
+
#
|
106
|
+
def singular(rule, replacement)
|
107
|
+
rule(rule, replacement, singulars)
|
108
|
+
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
# Add a new irregular pluralization
|
113
|
+
#
|
114
|
+
# Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
|
115
|
+
# for strings, not regular expressions. You simply pass the irregular in singular and plural form.
|
116
|
+
#
|
117
|
+
# @example
|
118
|
+
#
|
119
|
+
# Flexus.irregular('octopus', 'octopi')
|
120
|
+
# Flexus.irregular('person', 'people')
|
121
|
+
#
|
122
|
+
# @param [String] singular
|
123
|
+
# @param [String] plural
|
124
|
+
#
|
125
|
+
# @return [self]
|
126
|
+
#
|
127
|
+
# @api private
|
128
|
+
#
|
129
|
+
def irregular(singular, plural)
|
130
|
+
uncountables.delete(singular)
|
131
|
+
uncountables.delete(plural)
|
132
|
+
|
133
|
+
add_irregular(singular, plural, plurals)
|
134
|
+
add_irregular(plural, singular, singulars)
|
135
|
+
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
# Add uncountable words
|
140
|
+
#
|
141
|
+
# Uncountable will not be inflected
|
142
|
+
#
|
143
|
+
# @example
|
144
|
+
#
|
145
|
+
# Flexus.uncountable "money"
|
146
|
+
# Flexus.uncountable "money", "information"
|
147
|
+
# Flexus.uncountable %w( money information rice )
|
148
|
+
#
|
149
|
+
# @param [Enumerable<String>] words
|
150
|
+
#
|
151
|
+
# @return [self]
|
152
|
+
#
|
153
|
+
# @api private
|
154
|
+
#
|
155
|
+
def uncountable(*words)
|
156
|
+
uncountables.merge(words.flatten)
|
157
|
+
|
158
|
+
self
|
159
|
+
end
|
160
|
+
|
161
|
+
# Add humanize rule
|
162
|
+
#
|
163
|
+
# Specifies a humanized form of a string by a regular expression rule or by a string mapping.
|
164
|
+
# When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
|
165
|
+
# When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
|
166
|
+
#
|
167
|
+
# @example
|
168
|
+
# Flexus.human(/_cnt$/i, '\1_count')
|
169
|
+
# Flexus.human("legacy_col_person_name", "Name")
|
170
|
+
#
|
171
|
+
# @param [String, Regexp] rule
|
172
|
+
# @param [String, Regexp] replacement
|
173
|
+
#
|
174
|
+
# @api private
|
175
|
+
#
|
176
|
+
# @return [self]
|
177
|
+
#
|
178
|
+
def human(rule, replacement)
|
179
|
+
humans.insert(0, [rule, replacement])
|
180
|
+
self
|
181
|
+
end
|
182
|
+
|
183
|
+
# Clear all inflection rules
|
184
|
+
#
|
185
|
+
# @example
|
186
|
+
#
|
187
|
+
# Flexus.clear
|
188
|
+
#
|
189
|
+
# @return [self]
|
190
|
+
#
|
191
|
+
# @api private
|
192
|
+
#
|
193
|
+
def clear
|
194
|
+
initialize
|
195
|
+
self
|
196
|
+
end
|
197
|
+
|
198
|
+
private
|
199
|
+
|
200
|
+
# Add irregular inflection
|
201
|
+
#
|
202
|
+
# @param [String] rule
|
203
|
+
# @param [String] replacement
|
204
|
+
#
|
205
|
+
# @return [undefined]
|
206
|
+
#
|
207
|
+
# @api private
|
208
|
+
#
|
209
|
+
def add_irregular(rule, replacement, target)
|
210
|
+
head, *tail = rule.chars.to_a
|
211
|
+
rule(/(#{head})#{tail.join}\z/i, '\1' + replacement[1..-1], target)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Add a new rule
|
215
|
+
#
|
216
|
+
# @param [String, Regexp] rule
|
217
|
+
# @param [String, Regexp] replacement
|
218
|
+
# @param [Array] target
|
219
|
+
#
|
220
|
+
# @return [undefined]
|
221
|
+
#
|
222
|
+
# @api private
|
223
|
+
#
|
224
|
+
def rule(rule, replacement, target)
|
225
|
+
uncountables.delete(rule)
|
226
|
+
uncountables.delete(replacement)
|
227
|
+
|
228
|
+
target.insert(0, [rule, replacement])
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Flexus
|
2
|
+
# Wraps inflections array
|
3
|
+
#
|
4
|
+
class RulesCollection
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegators :@collection,
|
8
|
+
:each, :insert, :empty?,
|
9
|
+
:size, :last, :include?
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
@collection = Array.new(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Applies first found rule to given word
|
16
|
+
#
|
17
|
+
# @param [String] word
|
18
|
+
#
|
19
|
+
# @return [String]
|
20
|
+
# modified word
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
#
|
24
|
+
def apply_to(word)
|
25
|
+
result = word.dup
|
26
|
+
each { |rule, replacement| break if result.gsub!(rule, replacement) }
|
27
|
+
result
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(other)
|
31
|
+
@collection == other.to_a
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_a
|
35
|
+
@collection.dup
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/flexus.rb
ADDED
@@ -0,0 +1,331 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
# The Flexus transforms words from singular to plural, class names to table names, modularized class names to ones without,
|
4
|
+
# and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
|
5
|
+
# in inflections.rb.
|
6
|
+
#
|
7
|
+
# The Rails core team has stated patches for the inflections library will not be accepted
|
8
|
+
# in order to avoid breaking legacy applications which may be relying on errant inflections.
|
9
|
+
# If you discover an incorrect inflection and require it for your application, you'll need
|
10
|
+
# to correct it yourself (explained below).
|
11
|
+
module Flexus
|
12
|
+
|
13
|
+
# Convert input to UpperCamelCase
|
14
|
+
#
|
15
|
+
# Will also convert '/' to '::' which is useful for converting paths to namespaces.
|
16
|
+
#
|
17
|
+
# @param [String] input
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# Flexus.camelize("data_mapper") # => "DataMapper"
|
21
|
+
# Flexus.camelize("data_mapper/errors") # => "DataMapper::Errors"
|
22
|
+
#
|
23
|
+
# @return [String]
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
#
|
27
|
+
def self.camelize(input)
|
28
|
+
input.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:\A|_)(.)/) { $1.upcase }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Convert input to underscored, lowercase string
|
32
|
+
#
|
33
|
+
# Changes '::' to '/' to convert namespaces to paths.
|
34
|
+
#
|
35
|
+
# @param [String] input
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# Flexus.underscore("DataMapper") # => "data_mapper"
|
39
|
+
# Flexus.underscore("DataMapper::Errors") # => "data_mapper/errors"
|
40
|
+
#
|
41
|
+
# @return [String]
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
#
|
45
|
+
def self.underscore(input)
|
46
|
+
word = input.gsub(/::/, '/')
|
47
|
+
underscorize(word)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Convert input underscores to dashes
|
51
|
+
#
|
52
|
+
# @param [String] input
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
# Flexus.dasherize("foo_bar") # => "foo-bar"
|
56
|
+
#
|
57
|
+
# @return [String]
|
58
|
+
#
|
59
|
+
# @api public
|
60
|
+
#
|
61
|
+
def self.dasherize(input)
|
62
|
+
input.tr('_', '-')
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return unscoped constant name
|
66
|
+
#
|
67
|
+
# @param [String] input
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
#
|
71
|
+
# Flexus.demodulize("DataMapper::Error") # => "Error"
|
72
|
+
# Flexus.demodulize("DataMapper") # => "DataMapper"
|
73
|
+
#
|
74
|
+
# @return [String]
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
#
|
78
|
+
def self.demodulize(input)
|
79
|
+
input.split('::').last
|
80
|
+
end
|
81
|
+
|
82
|
+
# Creates a foreign key name
|
83
|
+
#
|
84
|
+
# @param [String] input
|
85
|
+
#
|
86
|
+
# @example
|
87
|
+
#
|
88
|
+
# Flexus.foreign_key("Message") => "message_id"
|
89
|
+
#
|
90
|
+
# @return [String]
|
91
|
+
#
|
92
|
+
# @api public
|
93
|
+
#
|
94
|
+
def self.foreign_key(input)
|
95
|
+
"#{underscorize(demodulize(input))}_id"
|
96
|
+
end
|
97
|
+
|
98
|
+
# Find a constant with the name specified in the argument string
|
99
|
+
#
|
100
|
+
# The name is assumed to be the one of a top-level constant, constant scope of caller is ignored
|
101
|
+
#
|
102
|
+
# @param [String] input
|
103
|
+
#
|
104
|
+
# @example
|
105
|
+
#
|
106
|
+
# Flexus.constantize("Module") # => Module
|
107
|
+
# Flexus.constantize("DataMapper::Error") # => DataMapper::Error
|
108
|
+
#
|
109
|
+
# @return [Class, Module]
|
110
|
+
#
|
111
|
+
# @api public
|
112
|
+
#
|
113
|
+
def self.constantize(input)
|
114
|
+
names = input.split('::')
|
115
|
+
names.shift if names.first.empty?
|
116
|
+
|
117
|
+
names.inject(Object) do |constant, name|
|
118
|
+
if constant.const_defined?(name, false)
|
119
|
+
constant.const_get(name)
|
120
|
+
else
|
121
|
+
constant.const_missing(name)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
ORDINALIZE_TH = (4..16).to_set.freeze
|
127
|
+
|
128
|
+
# Convert a number into an ordinal string
|
129
|
+
#
|
130
|
+
# @param [Fixnum] number
|
131
|
+
#
|
132
|
+
# @example
|
133
|
+
#
|
134
|
+
# ordinalize(1) # => "1st"
|
135
|
+
# ordinalize(2) # => "2nd"
|
136
|
+
# ordinalize(1002) # => "1002nd"
|
137
|
+
# ordinalize(1003) # => "1003rd"
|
138
|
+
#
|
139
|
+
# @return [String]
|
140
|
+
#
|
141
|
+
# @api public
|
142
|
+
#
|
143
|
+
def self.ordinalize(number)
|
144
|
+
abs_value = number.abs
|
145
|
+
|
146
|
+
if ORDINALIZE_TH.include?(abs_value % 100)
|
147
|
+
"#{number}th"
|
148
|
+
else
|
149
|
+
case abs_value % 10
|
150
|
+
when 1; "#{number}st"
|
151
|
+
when 2; "#{number}nd"
|
152
|
+
when 3; "#{number}rd"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Yields a singleton instance of Flexus::Inflections
|
158
|
+
#
|
159
|
+
# @example
|
160
|
+
#
|
161
|
+
# Flexus.inflections do |inflect|
|
162
|
+
# inflect.uncountable "rails"
|
163
|
+
# end
|
164
|
+
#
|
165
|
+
# @return [Flexus::Inflections]
|
166
|
+
#
|
167
|
+
# @api public
|
168
|
+
#
|
169
|
+
def self.inflections
|
170
|
+
instance = Inflections.instance
|
171
|
+
block_given? ? yield(instance) : instance
|
172
|
+
end
|
173
|
+
|
174
|
+
# Convert input word string to plural
|
175
|
+
#
|
176
|
+
# @param [String] word
|
177
|
+
#
|
178
|
+
# @example
|
179
|
+
#
|
180
|
+
# Flexus.pluralize("post") # => "posts"
|
181
|
+
# Flexus.pluralize("octopus") # => "octopi"
|
182
|
+
# Flexus.pluralize("sheep") # => "sheep"
|
183
|
+
# Flexus.pluralize("words") # => "words"
|
184
|
+
# Flexus.pluralize("CamelOctopus") # => "CamelOctopi"
|
185
|
+
#
|
186
|
+
# @return [String]
|
187
|
+
#
|
188
|
+
# @api public
|
189
|
+
#
|
190
|
+
def self.pluralize(word)
|
191
|
+
return word if uncountable?(word)
|
192
|
+
inflections.plurals.apply_to(word)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Convert word to singular
|
196
|
+
#
|
197
|
+
# @param [String] word
|
198
|
+
#
|
199
|
+
# @example
|
200
|
+
#
|
201
|
+
# Flexus.singularize("posts") # => "post"
|
202
|
+
# Flexus.singularize("octopi") # => "octopus"
|
203
|
+
# Flexus.singularize("sheep") # => "sheep"
|
204
|
+
# Flexus.singularize("word") # => "word"
|
205
|
+
# Flexus.singularize("CamelOctopi") # => "CamelOctopus"
|
206
|
+
#
|
207
|
+
# @return [String]
|
208
|
+
#
|
209
|
+
# @api public
|
210
|
+
#
|
211
|
+
def self.singularize(word)
|
212
|
+
return word if uncountable?(word)
|
213
|
+
inflections.singulars.apply_to(word)
|
214
|
+
end
|
215
|
+
|
216
|
+
# Humanize string
|
217
|
+
#
|
218
|
+
# @param [String] input
|
219
|
+
#
|
220
|
+
# capitalizes the first word and turns underscores into spaces and strips a # trailing "_id", if any.
|
221
|
+
# Like +titleize+, this is meant for creating pretty output.
|
222
|
+
#
|
223
|
+
# @example
|
224
|
+
#
|
225
|
+
# Flexus.humanize("employee_salary") # => "Employee salary"
|
226
|
+
# Flexus.humanize("author_id") # => "Author"
|
227
|
+
#
|
228
|
+
# @return [String]
|
229
|
+
#
|
230
|
+
# @api public
|
231
|
+
#
|
232
|
+
def self.humanize(input)
|
233
|
+
result = inflections.humans.apply_to(input)
|
234
|
+
result.gsub!(/_id\z/, "")
|
235
|
+
result.tr!('_', " ")
|
236
|
+
result.capitalize!
|
237
|
+
result
|
238
|
+
end
|
239
|
+
|
240
|
+
# Tabelize input string
|
241
|
+
#
|
242
|
+
# @param [String] input
|
243
|
+
#
|
244
|
+
# Create the name of a table like Rails does for models to table names.
|
245
|
+
# This method # uses the +pluralize+ method on the last word in the string.
|
246
|
+
#
|
247
|
+
# @example
|
248
|
+
#
|
249
|
+
# Flexus.tableize("RawScaledScorer") # => "raw_scaled_scorers"
|
250
|
+
# Flexus.tableize("egg_and_ham") # => "egg_and_hams"
|
251
|
+
# Flexus.tableize("fancyCategory") # => "fancy_categories"
|
252
|
+
#
|
253
|
+
# @return [String]
|
254
|
+
#
|
255
|
+
# @api public
|
256
|
+
#
|
257
|
+
def self.tableize(input)
|
258
|
+
word = input.gsub(/::/, '_')
|
259
|
+
pluralize(underscorize(word))
|
260
|
+
end
|
261
|
+
|
262
|
+
# Classify input
|
263
|
+
#
|
264
|
+
# Create a class name from a plural table name like Rails does for table names to models.
|
265
|
+
# Note that this returns a string and not a Class.
|
266
|
+
#
|
267
|
+
# To convert to an actual class # follow +classify+ with +constantize+.
|
268
|
+
#
|
269
|
+
# @examples:
|
270
|
+
#
|
271
|
+
# Flexus.classify("egg_and_hams") # => "EggAndHam"
|
272
|
+
# Flexus.classify("posts") # => "Post"
|
273
|
+
#
|
274
|
+
# # Singular names are not handled correctly:
|
275
|
+
# Flexus.classify("business") # => "Busines"
|
276
|
+
#
|
277
|
+
# @return [String]
|
278
|
+
#
|
279
|
+
# @api public
|
280
|
+
#
|
281
|
+
def self.classify(table_name)
|
282
|
+
# strip out any leading schema name
|
283
|
+
camelize(singularize(table_name.sub(/.*\./, '')))
|
284
|
+
end
|
285
|
+
|
286
|
+
# Test if word is uncountable
|
287
|
+
#
|
288
|
+
# @example
|
289
|
+
#
|
290
|
+
# Flexus.uncountable?('rice') #=> true
|
291
|
+
# Flexus.uncountable?('apple') #=> false
|
292
|
+
#
|
293
|
+
# @param [String] word
|
294
|
+
#
|
295
|
+
# @return [Boolean]
|
296
|
+
# true, if word is uncountable
|
297
|
+
#
|
298
|
+
# @api public
|
299
|
+
#
|
300
|
+
def self.uncountable?(word)
|
301
|
+
word.empty? || inflections.uncountables.include?(word.downcase)
|
302
|
+
end
|
303
|
+
|
304
|
+
# Convert input to underscored, lowercase string
|
305
|
+
#
|
306
|
+
# Contains main logic for .underscore and .tableize
|
307
|
+
# Does nothing with '::' divider
|
308
|
+
#
|
309
|
+
# @param [String] input
|
310
|
+
#
|
311
|
+
# @example
|
312
|
+
# Flexus.underscorize("DataMapper") # => "data_mapper"
|
313
|
+
# Flexus.underscorize("DataMapper::Errors") # => "data_mapper::errors"
|
314
|
+
#
|
315
|
+
# @return [String]
|
316
|
+
#
|
317
|
+
# @api private
|
318
|
+
#
|
319
|
+
def self.underscorize(word)
|
320
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
321
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
322
|
+
word.tr!('-', '_')
|
323
|
+
word.downcase!
|
324
|
+
word
|
325
|
+
end
|
326
|
+
private_class_method :underscorize
|
327
|
+
end
|
328
|
+
|
329
|
+
require 'flexus/rules_collection'
|
330
|
+
require 'flexus/inflections'
|
331
|
+
require 'flexus/defaults'
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: flexus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.beta.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- The rails, merb & datamapper team
|
8
|
+
- Markus Schirp
|
9
|
+
- Andrii Savchenko
|
10
|
+
- Abinoam P. Marques Jr.
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2017-11-03 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: bundler
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: rspec
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
type: :development
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
description: Inflector for strings
|
59
|
+
email:
|
60
|
+
- andrey@aejis.eu
|
61
|
+
- abinoam@gmail.com
|
62
|
+
executables: []
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files:
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- TODO
|
68
|
+
files:
|
69
|
+
- ".gitignore"
|
70
|
+
- ".rspec"
|
71
|
+
- ".travis.yml"
|
72
|
+
- Changelog.md
|
73
|
+
- Gemfile
|
74
|
+
- Guardfile
|
75
|
+
- LICENSE.txt
|
76
|
+
- README.md
|
77
|
+
- Rakefile
|
78
|
+
- TODO
|
79
|
+
- flexus.gemspec
|
80
|
+
- lib/flexus.rb
|
81
|
+
- lib/flexus/defaults.rb
|
82
|
+
- lib/flexus/inflections.rb
|
83
|
+
- lib/flexus/rules_collection.rb
|
84
|
+
- lib/flexus/version.rb
|
85
|
+
homepage: https://github.com/Ptico/flexus
|
86
|
+
licenses:
|
87
|
+
- MIT
|
88
|
+
metadata: {}
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '2.1'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 1.3.1
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 2.6.8
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: Inflector for strings
|
109
|
+
test_files: []
|