1 /*
  2  * A port of the Rails/Sequel inflections class
  3  * http://sequel.rubyforge.org/rdoc/classes/Sequel/Inflections.html
  4  */
  5 
  6 var array = require("./array").array, misc = require("./misc");
  7 
  8 var comb = exports;
  9 
 10 var CAMELIZE_CONVERT_REGEXP = /_(.)/g;
 11 var DASH = '-';
 12 var UNDERSCORE = '_';
 13 var UNDERSCORE_CONVERT_REGEXP1 = /([A-Z]+)([A-Z][a-z])/g;
 14 var UNDERSCORE_CONVERT_REGEXP2 = /([a-z\d])([A-Z])/g;
 15 var UNDERSCORE_CONVERT_REPLACE = '$1_$2';
 16 
 17 var PLURALS = [], SINGULARS = [], UNCOUNTABLES = [];
 18 
 19 
 20 var _plural = function (rule, replacement) {
 21     PLURALS.unshift([rule, replacement])
 22 };
 23 var _singular = function (rule, replacement) {
 24     SINGULARS.unshift([rule, replacement])
 25 };
 26 
 27 /**
 28  * Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
 29  # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
 30  #
 31  # Examples:
 32  #   irregular 'octopus', 'octopi'
 33  #   irregular 'person', 'people'
 34  * @param singular the singular version
 35  * @param plural  the plural version
 36  */
 37 var _irregular = function (singular, plural) {
 38     _plural(new RegExp("(" + singular.substr(0, 1) + ")" + singular.substr(1) + "$"), "$1" + plural.substr(1));
 39     _singular(new RegExp("(" + plural.substr(0, 1) + ")" + plural.substr(1) + "$"), "$1" + singular.substr(1));
 40 };
 41 var _uncountable = function (words) {
 42     UNCOUNTABLES.push(misc.argsToArray(arguments))
 43     UNCOUNTABLES = array.flatten(UNCOUNTABLES);
 44 };
 45 
 46 _plural(/$/, 's');
 47 _plural(/s$/i, 's');
 48 _plural(/(alias|(?:stat|octop|vir|b)us)$/i, '$1es');
 49 _plural(/(buffal|tomat)o$/i, '$1oes');
 50 _plural(/([ti])um$/i, '$1a');
 51 _plural(/sis$/i, 'ses');
 52 _plural(/(?:([^f])fe|([lr])f)$/i, '$1$2ves');
 53 _plural(/(hive)$/i, '$1s');
 54 _plural(/([^aeiouy]|qu)y$/i, '$1ies');
 55 _plural(/(x|ch|ss|sh)$/i, '$1es');
 56 _plural(/(matr|vert|ind)ix|ex$/i, '$1ices');
 57 _plural(/([m|l])ouse$/i, '$1ice');
 58 _plural(/^(ox)$/i, "$1en");
 59 
 60 _singular(/s$/i, '');
 61 _singular(/([ti])a$/i, '$1um');
 62 _singular(/(analy|ba|cri|diagno|parenthe|progno|synop|the)ses$/i, '$1sis');
 63 _singular(/([^f])ves$/i, '$1fe');
 64 _singular(/([h|t]ive)s$/i, '$1');
 65 _singular(/([lr])ves$/i, '$1f');
 66 _singular(/([^aeiouy]|qu)ies$/i, '$1y');
 67 _singular(/(m)ovies$/i, '$1ovie');
 68 _singular(/(x|ch|ss|sh)es$/i, '$1');
 69 _singular(/([m|l])ice$/i, '$1ouse');
 70 _singular(/buses$/i, 'bus');
 71 _singular(/oes$/i, 'o');
 72 _singular(/shoes$/i, 'shoe');
 73 _singular(/(alias|(?:stat|octop|vir|b)us)es$/i, '$1');
 74 _singular(/(vert|ind)ices$/i, '$1ex');
 75 _singular(/matrices$/i, 'matrix');
 76 
 77 _irregular('person', 'people');
 78 _irregular('man', 'men');
 79 _irregular('child', 'children');
 80 _irregular('sex', 'sexes');
 81 _irregular('move', 'moves');
 82 _irregular('quiz', 'quizzes');
 83 _irregular('testis', 'testes');
 84 
 85 _uncountable("equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "news");
 86 
 87 exports.singular = _singular;
 88 exports.plural = _plural;
 89 exports.uncountable = _uncountable;
 90 
 91 /**
 92  * Converts a string to camelcase
 93  *
 94  * @example
 95  *  comb.camelize('hello_world') => helloWorld
 96  *  comb.camelize('column_name') => columnName
 97  *  comb.camelize('columnName') => columnName
 98  *  comb.camelize(null) => null
 99  *  comb.camelize() => undefined
100  *
101  * @param {String} str the string to camelize
102  * @memberOf comb
103  * @returns {String} the camelized version of the string
104  */
105 comb.camelize = function (str) {
106     var ret = str;
107     if (!misc.isUndefinedOrNull(str)) {
108         ret = str.replace(CAMELIZE_CONVERT_REGEXP, function (a, b) {
109             return b.toUpperCase();
110         });
111     }
112     return ret;
113 };
114 
115 /**
116  * The reverse of camelize. Makes an underscored form from the expression in the string.
117  *
118  * @example
119  *  comb.underscore('helloWorld') => hello_world
120  *  comb.underscore('column_name') => column_name
121  *  comb.underscore('columnName') => column_name
122  *  comb.underscore(null) => null
123  *  comb.underscore() => undefined
124  * @param {String} str The string to underscore
125  * @memberOf comb
126  * @returns {String} the underscored version of the string
127  *   */
128 comb.underscore = function (str) {
129     var ret = str;
130     if (!misc.isUndefinedOrNull(str)) {
131         ret = str.replace(UNDERSCORE_CONVERT_REGEXP1, UNDERSCORE_CONVERT_REPLACE)
132             .replace(UNDERSCORE_CONVERT_REGEXP2, UNDERSCORE_CONVERT_REPLACE)
133             .replace(DASH, UNDERSCORE).toLowerCase();
134     }
135     return ret;
136 };
137 
138 /**
139  * Singularizes and camelizes the string.  Also strips out all characters preceding
140  * and including a period (".").
141  *
142  * @example
143  *   comb.classify('egg_and_hams') => "eggAndHam"
144  *   comb.classify('post') => "post"
145  *   comb.classify('schema.post') => "post"
146  *
147  * @param {String} str the string to classify
148  * @memberOf comb
149  * @returns {String} the classified version of the string
150  **/
151 comb.classify = function (str) {
152     var ret = str;
153     if (!misc.isUndefinedOrNull(str)) {
154         ret = comb.camelize(comb.singularize(str.replace(/.*\./g, '')));
155     }
156     return ret;
157 };
158 /**
159  * Returns the plural form of the word in the string.
160  *
161  * @example
162  *  comb.pluralize("post") => "posts"
163  *  comb.pluralize("octopus") => "octopi"
164  *  comb.pluralize("sheep") => "sheep"
165  *  comb.pluralize("words") => "words"
166  *  comb.pluralize("the blue mailman") => "the blue mailmen"
167  *  comb.pluralize("CamelOctopus") => "CamelOctopi"
168  *
169  * @param {String} str the string to pluralize
170  * @memberOf comb
171  * @returns {String} the pluralized version of the string
172  **/
173 comb.pluralize = function (str) {
174     var ret = str;
175     if (!misc.isUndefinedOrNull(str)) {
176         if (UNCOUNTABLES.indexOf(str) == -1) {
177             for (var i in PLURALS) {
178                 var s = PLURALS[i], rule = s[0], replacement = s[1];
179                 if ((ret = ret.replace(rule, replacement)) != str) {
180                     break;
181                 }
182             }
183         }
184     }
185     return ret;
186 };
187 /**
188  * The reverse of pluralize, returns the singular form of a word in a string.
189  *
190  * @example
191  *   comb.singularize("posts") => "post"
192  *   comb.singularize("octopi")=> "octopus"
193  *   comb.singularize("sheep") => "sheep"
194  *   comb.singularize("word") => "word"
195  *   comb.singularize("the blue mailmen") => "the blue mailman"
196  *   comb.singularize("CamelOctopi") => "CamelOctopus"
197  *
198  * @param {String} str the string to singularize
199  * @memberOf comb
200  * @returns {String} the singularized version of the string
201  * */
202 comb.singularize = function (str) {
203     var ret = str;
204     if (!misc.isUndefinedOrNull(str)) {
205         if (UNCOUNTABLES.indexOf(str) == -1) {
206             for (var i in SINGULARS) {
207                 var s = SINGULARS[i], rule = s[0], replacement = s[1];
208                 if ((ret = ret.replace(rule, replacement)) != str) {
209                     break;
210                 }
211             }
212         }
213     }
214     return ret;
215 };
216