module Sequel # Yield the Inflections module if a block is given, and return # the Inflections module. def self.inflections yield Inflections if block_given? Inflections end # This module acts as a singleton returned/yielded by Sequel.inflections, # which is used to override or specify additional inflection rules # for Sequel. Examples: # # Sequel.inflections do |inflect| # inflect.plural /^(ox)$/i, '\1\2en' # inflect.singular /^(ox)en/i, '\1' # # inflect.irregular 'octopus', 'octopi' # # inflect.uncountable "equipment" # end # # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may # already have been loaded. module Inflections CAMELIZE_CONVERT_REGEXP = /(^|_)(.)/.freeze CAMELIZE_MODULE_REGEXP = /\/(.?)/.freeze DASH = '-'.freeze DEMODULIZE_CONVERT_REGEXP = /^.*::/.freeze EMPTY_STRING= ''.freeze SLASH = '/'.freeze VALID_CONSTANT_NAME_REGEXP = /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/.freeze UNDERSCORE = '_'.freeze UNDERSCORE_CONVERT_REGEXP1 = /([A-Z]+)([A-Z][a-z])/.freeze UNDERSCORE_CONVERT_REGEXP2 = /([a-z\d])([A-Z])/.freeze UNDERSCORE_CONVERT_REPLACE = '\1_\2'.freeze UNDERSCORE_MODULE_REGEXP = /::/.freeze @plurals, @singulars, @uncountables = [], [], [] class << self attr_reader :plurals, :singulars, :uncountables end # Clears the loaded inflections within a given scope (default is :all). Give the scope as a symbol of the inflection type, # the options are: :plurals, :singulars, :uncountables # # Examples: # clear :all # clear :plurals def self.clear(scope = :all) case scope when :all @plurals, @singulars, @uncountables = [], [], [] else instance_variable_set("@#{scope}", []) end end # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used # for strings, not regular expressions. You simply pass the irregular in singular and plural form. # # Examples: # irregular 'octopus', 'octopi' # irregular 'person', 'people' def self.irregular(singular, plural) plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1]) singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1]) end # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression. # The replacement should always be a string that may include references to the matched data from the rule. # # Example: # plural(/(x|ch|ss|sh)$/i, '\1es') def self.plural(rule, replacement) @plurals.insert(0, [rule, replacement]) end # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression. # The replacement should always be a string that may include references to the matched data from the rule. # # Example: # singular(/([^aeiouy]|qu)ies$/i, '\1y') def self.singular(rule, replacement) @singulars.insert(0, [rule, replacement]) end # Add uncountable words that shouldn't be attempted inflected. # # Examples: # uncountable "money" # uncountable "money", "information" # uncountable %w( money information rice ) def self.uncountable(*words) (@uncountables << words).flatten! end instance_eval(&DEFAULT_INFLECTIONS_PROC) private # Convert the given string to CamelCase. Will also convert '/' to '::' which is useful for converting paths to namespaces. def camelize(s) s = s.to_s return s.camelize if s.respond_to?(:camelize) s = s.gsub(CAMELIZE_MODULE_REGEXP){|x| "::#{x[-1..-1].upcase unless x == SLASH}"}.gsub(CAMELIZE_CONVERT_REGEXP){|x| x[-1..-1].upcase} s end # Tries to find a declared constant with the name specified # in the string. It raises a NameError when the name is not in CamelCase # or is not initialized. def constantize(s) s = s.to_s return s.constantize if s.respond_to?(:constantize) raise(NameError, "#{inspect} is not a valid constant name!") unless m = VALID_CONSTANT_NAME_REGEXP.match(s.to_s) Object.module_eval("::#{m[1]}", __FILE__, __LINE__) end # Removes the module part from the expression in the string def demodulize(s) s = s.to_s return s.demodulize if s.respond_to?(:demodulize) s.gsub(DEMODULIZE_CONVERT_REGEXP, EMPTY_STRING) end # Returns the plural form of the word in the string. def pluralize(s) s = s.to_s return s.pluralize if s.respond_to?(:pluralize) result = s.dup Inflections.plurals.each{|(rule, replacement)| break if result.gsub!(rule, replacement)} unless Inflections.uncountables.include?(s.downcase) result end # The reverse of pluralize, returns the singular form of a word in a string. def singularize(s) s = s.to_s return s.singularize if s.respond_to?(:singularize) result = s.dup Inflections.singulars.each{|(rule, replacement)| break if result.gsub!(rule, replacement)} unless Inflections.uncountables.include?(s.downcase) result end # The reverse of camelize. Makes an underscored form from the expression in the string. # Also changes '::' to '/' to convert namespaces to paths. def underscore(s) s = s.to_s return s.underscore if s.respond_to?(:underscore) s.gsub(UNDERSCORE_MODULE_REGEXP, SLASH).gsub(UNDERSCORE_CONVERT_REGEXP1, UNDERSCORE_CONVERT_REPLACE). gsub(UNDERSCORE_CONVERT_REGEXP2, UNDERSCORE_CONVERT_REPLACE).tr(DASH, UNDERSCORE).downcase end end end