├── .gitignore ├── .jscsrc ├── .tm_properties ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── bower.json ├── gulpfile.js ├── index.js ├── lib └── lodash-inflection.js ├── package.json ├── script └── hook.sh └── test ├── gsub.js ├── index.html ├── inflector.js ├── irregular.js ├── ordinalize.js ├── plural.js ├── pluralize.js ├── setup.js ├── singular.js ├── singularize.js ├── titleize.js └── uncountable.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by http://www.gitignore.io 2 | 3 | ### Node ### 4 | # Logs 5 | logs 6 | *.log 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for bower dependencies 14 | bower_components 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directory 29 | # Commenting this out is preferred by some people, see 30 | # https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 31 | node_modules 32 | 33 | # Users Environment Variables 34 | .lock-wscript 35 | 36 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "google", 3 | "requireParenthesesAroundIIFE": true, 4 | "requireSpaceAfterLineComment": true, 5 | "requireSpaceBeforeBlockStatements": true, 6 | "maximumLineLength": 120, 7 | "validateLineBreaks": "LF", 8 | "validateIndentation": 2, 9 | "disallowMultipleVarDecl": true, 10 | "disallowEmptyBlocks": true, 11 | "disallowMultipleLineStrings": true, 12 | "disallowSpacesInsideParentheses": true, 13 | "disallowSpacesInsideArrayBrackets": true, 14 | "disallowSpacesInsideObjectBrackets": true, 15 | "disallowPaddingNewlinesInBlocks": true, 16 | "disallowImplicitTypeConversion": [ 17 | "numeric", 18 | "boolean", 19 | "binary", 20 | "string" 21 | ], 22 | "disallowKeywords": [ 23 | "with" 24 | ], 25 | "excludeFiles": [ 26 | "node_modules/**" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.tm_properties: -------------------------------------------------------------------------------- 1 | excludeInFolderSearch = "{$excludeInFolderSearch,node_modules}" 2 | 3 | excludeInFileChooser = "{$excludeInFileChooser,node_modules}" 4 | 5 | # Settings 6 | softWrap = false 7 | softTabs = true 8 | tabSize = 2 9 | include = "{$include,.jscsrc,.gitignore,.travis.yml}" 10 | 11 | [ text.html.markdown ] 12 | softWrap = true 13 | softTabs = false 14 | tabSize = 2 15 | 16 | [ .jscsrc ] 17 | fileType = "source.json" 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | sudo: false 4 | node_js: 5 | - "5.0" 6 | - "4.0" 7 | - "0.12" 8 | - "0.11" 9 | - "0.10" 10 | before_script: 11 | - npm run jscov 12 | after_script: 13 | - npm run coverage 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Jeremy Ruppel 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install: .git/hooks/pre-commit 2 | install: .git/hooks/pre-push 3 | 4 | .git/hooks/pre-commit: script/hook.sh 5 | cp $< $@ 6 | 7 | .git/hooks/pre-push: script/hook.sh 8 | cp $< $@ 9 | 10 | release-patch: 11 | $(shell npm bin)/mversion patch -m 12 | git push origin master 13 | git push origin --tags 14 | npm publish 15 | 16 | release-minor: 17 | $(shell npm bin)/mversion minor -m 18 | git push origin master 19 | git push origin --tags 20 | npm publish 21 | 22 | release-major: 23 | $(shell npm bin)/mversion major -m 24 | git push origin master 25 | git push origin --tags 26 | npm publish 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## lodash-inflection 2 | 3 | > [![NPM version][npm-badge]][npm] 4 | > [![Build Status][travis-badge]][travis-ci] 5 | > [![Coverage Status][coveralls-badge]][coveralls] 6 | 7 | This is a fork of [Jeremy Ruppel underscore.inflection](https://github.com/jeremyruppel/underscore.inflection) for lodash. 8 | 9 | **Another javascript inflector?!** 10 | 11 | I'll be the first to say it; this isn't the first port of [ActiveSupport::Inflector][activesupport] to js. Not by a long shot. But I'll definitely take [lodash][lodash] mixins over extending String.prototype or other clunky implementations any day. 12 | 13 | Also, this one has tests! 14 | 15 | Usage with CommonJS 16 | ------------------- 17 | 18 | ``` 19 | npm install lodash 20 | npm install lodash-inflection 21 | ``` 22 | 23 | ```js 24 | var _ = require("lodash"); 25 | _.mixin(require("lodash-inflection")); 26 | ``` 27 | 28 | Inflections 29 | ----------- 30 | 31 | The methods listed below are the ones you'll be using 99% of the time. 32 | 33 | ### pluralize 34 | 35 | **Signature:** `_.pluralize(word)` 36 | 37 | `pluralize` pluralizes the string passed to it. 38 | 39 | // functional style 40 | _.pluralize('word'); // => 'words' 41 | 42 | // object-oriented style 43 | _('word').pluralize(); // => 'words' 44 | 45 | It also can accept a number as the second parameter. If a number is provided, it will pluralize the word to match the number. 46 | 47 | _('word').pluralize(0); // => 'words' 48 | _('word').pluralize(1); // => 'word' 49 | _('word').pluralize(1.5); // => 'words' 50 | 51 | Optionally, you can pass `true` as a third parameter. If found, this will include the count with the output. 52 | 53 | _('word').pluralize(0, true); // => '0 words' 54 | _('word').pluralize(1, true); // => '1 word' 55 | 56 | ### singularize 57 | 58 | **Signature:** `_.singularize(word)` 59 | 60 | `singularize` returns the singular version of the plural passed to it. 61 | 62 | // functional style 63 | _.singularize('words'); // => 'word' 64 | 65 | // object-oriented style 66 | _('words').singularize(); // => 'word' 67 | 68 | ### gsub 69 | 70 | **Signature:** `_.gsub(word, rule, replacement)` 71 | 72 | `gsub` is a method that is just slightly different than our standard `String#replace`. The main differences are that it matches globally every time, and if no substitution is made it returns `null`. It accepts a string for `word` and `replacement`, and `rule` can be either a string or a regex. 73 | 74 | // functional style 75 | _.gsub('word', /wo/, 'ne'); // => 'nerd' 76 | 77 | // object-oriented style 78 | _('word').gsub(/wo/, 'ne'); // => 'nerd' 79 | 80 | ### ordinalize 81 | 82 | **Signature:** `_.ordinalize(number)` 83 | 84 | `ordinalize` adds an ordinal suffix to `number`. 85 | 86 | _.ordinalize(1); // => '1st' 87 | _.ordinalize("5"); // => '5th' 88 | _.ordinalize(11); // => '11th' 89 | _.ordinalize(1033); // => '1033rd' 90 | _.ordinalize(-15); // => '-15th' 91 | 92 | ### titleize 93 | 94 | **Signature:** `_.titleize( words )` 95 | 96 | `titleize` capitalizes the first letter of each word in the string `words`. It preserves the existing whitespace. 97 | 98 | _.titleize('banana'); // => 'Banana' 99 | _.titleize('banana potato fork'); // => 'Banana Potato Fork' 100 | _.titleize('banana potato\tfork'); // => 'Banana Potato\tFork' 101 | 102 | ## Configuring the Inflector 103 | 104 | Should you ever need to configure the Inflector beyond the defaults, use these methods to do so: 105 | 106 | ### plural 107 | 108 | **Signature:** `_.plural(rule, replacement)` 109 | 110 | `plural` creates a new pluralization rule for the inflector. `rule` can be either a string or a regex. 111 | 112 | // functional style with explicit string 113 | _.plural('axis', 'axes'); 114 | 115 | // object-oriented style with explicit string 116 | _('axis').plural('axes'); 117 | 118 | // functional style with regex 119 | _.plural(/(ax)is$/i, '$1es'); 120 | 121 | // object-oriented style with regex 122 | _(/(ax)is$/i).plural('$1es'); 123 | 124 | ### singular 125 | 126 | **Signature:** `_.singular(rule, replacement)` 127 | 128 | `singular` creates a new singularization rule for the inflector. `rule` can be either a string or a regex. 129 | 130 | // functional style with explicit string 131 | _.singular('data', 'datum'); 132 | 133 | // object-oriented style with explicit string 134 | _('data').singular('datum'); 135 | 136 | // functional style with regex 137 | _.singular(/(t)a$/i, '$1um'); 138 | 139 | // object-oriented style with regex 140 | _(/(t)a$/i).singular('$1um'); 141 | 142 | ### irregular 143 | 144 | **Signature:** `_.irregular(singular, plural)` 145 | 146 | `irregular` is a shortcut method to create both a pluralization and singularization rule for the word at the same time. You must supply both the singular form and the plural form as explicit strings. 147 | 148 | // functional style 149 | _.irregular('haxor', 'hax0rs!'); 150 | 151 | // object-oriented style 152 | _('haxor').irregular('hax0rs!'); 153 | 154 | ### uncountable 155 | 156 | **Signature:** `_.uncountable(word)` 157 | 158 | `uncountable` creates a new uncountable rule for `word`. Uncountable words do not get pluralized or singularized. 159 | 160 | // functional style 161 | _.uncountable('equipment'); 162 | 163 | // object-oriented style 164 | _('equipment').uncountable(); 165 | 166 | ### resetInflections 167 | 168 | **Signature:** `_.resetInflections()` 169 | 170 | `resetInflections` resets the inflector's rules to their initial state, clearing out any custom rules that have been added. 171 | 172 | ## Thanks to... 173 | 174 | The [Rails][rails] team for [ActiveSupport][activesupport] 175 | 176 | The [lodash][lodash] team for [lodash][lodash] 177 | 178 | These other Inflector implementations: 179 | 180 | - [active-support-for-javascript](http://code.google.com/p/active-support-for-javascript/) 181 | - [inflection-js](http://code.google.com/p/inflection-js/) 182 | - [Javascript Rails-like inflector](http://snippets.dzone.com/posts/show/3205) 183 | 184 | Though no code was taken directly from them, they deserve plenty of props for doing it before me. If lodash isn't your thing, check them out! 185 | 186 | ## Contributors 187 | 188 | ``` 189 | 65 Jeremy Ruppel 190 | 15 Daniel Perez 191 | 7 Landon Schropp 192 | 2 Johnathon Sanders 193 | 2 Seggy Umboh 194 | 1 Dayton Nolan 195 | 1 Joseph Spens 196 | 1 Kris Neuharth 197 | 1 Monica Olinescu 198 | 1 Sam Dornan 199 | 1 Shane Riley 200 | 1 bramski 201 | 1 maratfakhreev 202 | 1 trevor 203 | ``` 204 | 205 | ## License 206 | 207 | [MIT License][LICENSE] 208 | 209 | [npm]: http://badge.fury.io/js/lodash-inflection 210 | [npm-badge]: https://badge.fury.io/js/lodash-inflection.svg 211 | [travis-ci]: https://travis-ci.org/tuvistavie/lodash-inflection 212 | [travis-badge]: https://travis-ci.org/tuvistavie/lodash-inflection.svg?branch=master 213 | [coveralls]: https://coveralls.io/r/tuvistavie/lodash-inflection?branch=master 214 | [coveralls-badge]: https://img.shields.io/coveralls/tuvistavie/lodash-inflection.svg 215 | [rails]: https://github.com/rails/rails 216 | [activesupport]: https://github.com/rails/rails/tree/master/activesupport 217 | [lodash]: http://lodash.com 218 | [LICENSE]: https://github.com/tuvistavie/lodash-inflection/blob/master/LICENSE 219 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lodash-inflection", 3 | "version": "1.4.1", 4 | "dependencies": { 5 | "lodash": "~4.15.0" 6 | }, 7 | "main": "lib/lodash-inflection.js", 8 | "ignore": [ 9 | "lib-cov", 10 | "test" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var jscs = require('gulp-jscs'); 3 | var jscov = require('gulp-jscoverage'); 4 | var contribs = require('gulp-contribs'); 5 | 6 | gulp.task('jscs', function() { 7 | return gulp.src([ 8 | 'lib/*.js', 9 | 'test/*.js' 10 | ]).pipe(jscs()); 11 | }); 12 | 13 | gulp.task('jscov', function() { 14 | return gulp.src('lib/*.js') 15 | .pipe(jscov('index.js')) 16 | .pipe(gulp.dest('./lib-cov')); 17 | }); 18 | 19 | gulp.task('contribs', function() { 20 | return gulp.src('README.md') 21 | .pipe(contribs()) 22 | .pipe(gulp.dest('./')); 23 | }); 24 | 25 | gulp.task('docs', ['contribs']); 26 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = process.env.JSCOV 2 | ? require('./lib-cov') 3 | : require('./lib/lodash-inflection.js'); 4 | -------------------------------------------------------------------------------- /lib/lodash-inflection.js: -------------------------------------------------------------------------------- 1 | // lodash-inflection.js 2 | // (c) 2014 Jeremy Ruppel 3 | // lodash-inflection is freely distributable under the MIT license. 4 | // Portions of lodash-inflection are inspired or borrowed from ActiveSupport 5 | // Version 1.0.0 6 | 7 | (function(root, factory) { 8 | if (typeof define === 'function' && define.amd) { 9 | // AMD. Register as an anonymous module. 10 | define(['lodash'], factory); 11 | } else if (typeof require === 'function' && typeof exports === 'object') { 12 | // Node. Does not work with strict CommonJS, but 13 | // only CommonJS-like environments that support module.exports, 14 | // like Node. 15 | module.exports = factory(require('lodash').runInContext()); 16 | } else { 17 | // Browser globals (root is window) 18 | factory(root._); 19 | } 20 | })(this, function(_, undefined) { 21 | var plurals = []; 22 | var singulars = []; 23 | var uncountables = []; 24 | 25 | function includes(haystack, needle) { 26 | var f = _.include || _.includes; 27 | return f(haystack, needle); 28 | } 29 | 30 | /** 31 | * Inflector 32 | */ 33 | var inflector = { 34 | 35 | /** 36 | * `gsub` is a method that is just slightly different than our 37 | * standard `String#replace`. The main differences are that it 38 | * matches globally every time, and if no substitution is made 39 | * it returns `null`. It accepts a string for `word` and 40 | * `replacement`, and `rule` can be either a string or a regex. 41 | */ 42 | gsub: function(word, rule, replacement) { 43 | var pattern = new RegExp(rule.source || rule, 'gi'); 44 | 45 | return pattern.test(word) ? word.replace(pattern, replacement) : null; 46 | }, 47 | 48 | /** 49 | * `plural` creates a new pluralization rule for the inflector. 50 | * `rule` can be either a string or a regex. 51 | */ 52 | plural: function(rule, replacement) { 53 | plurals.unshift([rule, replacement]); 54 | }, 55 | 56 | /** 57 | * Pluralizes the string passed to it. It also can accept a 58 | * number as the second parameter. If a number is provided, 59 | * it will pluralize the word to match the number. Optionally, 60 | * you can pass `true` as a third parameter. If found, this 61 | * will include the count with the output. 62 | */ 63 | pluralize: function(word, count, includeNumber) { 64 | var result; 65 | 66 | if (count !== undefined) { 67 | count = parseFloat(count); 68 | result = (count === 1) ? this.singularize(word) : this.pluralize(word); 69 | result = (includeNumber) ? [count, result].join(' ') : result; 70 | } else { 71 | if (includes(uncountables, word)) { 72 | return word; 73 | } 74 | 75 | result = word; 76 | 77 | _(plurals).find(function(rule) { 78 | var gsub = this.gsub(word, rule[0], rule[1]); 79 | 80 | return gsub ? (result = gsub) : false; 81 | }.bind(this)); 82 | } 83 | 84 | return result; 85 | }, 86 | 87 | /** 88 | * `singular` creates a new singularization rule for the 89 | * inflector. `rule` can be either a string or a regex. 90 | */ 91 | singular: function(rule, replacement) { 92 | singulars.unshift([rule, replacement]); 93 | }, 94 | 95 | /** 96 | * `singularize` returns the singular version of the plural 97 | * passed to it. 98 | */ 99 | singularize: function(word) { 100 | if (includes(uncountables, word)) { 101 | return word; 102 | } 103 | 104 | var result = word; 105 | 106 | _(singulars).find(function(rule) { 107 | var gsub = this.gsub(word, rule[0], rule[1]); 108 | 109 | return gsub ? (result = gsub) : false; 110 | }.bind(this)); 111 | 112 | return result; 113 | }, 114 | 115 | /** 116 | * `irregular` is a shortcut method to create both a 117 | * pluralization and singularization rule for the word at 118 | * the same time. You must supply both the singular form 119 | * and the plural form as explicit strings. 120 | */ 121 | irregular: function(singular, plural) { 122 | this.plural('\\b' + singular + '\\b', plural); 123 | this.singular('\\b' + plural + '\\b', singular); 124 | }, 125 | 126 | /** 127 | * `uncountable` creates a new uncountable rule for `word`. 128 | * Uncountable words do not get pluralized or singularized. 129 | */ 130 | uncountable: function(word) { 131 | uncountables.unshift(word); 132 | }, 133 | 134 | /** 135 | * `ordinalize` adds an ordinal suffix to `number`. 136 | */ 137 | ordinalize: function(number) { 138 | if (isNaN(number)) { 139 | return number; 140 | } 141 | 142 | number = number.toString(); 143 | var lastDigit = number.slice(-1); 144 | var lastTwoDigits = number.slice(-2); 145 | 146 | if (lastTwoDigits === '11' || lastTwoDigits === '12' || lastTwoDigits === '13') { 147 | return number + 'th'; 148 | } 149 | 150 | switch (lastDigit) { 151 | case '1': 152 | return number + 'st'; 153 | case '2': 154 | return number + 'nd'; 155 | case '3': 156 | return number + 'rd'; 157 | default: 158 | return number + 'th'; 159 | } 160 | }, 161 | 162 | /** 163 | * `titleize` capitalizes the first letter of each word in 164 | * the string `words`. It preserves the existing whitespace. 165 | */ 166 | titleize: function(words) { 167 | if (typeof words !== 'string') { 168 | return words; 169 | } 170 | 171 | return words.replace(/\S+/g, function(word) { 172 | return word.charAt(0).toUpperCase() + word.slice(1); 173 | }); 174 | }, 175 | 176 | /** 177 | * Resets the inflector's rules to their initial state, 178 | * clearing out any custom rules that have been added. 179 | */ 180 | resetInflections: function() { 181 | plurals = []; 182 | singulars = []; 183 | uncountables = []; 184 | 185 | this.plural(/$/, 's'); 186 | this.plural(/s$/, 's'); 187 | this.plural(/^(ax|test)is$/, '$1es'); 188 | this.plural(/(octop|vir)us$/, '$1i'); 189 | this.plural(/(octop|vir)i$/, '$1i'); 190 | this.plural(/(alias|status)$/, '$1es'); 191 | this.plural(/(bu)s$/, '$1ses'); 192 | this.plural(/(buffal|tomat)o$/, '$1oes'); 193 | this.plural(/([ti])um$/, '$1a'); 194 | this.plural(/([ti])a$/, '$1a'); 195 | this.plural(/sis$/, 'ses'); 196 | this.plural(/(?:([^f])fe|([lr])?f)$/, '$1$2ves'); 197 | this.plural(/(hive)$/, '$1s'); 198 | this.plural(/([^aeiouy]|qu)y$/, '$1ies'); 199 | this.plural(/(x|ch|ss|sh)$/, '$1es'); 200 | this.plural(/(matr|vert|ind)(?:ix|ex)$/, '$1ices'); 201 | this.plural(/(m|l)ouse$/, '$1ice'); 202 | this.plural(/(m|l)ice$/, '$1ice'); 203 | this.plural(/^(ox)$/, '$1en'); 204 | this.plural(/^(oxen)$/, '$1'); 205 | this.plural(/(quiz)$/, '$1zes'); 206 | 207 | this.singular(/s$/, ''); 208 | this.singular(/(ss)$/, '$1'); 209 | this.singular(/(n)ews$/, '$1ews'); 210 | this.singular(/([ti])a$/, '$1um'); 211 | this.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/, '$1$2sis'); 212 | this.singular(/(^analy)(sis|ses)$/, '$1sis'); 213 | this.singular(/([^f])ves$/, '$1fe'); 214 | this.singular(/(hive)s$/, '$1'); 215 | this.singular(/(tive)s$/, '$1'); 216 | this.singular(/([lrae])ves$/, '$1f'); 217 | this.singular(/([^aeiouy]|qu)ies$/, '$1y'); 218 | this.singular(/(s)eries$/, '$1eries'); 219 | this.singular(/(m)ovies$/, '$1ovie'); 220 | this.singular(/(x|ch|ss|sh)es$/, '$1'); 221 | this.singular(/(m|l)ice$/, '$1ouse'); 222 | this.singular(/(bus)(es)?$/, '$1'); 223 | this.singular(/(o)es$/, '$1'); 224 | this.singular(/(shoe)s$/, '$1'); 225 | this.singular(/(cris|test)(is|es)$/, '$1is'); 226 | this.singular(/^(a)x[ie]s$/, '$1xis'); 227 | this.singular(/(octop|vir)(us|i)$/, '$1us'); 228 | this.singular(/(alias|status)(es)?$/, '$1'); 229 | this.singular(/^(ox)en/, '$1'); 230 | this.singular(/(vert|ind)ices$/, '$1ex'); 231 | this.singular(/(matr)ices$/, '$1ix'); 232 | this.singular(/(quiz)zes$/, '$1'); 233 | this.singular(/(database)s$/, '$1'); 234 | 235 | this.irregular('person', 'people'); 236 | this.irregular('man', 'men'); 237 | this.irregular('child', 'children'); 238 | this.irregular('sex', 'sexes'); 239 | this.irregular('move', 'moves'); 240 | this.irregular('cow', 'kine'); 241 | this.irregular('zombie', 'zombies'); 242 | 243 | this.uncountable('equipment'); 244 | this.uncountable('information'); 245 | this.uncountable('rice'); 246 | this.uncountable('money'); 247 | this.uncountable('species'); 248 | this.uncountable('series'); 249 | this.uncountable('fish'); 250 | this.uncountable('sheep'); 251 | this.uncountable('jeans'); 252 | this.uncountable('moose'); 253 | this.uncountable('deer'); 254 | this.uncountable('news'); 255 | this.uncountable('music'); 256 | 257 | return this; 258 | } 259 | }; 260 | 261 | /** 262 | * Lodash integration 263 | */ 264 | _.mixin(inflector.resetInflections()); 265 | 266 | return inflector; 267 | }); 268 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lodash-inflection", 3 | "version": "1.5.0", 4 | "description": "ActiveSupport::Inflector, for lodash!", 5 | "main": "index.js", 6 | "browser": "lib/lodash-inflection.js", 7 | "scripts": { 8 | "test": "mocha", 9 | "browser": "open test/index.html", 10 | "coverage": "JSCOV=1 mocha -R mocha-lcov-reporter | coveralls", 11 | "jscov": "gulp jscov", 12 | "jscs": "gulp jscs", 13 | "docs": "gulp docs" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git://github.com/tuvistavie/lodash-inflection.git" 18 | }, 19 | "keywords": [ 20 | "lodash", 21 | "inflection", 22 | "inflector", 23 | "activesupport", 24 | "plural", 25 | "pluralize" 26 | ], 27 | "author": "Jeremy Ruppel", 28 | "maintainers": [ 29 | "Daniel Perez " 30 | ], 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/tuvistavie/lodash-inflection/issues" 34 | }, 35 | "homepage": "https://github.com/tuvistavie/lodash-inflection", 36 | "devDependencies": { 37 | "chai": "^3.5.0", 38 | "coveralls": "^2.11.9", 39 | "gulp": "^3.9.1", 40 | "gulp-contribs": "0.0.3", 41 | "gulp-jscoverage": "^0.1.0", 42 | "gulp-jscs": "^3.0.2", 43 | "jscoverage": "^0.6.0", 44 | "lodash": "^4.15.0", 45 | "mocha": "^2.4.5", 46 | "mocha-lcov-reporter": "^1.2.0", 47 | "mversion": "^1.10.1" 48 | }, 49 | "dependencies": {} 50 | } 51 | -------------------------------------------------------------------------------- /script/hook.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ex 4 | 5 | npm run jscs 6 | npm test 7 | 8 | node bower.json 9 | node package.json 10 | -------------------------------------------------------------------------------- /test/gsub.js: -------------------------------------------------------------------------------- 1 | describe('#gsub', function() { 2 | describe('with a regex', function() { 3 | it('replaces one instance of the match', function() { 4 | expect(_.gsub('word', /wo/, 'ne')).to.equal('nerd'); 5 | }); 6 | it('replaces two instances of the match', function() { 7 | expect(_.gsub('word word', /wo/, 'ne')).to.equal('nerd nerd'); 8 | }); 9 | it('returns null if no match', function() { 10 | expect(_.gsub('word', /zz/, 'ne')).to.be.null; 11 | }); 12 | }); 13 | describe('with a string', function() { 14 | it('replaces one instance of the match', function() { 15 | expect(_.gsub('word', 'wo', 'ne')).to.equal('nerd'); 16 | }); 17 | it('replaces two instances of the match', function() { 18 | expect(_.gsub('word word', 'wo', 'ne')).to.equal('nerd nerd'); 19 | }); 20 | it('returns null if no match', function() { 21 | expect(_.gsub('word', 'zz', 'ne')).to.be.null; 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/inflector.js: -------------------------------------------------------------------------------- 1 | describe('inflector', function() { 2 | /** 3 | * Test macro for pluralize & singularize tests 4 | */ 5 | function example(method, from, to) { 6 | it(method + 's "' + from + '" to "' + to + '"', function() { 7 | expect(_[method](from)).to.equal(to); 8 | }); 9 | } 10 | 11 | var combinations = [ 12 | ['rose', 'roses'], 13 | ['tomato', 'tomatoes'], 14 | ['datum', 'data'], 15 | ['boss', 'bosses'], 16 | ['soliloquy', 'soliloquies'], 17 | ['wish', 'wishes'], 18 | ['parenthesis', 'parentheses'], 19 | ['thesis', 'theses'], 20 | ['analysis', 'analyses'], 21 | ['life', 'lives'], 22 | ['hive', 'hives'], 23 | ['tive', 'tives'], 24 | ['leaf', 'leaves'], 25 | ['loaf', 'loaves'], 26 | ['elf', 'elves'], 27 | ['thief', 'thieves'], 28 | ['series', 'series'], 29 | ['movie', 'movies'], 30 | ['x', 'xes'], 31 | ['mouse', 'mice'], 32 | ['louse', 'lice'], 33 | ['bus', 'buses'], 34 | ['shoe', 'shoes'], 35 | ['crisis', 'crises'], 36 | ['axis', 'axes'], 37 | ['octopus', 'octopi'], 38 | ['virus', 'viri'], 39 | ['status', 'statuses'], 40 | ['alias', 'aliases'], 41 | ['ox', 'oxen'], 42 | ['vertex', 'vertices'], 43 | ['index', 'indices'], 44 | ['matrix', 'matrices'], 45 | ['quiz', 'quizzes'], 46 | ['database', 'databases'] 47 | ]; 48 | 49 | describe('plurals from singular', function() { 50 | combinations.forEach(function(word) { 51 | example('pluralize', word[0], word[1]); 52 | }); 53 | }); 54 | describe('plurals from plural', function() { 55 | combinations.forEach(function(word) { 56 | example('pluralize', word[1], word[1]); 57 | }); 58 | }); 59 | describe('singulars from plural', function() { 60 | combinations.forEach(function(word) { 61 | example('singularize', word[1], word[0]); 62 | }); 63 | }); 64 | describe('singulars from single', function() { 65 | combinations.forEach(function(word) { 66 | example('singularize', word[0], word[0]); 67 | }); 68 | }); 69 | describe('irregulars', function() { 70 | [ 71 | ['person', 'people'], 72 | ['man', 'men'], 73 | ['child', 'children'], 74 | ['sex', 'sexes'], 75 | ['move', 'moves'], 76 | ['cow', 'kine'], 77 | ['zombie', 'zombies'] 78 | ].forEach(function(word) { 79 | example('pluralize', word[0], word[1]); 80 | example('singularize', word[1], word[0]); 81 | }); 82 | }); 83 | describe('uncountables', function() { 84 | [ 85 | 'equipment', 86 | 'information', 87 | 'rice', 88 | 'money', 89 | 'species', 90 | 'series', 91 | 'fish', 92 | 'sheep', 93 | 'jeans', 94 | 'moose', 95 | 'deer', 96 | 'news', 97 | 'music' 98 | ].forEach(function(word) { 99 | example('pluralize', word, word); 100 | example('singularize', word, word); 101 | }); 102 | }); 103 | describe('#resetInflections', function() { 104 | it('resets the default inflections', function() { 105 | _.plural('haxor', 'hax0rs!'); 106 | expect(_.pluralize('haxor')).to.equal('hax0rs!'); 107 | _.resetInflections(); 108 | expect(_.pluralize('haxor')).to.equal('haxors'); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/irregular.js: -------------------------------------------------------------------------------- 1 | describe('#irregular', function() { 2 | it('adds a rule to pluralize the special case', function() { 3 | _.irregular('haxor', 'hax0rs!'); 4 | expect(_.pluralize('haxor')).to.equal('hax0rs!'); 5 | }); 6 | it('adds a rule to singularize the special case', function() { 7 | _.irregular('hax0r!', 'haxors'); 8 | expect(_.singularize('haxors')).to.equal('hax0r!'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/ordinalize.js: -------------------------------------------------------------------------------- 1 | describe('#ordinalize', function() { 2 | it('returns a stirng that is not a number or string', function() { 3 | expect(_.ordinalize('hello')).to.equal('hello'); 4 | }); 5 | it('ordinalizes a number', function() { 6 | expect(_.ordinalize(4)).to.equal('4th'); 7 | }); 8 | it('ordinalizes a number string', function() { 9 | expect(_.ordinalize('4')).to.equal('4th'); 10 | }); 11 | it('ordinalizes 0 to "0th"', function() { 12 | expect(_.ordinalize(0)).to.equal('0th'); 13 | }); 14 | it('ordinalizes 1 to "1st"', function() { 15 | expect(_.ordinalize(1)).to.equal('1st'); 16 | }); 17 | it('ordinalizes 2 to "2nd', function() { 18 | expect(_.ordinalize(2)).to.equal('2nd'); 19 | }); 20 | it('ordinalizes 3 to "3rd"', function() { 21 | expect(_.ordinalize(3)).to.equal('3rd'); 22 | }); 23 | it('ordinalizes 11 to "11th"', function() { 24 | expect(_.ordinalize(11)).to.equal('11th'); 25 | }); 26 | it('ordinalizes 12 to "12th"', function() { 27 | expect(_.ordinalize(12)).to.equal('12th'); 28 | }); 29 | it('ordinalizes 13 to "13th"', function() { 30 | expect(_.ordinalize(13)).to.equal('13th'); 31 | }); 32 | it('ordinalizes 1003 to "1003rd"', function() { 33 | expect(_.ordinalize(1003)).to.equal('1003rd'); 34 | }); 35 | it('ordinalizes -11 to "-11th', function() { 36 | expect(_.ordinalize(-11)).to.equal('-11th'); 37 | }); 38 | it('ordinalizes -1021 to "-1021st', function() { 39 | expect(_.ordinalize(-1021)).to.equal('-1021st'); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/plural.js: -------------------------------------------------------------------------------- 1 | describe('#plural', function() { 2 | it('adds a new pluralization rule by explict string', function() { 3 | _.plural('axis', 'axes'); 4 | expect(_.pluralize('axis')).to.equal('axes'); 5 | }); 6 | it('adds a new pluralization rule by regex', function() { 7 | _.plural(/(ax)is$/i, '$1es'); 8 | expect(_.pluralize('axis')).to.equal('axes'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/pluralize.js: -------------------------------------------------------------------------------- 1 | describe('#pluralize', function() { 2 | it('pluralizes the given noun', function() { 3 | expect(_.pluralize('post')).to.equal('posts'); 4 | }); 5 | it('returns the same word if it cannot be pluralized', function() { 6 | expect(_.pluralize('posts')).to.equal('posts'); 7 | }); 8 | describe('with a number', function() { 9 | it('pluralizes the word if not 1', function() { 10 | expect(_.pluralize('post', 0)).to.equal('posts'); 11 | }); 12 | it('pluralizes the word if not "1"', function() { 13 | expect(_.pluralize('post', '0')).to.equal('posts'); 14 | }); 15 | it('pluralizes the word if non-1 float', function() { 16 | expect(_.pluralize('post', 1.5)).to.equal('posts'); 17 | }); 18 | it('singularizes the word if 1', function() { 19 | expect(_.pluralize('posts', 1)).to.equal('post'); 20 | }); 21 | it('singularizes the word if "1"', function() { 22 | expect(_.pluralize('posts', '1')).to.equal('post'); 23 | }); 24 | describe('and true', function() { 25 | it('includes the word with the plural', function() { 26 | expect(_.pluralize('post', 0, true)).to.equal('0 posts'); 27 | }); 28 | it('includes the word with non-1 float', function() { 29 | expect(_.pluralize('post', '1.3', true)).to.equal('1.3 posts'); 30 | }); 31 | it('includes the word with the singular', function() { 32 | expect(_.pluralize('post', 1, true)).to.equal('1 post'); 33 | }); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Export lodash globally 3 | */ 4 | global._ = require('lodash'); 5 | 6 | /** 7 | * Export `expect` globally 8 | */ 9 | global.expect = require('chai').expect; 10 | 11 | /** 12 | * Require the subject under test 13 | */ 14 | _.mixin(require('..')); 15 | 16 | /** 17 | * Reset inflections befor each test 18 | */ 19 | beforeEach(function() { 20 | _.resetInflections(); 21 | }); 22 | -------------------------------------------------------------------------------- /test/singular.js: -------------------------------------------------------------------------------- 1 | describe('#singular', function() { 2 | it('adds a new singularization rule by explicit string', function() { 3 | _.singular('data', 'datum'); 4 | expect(_.singularize('data')).to.equal('datum'); 5 | }); 6 | it('adds a new singularization rule by regex', function() { 7 | _.singular(/(t)a$/i, '$1um'); 8 | expect(_.singularize('data')).to.equal('datum'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/singularize.js: -------------------------------------------------------------------------------- 1 | describe('#singularize', function() { 2 | it('singularizes the given noun', function() { 3 | expect(_.singularize('posts')).to.equal('post'); 4 | }); 5 | it('returns the same word if it cannot be singularized', function() { 6 | expect(_.singularize('post')).to.equal('post'); 7 | }); 8 | it('singularizes a word that contains an irregular', function() { 9 | expect(_.singularize('comments')).to.equal('comment'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /test/titleize.js: -------------------------------------------------------------------------------- 1 | describe('#titleize', function() { 2 | it('returns non-strings', function() { 3 | expect(_.titleize(5)).to.equal(5); 4 | }); 5 | it('returns the empty string when provided with the empty string', function() { 6 | expect(_.titleize('')).to.equal(''); 7 | }); 8 | it('titleizes a word', function() { 9 | expect(_.titleize('banana')).to.equal('Banana'); 10 | }); 11 | it('titleizes multiple words', function() { 12 | expect(_.titleize('banana potato fork')).to.equal('Banana Potato Fork'); 13 | }); 14 | it('does not change the whitespace', function() { 15 | expect(_.titleize('\tbanana\npotato fork\r\n')).to.equal('\tBanana\nPotato Fork\r\n'); 16 | }); 17 | it('does not alter words that begin with non-alphabetic characters', function() { 18 | expect(_.titleize('123banana')).to.equal('123banana'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/uncountable.js: -------------------------------------------------------------------------------- 1 | describe('#uncountable', function() { 2 | it('notes the word as a special case in pluralization', function() { 3 | _.uncountable('asdf'); 4 | expect(_.pluralize('asdf')).to.equal('asdf'); 5 | }); 6 | it('notes the word as a special case in singularization', function() { 7 | _.uncountable('asdf'); 8 | expect(_.singularize('asdf')).to.equal('asdf'); 9 | }); 10 | }); 11 | --------------------------------------------------------------------------------