strings-inflection 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,8 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ FileList["tasks/**/*.rake"].each(&method(:import))
4
+
5
+ desc "Run all specs"
6
+ task ci: %w[ spec ]
7
+
8
+ task default: :spec
@@ -0,0 +1 @@
1
+ require_relative "strings/inflection"
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+ require_relative "inflection/combined_noun"
3
+ require_relative "inflection/configuration"
4
+ require_relative "inflection/noun"
5
+ require_relative "inflection/parser"
6
+ require_relative "inflection/verb"
7
+ require_relative "inflection/version"
8
+
9
+ module Strings
10
+ module Inflection
11
+ # Error raised by this inflector
12
+ class Error < StandardError; end
13
+
14
+ # Create a noun object
15
+ #
16
+ # @api public
17
+ def Noun(word)
18
+ Noun[word]
19
+ end
20
+ module_function :Noun
21
+
22
+ # Create a verb object
23
+ #
24
+ # @api public
25
+ def Verb(word)
26
+ Verb[word]
27
+ end
28
+ module_function :Verb
29
+
30
+ # A configuration object
31
+ #
32
+ # @api public
33
+ def configuration
34
+ @configuration ||= Configuration.new
35
+ end
36
+ module_function :configuration
37
+
38
+ # Reset configuration and remove loaded inflections
39
+ #
40
+ # @api public
41
+ def reset(scope = :all)
42
+ configuration.reset(scope)
43
+ end
44
+ module_function :reset
45
+
46
+ # Configure custom inflections
47
+ #
48
+ # @example
49
+ # configure do |config|
50
+ # config.plural "index", "indexes"
51
+ # config.singular "axes", "ax"
52
+ # end
53
+ #
54
+ # @api public
55
+ def configure
56
+ if block_given?
57
+ yield configuration
58
+ else
59
+ configuration
60
+ end
61
+ end
62
+ module_function :configure
63
+
64
+ # Check if word is uncountable
65
+ #
66
+ # @param [String] word
67
+ # the word to check
68
+ #
69
+ # @return [Boolean]
70
+ #
71
+ # @api private
72
+ def uncountable?(word)
73
+ Noun[word].uncountable?
74
+ end
75
+ module_function :uncountable?
76
+
77
+ # Inflect a noun into a correct form
78
+ #
79
+ # @example
80
+ # Strings::Inflection.inflect("error", 3)
81
+ # # => "errors"
82
+ #
83
+ # @param [String] word
84
+ # the word to inflect
85
+ # @param [Integer] count
86
+ # the count of items
87
+ #
88
+ # @api public
89
+ def inflect(word, count, term: :noun)
90
+ template = !!(word =~ /\{\{([^\}]+)\}\}/)
91
+
92
+ Parser.parse(template ? word : "{{#{term.upcase[0]}:#{word}}}", count)
93
+ end
94
+ module_function :inflect
95
+
96
+ # Inflect a pural word to its singular form
97
+ #
98
+ # @example
99
+ # Strings::Inflection.singularize("errors")
100
+ # # => "error"
101
+ #
102
+ # @param [String] word
103
+ # the noun to inflect to singular form
104
+ #
105
+ # @api public
106
+ def singularize(word, term: :noun)
107
+ case term
108
+ when :noun, :n
109
+ Noun[word].singular
110
+ when :verb, :v
111
+ Verb[word].singular
112
+ end
113
+ end
114
+ module_function :singularize
115
+
116
+ # Inflect a word to its plural form
117
+ #
118
+ # @example
119
+ # Strings::Inflection.pluralize("error")
120
+ # # => "errors"
121
+ #
122
+ # @param [String] word
123
+ # noun to inflect to plural form
124
+ #
125
+ # @api public
126
+ def pluralize(word, term: :noun)
127
+ case term
128
+ when :noun, :n
129
+ Noun[word].plural
130
+ when :verb, :v
131
+ Verb[word].plural
132
+ end
133
+ end
134
+ module_function :pluralize
135
+
136
+ # Check if noun is in singular form
137
+ #
138
+ # @example
139
+ # Strings::Inflection.singular?("error")
140
+ # # => true
141
+ #
142
+ # @return [Boolean]
143
+ #
144
+ # @api public
145
+ def singular?(word, term: :noun)
146
+ case term.to_sym
147
+ when :noun, :n
148
+ Noun[word].singular?
149
+ when :verb, :v
150
+ Verb[word].singular?
151
+ else
152
+ raise Error, "Unknown option '#{term}' as a term"
153
+ end
154
+ end
155
+ module_function :singular?
156
+
157
+ # Check if noun is in plural form
158
+ #
159
+ # @example
160
+ # Strings::Inflection.plural?("errors")
161
+ # # => true
162
+ #
163
+ # @return [Boolean]
164
+ #
165
+ # @api public
166
+ def plural?(word, term: :noun)
167
+ case term.to_sym
168
+ when :noun, :n
169
+ Noun[word].plural?
170
+ when :verb, :v
171
+ Verb[word].plural?
172
+ else
173
+ raise Error, "Unknown option '#{term}' as a term"
174
+ end
175
+ end
176
+ module_function :plural?
177
+
178
+
179
+ # Join a list of words into a single sentence
180
+ #
181
+ # @example
182
+ # Strings::Inflection.join_words("one", "two", "three")
183
+ # # => "one, two and three"
184
+ #
185
+ # @param [Array[String]] words
186
+ # the words to join
187
+ # @param [String] separator
188
+ # the character to use to join words, defaults to `,`
189
+ # @param [String] final_separator
190
+ # the separator used before joining the last word
191
+ # @param [String] conjunctive
192
+ # the word used for combining the last word with the rest
193
+ #
194
+ # @return [String]
195
+ #
196
+ # @api public
197
+ def join_words(*words, **options)
198
+ CombinedNoun.new(words).join_words(**options)
199
+ end
200
+ module_function :join_words
201
+ end # Inflection
202
+ end # Strings
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strings
4
+ module Inflection
5
+ class CombinedNoun
6
+ WHITESPACE_REGEX = /(\s)\s+/.freeze
7
+
8
+ attr_reader :words
9
+
10
+ # Create a combined noun
11
+ #
12
+ # @api private
13
+ def initialize(words = [])
14
+ @words = words
15
+ end
16
+
17
+ # Combined with another noun
18
+ #
19
+ # @param [String] word
20
+ # the word to combine with
21
+ #
22
+ # @api public
23
+ def +(word)
24
+ CombinedNoun.new(@words + [word])
25
+ end
26
+
27
+ # Join a list of words into a single sentence
28
+ #
29
+ # @example
30
+ # CombinedNoun(["one", "two", "three"]).join_words
31
+ # # => "one, two and three"
32
+ #
33
+ # @param [Array[String]] words
34
+ # the words to join
35
+ # @param [String] separator
36
+ # the character to use to join words, defaults to `,`
37
+ # @param [String] final_separator
38
+ # the separator used before joining the last word
39
+ # @param [String] conjunctive
40
+ # the word used for combining the last word with the rest
41
+ #
42
+ # @return [String]
43
+ #
44
+ # @api public
45
+ def join_words(separator: ", ", conjunctive: "and", final_separator: nil)
46
+ oxford_comma = final_separator || separator
47
+
48
+ case words.length
49
+ when 0
50
+ ""
51
+ when 1, 2
52
+ words.join(" #{conjunctive} ").gsub(WHITESPACE_REGEX, "\\1")
53
+ else
54
+ ((words[0...-1]).join(separator.to_s) +
55
+ "#{oxford_comma} #{conjunctive} " + words[-1])
56
+ .gsub(WHITESPACE_REGEX, "\\1").gsub(/\s*(,)/, "\\1")
57
+ end
58
+ end
59
+
60
+ # The combined words
61
+ #
62
+ # @return [Array[String]]
63
+ #
64
+ # @api public
65
+ def to_ary
66
+ @words.to_ary
67
+ end
68
+ end # CombinedNoun
69
+ end # Inflection
70
+ end # Strings
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strings
4
+ module Inflection
5
+ class Configuration
6
+
7
+ attr_reader :plurals, :singulars, :uncountables
8
+
9
+ # Initialize a configuration
10
+ #
11
+ # @api private
12
+ def initialize
13
+ reset
14
+ end
15
+
16
+ # Add a singular rule form for a term. By default noun.
17
+ #
18
+ # @example
19
+ # singular "axes", "ax"
20
+ #
21
+ # @param [String,Regex] rule
22
+ # @param [String] replacement
23
+ #
24
+ # @api public
25
+ def singular(rule, replacement, term: :noun)
26
+ @singulars[term] << [Regexp.new(rule, "i"), replacement]
27
+ end
28
+
29
+ # Add a plural rule form for a term. By default noun.
30
+ #
31
+ # @example
32
+ # plural "index", "indexes"
33
+ #
34
+ # @param [String,Regex] rule
35
+ # @param [String] replacement
36
+ #
37
+ # @api public
38
+ def plural(rule, replacement, term: :noun)
39
+ @plurals[term] << [Regexp.new(rule, "i"), replacement]
40
+ end
41
+
42
+ # Add a rule that provides transformation for both singular and
43
+ # plural inflections.
44
+ #
45
+ # @example
46
+ # rule "ax", "axes"
47
+ #
48
+ # @api public
49
+ def rule(sing, plur, term: :noun)
50
+ plural(sing, plur, term: term)
51
+ singular(plur, sing, term: term)
52
+ end
53
+
54
+ # Add an uncountable word. By default noun.
55
+ #
56
+ # @api public
57
+ def uncountable(word, term: :noun)
58
+ @uncountables[term] << word
59
+ end
60
+
61
+ # Reset configuration and remove loaded inflections. By default
62
+ # all scopes are reset. The available scopes are: :plurals,
63
+ # :singulars, :uncountable
64
+ #
65
+ # @example
66
+ # reset(:all)
67
+ # reset(:plurals)
68
+ #
69
+ # @api public
70
+ def reset(scope = :all)
71
+ case scope
72
+ when :all
73
+ %i[singulars plurals uncountables].map {|s| clear(s) }
74
+ else
75
+ clear(scope)
76
+ end
77
+ end
78
+
79
+ def clear(name)
80
+ instance_variable_set "@#{name}", { noun: [], verb: [] }
81
+ end
82
+ end # Configuration
83
+ end # Inflection
84
+ end # Strings
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strings
4
+ module Inflection
5
+ module Extensions
6
+ refine String do
7
+ def inflect(*args, **options)
8
+ Strings::Inflection.inflect(self, *args, **options)
9
+ end
10
+
11
+ def singularize(**options)
12
+ Strings::Inflection.singularize(self, **options)
13
+ end
14
+
15
+ def pluralize(**options)
16
+ Strings::Inflection.pluralize(self, **options)
17
+ end
18
+
19
+ def singular?(**options)
20
+ Strings::Inflection.singular?(self, **options)
21
+ end
22
+
23
+ def plural?(**options)
24
+ Strings::Inflection.plural?(self, **options)
25
+ end
26
+
27
+ def join_words(*words, **options)
28
+ Strings::Inflection.join_words(self, *words, **options)
29
+ end
30
+ end
31
+ end # Extensions
32
+ end # Inflection
33
+ end # Strings
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "combined_noun"
4
+ require_relative "term"
5
+ require_relative "nouns"
6
+
7
+ module Strings
8
+ module Inflection
9
+ class Noun < Term
10
+ # Check if word is uncountable
11
+ #
12
+ # @param [String] word
13
+ # the word to check
14
+ #
15
+ # @return [Boolean]
16
+ #
17
+ # @api private
18
+ def uncountable?
19
+ Inflection.configuration.uncountables[:noun].include?(word.downcase) ||
20
+ Nouns.uncountable.include?(word.downcase)
21
+ end
22
+
23
+ # Inflect a word to its singular form
24
+ #
25
+ # @example
26
+ # Strings::Inflection::Noun.new("errors").singular
27
+ # # => "error"
28
+ #
29
+ # @param [String] word
30
+ # the noun to inflect to singular form
31
+ #
32
+ # @api public
33
+ def singular
34
+ return word if word.to_s.empty?
35
+
36
+ find_match(Inflection.configuration.singulars[:noun]) ||
37
+ (uncountable? && word) || find_match(Nouns.singulars) || word
38
+ end
39
+
40
+ # Inflect a word to its plural form
41
+ #
42
+ # @example
43
+ # Strings::Inflection::Noun.new("error").plural
44
+ # # => "errors"
45
+ #
46
+ # @param [String] word
47
+ # noun to inflect to plural form
48
+ #
49
+ # @api public
50
+ def plural
51
+ return word if word.to_s.empty?
52
+
53
+ find_match(Inflection.configuration.plurals[:noun]) ||
54
+ (uncountable? && word) || find_match(Nouns.plurals) || word
55
+ end
56
+
57
+ # Combine this noun with another word
58
+ #
59
+ # @param [String] other_word
60
+ # the other word to combined with
61
+ #
62
+ # @return [CombinedNoun]
63
+ #
64
+ # @api public
65
+ def +(other_word)
66
+ CombinedNoun.new([word, other_word])
67
+ end
68
+ end # Noun
69
+ end # Inflection
70
+ end # Strings