text: #!/usr/bin/env python
  # encoding: utf-8
  '''
  Word transformation.
  
  Transforms words from singular to plural, class names to table names, 
  modularized class names to ones without, and class names to foreign keys.
  
  Inflection is language specific and the default `inflection` handles English
  language inflection. You can access inflection handlers for other languages
  by importing the appropriate inflector. For example
  ``from miwa.inflection.sv import inflection`` to use Swedish inflection.
  
  :Author:          Rasmus Andersson http://hunch.se/
  :var inflections: locale-to-Inflection-object map
  :var inflection:  English inflection
  '''
  
  import re, logging
  
  inflections = {}
  log = logging.getLogger(__name__)
  
  class Inflector(object):
    '''Language inflector.
    
    :ivar locales:      languages this inflection handles
    :ivar plurals:      plural rules
    :ivar singulars:    singular rules
    :ivar uncountables: list of uncountable words
    '''
    def __init__(self, *locales):
      '''Create a inflection handler.
      
      Locale codes should comply to:
      * `RFC 1766 <http://www.faqs.org/rfcs/rfc1766.html>`__, preferably as
      * `ISO 639-1 <http://en.wikipedia.org/wiki/ISO_639-1>`__ (two-letter code).
      
      A list of language codes can be found here:
      http://www.loc.gov/standards/iso639-2/php/code_list.php
      
      :param locales: Language codes representing languages this `Inflector` handles.
      :type  locales: string
      '''
      self.plurals = []
      self.singulars = []
      self.uncountables = []
      self.locales = locales
      for locale in locales:
        inflections[locale] = self
    
    
    def plural(self, rule, replacement):
      '''Specifies a new pluralization rule and its replacement.
      
      :type rule:        RegExp pattern
      :type replacement: string
      :rtype: None
      '''
      self.plurals[0:0] = [tuple([rule, replacement])]
    
    
    def singular(self, rule, replacement):
      '''Specifies a new singularization rule and its replacement.
      
      :type rule:        RegExp pattern
      :type replacement: string
      :rtype: None
      '''
      self.singulars[0:0] = [tuple([rule, replacement])]
    
    
    def regular(self, plural_find, plural_replace, singular_find, singular_replace=''):
      '''Specified a new regular inflection.
      
      :param plural_find:      regular expression pattern (which will be compiled)
      :type  plural_find:      string
      :param plural_replace:   replacement (may contain back-references to 
                               regexp groups from ``plural_find``)
      :type  plural_replace:   string
      :param singular_find:    regular expression pattern (which will be compiled)
      :type  singular_find:    string
      :param singular_replace: replacement (may contain back-references to 
                               regexp groups from ``singular_find``)
      :type  singular_replace: string
      :rtype: None
      '''
      self.plural(re.compile(plural_find, re.I), plural_replace)
      self.singular(re.compile(singular_find, re.I), singular_replace)
    
    
    def irregular(self, singular, plural, first_letter_is_the_same=True):
      '''Specifies a new irregular that applies to both pluralization and 
      singularization at the same time.
      
      Examples:
        inf.irregular('octopus', 'octopi')
        inf.irregular('person', 'people')
      
      :type singular: string
      :type plural:   string
      :type first_letter_is_the_same: bool
      :rtype: None
      '''
      if first_letter_is_the_same:
        self.plural(re.compile(ur"(%s)%s$" % (singular[0], singular[1:]), re.IGNORECASE), ur'\1' + plural[1:])
        self.singular(re.compile(ur"(%s)%s$" % (plural[0], plural[1:]), re.IGNORECASE), ur'\1' + singular[1:])
      else:
        self.plural(re.compile(ur"%s$" % singular), plural)
        self.plural(re.compile(ur"%s$" % singular.capitalize()), plural.capitalize())
        self.singular(re.compile(ur"%s$" % plural.capitalize()), singular.capitalize())
        self.singular(re.compile(ur"%s$" % plural), singular)
    
    
    def uncountable(self, *words):
      '''Add uncountable words that shouldn't be attempted inflected.
      
      Examples:
        uncountable "money"
        uncountable "money"), "information"
        uncountable %w( money information rice )
      
      :param words: strings
      :rtype: None
      '''
      self.uncountables[0:0] = [w.lower() for w in words]
    
    
    def clear(self):
      '''Clears any loaded inflections.
      
      :rtype: None
      '''
      self.plurals = []
      self.singulars = []
      self.uncountables = []
    
    
    def pluralize(self, word):
      '''Returns the plural form of the word in the string.
      
      Examples
        "post".pluralize              -> "posts"
        "octopus".pluralize           -> "octopi"
        "sheep".pluralize             -> "sheep"
        "words".pluralize             -> "words"
        "the blue mailman".pluralize  -> "the blue mailmen"
        "CamelOctopus".pluralize      -> "CamelOctopi"
      
      :rtype: unicode
      '''
      word = unicode(word)
      if word.lower() in self.uncountables:
        return word
      else:
        for (rule, replacement) in self.plurals:
          #log.debug("pluralize(): rule: %r, replacement: %r", rule.pattern, replacement)
          m = rule.subn(replacement, word)
          if m[1] > 0:
            return m[0]
      return word
    
    
    def singularize(self, word):
      '''The reverse of pluralize, returns the singular form of a word in a string.
      
      Examples
        "posts".singularize #=> "post"
        "octopi".singularize #=> "octopus"
        "sheep".singluarize #=> "sheep"
        "word".singluarize #=> "word"
        "the blue mailmen".singularize #=> "the blue mailman"
        "CamelOctopi".singularize #=> "CamelOctopus"
      
      :param word: a possibly plural word which should be converted to singular form.
      :rtype: unicode
      '''
      word = unicode(word)
      if word.lower() in self.uncountables:
        return word
      else:
        for (rule, replacement) in self.singulars:
          m = rule.subn(replacement, word)
          if m[1] > 0:
            return m[0]
      return word
    
    def camelize(self, lower_case_and_underscored_word, first_letter_uppercase=True):
      '''By default, camelize converts strings to UpperCamelCase. If the
      ``first_letter_uppercase`` argument is set to False, `camelize` produces
      lowerCamelCase.
  
      `camelize` will also convert ``/`` to ``.`` which is useful for converting
      paths to namespaces
  
      Examples
        "active_record".camelize            -> "ActiveRecord"
        "active_record".camelize(False)     -> "activeRecord"
        "active_record/errors".camelize     -> "ActiveRecord.Errors"
        "active_record/errors".camelize(0)  -> "activeRecord.Errors"
      
      :rtype: unicode
      '''
      if first_letter_uppercase:
        p2 = re.compile(ur"(^|_)(.)")
        lower_case_and_underscored_word = Inflector.camelize.re1.sub(
          lambda m: '.' + m.group(1).upper(), lower_case_and_underscored_word)
        return Inflector.camelize.re2.sub(lambda m: m.group(2).upper(), 
          lower_case_and_underscored_word)
      else:
        return lower_case_and_underscored_word[0] + camelize(lower_case_and_underscored_word)[1:]
    camelize.re1 = re.compile(ur"/(.?)")
    camelize.re2 = re.compile(ur"(^|_)(.)")
    
    
    def underscore(self, camel_cased_word):
      '''The reverse of `camelize`. Makes an underscored form from the expression
      in the string.
      
      Changes '.' to '/' to convert namespaces to paths.
      
      Examples
        "ActiveRecord".underscore         -> "active_record"
        "ActiveRecord::Errors".underscore -> active_record/errors
      
      :rtype: unicode
      '''
      return Inflector.underscore.re2.sub(ur'\1_\2', 
        Inflector.underscore.re1.sub(ur'\1_\2',
        camel_cased_word.replace(u'.',u'/'))).replace(u'-',u'_').lower()
    underscore.re1 = re.compile(ur'([A-Z]+)([A-Z][a-z])')
    underscore.re2 = re.compile(ur'([a-z\d])([A-Z])')
    
    
    def humanize(self, lower_case_and_underscored_word):
      '''Capitalizes the first word and turns underscores into spaces and strips _id.
      Like titleize, this is meant for creating pretty output.
      
      Examples
        "employee_salary" -> "Employee salary"
        "author_id"       -> "Author"
      
      :rtype: unicode
      '''
      if len(lower_case_and_underscored_word) >= 3 and lower_case_and_underscored_word[-3:] == '_id':
        lower_case_and_underscored_word = lower_case_and_underscored_word[:-3]
      return lower_case_and_underscored_word.replace('_',' ').capitalize()
    
    
    def demodulize(self, class_name_in_module):
      '''Removes the module part from the expression in the string
      
      Examples
        "ActiveRecord.CoreExtensions.String.Inflectors".demodulize #=> "Inflectors"
        "Inflectors".demodulize #=> "Inflectors"
      
      :rtype: unicode
      '''
      p = class_name_in_module.rfind('.')
      if p != -1:
        return class_name_in_module[p+1:]
      return class_name_in_module
    
    
    def tableize(self, class_name):
      '''Create the name of a table like Rails does for models to table names. This method
      uses the pluralize method on the last word in the string.
      
      Examples
        "RawScaledScorer".tableize  -> "raw_scaled_scorers"
        "egg_and_ham".tableize      -> "egg_and_hams"
        "fancyCategory".tableize    -> "fancy_categories"
      
      :rtype: unicode
      '''
      return self.pluralize(self.underscore(class_name))
    
    
    def classify(self, table_name):
      '''Create a class name from a table name.
      
      Examples
        "egg_and_hams".classify -> "EggAndHam"
        "post".classify         -> "Post"
      
      :rtype: unicode
      '''
      return self.camelize(self.singularize(self.demodulize(table_name)))
    
    
    def foreignKey(self, class_name, separate_class_name_and_id_with_underscore=True):
      '''Creates a foreign key name from a class name.
      +separate_class_name_and_id_with_underscore+ sets whether
      the method should put '_' between the name and 'id'.
      
      Examples
        "Message".foreignKey        -> "message_id"
        "Message".foreignKey(false) -> "messageid"
        "Admin::Post".foreignKey    -> "post_id"
      
      :rtype: unicode
      '''
      return self.underscore(self.demodulize(class_name)) +\
        (separate_class_name_and_id_with_underscore and "_id" or "id")
    
    
    def ordinalize(self, number):
      '''Ordinalize turns a number into an ordinal string used to denote the
      position in an ordered sequence such as 1st, 2nd, 3rd, 4th.
      
      Examples
        ordinalize(1)     -> "1st"
        ordinalize(2)     -> "2nd"
        ordinalize(1002)  -> "1002nd"
        ordinalize(1003)  -> "1003rd"
      
      :rtype: unicode
      '''
      i = int(number)
      if i % 100 in [11,12,13]:
        return u'%dth' % i
      else:
        x = i % 10
        if x == 1:
          return u'%sst' % i
        elif x == 2:
          return u'%dnd' % i
        elif x == 3:
          return u'%drd' % i
        else:
          return u'%dth' % i
    
    
  
  # Load default inflections (English)
  from smisk.inflection.en import inflection
  
  if __name__ == '__main__':
    from smisk.test.inflection import test
    test()
  