      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