Skip to content

Commit

Permalink
Added util.Inflector to handle pluralization and singularization.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ron Hopper committed Sep 15, 2009
1 parent 889cb5c commit dfd2713
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 0 deletions.
1 change: 1 addition & 0 deletions spec/util/index.cfm
@@ -0,0 +1 @@
<cfmodule template="/cfspec/suite.cfm">
108 changes: 108 additions & 0 deletions spec/util/inflectorSpec.cfm
@@ -0,0 +1,108 @@
<cfimport taglib="/cfspec" prefix="">

<describe hint="Inflector">

<before>
<cfset inflector = createObject("component", "muon.util.Inflector").init()>
</before>

<describe hint="plurals">

<it should="pluralize regular words">
<cfset $(inflector).pluralize("post").shouldEqual("posts")>
</it>

<it should="pluralize irregular words">
<cfset $(inflector).pluralize("octopus").shouldEqual("octopi")>
</it>

<it should="pluralize uncountable words">
<cfset $(inflector).pluralize("sheep").shouldEqual("sheep")>
</it>

<it should="not re-pluralize words">
<cfset $(inflector).pluralize("words").shouldEqual("words")>
</it>

<it should="pluralize phrases">
<cfset $(inflector).pluralize("the blue mailman").shouldEqual("the blue mailmen")>
</it>

<it should="pluralize compound words">
<cfset $(inflector).pluralize("CamelOctopus").shouldEqual("CamelOctopi")>
</it>

<it should="pluralize according to user-defined rules">
<cfset inflector.plural("osha(d|t)$", "osha\1ki")>
<cfset $(inflector).pluralize("loshad").shouldEqual("loshadki")>
</it>

</describe>

<describe hint="singulars">

<it should="singularize regular words">
<cfset $(inflector).singularize("posts").shouldEqual("post")>
</it>

<it should="singularize irregular words">
<cfset $(inflector).singularize("octopi").shouldEqual("octopus")>
</it>

<it should="singularize uncountable words">
<cfset $(inflector).singularize("sheep").shouldEqual("sheep")>
</it>

<it should="not re-singularize words">
<cfset $(inflector).singularize("word").shouldEqual("word")>
</it>

<it should="singularize phrases">
<cfset $(inflector).singularize("the blue mailmen").shouldEqual("the blue mailman")>
</it>

<it should="singularize compound words">
<cfset $(inflector).singularize("CamelOctopi").shouldEqual("CamelOctopus")>
</it>

<it should="singularize according to user-defined rules">
<cfset inflector.singular("osha(d|t)ki$", "osha\1")>
<cfset $(inflector).singularize("loshadki").shouldEqual("loshad")>
</it>

</describe>

<describe hint="irregulars">

<before>
<cfset inflector.irregular("foo", "fooz")>
</before>

<it should="pluralize user-defined irregulars">
<cfset $(inflector).pluralize("foo").shouldEqual("fooz")>
</it>

<it should="singularize user-defined irregulars">
<cfset $(inflector).singularize("fooz").shouldEqual("foo")>
</it>

</describe>

<describe hint="uncountables">

<it should="recognize common uncountable words">
<cfset $(inflector).shouldBeUncountable("fish")>
</it>

<it should="recognize common countable words">
<cfset $(inflector).shouldNotBeUncountable("cat")>
</it>

<it should="recognize user-defined uncountable words">
<cfset inflector.uncountable("dog,cat,fox")>
<cfset $(inflector).shouldBeUncountable("cat")>
</it>

</describe>

</describe>
126 changes: 126 additions & 0 deletions util/Inflector.cfc
@@ -0,0 +1,126 @@
<cfcomponent><cfscript>

function init() {
_plurals = [];
_singulars = [];
_uncountables = "";

_loadDefaultInflections();

return this;
}

function pluralize(word) {
var local = {};

if (trim(word) eq "") return word;
if (isUncountable(word)) return word;

for (local.i = 1; local.i <= arrayLen(_plurals); local.i++) {
local.pair = _plurals[local.i];
if (reFindNoCase(local.pair.rule, word)) {
return reReplaceNoCase(word, local.pair.rule, local.pair.replacement);
}
}

return word;
}

function singularize(word) {
var local = {};

if (trim(word) eq "") return word;
if (isUncountable(word)) return word;

for (local.i = 1; local.i <= arrayLen(_singulars); local.i++) {
local.pair = _singulars[local.i];
if (reFindNoCase(local.pair.rule, word)) {
return reReplaceNoCase(word, local.pair.rule, local.pair.replacement);
}
}

return word;
}

function isUncountable(word) {
return listFindNoCase(_uncountables, trim(word));
}

function plural(rule, replacement) {
arrayPrepend(_plurals, arguments);
}

function singular(rule, replacement) {
arrayPrepend(_singulars, arguments);
}

function irregular(singularWord, pluralWord) {
var local = {};

local.rule = reReplace(singularWord, "^(.)(.*)$", "(\1)\2$");
local.replacement = reReplace(pluralWord, "^(.)(.*)$", "\\1\2");
plural(local.rule, local.replacement);

local.rule = reReplace(pluralWord, "^(.)(.*)$", "(\1)\2$");
local.replacement = reReplace(singularWord, "^(.)(.*)$", "\\1\2");
singular(local.rule, local.replacement);
}

function uncountable(words) {
_uncountables = listAppend(_uncountables, words);
}

function _loadDefaultInflections() {
plural("$", "s");
plural("s$", "s");
plural("(ax|test)is$", "\1es");
plural("(octop|vir)us$", "\1i");
plural("(alias|status)$", "\1es");
plural("(bu)s$", "\1ses");
plural("(buffal|tomat)o$", "\1oes");
plural("([ti])um$", "\1a");
plural("sis$", "ses");
plural("(?:([^f])fe|([lr])f)$", "\1\2ves");
plural("(hive)$", "\1s");
plural("([^aeiouy]|qu)y$", "\1ies");
plural("(x|ch|ss|sh)$", "\1es");
plural("(matr|vert|ind)ix|ex$", "\1ices");
plural("([m|l])ouse$", "\1ice");
plural("^(ox)$", "\1en");
plural("(quiz)$", "\1zes");

singular("s$", "");
singular("(n)ews$", "\1ews");
singular("([ti])a$", "\1um");
singular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "\1\2sis");
singular("(^analy)ses$", "\1sis");
singular("([^f])ves$", "\1fe");
singular("(hive)s$", "\1");
singular("(tive)s$", "\1");
singular("([lr])ves$", "\1f");
singular("([^aeiouy]|qu)ies$", "\1y");
singular("(s)eries$", "\1eries");
singular("(m)ovies$", "\1ovie");
singular("(x|ch|ss|sh)es$", "\1");
singular("([m|l])ice$", "\1ouse");
singular("(bus)es$", "\1");
singular("(o)es$", "\1");
singular("(shoe)s$", "\1");
singular("(cris|ax|test)es$", "\1is");
singular("(octop|vir)i$", "\1us");
singular("(alias|status)es$", "\1");
singular("^(ox)en", "\1");
singular("(vert|ind)ices$", "\1ex");
singular("(matr)ices$", "\1ix");
singular("(quiz)zes$", "\1");

irregular("person", "people");
irregular("man", "men");
irregular("child", "children");
irregular("sex", "sexes");
irregular("move", "moves");

uncountable("equipment,information,rice,money,species,series,fish,sheep");
}

</cfscript></cfcomponent>

0 comments on commit dfd2713

Please sign in to comment.