diff options
Diffstat (limited to 'activesupport/lib/active_support/inflector')
-rw-r--r-- | activesupport/lib/active_support/inflector/inflections.rb | 70 | ||||
-rw-r--r-- | activesupport/lib/active_support/inflector/methods.rb | 99 |
2 files changed, 110 insertions, 59 deletions
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 9cf4b2b2ba..486838bd15 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -52,21 +52,21 @@ module ActiveSupport # into a non-delimited single lowercase word when passed to +underscore+. # # acronym 'HTML' - # titleize 'html' #=> 'HTML' - # camelize 'html' #=> 'HTML' - # underscore 'MyHTML' #=> 'my_html' + # titleize 'html' # => 'HTML' + # camelize 'html' # => 'HTML' + # underscore 'MyHTML' # => 'my_html' # # The acronym, however, must occur as a delimited unit and not be part of # another word for conversions to recognize it: # # acronym 'HTTP' - # camelize 'my_http_delimited' #=> 'MyHTTPDelimited' - # camelize 'https' #=> 'Https', not 'HTTPs' - # underscore 'HTTPS' #=> 'http_s', not 'https' + # camelize 'my_http_delimited' # => 'MyHTTPDelimited' + # camelize 'https' # => 'Https', not 'HTTPs' + # underscore 'HTTPS' # => 'http_s', not 'https' # # acronym 'HTTPS' - # camelize 'https' #=> 'HTTPS' - # underscore 'HTTPS' #=> 'https' + # camelize 'https' # => 'HTTPS' + # underscore 'HTTPS' # => 'https' # # Note: Acronyms that are passed to +pluralize+ will no longer be # recognized, since the acronym will not occur as a delimited unit in the @@ -74,25 +74,25 @@ module ActiveSupport # form as an acronym as well: # # acronym 'API' - # camelize(pluralize('api')) #=> 'Apis' + # camelize(pluralize('api')) # => 'Apis' # # acronym 'APIs' - # camelize(pluralize('api')) #=> 'APIs' + # camelize(pluralize('api')) # => 'APIs' # # +acronym+ may be used to specify any word that contains an acronym or # otherwise needs to maintain a non-standard capitalization. The only # restriction is that the word must begin with a capital letter. # # acronym 'RESTful' - # underscore 'RESTful' #=> 'restful' - # underscore 'RESTfulController' #=> 'restful_controller' - # titleize 'RESTfulController' #=> 'RESTful Controller' - # camelize 'restful' #=> 'RESTful' - # camelize 'restful_controller' #=> 'RESTfulController' + # underscore 'RESTful' # => 'restful' + # underscore 'RESTfulController' # => 'restful_controller' + # titleize 'RESTfulController' # => 'RESTful Controller' + # camelize 'restful' # => 'RESTful' + # camelize 'restful_controller' # => 'RESTfulController' # # acronym 'McDonald' - # underscore 'McDonald' #=> 'mcdonald' - # camelize 'mcdonald' #=> 'McDonald' + # underscore 'McDonald' # => 'mcdonald' + # camelize 'mcdonald' # => 'McDonald' def acronym(word) @acronyms[word.downcase] = word @acronym_regex = /#{@acronyms.values.join("|")}/ @@ -128,27 +128,39 @@ module ActiveSupport def irregular(singular, plural) @uncountables.delete(singular) @uncountables.delete(plural) - if singular[0,1].upcase == plural[0,1].upcase - plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1]) - plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1]) - singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1]) + + s0 = singular[0] + srest = singular[1..-1] + + p0 = plural[0] + prest = plural[1..-1] + + if s0.upcase == p0.upcase + plural(/(#{s0})#{srest}$/i, '\1' + prest) + plural(/(#{p0})#{prest}$/i, '\1' + prest) + + singular(/(#{s0})#{srest}$/i, '\1' + srest) + singular(/(#{p0})#{prest}$/i, '\1' + srest) else - plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1]) - plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1]) - plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1]) - plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1]) - singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1]) - singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1]) + plural(/#{s0.upcase}(?i)#{srest}$/, p0.upcase + prest) + plural(/#{s0.downcase}(?i)#{srest}$/, p0.downcase + prest) + plural(/#{p0.upcase}(?i)#{prest}$/, p0.upcase + prest) + plural(/#{p0.downcase}(?i)#{prest}$/, p0.downcase + prest) + + singular(/#{s0.upcase}(?i)#{srest}$/, s0.upcase + srest) + singular(/#{s0.downcase}(?i)#{srest}$/, s0.downcase + srest) + singular(/#{p0.upcase}(?i)#{prest}$/, s0.upcase + srest) + singular(/#{p0.downcase}(?i)#{prest}$/, s0.downcase + srest) end end - # Add uncountable words that shouldn't be attempted inflected. + # Specifies words that are uncountable and should not be inflected. # # uncountable 'money' # uncountable 'money', 'information' # uncountable %w( money information rice ) def uncountable(*words) - (@uncountables << words).flatten! + @uncountables += words.flatten.map(&:downcase) end # Specifies a humanized form of a string by a regular expression rule or diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 1eb2b4212b..74b3a7c2a9 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,6 +1,5 @@ # encoding: utf-8 -require 'active_support/inflector/inflections' require 'active_support/inflections' module ActiveSupport @@ -37,7 +36,7 @@ module ActiveSupport # string. # # If passed an optional +locale+ parameter, the word will be - # pluralized using rules defined for that language. By default, + # singularized using rules defined for that language. By default, # this parameter is set to <tt>:en</tt>. # # 'posts'.singularize # => "post" @@ -73,7 +72,9 @@ module ActiveSupport else string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase } end - string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::') + string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" } + string.gsub!(/\//, '::') + string end # Makes an underscored, lowercase form from the expression in the string. @@ -88,9 +89,9 @@ module ActiveSupport # # 'SSLError'.underscore.camelize # => "SslError" def underscore(camel_cased_word) - word = camel_cased_word.to_s.dup - word.gsub!('::', '/') - word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" } + return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/ + word = camel_cased_word.to_s.gsub(/::/, '/') + word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'}#{$2.downcase}" } word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2') word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') word.tr!("-", "_") @@ -98,20 +99,47 @@ module ActiveSupport word end - # Capitalizes the first word and turns underscores into spaces and strips a - # trailing "_id", if any. Like +titleize+, this is meant for creating pretty - # output. + # Tweaks an attribute name for display to end users. + # + # Specifically, +humanize+ performs these transformations: + # + # * Applies human inflection rules to the argument. + # * Deletes leading underscores, if any. + # * Removes a "_id" suffix if present. + # * Replaces underscores with spaces, if any. + # * Downcases all words except acronyms. + # * Capitalizes the first word. + # + # The capitalization of the first word can be turned off by setting the + # +:capitalize+ option to false (default is true). + # + # humanize('employee_salary') # => "Employee salary" + # humanize('author_id') # => "Author" + # humanize('author_id', capitalize: false) # => "author" + # humanize('_id') # => "Id" # - # 'employee_salary'.humanize # => "Employee salary" - # 'author_id'.humanize # => "Author" - def humanize(lower_case_and_underscored_word) + # If "SSL" was defined to be an acronym: + # + # humanize('ssl_error') # => "SSL error" + # + def humanize(lower_case_and_underscored_word, options = {}) result = lower_case_and_underscored_word.to_s.dup + inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) } - result.gsub!(/_id$/, "") + + result.sub!(/\A_+/, '') + result.sub!(/_id\z/, '') result.tr!('_', ' ') - result.gsub(/([a-z\d]*)/i) { |match| + + result.gsub!(/([a-z\d]*)/i) do |match| "#{inflections.acronyms[match] || match.downcase}" - }.gsub(/^\w/) { $&.upcase } + end + + if options.fetch(:capitalize, true) + result.sub!(/\A\w/) { |match| match.upcase } + end + + result end # Capitalizes all the words and replaces some characters in the string to @@ -147,7 +175,7 @@ module ActiveSupport # # Singular names are not handled correctly: # - # 'business'.classify # => "Busines" + # 'calculus'.classify # => "Calculu" def classify(table_name) # strip out any leading schema name camelize(singularize(table_name.to_s.sub(/.*\./, ''))) @@ -164,6 +192,8 @@ module ActiveSupport # # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" # 'Inflections'.demodulize # => "Inflections" + # '::Inflections'.demodulize # => "Inflections" + # ''.demodulize # => "" # # See also +deconstantize+. def demodulize(path) @@ -185,7 +215,7 @@ module ActiveSupport # # See also +demodulize+. def deconstantize(path) - path.to_s[0...(path.rindex('::') || 0)] # implementation based on the one in facets' Module#spacename + path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename end # Creates a foreign key name from a class name. @@ -219,7 +249,12 @@ module ActiveSupport # unknown. def constantize(camel_cased_word) names = camel_cased_word.split('::') - names.shift if names.empty? || names.first.empty? + + # Trigger a built-in NameError exception including the ill-formed constant in the message. + Object.const_get(camel_cased_word) if names.empty? + + # Remove the first blank element in case of '::ClassName' notation. + names.shift if names.size > 1 && names.first.empty? names.inject(Object) do |constant, name| if constant == Object @@ -229,8 +264,8 @@ module ActiveSupport next candidate if constant.const_defined?(name, false) next candidate unless Object.const_defined?(name) - # Go down the ancestors to check it it's owned - # directly before we reach Object or the end of ancestors. + # Go down the ancestors to check if it is owned directly. The check + # stops when we reach Object or the end of ancestors tree. constant = constant.ancestors.inject do |const, ancestor| break const if ancestor == Object break ancestor if ancestor.const_defined?(name, false) @@ -266,14 +301,12 @@ module ActiveSupport # 'UnknownModule'.safe_constantize # => nil # 'UnknownModule::Foo::Bar'.safe_constantize # => nil def safe_constantize(camel_cased_word) - begin - constantize(camel_cased_word) - rescue NameError => e - raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ || - e.name.to_s == camel_cased_word.to_s - rescue ArgumentError => e - raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/ - end + constantize(camel_cased_word) + rescue NameError => e + raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) || + e.name.to_s == camel_cased_word.to_s) + rescue ArgumentError => e + raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/ end # Returns the suffix that should be added to a number to denote the position @@ -315,10 +348,16 @@ module ActiveSupport private - # Mount a regular expression that will match part by part of the constant. - # For instance, Foo::Bar::Baz will generate Foo(::Bar(::Baz)?)? + # Mounts a regular expression, returned as a string to ease interpolation, + # that will match part by part the given constant. + # + # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?" + # const_regexp("::") # => "::" def const_regexp(camel_cased_word) #:nodoc: parts = camel_cased_word.split("::") + + return Regexp.escape(camel_cased_word) if parts.blank? + last = parts.pop parts.reverse.inject(last) do |acc, part| |