Froze rails gems
[depot.git] / vendor / rails / activesupport / lib / active_support / inflector.rb
1 # encoding: utf-8
2 require 'singleton'
3 require 'iconv'
4
5 module ActiveSupport
6 # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
7 # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
8 # in inflections.rb.
9 #
10 # The Rails core team has stated patches for the inflections library will not be accepted
11 # in order to avoid breaking legacy applications which may be relying on errant inflections.
12 # If you discover an incorrect inflection and require it for your application, you'll need
13 # to correct it yourself (explained below).
14 module Inflector
15 extend self
16
17 # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
18 # inflection rules. Examples:
19 #
20 # ActiveSupport::Inflector.inflections do |inflect|
21 # inflect.plural /^(ox)$/i, '\1\2en'
22 # inflect.singular /^(ox)en/i, '\1'
23 #
24 # inflect.irregular 'octopus', 'octopi'
25 #
26 # inflect.uncountable "equipment"
27 # end
28 #
29 # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
30 # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
31 # already have been loaded.
32 class Inflections
33 include Singleton
34
35 attr_reader :plurals, :singulars, :uncountables, :humans
36
37 def initialize
38 @plurals, @singulars, @uncountables, @humans = [], [], [], []
39 end
40
41 # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
42 # The replacement should always be a string that may include references to the matched data from the rule.
43 def plural(rule, replacement)
44 @uncountables.delete(rule) if rule.is_a?(String)
45 @uncountables.delete(replacement)
46 @plurals.insert(0, [rule, replacement])
47 end
48
49 # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
50 # The replacement should always be a string that may include references to the matched data from the rule.
51 def singular(rule, replacement)
52 @uncountables.delete(rule) if rule.is_a?(String)
53 @uncountables.delete(replacement)
54 @singulars.insert(0, [rule, replacement])
55 end
56
57 # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
58 # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
59 #
60 # Examples:
61 # irregular 'octopus', 'octopi'
62 # irregular 'person', 'people'
63 def irregular(singular, plural)
64 @uncountables.delete(singular)
65 @uncountables.delete(plural)
66 if singular[0,1].upcase == plural[0,1].upcase
67 plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
68 singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
69 else
70 plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
71 plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
72 singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1])
73 singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1])
74 end
75 end
76
77 # Add uncountable words that shouldn't be attempted inflected.
78 #
79 # Examples:
80 # uncountable "money"
81 # uncountable "money", "information"
82 # uncountable %w( money information rice )
83 def uncountable(*words)
84 (@uncountables << words).flatten!
85 end
86
87 # Specifies a humanized form of a string by a regular expression rule or by a string mapping.
88 # When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
89 # When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
90 #
91 # Examples:
92 # human /_cnt$/i, '\1_count'
93 # human "legacy_col_person_name", "Name"
94 def human(rule, replacement)
95 @humans.insert(0, [rule, replacement])
96 end
97
98 # Clears the loaded inflections within a given scope (default is <tt>:all</tt>).
99 # Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>,
100 # <tt>:singulars</tt>, <tt>:uncountables</tt>, <tt>:humans</tt>.
101 #
102 # Examples:
103 # clear :all
104 # clear :plurals
105 def clear(scope = :all)
106 case scope
107 when :all
108 @plurals, @singulars, @uncountables = [], [], []
109 else
110 instance_variable_set "@#{scope}", []
111 end
112 end
113 end
114
115 # Yields a singleton instance of Inflector::Inflections so you can specify additional
116 # inflector rules.
117 #
118 # Example:
119 # ActiveSupport::Inflector.inflections do |inflect|
120 # inflect.uncountable "rails"
121 # end
122 def inflections
123 if block_given?
124 yield Inflections.instance
125 else
126 Inflections.instance
127 end
128 end
129
130 # Returns the plural form of the word in the string.
131 #
132 # Examples:
133 # "post".pluralize # => "posts"
134 # "octopus".pluralize # => "octopi"
135 # "sheep".pluralize # => "sheep"
136 # "words".pluralize # => "words"
137 # "CamelOctopus".pluralize # => "CamelOctopi"
138 def pluralize(word)
139 result = word.to_s.dup
140
141 if word.empty? || inflections.uncountables.include?(result.downcase)
142 result
143 else
144 inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
145 result
146 end
147 end
148
149 # The reverse of +pluralize+, returns the singular form of a word in a string.
150 #
151 # Examples:
152 # "posts".singularize # => "post"
153 # "octopi".singularize # => "octopus"
154 # "sheep".singluarize # => "sheep"
155 # "word".singularize # => "word"
156 # "CamelOctopi".singularize # => "CamelOctopus"
157 def singularize(word)
158 result = word.to_s.dup
159
160 if inflections.uncountables.include?(result.downcase)
161 result
162 else
163 inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
164 result
165 end
166 end
167
168 # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+
169 # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase.
170 #
171 # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
172 #
173 # Examples:
174 # "active_record".camelize # => "ActiveRecord"
175 # "active_record".camelize(:lower) # => "activeRecord"
176 # "active_record/errors".camelize # => "ActiveRecord::Errors"
177 # "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
178 def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
179 if first_letter_in_uppercase
180 lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
181 else
182 lower_case_and_underscored_word.first.downcase + camelize(lower_case_and_underscored_word)[1..-1]
183 end
184 end
185
186 # Capitalizes all the words and replaces some characters in the string to create
187 # a nicer looking title. +titleize+ is meant for creating pretty output. It is not
188 # used in the Rails internals.
189 #
190 # +titleize+ is also aliased as as +titlecase+.
191 #
192 # Examples:
193 # "man from the boondocks".titleize # => "Man From The Boondocks"
194 # "x-men: the last stand".titleize # => "X Men: The Last Stand"
195 def titleize(word)
196 humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize }
197 end
198
199 # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
200 #
201 # Changes '::' to '/' to convert namespaces to paths.
202 #
203 # Examples:
204 # "ActiveRecord".underscore # => "active_record"
205 # "ActiveRecord::Errors".underscore # => active_record/errors
206 def underscore(camel_cased_word)
207 camel_cased_word.to_s.gsub(/::/, '/').
208 gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
209 gsub(/([a-z\d])([A-Z])/,'\1_\2').
210 tr("-", "_").
211 downcase
212 end
213
214 # Replaces underscores with dashes in the string.
215 #
216 # Example:
217 # "puni_puni" # => "puni-puni"
218 def dasherize(underscored_word)
219 underscored_word.gsub(/_/, '-')
220 end
221
222 # Capitalizes the first word and turns underscores into spaces and strips a
223 # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output.
224 #
225 # Examples:
226 # "employee_salary" # => "Employee salary"
227 # "author_id" # => "Author"
228 def humanize(lower_case_and_underscored_word)
229 result = lower_case_and_underscored_word.to_s.dup
230
231 inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
232 result.gsub(/_id$/, "").gsub(/_/, " ").capitalize
233 end
234
235 # Removes the module part from the expression in the string.
236 #
237 # Examples:
238 # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
239 # "Inflections".demodulize # => "Inflections"
240 def demodulize(class_name_in_module)
241 class_name_in_module.to_s.gsub(/^.*::/, '')
242 end
243
244 # Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
245 #
246 # ==== Examples
247 #
248 # class Person
249 # def to_param
250 # "#{id}-#{name.parameterize}"
251 # end
252 # end
253 #
254 # @person = Person.find(1)
255 # # => #<Person id: 1, name: "Donald E. Knuth">
256 #
257 # <%= link_to(@person.name, person_path %>
258 # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
259 def parameterize(string, sep = '-')
260 re_sep = Regexp.escape(sep)
261 # replace accented chars with ther ascii equivalents
262 parameterized_string = transliterate(string)
263 # Turn unwanted chars into the seperator
264 parameterized_string.gsub!(/[^a-z0-9\-_\+]+/i, sep)
265 # No more than one of the separator in a row.
266 parameterized_string.squeeze!(sep)
267 # Remove leading/trailing separator.
268 parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '')
269 parameterized_string.downcase
270 end
271
272
273 # Replaces accented characters with their ascii equivalents.
274 def transliterate(string)
275 Iconv.iconv('ascii//ignore//translit', 'utf-8', string).to_s
276 end
277
278 # The iconv transliteration code doesn't function correctly
279 # on some platforms, but it's very fast where it does function.
280 if "foo" != Inflector.transliterate("föö")
281 undef_method :transliterate
282 def transliterate(string)
283 string.mb_chars.normalize(:kd). # Decompose accented characters
284 gsub(/[^\x00-\x7F]+/, '') # Remove anything non-ASCII entirely (e.g. diacritics).
285 end
286 end
287
288 # Create the name of a table like Rails does for models to table names. This method
289 # uses the +pluralize+ method on the last word in the string.
290 #
291 # Examples
292 # "RawScaledScorer".tableize # => "raw_scaled_scorers"
293 # "egg_and_ham".tableize # => "egg_and_hams"
294 # "fancyCategory".tableize # => "fancy_categories"
295 def tableize(class_name)
296 pluralize(underscore(class_name))
297 end
298
299 # Create a class name from a plural table name like Rails does for table names to models.
300 # Note that this returns a string and not a Class. (To convert to an actual class
301 # follow +classify+ with +constantize+.)
302 #
303 # Examples:
304 # "egg_and_hams".classify # => "EggAndHam"
305 # "posts".classify # => "Post"
306 #
307 # Singular names are not handled correctly:
308 # "business".classify # => "Busines"
309 def classify(table_name)
310 # strip out any leading schema name
311 camelize(singularize(table_name.to_s.sub(/.*\./, '')))
312 end
313
314 # Creates a foreign key name from a class name.
315 # +separate_class_name_and_id_with_underscore+ sets whether
316 # the method should put '_' between the name and 'id'.
317 #
318 # Examples:
319 # "Message".foreign_key # => "message_id"
320 # "Message".foreign_key(false) # => "messageid"
321 # "Admin::Post".foreign_key # => "post_id"
322 def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
323 underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
324 end
325
326 # Ruby 1.9 introduces an inherit argument for Module#const_get and
327 # #const_defined? and changes their default behavior.
328 if Module.method(:const_get).arity == 1
329 # Tries to find a constant with the name specified in the argument string:
330 #
331 # "Module".constantize # => Module
332 # "Test::Unit".constantize # => Test::Unit
333 #
334 # The name is assumed to be the one of a top-level constant, no matter whether
335 # it starts with "::" or not. No lexical context is taken into account:
336 #
337 # C = 'outside'
338 # module M
339 # C = 'inside'
340 # C # => 'inside'
341 # "C".constantize # => 'outside', same as ::C
342 # end
343 #
344 # NameError is raised when the name is not in CamelCase or the constant is
345 # unknown.
346 def constantize(camel_cased_word)
347 names = camel_cased_word.split('::')
348 names.shift if names.empty? || names.first.empty?
349
350 constant = Object
351 names.each do |name|
352 constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
353 end
354 constant
355 end
356 else
357 def constantize(camel_cased_word) #:nodoc:
358 names = camel_cased_word.split('::')
359 names.shift if names.empty? || names.first.empty?
360
361 constant = Object
362 names.each do |name|
363 constant = constant.const_get(name, false) || constant.const_missing(name)
364 end
365 constant
366 end
367 end
368
369 # Turns a number into an ordinal string used to denote the position in an
370 # ordered sequence such as 1st, 2nd, 3rd, 4th.
371 #
372 # Examples:
373 # ordinalize(1) # => "1st"
374 # ordinalize(2) # => "2nd"
375 # ordinalize(1002) # => "1002nd"
376 # ordinalize(1003) # => "1003rd"
377 def ordinalize(number)
378 if (11..13).include?(number.to_i % 100)
379 "#{number}th"
380 else
381 case number.to_i % 10
382 when 1; "#{number}st"
383 when 2; "#{number}nd"
384 when 3; "#{number}rd"
385 else "#{number}th"
386 end
387 end
388 end
389 end
390 end
391
392 # in case active_support/inflector is required without the rest of active_support
393 require 'active_support/inflections'
394 require 'active_support/core_ext/string/inflections'
395 unless String.included_modules.include?(ActiveSupport::CoreExtensions::String::Inflections)
396 String.send :include, ActiveSupport::CoreExtensions::String::Inflections
397 end