lib/lotus/utils/inflector.rb in lotus-utils-0.5.2 vs lib/lotus/utils/inflector.rb in lotus-utils-0.6.0
- old
+ new
@@ -1,65 +1,47 @@
+require 'lotus/utils/class_attribute'
+
module Lotus
module Utils
# String inflector
#
# @since 0.4.1
- # @api private
module Inflector
- # Rule for irregular plural
+ # Rules for irregular plurals
#
- # @since 0.4.1
+ # @since 0.6.0
# @api private
- class IrregularRule
- # @since 0.4.1
+ class IrregularRules
+ # @since 0.6.0
# @api private
def initialize(rules)
@rules = rules
- @rules.freeze
end
- # @since 0.4.1
+ # @since 0.6.0
# @api private
+ def add(key, value)
+ @rules[key.downcase] = value.downcase
+ end
+
+ # @since 0.6.0
+ # @api private
def ===(other)
key = other.downcase
@rules.key?(key) || @rules.value?(key)
end
- # @since 0.4.1
+ # @since 0.6.0
# @api private
def apply(string)
key = string.downcase
result = @rules[key] || @rules.rassoc(key).last
string[0] + result[1..-1]
end
end
- # Rule for irregular plural, that uses a suffix.
- #
- # @since 0.4.1
- # @api private
- class SuffixRule < IrregularRule
- def initialize(matcher, replacement, rules)
- super(rules)
- @matcher = matcher
- @replacement = replacement
- end
-
- # @since 0.4.1
- # @api private
- def ===(other)
- @rules.key?(other.downcase)
- end
-
- # @since 0.4.1
- # @api private
- def apply(string)
- string.sub(@matcher, @replacement)
- end
- end
-
# Matcher for blank strings
#
# @since 0.4.1
# @api private
BLANK_STRING_MATCHER = /\A[[:space:]]*\z/.freeze
@@ -78,10 +60,14 @@
# @since 0.4.1
# @api private
EAUX = 'eaux'.freeze
+ # @since 0.6.0
+ # @api private
+ ES = 'es'.freeze
+
# @since 0.4.1
# @api private
F = 'f'.freeze
# @since 0.4.1
@@ -134,10 +120,18 @@
# @since 0.4.1
# @api private
MINA = 'mina'.freeze
+ # @since 0.6.0
+ # @api private
+ NA = 'na'.freeze
+
+ # @since 0.6.0
+ # @api private
+ NON = 'non'.freeze
+
# @since 0.4.1
# @api private
O = 'o'.freeze
# @since 0.4.1
@@ -158,10 +152,14 @@
# @since 0.4.1
# @api private
SSES = 'sses'.freeze
+ # @since 0.6.0
+ # @api private
+ TA = 'ta'.freeze
+
# @since 0.4.1
# @api private
UM = 'um'.freeze
# @since 0.4.1
@@ -186,70 +184,18 @@
# @since 0.4.1
# @api private
Y = 'y'.freeze
- # Plural rule "is" => "es"
- #
- # @since 0.4.1
- # @api private
- PLURAL_IS_ES = SuffixRule.new( /is\z/, 'es', {
- 'analysis' => true,
- 'axis' => true,
- 'basis' => true,
- 'crisis' => true,
- 'diagnosis' => true,
- 'ellipsis' => true,
- 'hypothesis' => true,
- 'oasis' => true,
- 'paralysis' => true,
- 'parenthesis' => true,
- 'synopsis' => true,
- 'synthesis' => true,
- 'thesis' => true,
- })
+ include Utils::ClassAttribute
- # Plural rule "is" => "ides"
+ # Irregular rules for plurals
#
- # @since 0.4.1
+ # @since 0.6.0
# @api private
- PLURAL_IS_IDES = SuffixRule.new( /is\z/, 'ides', {
- 'clitoris' => true,
- 'iris' => true,
- })
-
- # Plural rule "f" => "s"
- #
- # @since 0.4.1
- # @api private
- PLURAL_F_S = SuffixRule.new( /\z/, 's', {
- 'chief' => true,
- 'spoof' => true,
- })
-
- # Plural rule "o" => "oes"
- #
- # @since 0.4.1
- # @api private
- PLURAL_O_OES = SuffixRule.new( /\z/, 'es', {
- 'buffalo' => true,
- 'domino' => true,
- 'echo' => true,
- 'embargo' => true,
- 'hero' => true,
- 'mosquito' => true,
- 'potato' => true,
- 'tomato' => true,
- 'torpedo' => true,
- 'veto' => true,
- })
-
- # Irregular rules
- #
- # @since 0.4.1
- # @api private
- PLURAL_IRREGULAR = IrregularRule.new({
+ class_attribute :plurals
+ self.plurals = IrregularRules.new({
# irregular
'cactus' => 'cacti',
'child' => 'children',
'corpus' => 'corpora',
'foot' => 'feet',
@@ -274,100 +220,18 @@
'offspring' => 'offspring',
'rice' => 'rice',
'series' => 'series',
'sheep' => 'sheep',
'species' => 'species',
- # a => ae
- 'alumna' => 'alumnae',
- 'alga' => 'algae',
- 'vertebra' => 'vertebrae',
- 'persona' => 'personae',
- 'antenna' => 'antennae',
- 'formula' => 'formulae',
- 'nebula' => 'nebulae',
- 'vita' => 'vitae',
- # on => a
- 'criterion' => 'criteria',
- 'perihelion' => 'perihelia',
- 'aphelion' => 'aphelia',
- 'phenomenon' => 'phenomena',
- 'prolegomenon' => 'prolegomena',
- 'noumenon' => 'noumena',
- 'organon' => 'organa',
- 'asyndeton' => 'asyndeta',
- 'hyperbaton' => 'hyperbata',
- # us => i
- 'alumnus' => 'alumni',
- 'alveolus' => 'alveoli',
- 'bacillus' => 'bacilli',
- 'bronchus' => 'bronchi',
- 'locus' => 'loci',
- 'nucleus' => 'nuclei',
- 'stimulus' => 'stimuli',
- 'meniscus' => 'menisci',
- 'thesaurus' => 'thesauri',
- # a => ata
- 'anathema' => 'anathemata',
- 'enema' => 'enemata',
- 'oedema' => 'oedemata',
- 'bema' => 'bemata',
- 'enigma' => 'enigmata',
- 'sarcoma' => 'sarcomata',
- 'carcinoma' => 'carcinomata',
- 'gumma' => 'gummata',
- 'schema' => 'schemata',
- 'charisma' => 'charismata',
- 'lemma' => 'lemmata',
- 'soma' => 'somata',
- 'diploma' => 'diplomata',
- 'lymphoma' => 'lymphomata',
- 'stigma' => 'stigmata',
- 'dogma' => 'dogmata',
- 'magma' => 'magmata',
- 'stoma' => 'stomata',
- 'drama' => 'dramata',
- 'melisma' => 'melismata',
- 'trauma' => 'traumata',
- 'edema' => 'edemata',
- 'miasma' => 'miasmata',
- # s => es
- 'acropolis' => 'acropolises',
- 'chaos' => 'chaoses',
- 'lens' => 'lenses',
- 'aegis' => 'aegises',
- 'cosmos' => 'cosmoses',
- 'mantis' => 'mantises',
- 'alias' => 'aliases',
- 'dais' => 'daises',
- 'marquis' => 'marquises',
- 'asbestos' => 'asbestoses',
- 'digitalis' => 'digitalises',
- 'metropolis' => 'metropolises',
- 'atlas' => 'atlases',
- 'epidermis' => 'epidermises',
- 'pathos' => 'pathoses',
- 'bathos' => 'bathoses',
- 'ethos' => 'ethoses',
- 'pelvis' => 'pelvises',
- 'bias' => 'biases',
- 'gas' => 'gases',
- 'polis' => 'polises',
- 'caddis' => 'caddises',
- 'rhinoceros' => 'rhinoceroses',
- 'cannabis' => 'cannabises',
- 'glottis' => 'glottises',
- 'sassafras' => 'sassafrases',
- 'canvas' => 'canvases',
- 'ibis' => 'ibises',
- 'trellis' => 'trellises',
})
- # Irregular rules
+ # Irregular rules for singulars
#
- # @since 0.4.1
+ # @since 0.6.0
# @api private
- SINGULAR_IRREGULAR = IrregularRule.new({
+ class_attribute :singulars
+ self.singulars = IrregularRules.new({
# irregular
'cacti' => 'cactus',
'children'=> 'child',
'corpora' => 'corpus',
'feet' => 'foot',
@@ -393,93 +257,78 @@
'rice' => 'rice',
'series' => 'series',
'sheep' => 'sheep',
'species' => 'species',
'police' => 'police',
- # ae => a
- 'alumnae' => 'alumna',
- 'algae' => 'alga',
- 'vertebrae' => 'vertebra',
- 'personae' => 'persona',
- 'antennae' => 'antenna',
- 'formulae' => 'formula',
- 'nebulae' => 'nebula',
- 'vitae' => 'vita',
- # a = on
- 'criteria' => 'criterion',
- 'perihelia' => 'perihelion',
- 'aphelia' => 'aphelion',
- 'phenomena' => 'phenomenon',
- 'prolegomena' => 'prolegomenon',
- 'noumena' => 'noumenon',
- 'organa' => 'organon',
- 'asyndeta' => 'asyndeton',
- 'hyperbata' => 'hyperbaton',
- # ses => s
- 'acropolises' => 'acropolis',
- 'chaoses' => 'chaos',
- 'lenses' => 'lens',
- 'aegises' => 'aegis',
- 'cosmoses' => 'cosmos',
- 'mantises' => 'mantis',
- 'aliases' => 'alias',
- 'daises' => 'dais',
- 'marquises' => 'marquis',
- 'asbestoses' => 'asbestos',
- 'digitalises' => 'digitalis',
- 'metropolises' => 'metropolis',
- 'atlases' => 'atlas',
- 'epidermises' => 'epidermis',
- 'pathoses' => 'pathos',
- 'bathoses' => 'bathos',
- 'ethoses' => 'ethos',
- 'pelvises' => 'pelvis',
- 'biases' => 'bias',
- 'gases' => 'gas',
- 'polises' => 'polis',
- 'caddises' => 'caddis',
- 'rhinoceroses' => 'rhinoceros',
- 'cannabises' => 'cannabis',
- 'glottises' => 'glottis',
- 'sassafrases' => 'sassafras',
- 'canvases' => 'canvas',
- 'ibises' => 'ibis',
- 'trellises' => 'trellis',
# fallback
- 'hives' => 'hive',
- # ices => ex
- "codices" => "codex",
- "murices" => "murex",
- "silices" => "silex",
- "apices" => "apex",
- "latices" => "latex",
- "vertices" => "vertex",
- "cortices" => "cortex",
- "pontifices" => "pontifex",
- "vortices" => "vortex",
- "indices" => "index",
- "simplices" => "simplex",
- # ices => ix
- "radices" => "radix",
- "helices" => "helix",
- "appendices" => "appendix",
- # es => is
- "axes" => "axis",
- "analyses" => "analysis",
- "bases" => "basis",
- "crises" => "crisis",
- "diagnoses" => "diagnosis",
- "ellipses" => "ellipsis",
- "hypotheses" => "hypothesis",
- "oases" => "oasis",
- "paralyses" => "paralysis",
- "parentheses" => "parenthesis",
- "syntheses" => "synthesis",
- "synopses" => "synopsis",
- "theses" => "thesis",
+ 'hives' => 'hive',
+ 'horses' => 'horse',
})
+ # Block for custom inflection rules.
+ #
+ # @param [Proc] blk custom inflections
+ #
+ # @since 0.6.0
+ #
+ # @see Lotus::Utils::Inflector.exception
+ # @see Lotus::Utils::Inflector.uncountable
+ #
+ # @example
+ # require 'lotus/utils/inflector'
+ #
+ # Lotus::Utils::Inflector.inflections do
+ # exception 'analysis', 'analyses'
+ # exception 'alga', 'algae'
+ # uncountable 'music', 'butter'
+ # end
+ def self.inflections(&blk)
+ class_eval(&blk)
+ end
+
+ # Add a custom inflection exception
+ #
+ # @param [String] singular form
+ # @param [String] plural form
+ #
+ # @since 0.6.0
+ #
+ # @see Lotus::Utils::Inflector.inflections
+ # @see Lotus::Utils::Inflector.uncountable
+ #
+ # @example
+ # require 'lotus/utils/inflector'
+ #
+ # Lotus::Utils::Inflector.inflections do
+ # exception 'alga', 'algae'
+ # end
+ def self.exception(singular, plural)
+ singulars.add(plural, singular)
+ plurals.add(singular, plural)
+ end
+
+ # Add an uncountable word
+ #
+ # @param [Array<String>] words
+ #
+ # @since 0.6.0
+ #
+ # @see Lotus::Utils::Inflector.inflections
+ # @see Lotus::Utils::Inflector.exception
+ #
+ # @example
+ # require 'lotus/utils/inflector'
+ #
+ # Lotus::Utils::Inflector.inflections do
+ # uncountable 'music', 'art'
+ # end
+ def self.uncountable(*words)
+ Array(words).each do |word|
+ exception(word, word)
+ end
+ end
+
# Pluralize the given string
#
# @param string [String] a string to pluralize
#
# @return [String,NilClass] the pluralized string, if present
@@ -488,40 +337,40 @@
# @since 0.4.1
def self.pluralize(string)
return string if string.nil? || string.match(BLANK_STRING_MATCHER)
case string
- when PLURAL_IRREGULAR
- PLURAL_IRREGULAR.apply(string)
+ when plurals
+ plurals.apply(string)
when /\A((.*)[^aeiou])ch\z/
$1 + CHES
when /\A((.*)[^aeiou])y\z/
$1 + IES
when /\A(.*)(ex|ix)\z/
$1 + ICES
when /\A(.*)(eau|#{ EAUX })\z/
$1 + EAUX
when /\A(.*)x\z/
$1 + XES
+ when /\A(.*)ma\z/
+ string + TA
when /\A(.*)(um|#{ A })\z/
$1 + A
when /\A(.*)(ouse|#{ ICE })\z/
$1 + ICE
- when PLURAL_O_OES
- PLURAL_O_OES.apply(string)
+ when /\A(buffal|domin|ech|embarg|her|mosquit|potat|tomat)#{ O }\z/i
+ $1 + OES
when /\A(.*)(en|#{ INA })\z/
$1 + INA
- when PLURAL_F_S
- PLURAL_F_S.apply(string)
when /\A(.*)(?:([^f]))f[e]*\z/
$1 + $2 + VES
when /\A(.*)us\z/
$1 + USES
- when PLURAL_IS_ES
- PLURAL_IS_ES.apply(string)
- when PLURAL_IS_IDES
- PLURAL_IS_IDES.apply(string)
+ when /\A(.*)non\z/
+ $1 + NA
+ when /\A((.*)[^aeiou])is\z/
+ $1 + ES
when /\A(.*)ss\z/
$1 + SSES
when /s\z/
string
else
@@ -539,12 +388,12 @@
# @since 0.4.1
def self.singularize(string)
return string if string.nil? || string.match(BLANK_STRING_MATCHER)
case string
- when SINGULAR_IRREGULAR
- SINGULAR_IRREGULAR.apply(string)
+ when singulars
+ singulars.apply(string)
when /\A.*[^aeiou]#{CHES}\z/
string.sub(CHES, CH)
when /\A.*[^aeiou]#{IES}\z/
string.sub(IES, Y)
when /\A(.*)#{ICE}\z/
@@ -571,9 +420,13 @@
$1 + IFE
when /\A(.*)#{VES}\z/
$1 + F
when /\A(.*)#{I}\z/
$1 + US
+ when /\A(.*)ae\z/
+ $1 + A
+ when /\A(.*)na\z/
+ $1 + NON
when /\A(.*)#{A}\z/
$1 + UM
when /[^s]\z/
string
else