~ubuntu-branches/ubuntu/oneiric/ruby-activesupport-2.3/oneiric-security

« back to all changes in this revision

Viewing changes to lib/active_support/inflector.rb

  • Committer: Bazaar Package Importer
  • Author(s): Ondřej Surý
  • Date: 2011-06-01 14:30:04 UTC
  • Revision ID: james.westby@ubuntu.com-20110601143004-wy5jrvj06x72qiqs
Tags: upstream-2.3.11
Import upstream version 2.3.11

Show diffs side-by-side

added added

removed removed

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