diff --git a/lib/inflector.rb b/lib/inflector.rb
new file mode 100644
index 00000000..652aaaca
--- /dev/null
+++ b/lib/inflector.rb
@@ -0,0 +1,7 @@
+# Library namespace
+module Inflector
+end
+
+require 'inflector/inflections'
+require 'inflector/defaults'
+require 'inflector/methods'
diff --git a/lib/inflector/defaults.rb b/lib/inflector/defaults.rb
new file mode 100644
index 00000000..fb8d56dd
--- /dev/null
+++ b/lib/inflector/defaults.rb
@@ -0,0 +1,62 @@
+module Inflector
+ Inflector::Inflections.instance.instance_eval do
+ plural(/$/, 's')
+ plural(/s$/i, 's')
+ plural(/us$/i, 'i')
+ plural(/(ax|test)is$/i, '\1es')
+ plural(/(octop|vir)us$/i, '\1i')
+ plural(/(octop|vir)i$/i, '\1i')
+ plural(/(alias|status)$/i, '\1es')
+ plural(/(bu)s$/i, '\1ses')
+ plural(/(buffal|tomat)o$/i, '\1oes')
+ plural(/([ti])um$/i, '\1a')
+ plural(/([ti])a$/i, '\1a')
+ plural(/sis$/i, 'ses')
+ plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
+ plural(/(hive)$/i, '\1s')
+ plural(/([^aeiouy]|qu)y$/i, '\1ies')
+ plural(/(x|ch|ss|sh)$/i, '\1es')
+ plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
+ plural(/([m|l])ouse$/i, '\1ice')
+ plural(/([m|l])ice$/i, '\1ice')
+ plural(/^(ox)$/i, '\1en')
+ plural(/^(oxen)$/i, '\1')
+ plural(/(quiz)$/i, '\1zes')
+
+ singular(/s$/i, '')
+ singular(/i$/i, 'us')
+ singular(/(n)ews$/i, '\1ews')
+ singular(/([ti])a$/i, '\1um')
+ singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
+ singular(/(^analy)ses$/i, '\1sis')
+ singular(/([^f])ves$/i, '\1fe')
+ singular(/(hive)s$/i, '\1')
+ singular(/(tive)s$/i, '\1')
+ singular(/([lr])ves$/i, '\1f')
+ singular(/([^aeiouy]|qu)ies$/i, '\1y')
+ singular(/(s)eries$/i, '\1eries')
+ singular(/(m)ovies$/i, '\1ovie')
+ singular(/(x|ch|ss|sh)es$/i, '\1')
+ singular(/([m|l])ice$/i, '\1ouse')
+ singular(/(bus)es$/i, '\1')
+ singular(/(o)es$/i, '\1')
+ singular(/(shoe)s$/i, '\1')
+ singular(/(cris|ax|test)es$/i, '\1is')
+ singular(/(octop|vir)i$/i, '\1us')
+ singular(/(alias|status)es$/i, '\1')
+ singular(/^(ox)en/i, '\1')
+ singular(/(vert|ind)ices$/i, '\1ex')
+ singular(/(matr)ices$/i, '\1ix')
+ singular(/(quiz)zes$/i, '\1')
+ singular(/(database)s$/i, '\1')
+
+ irregular('person', 'people')
+ irregular('man', 'men')
+ irregular('child', 'children')
+ irregular('sex', 'sexes')
+ irregular('move', 'moves')
+ irregular('cow', 'kine')
+
+ uncountable(%w(grass equipment information rice money species series fish sheep jeans))
+ end
+end
diff --git a/lib/inflector/inflections.rb b/lib/inflector/inflections.rb
new file mode 100644
index 00000000..01f3cd82
--- /dev/null
+++ b/lib/inflector/inflections.rb
@@ -0,0 +1,209 @@
+module Inflector
+ # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
+ # inflection rules. Examples:
+ #
+ # Inflector.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.
+ class Inflections
+ def self.instance
+ @__instance__ ||= new
+ end
+
+ attr_reader :plurals, :singulars, :uncountables, :humans
+
+ def initialize
+ @plurals, @singulars, @uncountables, @humans = [], [], [], []
+ 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.
+ def plural(rule, replacement)
+ @uncountables.delete(rule) if rule.is_a?(String)
+ @uncountables.delete(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.
+ def singular(rule, replacement)
+ @uncountables.delete(rule) if rule.is_a?(String)
+ @uncountables.delete(replacement)
+ @singulars.insert(0, [rule, replacement])
+ 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 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])
+ 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])
+ end
+ end
+
+ # Add uncountable words that shouldn't be attempted inflected.
+ #
+ # Examples:
+ # uncountable "money"
+ # uncountable "money", "information"
+ # uncountable %w( money information rice )
+ def uncountable(*words)
+ (@uncountables << words).flatten!
+ end
+
+ # Specifies a humanized form of a string by a regular expression rule or by a string mapping.
+ # When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
+ # When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
+ #
+ # Examples:
+ # human /_cnt$/i, '\1_count'
+ # human "legacy_col_person_name", "Name"
+ def human(rule, replacement)
+ @humans.insert(0, [rule, replacement])
+ 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, :humans.
+ #
+ # Examples:
+ # clear :all
+ # clear :plurals
+ def clear(scope = :all)
+ case scope
+ when :all
+ @plurals, @singulars, @uncountables = [], [], []
+ else
+ instance_variable_set "@#{scope}", []
+ end
+ end
+ end
+
+ # Yields a singleton instance of Inflector::Inflections so you can specify additional
+ # inflector rules.
+ #
+ # Example:
+ # Inflector.inflections do |inflect|
+ # inflect.uncountable "rails"
+ # end
+ def inflections
+ if block_given?
+ yield Inflections.instance
+ else
+ Inflections.instance
+ end
+ end
+
+ # Returns the plural form of the word in the string.
+ #
+ # Examples:
+ # "post".pluralize # => "posts"
+ # "octopus".pluralize # => "octopi"
+ # "sheep".pluralize # => "sheep"
+ # "words".pluralize # => "words"
+ # "CamelOctopus".pluralize # => "CamelOctopi"
+ def pluralize(word)
+ result = word.to_s.dup
+
+ if word.empty? || inflections.uncountables.include?(result.downcase)
+ result
+ else
+ inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
+ result
+ end
+ end
+
+ # The reverse of +pluralize+, returns the singular form of a word in a string.
+ #
+ # Examples:
+ # "posts".singularize # => "post"
+ # "octopi".singularize # => "octopus"
+ # "sheep".singularize # => "sheep"
+ # "word".singularize # => "word"
+ # "CamelOctopi".singularize # => "CamelOctopus"
+ def singularize(word)
+ result = word.to_s.dup
+
+ if inflections.uncountables.any? { |inflection| result =~ /\b(#{inflection})\Z/i }
+ result
+ else
+ inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
+ result
+ end
+ 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.
+ #
+ # Examples:
+ # "employee_salary" # => "Employee salary"
+ # "author_id" # => "Author"
+ def humanize(lower_case_and_underscored_word)
+ result = lower_case_and_underscored_word.to_s.dup
+
+ inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
+ result.gsub(/_id$/, "").gsub(/_/, " ").capitalize
+ end
+
+ # Capitalizes all the words and replaces some characters in the string to create
+ # a nicer looking title. +titleize+ is meant for creating pretty output. It is not
+ # used in the Rails internals.
+ #
+ # +titleize+ is also aliased as as +titlecase+.
+ #
+ # Examples:
+ # "man from the boondocks".titleize # => "Man From The Boondocks"
+ # "x-men: the last stand".titleize # => "X Men: The Last Stand"
+ def titleize(word)
+ humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize }
+ end
+
+ # 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"
+ def tableize(class_name)
+ pluralize(underscore(class_name))
+ end
+
+ # Create a class name from a plural table name like Rails does for table names to models.
+ # Note that this returns a string and not a Class. (To convert to an actual class
+ # follow +classify+ with +constantize+.)
+ #
+ # Examples:
+ # "egg_and_hams".classify # => "EggAndHam"
+ # "posts".classify # => "Post"
+ #
+ # Singular names are not handled correctly:
+ # "business".classify # => "Busines"
+ def classify(table_name)
+ # strip out any leading schema name
+ camelize(singularize(table_name.to_s.sub(/.*\./, '')))
+ end
+end
diff --git a/lib/mutant/inflector/methods.rb b/lib/inflector/methods.rb
similarity index 100%
rename from lib/mutant/inflector/methods.rb
rename to lib/inflector/methods.rb
diff --git a/lib/mutant/inflector/version.rb b/lib/inflector/version.rb
similarity index 100%
rename from lib/mutant/inflector/version.rb
rename to lib/inflector/version.rb
diff --git a/lib/mutant/inflector.rb b/lib/mutant/inflector.rb
deleted file mode 100644
index fb0c2e3a..00000000
--- a/lib/mutant/inflector.rb
+++ /dev/null
@@ -1,425 +0,0 @@
-# = English Nouns Number Inflection.
-#
-# This module provides english singular <-> plural noun inflections.
-module Inflector
-
- # Take an underscored name and make it into a camelized name
- #
- # @example
- # "egg_and_hams".classify #=> "EggAndHam"
- # "enlarged_testes".classify #=> "EnlargedTestis"
- # "post".classify #=> "Post"
- #
- def self.classify(name)
- words = name.to_s.sub(/.*\./, '').split('_')
- words[-1] = singularize(words[-1])
- words.collect { |word| word.capitalize }.join
- end
-
- # By default, camelize converts strings to UpperCamelCase.
- #
- # camelize will also convert '/' to '::' which is useful for converting paths to namespaces
- #
- # @example
- # "active_record".camelize #=> "ActiveRecord"
- # "active_record/errors".camelize #=> "ActiveRecord::Errors"
- #
- def self.camelize(lower_case_and_underscored_word, *args)
- lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
- end
-
-
- # The reverse of +camelize+. Makes an underscored form from the expression in the string.
- #
- # Changes '::' to '/' to convert namespaces to paths.
- #
- # @example
- # "ActiveRecord".underscore #=> "active_record"
- # "ActiveRecord::Errors".underscore #=> active_record/errors
- #
- def self.underscore(camel_cased_word)
- camel_cased_word.to_const_path
- end
-
- # Capitalizes the first word and turns underscores into spaces and strips _id.
- # Like titleize, this is meant for creating pretty output.
- #
- # @example
- # "employee_salary" #=> "Employee salary"
- # "author_id" #=> "Author"
- def self.humanize(lower_case_and_underscored_word)
- lower_case_and_underscored_word.to_s.gsub(/_id$/, '').tr('_', ' ').capitalize
- end
-
- # Removes the module part from the expression in the string
- #
- # @example
- # "ActiveRecord::CoreExtensions::String::Inflections".demodulize #=> "Inflections"
- # "Inflections".demodulize #=> "Inflections"
- def self.demodulize(class_name_in_module)
- class_name_in_module.to_s.gsub(/^.*::/, '')
- end
-
- # 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.
- #
- # @example
- # "RawScaledScorer".tableize #=> "raw_scaled_scorers"
- # "EnlargedTestis".tableize #=> "enlarged_testes"
- # "egg_and_ham".tableize #=> "egg_and_hams"
- # "fancyCategory".tableize #=> "fancy_categories"
- def self.tableize(class_name)
- words = class_name.to_const_path.tr('/', '_').split('_')
- words[-1] = pluralize(words[-1])
- words.join('_')
- end
-
- # Creates a foreign key name from a class name.
- #
- # @example
- # "Message".foreign_key #=> "message_id"
- # "Admin::Post".foreign_key #=> "post_id"
- def self.foreign_key(class_name, key = "id")
- underscore(demodulize(class_name.to_s)) << "_" << key.to_s
- end
-
- # Constantize 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.
- #
- # @example
- # "Module".constantize #=> Module
- # "Class".constantize #=> Class
- def self.constantize(camel_cased_word)
- unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
- raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
- end
-
- Object.module_eval("::#{$1}", __FILE__, __LINE__)
- end
-end
-
-@singular_of = {}
-@plural_of = {}
-
-@singular_rules = []
-@plural_rules = []
-
-class << self
- # Defines a general inflection exception case.
- #
- # ==== Parameters
- # singular::
- # singular form of the word
- # plural::
- # plural form of the word
- #
- # ==== Examples
- #
- # Here we define erratum/errata exception case:
- #
- # English::Inflect.word "erratum", "errata"
- #
- # In case singular and plural forms are the same omit
- # second argument on call:
- #
- # English::Inflect.word 'information'
- def self.word(singular, plural=nil)
- plural = singular unless plural
- singular_word(singular, plural)
- plural_word(singular, plural)
- end
-
- def self.clear(type = :all)
- if type == :singular || type == :all
- @singular_of = {}
- @singular_rules = []
- @singularization_rules, @singularization_regex = nil, nil
- end
- if type == :plural || type == :all
- @singular_of = {}
- @singular_rules = []
- @singularization_rules, @singularization_regex = nil, nil
- end
- end
-
-
- # Define a singularization exception.
- #
- # ==== Parameters
- # singular::
- # singular form of the word
- # plural::
- # plural form of the word
- def self.singular_word(singular, plural)
- @singular_of[plural] = singular
- @singular_of[plural.capitalize] = singular.capitalize
- end
-
- # Define a pluralization exception.
- #
- # ==== Parameters
- # singular::
- # singular form of the word
- # plural::
- # plural form of the word
- def self.plural_word(singular, plural)
- @plural_of[singular] = plural
- @plural_of[singular.capitalize] = plural.capitalize
- end
-
- # Define a general rule.
- #
- # ==== Parameters
- # singular::
- # ending of the word in singular form
- # plural::
- # ending of the word in plural form
- # whole_word::
- # for capitalization, since words can be
- # capitalized (Man => Men) #
- # ==== Examples
- # Once the following rule is defined:
- # English::Inflect.rule 'y', 'ies'
- #
- # You can see the following results:
- # irb> "fly".plural
- # => flies
- # irb> "cry".plural
- # => cries
- # Define a general rule.
-
- def self.rule(singular, plural, whole_word = false)
- singular_rule(singular, plural)
- plural_rule(singular, plural)
- word(singular, plural) if whole_word
- end
-
- # Define a singularization rule.
- #
- # ==== Parameters
- # singular::
- # ending of the word in singular form
- # plural::
- # ending of the word in plural form
- #
- # ==== Examples
- # Once the following rule is defined:
- # English::Inflect.singular_rule 'o', 'oes'
- #
- # You can see the following results:
- # irb> "heroes".singular
- # => hero
- def self.singular_rule(singular, plural)
- @singular_rules << [singular, plural]
- end
-
- # Define a plurualization rule.
- #
- # ==== Parameters
- # singular::
- # ending of the word in singular form
- # plural::
- # ending of the word in plural form
- #
- # ==== Examples
- # Once the following rule is defined:
- # English::Inflect.singular_rule 'fe', 'ves'
- #
- # You can see the following results:
- # irb> "wife".plural
- # => wives
- def self.plural_rule(singular, plural)
- @plural_rules << [singular, plural]
- end
-
- # Read prepared singularization rules.
- def self.singularization_rules
- if defined?(@singularization_regex) && @singularization_regex
- return [@singularization_regex, @singularization_hash]
- end
- # No sorting needed: Regexen match on longest string
- @singularization_regex = Regexp.new("(" + @singular_rules.map {|s,p| p}.join("|") + ")$", "i")
- @singularization_hash = Hash[*@singular_rules.flatten].invert
- [@singularization_regex, @singularization_hash]
- end
-
- # Read prepared pluralization rules.
- def self.pluralization_rules
- if defined?(@pluralization_regex) && @pluralization_regex
- return [@pluralization_regex, @pluralization_hash]
- end
- @pluralization_regex = Regexp.new("(" + @plural_rules.map {|s,p| s}.join("|") + ")$", "i")
- @pluralization_hash = Hash[*@plural_rules.flatten]
- [@pluralization_regex, @pluralization_hash]
- end
-
- attr_reader :singular_of, :plural_of
-
- # Convert an English word from plural to singular.
- #
- # "boys".singular #=> boy
- # "tomatoes".singular #=> tomato
- #
- # ==== Parameters
- # word:: word to singularize
- #
- # ==== Returns
- # :: singularized form of word
- #
- # ==== Notes
- # Aliased as singularize (a Railism)
- def self.singular(word)
- if result = singular_of[word]
- return result.dup
- end
- result = word.dup
- regex, hash = singularization_rules
- result.sub!(regex) {|m| hash[m]}
- singular_of[word] = result
- return result
- end
-
- # Alias for #singular (a Railism).
- #
- alias_method(:singularize, :singular)
-
- # Convert an English word from singular to plural.
- #
- # "boy".plural #=> boys
- # "tomato".plural #=> tomatoes
- #
- # ==== Parameters
- # word:: word to pluralize
- #
- # ==== Returns
- # :: pluralized form of word
- #
- # ==== Notes
- # Aliased as pluralize (a Railism)
- def self.plural(word)
- # special exceptions
- return "" if word == ""
- if result = plural_of[word]
- return result.dup
- end
- result = word.dup
- regex, hash = pluralization_rules
- result.sub!(regex) {|m| hash[m]}
- plural_of[word] = result
- return result
- end
-
- # Alias for #plural (a Railism).
- alias_method(:pluralize, :plural)
- end
-
- # One argument means singular and plural are the same.
-
- word 'equipment'
- word 'fish'
- word 'grass'
- word 'hovercraft'
- word 'information'
- word 'milk'
- word 'money'
- word 'moose'
- word 'plurals'
- word 'postgres'
- word 'rain'
- word 'rice'
- word 'series'
- word 'sheep'
- word 'species'
- word 'status'
-
- # Two arguments defines a singular and plural exception.
- word 'alias' , 'aliases'
- word 'analysis' , 'analyses'
- word 'axis' , 'axes'
- word 'basis' , 'bases'
- word 'buffalo' , 'buffaloes'
- word 'cactus' , 'cacti'
- word 'crisis' , 'crises'
- word 'criterion' , 'criteria'
- word 'cross' , 'crosses'
- word 'datum' , 'data'
- word 'diagnosis' , 'diagnoses'
- word 'drive' , 'drives'
- word 'erratum' , 'errata'
- word 'goose' , 'geese'
- word 'index' , 'indices'
- word 'life' , 'lives'
- word 'louse' , 'lice'
- word 'matrix' , 'matrices'
- word 'medium' , 'media'
- word 'mouse' , 'mice'
- word 'movie' , 'movies'
- word 'octopus' , 'octopi'
- word 'ox' , 'oxen'
- word 'phenomenon' , 'phenomena'
- word 'plus' , 'plusses'
- word 'potato' , 'potatoes'
- word 'quiz' , 'quizzes'
- word 'status' , 'status'
- word 'status' , 'statuses'
- word 'Swiss' , 'Swiss'
- word 'testis' , 'testes'
- word 'thesaurus' , 'thesauri'
- word 'thesis' , 'theses'
- word 'thief' , 'thieves'
- word 'tomato' , 'tomatoes'
- word 'torpedo' , 'torpedoes'
- word 'vertex' , 'vertices'
- word 'wife' , 'wives'
-
- # One-way singularization exception (convert plural to singular).
-
- # General rules.
- rule 'person' , 'people', true
- rule 'shoe' , 'shoes', true
- rule 'hive' , 'hives', true
- rule 'man' , 'men', true
- rule 'child' , 'children', true
- rule 'news' , 'news', true
- rule 'rf' , 'rves'
- rule 'af' , 'aves'
- rule 'ero' , 'eroes'
- rule 'man' , 'men'
- rule 'ch' , 'ches'
- rule 'sh' , 'shes'
- rule 'ss' , 'sses'
- rule 'ta' , 'tum'
- rule 'ia' , 'ium'
- rule 'ra' , 'rum'
- rule 'ay' , 'ays'
- rule 'ey' , 'eys'
- rule 'oy' , 'oys'
- rule 'uy' , 'uys'
- rule 'y' , 'ies'
- rule 'x' , 'xes'
- rule 'lf' , 'lves'
- rule 'ffe' , 'ffes'
- rule 'afe' , 'aves'
- rule 'ouse' , 'ouses'
- # more cases of words ending in -oses not being singularized properly
- # than cases of words ending in -osis
-# rule 'osis' , 'oses'
- rule 'ox' , 'oxes'
- rule 'us' , 'uses'
- rule '' , 's'
-
- # One-way singular rules.
-
- singular_rule 'of' , 'ofs' # proof
- singular_rule 'o' , 'oes' # hero, heroes
- singular_rule 'f' , 'ves'
-
- # One-way plural rules.
-
- #plural_rule 'fe' , 'ves' # safe, wife
- plural_rule 's' , 'ses'
- plural_rule 'ive' , 'ives' # don't want to snag wife
- plural_rule 'fe' , 'ves' # don't want to snag perspectives
-
-end