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