Merged updates from trunk into stable branch
[feedcatcher.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(@person)) %>
258 # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
259 def parameterize(string, sep = '-')
260 # replace accented chars with ther ascii equivalents
261 parameterized_string = transliterate(string)
262 # Turn unwanted chars into the seperator
263 parameterized_string.gsub!(/[^a-z0-9\-_\+]+/i, sep)
264 unless sep.blank?
265 re_sep = Regexp.escape(sep)
266 # No more than one of the separator in a row.
267 parameterized_string.gsub!(/#{re_sep}{2,}/, sep)
268 # Remove leading/trailing separator.
269 parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '')
270 end
271 parameterized_string.downcase
272 end
273
274
275 # Replaces accented characters with their ascii equivalents.
276 def transliterate(string)
277 Iconv.iconv('ascii//ignore//translit', 'utf-8', string).to_s
278 end
279
280 if RUBY_VERSION >= '1.9'
281 undef_method :transliterate
282 def transliterate(string)
283 warn "Ruby 1.9 doesn't support Unicode normalization yet"
284 string.dup
285 end
286
287 # The iconv transliteration code doesn't function correctly
288 # on some platforms, but it's very fast where it does function.
289 elsif "foo" != (Inflector.transliterate("föö") rescue nil)
290 undef_method :transliterate
291 def transliterate(string)
292 string.mb_chars.normalize(:kd). # Decompose accented characters
293 gsub(/[^\x00-\x7F]+/, '') # Remove anything non-ASCII entirely (e.g. diacritics).
294 end
295 end
296
297 # Create the name of a table like Rails does for models to table names. This method
298 # uses the +pluralize+ method on the last word in the string.
299 #
300 # Examples
301 # "RawScaledScorer".tableize # => "raw_scaled_scorers"
302 # "egg_and_ham".tableize # => "egg_and_hams"
303 # "fancyCategory".tableize # => "fancy_categories"
304 def tableize(class_name)
305 pluralize(underscore(class_name))
306 end
307
308 # Create a class name from a plural table name like Rails does for table names to models.
309 # Note that this returns a string and not a Class. (To convert to an actual class
310 # follow +classify+ with +constantize+.)
311 #
312 # Examples:
313 # "egg_and_hams".classify # => "EggAndHam"
314 # "posts".classify # => "Post"
315 #
316 # Singular names are not handled correctly:
317 # "business".classify # => "Busines"
318 def classify(table_name)
319 # strip out any leading schema name
320 camelize(singularize(table_name.to_s.sub(/.*\./, '')))
321 end
322
323 # Creates a foreign key name from a class name.
324 # +separate_class_name_and_id_with_underscore+ sets whether
325 # the method should put '_' between the name and 'id'.
326 #
327 # Examples:
328 # "Message".foreign_key # => "message_id"
329 # "Message".foreign_key(false) # => "messageid"
330 # "Admin::Post".foreign_key # => "post_id"
331 def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
332 underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
333 end
334
335 # Ruby 1.9 introduces an inherit argument for Module#const_get and
336 # #const_defined? and changes their default behavior.
337 if Module.method(:const_get).arity == 1
338 # Tries to find a constant with the name specified in the argument string:
339 #
340 # "Module".constantize # => Module
341 # "Test::Unit".constantize # => Test::Unit
342 #
343 # The name is assumed to be the one of a top-level constant, no matter whether
344 # it starts with "::" or not. No lexical context is taken into account:
345 #
346 # C = 'outside'
347 # module M
348 # C = 'inside'
349 # C # => 'inside'
350 # "C".constantize # => 'outside', same as ::C
351 # end
352 #
353 # NameError is raised when the name is not in CamelCase or the constant is
354 # unknown.
355 def constantize(camel_cased_word)
356 names = camel_cased_word.split('::')
357 names.shift if names.empty? || names.first.empty?
358
359 constant = Object
360 names.each do |name|
361 constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
362 end
363 constant
364 end
365 else
366 def constantize(camel_cased_word) #:nodoc:
367 names = camel_cased_word.split('::')
368 names.shift if names.empty? || names.first.empty?
369
370 constant = Object
371 names.each do |name|
372 constant = constant.const_get(name, false) || constant.const_missing(name)
373 end
374 constant
375 end
376 end
377
378 # Turns a number into an ordinal string used to denote the position in an
379 # ordered sequence such as 1st, 2nd, 3rd, 4th.
380 #
381 # Examples:
382 # ordinalize(1) # => "1st"
383 # ordinalize(2) # => "2nd"
384 # ordinalize(1002) # => "1002nd"
385 # ordinalize(1003) # => "1003rd"
386 def ordinalize(number)
387 if (11..13).include?(number.to_i % 100)
388 "#{number}th"
389 else
390 case number.to_i % 10
391 when 1; "#{number}st"
392 when 2; "#{number}nd"
393 when 3; "#{number}rd"
394 else "#{number}th"
395 end
396 end
397 end
398 end
399 end
400
401 # in case active_support/inflector is required without the rest of active_support
402 require 'active_support/inflections'
403 require 'active_support/core_ext/string/inflections'
404 unless String.included_modules.include?(ActiveSupport::CoreExtensions::String::Inflections)
405 String.send :include, ActiveSupport::CoreExtensions::String::Inflections
406 end