├── .gitignore ├── docs ├── public │ ├── images │ │ └── gray.png │ ├── fonts │ │ ├── fleurons.eot │ │ ├── fleurons.ttf │ │ ├── aller-bold.eot │ │ ├── aller-bold.ttf │ │ ├── fleurons.woff │ │ ├── aller-bold.woff │ │ ├── aller-light.eot │ │ ├── aller-light.ttf │ │ ├── aller-light.woff │ │ ├── novecento-bold.eot │ │ ├── novecento-bold.ttf │ │ └── novecento-bold.woff │ └── stylesheets │ │ └── normalize.css ├── docco.css └── slang.html ├── package.json ├── LICENSE ├── slang.min.js ├── test.js ├── README.md └── slang.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /docs/public/images/gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devongovett/slang/HEAD/docs/public/images/gray.png -------------------------------------------------------------------------------- /docs/public/fonts/fleurons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devongovett/slang/HEAD/docs/public/fonts/fleurons.eot -------------------------------------------------------------------------------- /docs/public/fonts/fleurons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devongovett/slang/HEAD/docs/public/fonts/fleurons.ttf -------------------------------------------------------------------------------- /docs/public/fonts/aller-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devongovett/slang/HEAD/docs/public/fonts/aller-bold.eot -------------------------------------------------------------------------------- /docs/public/fonts/aller-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devongovett/slang/HEAD/docs/public/fonts/aller-bold.ttf -------------------------------------------------------------------------------- /docs/public/fonts/fleurons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devongovett/slang/HEAD/docs/public/fonts/fleurons.woff -------------------------------------------------------------------------------- /docs/public/fonts/aller-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devongovett/slang/HEAD/docs/public/fonts/aller-bold.woff -------------------------------------------------------------------------------- /docs/public/fonts/aller-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devongovett/slang/HEAD/docs/public/fonts/aller-light.eot -------------------------------------------------------------------------------- /docs/public/fonts/aller-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devongovett/slang/HEAD/docs/public/fonts/aller-light.ttf -------------------------------------------------------------------------------- /docs/public/fonts/aller-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devongovett/slang/HEAD/docs/public/fonts/aller-light.woff -------------------------------------------------------------------------------- /docs/public/fonts/novecento-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devongovett/slang/HEAD/docs/public/fonts/novecento-bold.eot -------------------------------------------------------------------------------- /docs/public/fonts/novecento-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devongovett/slang/HEAD/docs/public/fonts/novecento-bold.ttf -------------------------------------------------------------------------------- /docs/public/fonts/novecento-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devongovett/slang/HEAD/docs/public/fonts/novecento-bold.woff -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slang", 3 | "description": "A collection of utility functions for strings", 4 | "version": "0.3.0", 5 | "author": { 6 | "name": "Devon Govett", 7 | "email": "devongovett@gmail.com", 8 | "url": "http://badassjs.com/" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/devongovett/slang.git" 13 | }, 14 | "bugs": "http://github.com/devongovett/slang/issues", 15 | "main": "slang.js" 16 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | Copyright (c) 2013 Devon Govett 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /slang.min.js: -------------------------------------------------------------------------------- 1 | (function(){var slang={};if(typeof module!=="undefined"&&module.exports){module.exports=slang}else{this.slang=slang}slang.version="0.2.0";slang.isString=function isString(input){return Object.prototype.toString.call(input)==="[object String]"};slang.capitalize=function capitalize(input){return input.charAt(0).toUpperCase()+input.slice(1)};slang.uncapitalize=function uncapitalize(input){return input.charAt(0).toLowerCase()+input.slice(1)};slang.capitalizeWords=function capitalizeWords(input){return input.replace(/\w+/g,function(word){return slang.capitalize(word)})};slang.uncapitalizeWords=function uncapitalizeWords(input){return input.replace(/\w+/g,function(word){return slang.uncapitalize(word)})};slang.isUpperCaseAt=function isUpperCaseAt(input,index){return input.charAt(index).toUpperCase()===input.charAt(index)};slang.isLowerCaseAt=function isLowerCaseAt(input,index){return input.charAt(index).toLowerCase()===input.charAt(index)};slang.swapcase=function swapcase(input){return input.replace(/([a-z]+)|([A-Z]+)/g,function(match,lower,upper){return lower?match.toUpperCase():match.toLowerCase()})};slang.camelize=function camelize(input){return input.replace(/\W+(.)/g,function(match,letter){return letter.toUpperCase()})};slang.uncamelize=function uncamelize(input,separator){return input.replace(/([a-z\d])([A-Z])/g,"$1"+(separator||" ")+"$2")};slang.dasherize=function dasherize(input){return input.replace(/\W+/g,"-").replace(/([a-z\d])([A-Z])/g,"$1-$2").toLowerCase()};slang.repeat=function repeat(input,count){return count<1?"":new Array(count+1).join(input)};slang.insert=function insert(input,string,index){return input.slice(0,index)+string+input.slice(index)};slang.remove=function remove(input,start,end){return input.slice(0,start)+input.slice(end)};slang.chop=function chop(input){return input.slice(0,-1)};slang.trim=function strip(input){return input.trim?input.trim():input.replace(/^\s+/,"").replace(/\s+$/,"")};slang.trimLeft=function trimLeft(input){return input.trimLeft?input.trimLeft():input.replace(/^\s+/,"")};slang.trimRight=function trimRight(input){return input.trimRight?input.trimRight():input.replace(/\s+$/,"")};slang.truncate=function truncate(input,args){var limit=args&&args.limit||10,omission=args&&args.omission||"...";return input.length<=limit?input:input.slice(0,limit)+omission};slang.join=function join(array,last){var lastItem=array.pop(),last=last||"and";return array.join(", ")+" "+last+" "+lastItem};slang.humanize=function humanize(number){if(number%100>=11&&number%100<=13)return number+"th";switch(number%10){case 1:return number+"st";case 2:return number+"nd";case 3:return number+"rd"}return number+"th"};slang.contains=function contains(input,string){return input.indexOf(string)>-1};slang.startsWith=function startsWith(input,string){return input.indexOf(string)===0};slang.endsWith=function endsWith(input,string){var index=input.length-string.length;return index>=0&&input.indexOf(string,index)>-1};slang.isBlank=function isBlank(input){return/^\s*$/.test(input)};slang.successor=function successor(input){var alphabet="abcdefghijklmnopqrstuvwxyz",length=alphabet.length,result=input,i=input.length;while(i>=0){var last=input.charAt(--i),next="",carry=false;if(isNaN(last)){index=alphabet.indexOf(last.toLowerCase());if(index===-1){next=last;carry=true}else{var isUpperCase=last===last.toUpperCase();next=alphabet.charAt((index+1)%length);if(isUpperCase){next=next.toUpperCase()}carry=index+1>=length;if(carry&&i===0){var added=isUpperCase?"A":"a";result=added+next+result.slice(1);break}}}else{next=+last+1;if(next>9){next=0;carry=true}if(carry&&i===0){result="1"+next+result.slice(1);break}}result=result.slice(0,i)+next+result.slice(i+1);if(!carry){break}}return result};slang.guid=function guid(length){var buf=[],chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",charlen=chars.length,length=length||32;for(var i=0;i>', slang.successor('<>')); 105 | assert.equal('2000aaa', slang.successor('1999zzz')); 106 | assert.equal('AAAA0000', slang.successor('ZZZ9999')); 107 | 108 | // Test **slang.guid** 109 | assert.equal(32, slang.guid().length, 'slang.guid failed'); 110 | assert.equal(16, slang.guid(16).length, 'slang.guid failed'); 111 | 112 | // Test **slang.pluralize** 113 | assert.equal(slang.pluralize('cooperation'), 'cooperation'); 114 | assert.equal(slang.pluralize('man'), 'men'); 115 | assert.equal(slang.pluralize('quiz'), 'quizzes'); 116 | assert.equal(slang.pluralize('battery'), 'batteries'); 117 | assert.equal(slang.pluralize('bus'), 'buses'); 118 | assert.equal(slang.pluralize('mouse'), 'mice'); 119 | assert.equal(slang.pluralize('alias'), 'aliases'); 120 | assert.equal(slang.pluralize('octopus'), 'octopi'); 121 | assert.equal(slang.pluralize('fox'), 'foxes'); 122 | assert.equal(slang.pluralize('matrix'), 'matrices'); 123 | assert.equal(slang.pluralize('update'), 'updates'); 124 | assert.equal(slang.pluralize('potato'), 'potatoes'); 125 | assert.equal(slang.pluralize('todo'), 'todos'); 126 | assert.equal(slang.pluralize('sheep'), 'sheep'); 127 | assert.equal(slang.pluralize('person'), 'people'); 128 | assert.equal(slang.pluralize('this'), 'that'); 129 | assert.equal(slang.pluralize('crisis'), 'crises'); 130 | 131 | // Test **slang.singularize** 132 | assert.equal(slang.singularize('buses'), 'bus'); 133 | assert.equal(slang.singularize('wives'), 'wife'); 134 | assert.equal(slang.singularize('sheep'), 'sheep'); 135 | assert.equal(slang.singularize('lamps'), 'lamp'); 136 | assert.equal(slang.singularize('octopi'), 'octopus'); 137 | assert.equal(slang.singularize('crises'), 'crisis'); 138 | assert.equal(slang.singularize('mice'), 'mouse'); 139 | assert.equal(slang.singularize('families'), 'family'); 140 | assert.equal(slang.singularize('vertices'), 'vertex'); 141 | assert.equal(slang.singularize('men'), 'man'); 142 | assert.equal(slang.singularize('shoes'), 'shoe'); 143 | assert.equal(slang.singularize('synopses'), 'synopsis'); 144 | assert.equal(slang.singularize('batteries'), 'battery'); 145 | assert.equal(slang.singularize('updates'), 'update'); 146 | assert.equal(slang.singularize('people'), 'person'); 147 | assert.equal(slang.singularize('that'), 'this'); 148 | assert.equal(slang.singularize('foxes'), 'fox'); 149 | assert.equal(slang.singularize('todos'), 'todo'); 150 | assert.equal(slang.singularize('potatoes'), 'potato'); 151 | 152 | // Test **slang.language** 153 | slang.lang = 'foo'; 154 | assert.equal(slang.pluralize('bus'), 'bus'); 155 | assert.equal(slang.pluralize('bus', 'en'), 'buses'); 156 | assert.equal(slang.singularize('men'), 'men'); 157 | assert.equal(slang.singularize('men', 'en'), 'man'); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Slang 2 | ----- 3 | A collection of utility functions for working with strings in JavaScript in the browser or CommonJS. 4 | 5 | ## Node Installation 6 | npm install slang 7 | 8 | ## Annotated source code 9 | The annotated source code for slang, generated by [Docco](http://jashkenas.github.com/docco/), 10 | can be found [here](http://devongovett.github.com/slang/docs/slang.html). 11 | 12 | ## API 13 | 14 | ### slang.isString 15 | Returns whether `input` is a string 16 | 17 | slang.isString('testing'); // true 18 | slang.isString(543); // false 19 | 20 | ### slang.capitalize 21 | Capitalizes the first character of a string 22 | 23 | slang.capitalize('hello world!'); // "Hello world!" 24 | 25 | ### slang.uncapitalize 26 | Uncapitalizes the first character of a string 27 | 28 | slang.uncapitalize('Hello world!); // "hello world!" 29 | 30 | ### slang.capitalizeWords 31 | Capitalizes each word in the string 32 | 33 | slang.capitalizeWords('hello world!'); // "Hello World!" 34 | 35 | ### slang.uncapitalizeWords 36 | Uncapitalizes each word in the string 37 | 38 | slang.uncapitalizeWords('Hello World!'); // "hello world!" 39 | 40 | ### slang.isUpperCaseAt 41 | Returns whether the character at the provided character index is upper case. 42 | 43 | slang.isUpperCaseAt('Testing', 0); // true 44 | 45 | ### slang.isLowerCaseAt 46 | Returns whether the character at the provided character index is lower case. 47 | 48 | slang.isLowerCaseAt('Testing', 1); // true 49 | 50 | ### slang.swapcase 51 | Inverts the case for each letter in the string 52 | 53 | slang.swapcase('aaBBccDD'); // "AAbbCCdd" 54 | 55 | ### slang.camelize 56 | Converts a string of words seperated by dashes or spaces to camelCase 57 | 58 | slang.camelize('hello world'); // "helloWorld" 59 | slang.camelize('hello-world'); // "helloWorld" 60 | 61 | ### slang.uncamelize 62 | slang.uncamelize('helloWorld'); // "hello World" 63 | 64 | ### slang.dasherize 65 | Converts a string of words or a camelCased string into a series of words separated by a dash (`-`) 66 | 67 | slang.dasherize('this is dashed'); // "this-is-dashed" 68 | 69 | ### slang.repeat 70 | Concatenates the string `count` times 71 | 72 | slang.repeat('Ho! ', 3); // "Ho! Ho! Ho! " 73 | 74 | ### slang.insert 75 | Inserts `string` in `input` at `index` 76 | 77 | slang.insert('this is cool!', 'really ', 8); // "this is really cool!" 78 | 79 | ### slang.remove 80 | Removes the characters between the `start` and `end` indexes 81 | 82 | slang.remove('this is really cool!', 8, 15); // "this is cool!" 83 | 84 | ### slang.chop 85 | Removes the last character of `input` 86 | 87 | slang.chop('hello'); // "hell" 88 | 89 | ### slang.trim 90 | Removes leading and trailing whitespace from `input`. Uses ES5's native trim is available. 91 | 92 | slang.trim('hello '); // "hello" 93 | 94 | ### slang.trimLeft 95 | Removes the leading whitespace from `input` 96 | 97 | slang.trimLeft(' hello '); // "hello " 98 | 99 | ### slang.trimRight 100 | Removes the trailing whitespace from `input` 101 | 102 | slang.trimRight(' hello '); // " hello" 103 | 104 | ### slang.truncate 105 | Truncates `input` to `args.limit` or 10 and adds `args.omission` or "..." 106 | 107 | slang.truncate('Lorem ipsum dolor sit amet.'); // 'Lorem ipsu...' 108 | slang.truncate('Lorem ipsum dolor sit amet.', { limit: 5, omission: '...(read more)' }); // 'Lorem...(read more)' 109 | 110 | ### slang.join 111 | Joins an array into a humanized list. The last element is joined by "and" by default, but you can change it. 112 | 113 | slang.join(['red', 'blue', 'green']); // "red, blue and green" 114 | slang.join(['red', 'blue', 'green'], 'or'); // "red, blue or green" 115 | 116 | ### slang.humanize 117 | Returns a humanized number with the correct suffix such as 1st, 2nd, 3rd or 4th. 118 | 119 | slang.humanize(2); // "2nd" 120 | slang.humanize(103); // "103rd" 121 | 122 | ### slang.contains 123 | Returns whether `input` contains `string` 124 | 125 | slang.contains('hello world', 'world'); // true 126 | 127 | ### slang.startsWith 128 | Returns whether `input` starts with `string` 129 | 130 | slang.startsWith('hello world', 'hello'); // true 131 | 132 | ### slang.endsWith 133 | Returns whether `input` ends with `string` 134 | 135 | slang.endsWith('hello world', 'world'); // true 136 | 137 | ### slang.isBlank 138 | Returns whether `input` is empty or only contains whitespace 139 | 140 | slang.isBlank(' '); // true 141 | slang.isBlank(''); // true 142 | 143 | ### slang.successor 144 | Returns the successor to str. The successor is calculated by incrementing characters starting 145 | from the rightmost alphanumeric (or the rightmost character if there are no alphanumerics) in the 146 | string. Incrementing a digit always results in another digit, and incrementing a letter results in 147 | another letter of the same case. 148 | 149 | If the increment generates a carry, the character to the left of it is incremented. This 150 | process repeats until there is no carry, adding an additional character if necessary. 151 | 152 | slang.successor("abcd"); // "abce" 153 | slang.successor("THX1138"); // "THX1139" 154 | slang.successor("<>"); // "<>" 155 | slang.successor("1999zzz"); // "2000aaa" 156 | slang.successor("ZZZ9999"); // "AAAA0000" 157 | 158 | ### slang.guid 159 | Returns a unique guid of the specified length, or 32 by default 160 | 161 | slang.guid(); // "gE9FEtJknQVy3qkN9fxmTucYKTwFOno2" 162 | slang.guid(15); // "b0apU4OH7ZgmEoU" 163 | 164 | ### slang.addToPrototype 165 | Adds the methods from the slang object to String.prototype. Does not add slang.guid, slang.humanize, slang.isString, slang.version, or itself. 166 | 167 | slang.addToPrototype(); 168 | "test".capitalize(); // "Test" 169 | 170 | ### slang.lang 171 | The default language to be used with all inflection methods. Initially set to 'en' for English. 172 | 173 | ### slang.pluralize 174 | Pluralizes a string in the specified language or `slang.lang` by default 175 | 176 | inflector.pluralize('man') // 'men' 177 | inflector.pluralize('word', 'de') // non-default language 178 | 179 | ### slang.singularize 180 | Singularizes a string in the specified language or `slang.lang` by default 181 | 182 | inflector.singularize('men') // 'man' 183 | inflector.singularize('word', 'de') // non-default language 184 | 185 | ### slang.Language 186 | An object that describes a language's inflection rules. See source code for example usage. 187 | 188 | ### slang.languages 189 | An object holding `slang.Language` objects that describe various languages. English ('en') is provided 190 | by default and you can add additional languages by adding them to `slang.languages`. Then you can set 191 | the default language to this new language by setting `slang.lang` or just use your language by passing 192 | the language code as the second argument to `slang.pluralize` or `slang.singularize`. 193 | 194 | // Create a language 195 | var german = new slang.Language(); 196 | // Now add inflection rules 197 | // ... 198 | 199 | // Add language 200 | slang.languages['de'] = german; 201 | 202 | // Set default language 203 | slang.lang = 'de'; 204 | 205 | ## License 206 | 207 | slang is licensed under the MIT license. -------------------------------------------------------------------------------- /docs/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Typography ----------------------------*/ 2 | 3 | @font-face { 4 | font-family: 'aller-light'; 5 | src: url('public/fonts/aller-light.eot'); 6 | src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'), 7 | url('public/fonts/aller-light.woff') format('woff'), 8 | url('public/fonts/aller-light.ttf') format('truetype'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'aller-bold'; 15 | src: url('public/fonts/aller-bold.eot'); 16 | src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'), 17 | url('public/fonts/aller-bold.woff') format('woff'), 18 | url('public/fonts/aller-bold.ttf') format('truetype'); 19 | font-weight: normal; 20 | font-style: normal; 21 | } 22 | 23 | @font-face { 24 | font-family: 'novecento-bold'; 25 | src: url('public/fonts/novecento-bold.eot'); 26 | src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'), 27 | url('public/fonts/novecento-bold.woff') format('woff'), 28 | url('public/fonts/novecento-bold.ttf') format('truetype'); 29 | font-weight: normal; 30 | font-style: normal; 31 | } 32 | 33 | @font-face { 34 | font-family: 'fleurons'; 35 | src: url('public/fonts/fleurons.eot'); 36 | src: url('public/fonts/fleurons.eot?#iefix') format('embedded-opentype'), 37 | url('public/fonts/fleurons.woff') format('woff'), 38 | url('public/fonts/fleurons.ttf') format('truetype'); 39 | font-weight: normal; 40 | font-style: normal; 41 | } 42 | 43 | /*--------------------- Base Styles ----------------------------*/ 44 | 45 | body { 46 | font-family: "aller-light"; 47 | background: url('public/images/gray.png') #fff; 48 | background-size: 322px; 49 | margin: 0; 50 | } 51 | 52 | hr { 53 | height: 1px; 54 | background: #ddd; 55 | border: 0; 56 | } 57 | 58 | h1, h2, h3, h4, h5, h6 { 59 | color: #112233; 60 | font-weight: normal; 61 | font-family: "novecento-bold"; 62 | text-transform: uppercase; 63 | line-height: 1em; 64 | margin-top: 50px; 65 | } 66 | h1 { 67 | margin: 0; 68 | text-align: center; 69 | } 70 | h2 { 71 | font-size: 1.3em; 72 | } 73 | h1:after { 74 | content: "8"; 75 | display: block; 76 | font-family: "fleurons"; 77 | color: #999; 78 | font-size: 80px; 79 | padding: 10px 0 25px; 80 | } 81 | 82 | a { 83 | color: #000; 84 | } 85 | 86 | b, strong { 87 | font-weight: normal; 88 | font-family: "aller-bold"; 89 | } 90 | 91 | blockquote { 92 | border-left: 5px solid #ccc; 93 | margin-left: 0; 94 | padding: 1px 0 1px 1em; 95 | } 96 | .page blockquote p { 97 | font-family: Menlo, Consolas, Monaco, monospace; 98 | font-size: 14px; line-height: 19px; 99 | color: #999; 100 | margin: 10px 0 0; 101 | white-space: pre-wrap; 102 | } 103 | 104 | pre, tt, code { 105 | font-family: Menlo, Consolas, Monaco, monospace; 106 | font-size: 12px; 107 | display: inline-block; 108 | border: 1px solid #EAEAEA; 109 | background: #f8f8f8; 110 | color: #555; 111 | padding: 0 5px; 112 | line-height: 20px; 113 | } 114 | .page pre { 115 | margin: 0; 116 | width: 608px; 117 | padding: 10px 15px; 118 | background: #fcfcfc; 119 | -moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 120 | -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 121 | box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 122 | overflow-x: auto; 123 | } 124 | .page pre code { 125 | border: 0; 126 | padding: 0; 127 | background: transparent; 128 | } 129 | 130 | .fleur { 131 | font-family: "fleurons"; 132 | font-size: 100px; 133 | text-align: center; 134 | margin: 40px 0; 135 | color: #ccc; 136 | } 137 | 138 | /*--------------------- Layout ----------------------------*/ 139 | 140 | .container { 141 | width: 760px; 142 | margin: 0 auto; 143 | background: #fff; 144 | background: rgba(255,255,255, 0.4); 145 | overflow: hidden; 146 | } 147 | .page { 148 | width: 640px; 149 | padding: 30px; 150 | margin: 30px; 151 | background: #fff; 152 | font-size: 17px; 153 | line-height: 26px; 154 | } 155 | .page p { 156 | color: #30404f; 157 | margin: 26px 0; 158 | } 159 | 160 | ul.sections { 161 | list-style: none; 162 | padding:0 0 5px 0;; 163 | margin:0; 164 | } 165 | 166 | .page li p { 167 | margin: 12px 0; 168 | } 169 | 170 | .toc { 171 | max-height: 0; 172 | overflow: hidden; 173 | text-align: center; 174 | font-size: 13px; 175 | line-height: 20px; 176 | -moz-transition: max-height 1s; 177 | -webkit-transition: max-height 1s; 178 | transition: max-height 1s; 179 | } 180 | .header:hover .toc { 181 | max-height: 500px; 182 | } 183 | .toc h3 { 184 | margin-top: 20px; 185 | } 186 | .toc ol { 187 | margin: 0 0 20px 0; 188 | display: inline-block; 189 | text-align: left; 190 | list-style-type: upper-roman; 191 | } 192 | .toc li { 193 | font-family: 'novecento-bold'; 194 | } 195 | .toc li a { 196 | font-family: 'aller-light'; 197 | } 198 | 199 | 200 | /*---------------------- Syntax Highlighting -----------------------------*/ 201 | 202 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 203 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 204 | /* 205 | 206 | github.com style (c) Vasily Polovnyov 207 | 208 | */ 209 | 210 | pre code { 211 | display: block; padding: 0.5em; 212 | color: #000; 213 | background: #f8f8ff 214 | } 215 | 216 | pre .comment, 217 | pre .template_comment, 218 | pre .diff .header, 219 | pre .javadoc { 220 | color: #408080; 221 | font-style: italic 222 | } 223 | 224 | pre .keyword, 225 | pre .assignment, 226 | pre .literal, 227 | pre .css .rule .keyword, 228 | pre .winutils, 229 | pre .javascript .title, 230 | pre .lisp .title, 231 | pre .subst { 232 | color: #954121; 233 | /*font-weight: bold*/ 234 | } 235 | 236 | pre .number, 237 | pre .hexcolor { 238 | color: #40a070 239 | } 240 | 241 | pre .string, 242 | pre .tag .value, 243 | pre .phpdoc, 244 | pre .tex .formula { 245 | color: #219161; 246 | } 247 | 248 | pre .title, 249 | pre .id { 250 | color: #19469D; 251 | } 252 | pre .params { 253 | color: #00F; 254 | } 255 | 256 | pre .javascript .title, 257 | pre .lisp .title, 258 | pre .subst { 259 | font-weight: normal 260 | } 261 | 262 | pre .class .title, 263 | pre .haskell .label, 264 | pre .tex .command { 265 | color: #458; 266 | font-weight: bold 267 | } 268 | 269 | pre .tag, 270 | pre .tag .title, 271 | pre .rules .property, 272 | pre .django .tag .keyword { 273 | color: #000080; 274 | font-weight: normal 275 | } 276 | 277 | pre .attribute, 278 | pre .variable, 279 | pre .instancevar, 280 | pre .lisp .body { 281 | color: #008080 282 | } 283 | 284 | pre .regexp { 285 | color: #B68 286 | } 287 | 288 | pre .class { 289 | color: #458; 290 | font-weight: bold 291 | } 292 | 293 | pre .symbol, 294 | pre .ruby .symbol .string, 295 | pre .ruby .symbol .keyword, 296 | pre .ruby .symbol .keymethods, 297 | pre .lisp .keyword, 298 | pre .tex .special, 299 | pre .input_number { 300 | color: #990073 301 | } 302 | 303 | pre .builtin, 304 | pre .constructor, 305 | pre .built_in, 306 | pre .lisp .title { 307 | color: #0086b3 308 | } 309 | 310 | pre .preprocessor, 311 | pre .pi, 312 | pre .doctype, 313 | pre .shebang, 314 | pre .cdata { 315 | color: #999; 316 | font-weight: bold 317 | } 318 | 319 | pre .deletion { 320 | background: #fdd 321 | } 322 | 323 | pre .addition { 324 | background: #dfd 325 | } 326 | 327 | pre .diff .change { 328 | background: #0086b3 329 | } 330 | 331 | pre .chunk { 332 | color: #aaa 333 | } 334 | 335 | pre .tex .formula { 336 | opacity: 0.5; 337 | } 338 | -------------------------------------------------------------------------------- /docs/public/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 8/9. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | } 34 | 35 | /* 36 | * Prevents modern browsers from displaying `audio` without controls. 37 | * Remove excess height in iOS 5 devices. 38 | */ 39 | 40 | audio:not([controls]) { 41 | display: none; 42 | height: 0; 43 | } 44 | 45 | /* 46 | * Addresses styling for `hidden` attribute not present in IE 8/9. 47 | */ 48 | 49 | [hidden] { 50 | display: none; 51 | } 52 | 53 | /* ========================================================================== 54 | Base 55 | ========================================================================== */ 56 | 57 | /* 58 | * 1. Sets default font family to sans-serif. 59 | * 2. Prevents iOS text size adjust after orientation change, without disabling 60 | * user zoom. 61 | */ 62 | 63 | html { 64 | font-family: sans-serif; /* 1 */ 65 | -webkit-text-size-adjust: 100%; /* 2 */ 66 | -ms-text-size-adjust: 100%; /* 2 */ 67 | } 68 | 69 | /* 70 | * Removes default margin. 71 | */ 72 | 73 | body { 74 | margin: 0; 75 | } 76 | 77 | /* ========================================================================== 78 | Links 79 | ========================================================================== */ 80 | 81 | /* 82 | * Addresses `outline` inconsistency between Chrome and other browsers. 83 | */ 84 | 85 | a:focus { 86 | outline: thin dotted; 87 | } 88 | 89 | /* 90 | * Improves readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* ========================================================================== 99 | Typography 100 | ========================================================================== */ 101 | 102 | /* 103 | * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, 104 | * Safari 5, and Chrome. 105 | */ 106 | 107 | h1 { 108 | font-size: 2em; 109 | } 110 | 111 | /* 112 | * Addresses styling not present in IE 8/9, Safari 5, and Chrome. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: 1px dotted; 117 | } 118 | 119 | /* 120 | * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: bold; 126 | } 127 | 128 | /* 129 | * Addresses styling not present in Safari 5 and Chrome. 130 | */ 131 | 132 | dfn { 133 | font-style: italic; 134 | } 135 | 136 | /* 137 | * Addresses styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | 146 | /* 147 | * Corrects font family set oddly in Safari 5 and Chrome. 148 | */ 149 | 150 | code, 151 | kbd, 152 | pre, 153 | samp { 154 | font-family: monospace, serif; 155 | font-size: 1em; 156 | } 157 | 158 | /* 159 | * Improves readability of pre-formatted text in all browsers. 160 | */ 161 | 162 | pre { 163 | white-space: pre; 164 | white-space: pre-wrap; 165 | word-wrap: break-word; 166 | } 167 | 168 | /* 169 | * Sets consistent quote types. 170 | */ 171 | 172 | q { 173 | quotes: "\201C" "\201D" "\2018" "\2019"; 174 | } 175 | 176 | /* 177 | * Addresses inconsistent and variable font size in all browsers. 178 | */ 179 | 180 | small { 181 | font-size: 80%; 182 | } 183 | 184 | /* 185 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 186 | */ 187 | 188 | sub, 189 | sup { 190 | font-size: 75%; 191 | line-height: 0; 192 | position: relative; 193 | vertical-align: baseline; 194 | } 195 | 196 | sup { 197 | top: -0.5em; 198 | } 199 | 200 | sub { 201 | bottom: -0.25em; 202 | } 203 | 204 | /* ========================================================================== 205 | Embedded content 206 | ========================================================================== */ 207 | 208 | /* 209 | * Removes border when inside `a` element in IE 8/9. 210 | */ 211 | 212 | img { 213 | border: 0; 214 | } 215 | 216 | /* 217 | * Corrects overflow displayed oddly in IE 9. 218 | */ 219 | 220 | svg:not(:root) { 221 | overflow: hidden; 222 | } 223 | 224 | /* ========================================================================== 225 | Figures 226 | ========================================================================== */ 227 | 228 | /* 229 | * Addresses margin not present in IE 8/9 and Safari 5. 230 | */ 231 | 232 | figure { 233 | margin: 0; 234 | } 235 | 236 | /* ========================================================================== 237 | Forms 238 | ========================================================================== */ 239 | 240 | /* 241 | * Define consistent border, margin, and padding. 242 | */ 243 | 244 | fieldset { 245 | border: 1px solid #c0c0c0; 246 | margin: 0 2px; 247 | padding: 0.35em 0.625em 0.75em; 248 | } 249 | 250 | /* 251 | * 1. Corrects color not being inherited in IE 8/9. 252 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 253 | */ 254 | 255 | legend { 256 | border: 0; /* 1 */ 257 | padding: 0; /* 2 */ 258 | } 259 | 260 | /* 261 | * 1. Corrects font family not being inherited in all browsers. 262 | * 2. Corrects font size not being inherited in all browsers. 263 | * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome 264 | */ 265 | 266 | button, 267 | input, 268 | select, 269 | textarea { 270 | font-family: inherit; /* 1 */ 271 | font-size: 100%; /* 2 */ 272 | margin: 0; /* 3 */ 273 | } 274 | 275 | /* 276 | * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in 277 | * the UA stylesheet. 278 | */ 279 | 280 | button, 281 | input { 282 | line-height: normal; 283 | } 284 | 285 | /* 286 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 287 | * and `video` controls. 288 | * 2. Corrects inability to style clickable `input` types in iOS. 289 | * 3. Improves usability and consistency of cursor style between image-type 290 | * `input` and others. 291 | */ 292 | 293 | button, 294 | html input[type="button"], /* 1 */ 295 | input[type="reset"], 296 | input[type="submit"] { 297 | -webkit-appearance: button; /* 2 */ 298 | cursor: pointer; /* 3 */ 299 | } 300 | 301 | /* 302 | * Re-set default cursor for disabled elements. 303 | */ 304 | 305 | button[disabled], 306 | input[disabled] { 307 | cursor: default; 308 | } 309 | 310 | /* 311 | * 1. Addresses box sizing set to `content-box` in IE 8/9. 312 | * 2. Removes excess padding in IE 8/9. 313 | */ 314 | 315 | input[type="checkbox"], 316 | input[type="radio"] { 317 | box-sizing: border-box; /* 1 */ 318 | padding: 0; /* 2 */ 319 | } 320 | 321 | /* 322 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 323 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 324 | * (include `-moz` to future-proof). 325 | */ 326 | 327 | input[type="search"] { 328 | -webkit-appearance: textfield; /* 1 */ 329 | -moz-box-sizing: content-box; 330 | -webkit-box-sizing: content-box; /* 2 */ 331 | box-sizing: content-box; 332 | } 333 | 334 | /* 335 | * Removes inner padding and search cancel button in Safari 5 and Chrome 336 | * on OS X. 337 | */ 338 | 339 | input[type="search"]::-webkit-search-cancel-button, 340 | input[type="search"]::-webkit-search-decoration { 341 | -webkit-appearance: none; 342 | } 343 | 344 | /* 345 | * Removes inner padding and border in Firefox 4+. 346 | */ 347 | 348 | button::-moz-focus-inner, 349 | input::-moz-focus-inner { 350 | border: 0; 351 | padding: 0; 352 | } 353 | 354 | /* 355 | * 1. Removes default vertical scrollbar in IE 8/9. 356 | * 2. Improves readability and alignment in all browsers. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; /* 1 */ 361 | vertical-align: top; /* 2 */ 362 | } 363 | 364 | /* ========================================================================== 365 | Tables 366 | ========================================================================== */ 367 | 368 | /* 369 | * Remove most spacing between table cells. 370 | */ 371 | 372 | table { 373 | border-collapse: collapse; 374 | border-spacing: 0; 375 | } -------------------------------------------------------------------------------- /slang.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Module Setup 4 | // ----- 5 | 6 | // Define the internal slang variable 7 | var slang = {}; 8 | 9 | // Export the slang object as either a CommonJS module, or to the global object 10 | if (typeof module !== 'undefined' && module.exports) { 11 | module.exports = slang; 12 | } else { 13 | this.slang = slang; 14 | } 15 | 16 | // Set the slang version 17 | slang.version = '0.3.0'; 18 | 19 | // String utility functions 20 | // ------------------------ 21 | 22 | // Returns whether `input` is a string 23 | slang.isString = function isString(input) { 24 | return Object.prototype.toString.call(input) === '[object String]'; 25 | } 26 | 27 | // Capitalizes the first character of a string 28 | slang.capitalize = function capitalize(input) { 29 | return input.charAt(0).toUpperCase() + input.slice(1); 30 | } 31 | 32 | // Uncapitalizes the first character of a string 33 | slang.uncapitalize = function uncapitalize(input) { 34 | return input.charAt(0).toLowerCase() + input.slice(1); 35 | } 36 | 37 | // Capitalizes each word in the string 38 | slang.capitalizeWords = function capitalizeWords(input) { 39 | return input.replace(/\w+/g, function(word) { 40 | return slang.capitalize(word); 41 | }); 42 | } 43 | 44 | // Uncapitalizes each word in the string 45 | slang.uncapitalizeWords = function uncapitalizeWords(input) { 46 | return input.replace(/\w+/g, function(word) { 47 | return slang.uncapitalize(word); 48 | }); 49 | } 50 | 51 | // Returns whether the character at the provided character index 52 | // is upper case. 53 | slang.isUpperCaseAt = function isUpperCaseAt(input, index) { 54 | return input.charAt(index).toUpperCase() === input.charAt(index); 55 | } 56 | 57 | // Returns whether the character at the provided character index 58 | // is lower case. 59 | slang.isLowerCaseAt = function isLowerCaseAt(input, index) { 60 | return input.charAt(index).toLowerCase() === input.charAt(index); 61 | } 62 | 63 | // Inverts the case for each letter in the string 64 | slang.swapcase = function swapcase(input) { 65 | return input.replace(/([a-z]+)|([A-Z]+)/g, function(match, lower, upper) { 66 | return lower ? match.toUpperCase() : match.toLowerCase(); 67 | }); 68 | } 69 | 70 | // Converts a string of words seperated by dashes or spaces to camelCase 71 | slang.camelize = function camelize(input) { 72 | return input.replace(/\W+(.)/g, function(match, letter) { 73 | return letter.toUpperCase(); 74 | }); 75 | } 76 | 77 | // Converts a camelCased string into a series of words separated 78 | // by `separator` or a space by default 79 | slang.uncamelize = function uncamelize(input, separator) { 80 | return input.replace(/([a-z\d])([A-Z])/g, '$1' + (separator || ' ') + '$2'); 81 | } 82 | 83 | // Converts a string of words or a camelCased string into a series of words 84 | // separated by a dash (`-`) 85 | slang.dasherize = function dasherize(input) { 86 | return input.replace(/\W+/g, '-') 87 | .replace(/([a-z\d])([A-Z])/g, '$1-$2') 88 | .toLowerCase(); 89 | } 90 | 91 | // Concatenates the string `count` times 92 | slang.repeat = function repeat(input, count) { 93 | return count < 1 ? '' : new Array(count + 1).join(input); 94 | } 95 | 96 | // Inserts `string` in `input` at `index` 97 | slang.insert = function insert(input, string, index) { 98 | return input.slice(0, index) + string + input.slice(index); 99 | } 100 | 101 | // Removes the characters between the `start` and `end` indexes 102 | slang.remove = function remove(input, start, end) { 103 | return input.slice(0, start) + input.slice(end); 104 | } 105 | 106 | // Removes the last character of `input` 107 | slang.chop = function chop(input) { 108 | return input.slice(0, -1); 109 | } 110 | 111 | // Removes leading and trailing whitespace from `input` 112 | slang.trim = function strip(input) { 113 | return input.trim ? input.trim() : input.replace(/^\s+/, '').replace(/\s+$/, ''); 114 | } 115 | 116 | // Removes the leading whitespace from `input` 117 | slang.trimLeft = function trimLeft(input) { 118 | return input.trimLeft ? input.trimLeft() : input.replace(/^\s+/, ''); 119 | } 120 | 121 | // Removes the trailing whitespace from `input` 122 | slang.trimRight = function trimRight(input) { 123 | return input.trimRight ? input.trimRight() : input.replace(/\s+$/, ''); 124 | } 125 | 126 | // Truncates `input` to `args.limit` or 10 and adds `args.omission` or "..." 127 | slang.truncate = function truncate(input, args) { 128 | var limit = args && args.limit || 10, 129 | omission = args && args.omission || '...'; 130 | 131 | return input.length <= limit ? input : input.slice(0, limit) + omission; 132 | } 133 | 134 | // Joins an array into a humanized list. The last element is joined 135 | // by "and" by default, but you can change it. 136 | slang.join = function join(array, last) { 137 | var lastItem = array.pop(), 138 | last = last || 'and'; 139 | 140 | return array.join(', ') + ' ' + last + ' ' + lastItem; 141 | } 142 | 143 | // Returns a humanized number with the correct suffix 144 | // such as 1st, 2nd, 3rd or 4th 145 | slang.humanize = function humanize(number) { 146 | if(number % 100 >= 11 && number % 100 <= 13) 147 | return number + "th"; 148 | 149 | switch(number % 10) { 150 | case 1: return number + "st"; 151 | case 2: return number + "nd"; 152 | case 3: return number + "rd"; 153 | } 154 | 155 | return number + "th"; 156 | } 157 | 158 | // Returns whether `input` contains `string` 159 | slang.contains = function contains(input, string) { 160 | return input.indexOf(string) > -1; 161 | } 162 | 163 | // Returns whether `input` starts with `string` 164 | slang.startsWith = function startsWith(input, string) { 165 | return input.indexOf(string) === 0; 166 | } 167 | 168 | // Returns whether `input` ends with `string` 169 | slang.endsWith = function endsWith(input, string) { 170 | var index = input.length - string.length; 171 | return index >= 0 && input.indexOf(string, index) > -1; 172 | } 173 | 174 | // Returns whether `input` is empty or only contains whitespace 175 | slang.isBlank = function isBlank(input) { 176 | return /^\s*$/.test(input); 177 | } 178 | 179 | // Returns the successor to str. The successor is calculated by incrementing characters starting 180 | // from the rightmost alphanumeric (or the rightmost character if there are no alphanumerics) in the 181 | // string. Incrementing a digit always results in another digit, and incrementing a letter results in 182 | // another letter of the same case. 183 | // 184 | // If the increment generates a carry, the character to the left of it is incremented. This 185 | // process repeats until there is no carry, adding an additional character if necessary. 186 | // 187 | // slang.successor("abcd") == "abce" 188 | // slang.successor("THX1138") == "THX1139" 189 | // slang.successor("<>") == "<>" 190 | // slang.successor("1999zzz") == "2000aaa" 191 | // slang.successor("ZZZ9999") == "AAAA0000" 192 | slang.successor = function successor(input) { 193 | var alphabet = 'abcdefghijklmnopqrstuvwxyz', 194 | length = alphabet.length, 195 | result = input, 196 | i = input.length; 197 | 198 | while(i >= 0) { 199 | var last = input.charAt(--i), 200 | next = '', 201 | carry = false; 202 | 203 | if (isNaN(last)) { 204 | index = alphabet.indexOf(last.toLowerCase()); 205 | 206 | if (index === -1) { 207 | next = last; 208 | carry = true; 209 | } 210 | else { 211 | var isUpperCase = last === last.toUpperCase(); 212 | next = alphabet.charAt((index + 1) % length); 213 | if (isUpperCase) { 214 | next = next.toUpperCase(); 215 | } 216 | 217 | carry = index + 1 >= length; 218 | if (carry && i === 0) { 219 | var added = isUpperCase ? 'A' : 'a'; 220 | result = added + next + result.slice(1); 221 | break; 222 | } 223 | } 224 | } 225 | else { 226 | next = +last + 1; 227 | if(next > 9) { 228 | next = 0; 229 | carry = true 230 | } 231 | 232 | if (carry && i === 0) { 233 | result = '1' + next + result.slice(1); 234 | break; 235 | } 236 | } 237 | 238 | result = result.slice(0, i) + next + result.slice(i + 1); 239 | if (!carry) { 240 | break; 241 | } 242 | } 243 | return result; 244 | } 245 | 246 | // Returns a unique guid of the specified length, or 32 by default 247 | slang.guid = function guid(length) { 248 | var buf = [], 249 | chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 250 | charlen = chars.length, 251 | length = length || 32; 252 | 253 | for (var i = 0; i < length; i++) { 254 | buf[i] = chars.charAt(Math.floor(Math.random() * charlen)); 255 | } 256 | 257 | return buf.join(''); 258 | } 259 | 260 | // Inflection 261 | // ---------- 262 | 263 | // Set default language for inflection methods 264 | slang.lang = 'en'; 265 | 266 | // Object to hold languages for inflection 267 | // Add slang.Language objects to this 268 | slang.languages = {}; 269 | 270 | // Define object to hold information about a language 271 | function Language() { 272 | this.plural = []; 273 | this.singular = []; 274 | this.uncountable = []; 275 | this.irregular = { 276 | plural: {}, 277 | singular: {} 278 | }; 279 | } 280 | 281 | slang.Language = Language; 282 | 283 | // Adds an array of irregular words to the language. 284 | // Provide an array of arrays containing the singular 285 | // and plural versions of the word 286 | Language.prototype.addIrregular = function(irregular) { 287 | for (var i = 0, len = irregular.length; i < len; i++) { 288 | var item = irregular[i]; 289 | this.irregular.plural[item[0]] = item[1]; 290 | this.irregular.singular[item[1]] = item[0]; 291 | } 292 | } 293 | 294 | // Inflects a word by the specified type ('singular' or 'plural') 295 | Language.prototype.inflect = function(word, type) { 296 | // Check if this word is uncountable 297 | if (~this.uncountable.indexOf(word.toLowerCase())) 298 | return word; 299 | 300 | // Check if this word is irregular 301 | var irregular = this.irregular[type][word]; 302 | if (irregular) 303 | return irregular; 304 | 305 | // Check rules until a match is found 306 | var rules = this[type]; 307 | for (var i = 0, len = rules.length; i < len; i++) { 308 | var regexp = rules[i][0]; 309 | if (regexp.test(word)) 310 | return word.replace(regexp, rules[i][1]); 311 | } 312 | 313 | return word; 314 | } 315 | 316 | // Pluralize a word in the specified language 317 | // or `slang.lang` by default 318 | slang.pluralize = function(word, lang) { 319 | lang || (lang = slang.lang); 320 | lang = slang.languages[lang]; 321 | 322 | if (!lang) 323 | return word; 324 | 325 | return lang.inflect(word, 'plural'); 326 | } 327 | 328 | // Singularize a word in the specified language 329 | // or `slang.lang` by default 330 | slang.singularize = function(word, lang) { 331 | lang || (lang = slang.lang); 332 | lang = slang.languages[lang]; 333 | 334 | if (!lang) 335 | return word; 336 | 337 | return lang.inflect(word, 'singular'); 338 | } 339 | 340 | // Adds the methods from the slang object to String.prototype 341 | slang.addToPrototype = function addToPrototype() { 342 | for (key in slang) { 343 | if (key === 'guid' || 344 | key === 'lang' || 345 | key === 'languages' || 346 | key === 'Language' || 347 | key === 'humanize' || 348 | key === 'isString' || 349 | key === 'version' || 350 | key === 'addToPrototype') { 351 | continue; 352 | } 353 | 354 | (function(key) { 355 | String.prototype[key] = function() { 356 | var args = Array.prototype.slice.call(arguments) 357 | return slang[key].apply(slang, [this].concat(args)); 358 | } 359 | })(key); 360 | } 361 | } 362 | 363 | // English Inflector 364 | // ----------------- 365 | 366 | // Define language for English 367 | var en = slang.languages['en'] = new slang.Language(); 368 | 369 | en.plural = [ 370 | [/(todo)$/i, "$1s"], 371 | [/(matr|vert|ind)(?:ix|ex)$/i, "$1ices"], 372 | [/(octop|vir)us$/i, "$1i"], 373 | [/(alias|status)$/i, "$1es"], 374 | [/(cris|ax|test)is$/i, "$1es"], 375 | [/(s|ss|sh|ch|x|o)$/i, "$1es"], 376 | [/y$/i, "ies"], 377 | [/(o|e)y$/i, "$1ys"], 378 | [/([ti])um$/i, "$1a"], 379 | [/sis$/i, "ses"], 380 | [/(?:([^f])fe|([lr])f)$/i, "$1$2ves"], 381 | [/([^aeiouy]|qu)y$/i, "$1ies"], 382 | [/([m|l])ouse$/i, "$1ice"], 383 | [/^(ox)$/i, "$1en"], 384 | [/(quiz)$/i, "$1zes"], 385 | [/$/, "s"] 386 | ]; 387 | 388 | en.singular = [ 389 | [/(bu|mis|kis)s$/i, "$1s"], 390 | [/([ti])a$/i, "$1um"], 391 | [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, "$1$2sis"], 392 | [/(^analy)ses$/i, "$1sis"], 393 | [/([^f])ves$/i, "$1fe"], 394 | [/([lr])ves$/i, "$1f"], 395 | [/([^aeiouy]|qu)ies$/i, "$1y"], 396 | [/ies$/i, "ie"], 397 | [/(x|ch|ss|sh)es$/i, "$1"], 398 | [/([m|l])ice$/i, "$1ouse"], 399 | [/(bus)es$/i, "$1"], 400 | [/(shoe)s$/i, "$1"], 401 | [/(o)es$/i, "$1"], 402 | [/(cris|ax|test)es$/i, "$1is"], 403 | [/(octop|vir)i$/i, "$1us"], 404 | [/(alias|status)es$/i, "$1"], 405 | [/^(ox)en/i, "$1"], 406 | [/(vert|ind)ices$/i, "$1ex"], 407 | [/(matr)ices$/i, "$1ix"], 408 | [/(quiz)zes$/i, "$1"], 409 | [/s$/i, ""] 410 | ]; 411 | 412 | en.addIrregular([ 413 | ['i', 'we'], 414 | ['person', 'people'], 415 | ['man', 'men'], 416 | ['child', 'children'], 417 | ['move', 'moves'], 418 | ['she', 'they'], 419 | ['he', 'they'], 420 | ['myself', 'ourselves'], 421 | ['yourself', 'ourselves'], 422 | ['himself', 'themselves'], 423 | ['herself', 'themselves'], 424 | ['themself', 'themselves'], 425 | ['mine', 'ours'], 426 | ['hers', 'theirs'], 427 | ['his', 'theirs'], 428 | ['its', 'theirs'], 429 | ['theirs', 'theirs'], 430 | ['sex', 'sexes'], 431 | ['this', 'that'] 432 | ]); 433 | 434 | en.uncountable = [ 435 | 'advice', 436 | 'enegery', 437 | 'excretion', 438 | 'digestion', 439 | 'cooperation', 440 | 'health', 441 | 'justice', 442 | 'jeans', 443 | 'labour', 444 | 'machinery', 445 | 'equipment', 446 | 'information', 447 | 'pollution', 448 | 'sewage', 449 | 'paper', 450 | 'money', 451 | 'species', 452 | 'series', 453 | 'rain', 454 | 'rice', 455 | 'fish', 456 | 'sheep', 457 | 'moose', 458 | 'deer', 459 | 'bison', 460 | 'proceedings', 461 | 'shears', 462 | 'pincers', 463 | 'breeches', 464 | 'hijinks', 465 | 'clippers', 466 | 'chassis', 467 | 'innings', 468 | 'elk', 469 | 'rhinoceros', 470 | 'swine', 471 | 'you', 472 | 'news' 473 | ]; 474 | 475 | })(); 476 | -------------------------------------------------------------------------------- /docs/slang.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | slang.js 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 | 16 |

slang.js

17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 |
(function() {
27 | 28 | 29 | 30 |

Module Setup

31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |

Define the internal slang variable

40 | 41 | 42 |
    var slang = {};
43 | 44 | 45 | 46 |

Export the slang object as either a CommonJS module, or to the global object

47 | 48 | 49 |
    if (typeof module !== 'undefined' && module.exports) {
 50 |         module.exports = slang;
 51 |     } else {
 52 |         this.slang = slang;
 53 |     }
54 | 55 | 56 | 57 |

Set the slang version

58 | 59 | 60 |
    slang.version = '0.2.0';
61 | 62 | 63 | 64 |

String utility functions

65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |

Returns whether input is a string

74 | 75 | 76 |
    slang.isString = function isString(input) {
 77 |         return Object.prototype.toString.call(input) === '[object String]';
 78 |     }
79 | 80 | 81 | 82 |

Capitalizes the first character of a string

83 | 84 | 85 |
    slang.capitalize = function capitalize(input) {
 86 |         return input.charAt(0).toUpperCase() + input.slice(1);
 87 |     }
88 | 89 | 90 | 91 |

Uncapitalizes the first character of a string

92 | 93 | 94 |
    slang.uncapitalize = function uncapitalize(input) {
 95 |         return input.charAt(0).toLowerCase() + input.slice(1);
 96 |     }
97 | 98 | 99 | 100 |

Capitalizes each word in the string

101 | 102 | 103 |
    slang.capitalizeWords = function capitalizeWords(input) {
104 |         return input.replace(/\w+/g, function(word) {
105 |             return slang.capitalize(word);
106 |         });
107 |     }
108 | 109 | 110 | 111 |

Uncapitalizes each word in the string

112 | 113 | 114 |
    slang.uncapitalizeWords = function uncapitalizeWords(input) {
115 |         return input.replace(/\w+/g, function(word) {
116 |             return slang.uncapitalize(word);
117 |         });
118 |     }
119 | 120 | 121 | 122 |

Returns whether the character at the provided character index 123 | is upper case.

124 | 125 | 126 |
    slang.isUpperCaseAt = function isUpperCaseAt(input, index) {
127 |         return input.charAt(index).toUpperCase() === input.charAt(index);
128 |     }
129 | 130 | 131 | 132 |

Returns whether the character at the provided character index 133 | is lower case.

134 | 135 | 136 |
    slang.isLowerCaseAt = function isLowerCaseAt(input, index) {
137 |         return input.charAt(index).toLowerCase() === input.charAt(index);
138 |     }
139 | 140 | 141 | 142 |

Inverts the case for each letter in the string

143 | 144 | 145 |
    slang.swapcase = function swapcase(input) {
146 |         return input.replace(/([a-z]+)|([A-Z]+)/g, function(match, lower, upper) {
147 |             return lower ? match.toUpperCase() : match.toLowerCase();
148 |         });
149 |     }
150 | 151 | 152 | 153 |

Converts a string of words seperated by dashes or spaces to camelCase

154 | 155 | 156 |
    slang.camelize = function camelize(input) {
157 |         return input.replace(/\W+(.)/g, function(match, letter) {
158 |             return letter.toUpperCase();
159 |         });
160 |     }
161 | 162 | 163 | 164 |

Converts a camelCased string into a series of words separated 165 | by separator or a space by default

166 | 167 | 168 |
    slang.uncamelize = function uncamelize(input, separator) {
169 |         return input.replace(/([a-z\d])([A-Z])/g, '$1' + (separator || ' ') + '$2');
170 |     }
171 | 172 | 173 | 174 |

Converts a string of words or a camelCased string into a series of words 175 | separated by a dash (-)

176 | 177 | 178 |
    slang.dasherize = function dasherize(input) {
179 |         return input.replace(/\W+/g, '-')
180 |                     .replace(/([a-z\d])([A-Z])/g, '$1-$2')
181 |                     .toLowerCase();
182 |     }
183 | 184 | 185 | 186 |

Concatenates the string count times

187 | 188 | 189 |
    slang.repeat = function repeat(input, count) {
190 |         return count < 1 ? '' : new Array(count + 1).join(input);
191 |     }
192 | 193 | 194 | 195 |

Inserts string in input at index

196 | 197 | 198 |
    slang.insert = function insert(input, string, index) {
199 |         return input.slice(0, index) + string + input.slice(index);
200 |     }
201 | 202 | 203 | 204 |

Removes the characters between the start and end indexes

205 | 206 | 207 |
    slang.remove = function remove(input, start, end) {
208 |         return input.slice(0, start) + input.slice(end);
209 |     }
210 | 211 | 212 | 213 |

Removes the last character of input

214 | 215 | 216 |
    slang.chop = function chop(input) {
217 |         return input.slice(0, -1);
218 |     }
219 | 220 | 221 | 222 |

Removes leading and trailing whitespace from input

223 | 224 | 225 |
    slang.trim = function strip(input) {
226 |         return input.trim ? input.trim() : input.replace(/^\s+/, '').replace(/\s+$/, '');
227 |     }
228 | 229 | 230 | 231 |

Removes the leading whitespace from input

232 | 233 | 234 |
    slang.trimLeft = function trimLeft(input) {
235 |         return input.trimLeft ? input.trimLeft() : input.replace(/^\s+/, '');
236 |     }
237 | 238 | 239 | 240 |

Removes the trailing whitespace from input

241 | 242 | 243 |
    slang.trimRight = function trimRight(input) {
244 |         return input.trimRight ? input.trimRight() : input.replace(/\s+$/, '');
245 |     }
246 | 247 | 248 | 249 |

Truncates input to args.limit or 10 and adds args.omission or "..."

250 | 251 | 252 |
    slang.truncate = function truncate(input, args) {
253 |         var limit = args && args.limit || 10,
254 |             omission = args && args.omission || '...';
255 | 
256 |         return input.length <= limit ? input : input.slice(0, limit) + omission;
257 |     }
258 | 259 | 260 | 261 |

Joins an array into a humanized list. The last element is joined 262 | by "and" by default, but you can change it.

263 | 264 | 265 |
    slang.join = function join(array, last) {
266 |         var lastItem = array.pop(),
267 |             last = last || 'and';
268 |         
269 |         return array.join(', ') + ' ' + last + ' ' + lastItem;
270 |     }
271 | 272 | 273 | 274 |

Returns a humanized number with the correct suffix 275 | such as 1st, 2nd, 3rd or 4th

276 | 277 | 278 |
    slang.humanize = function humanize(number) {
279 |         if(number % 100 >= 11 && number % 100 <= 13)
280 |             return number + "th";
281 |         
282 |         switch(number % 10) {
283 |             case 1: return number + "st";
284 |             case 2: return number + "nd";
285 |             case 3: return number + "rd";
286 |         }
287 |         
288 |         return number + "th";
289 |     }
290 | 291 | 292 | 293 |

Returns whether input contains string

294 | 295 | 296 |
    slang.contains = function contains(input, string) {
297 |         return input.indexOf(string) > -1;
298 |     }
299 | 300 | 301 | 302 |

Returns whether input starts with string

303 | 304 | 305 |
    slang.startsWith = function startsWith(input, string) {
306 |         return input.indexOf(string) === 0;
307 |     }
308 | 309 | 310 | 311 |

Returns whether input ends with string

312 | 313 | 314 |
    slang.endsWith = function endsWith(input, string) {
315 |         var index = input.length - string.length;
316 |         return index >= 0 && input.indexOf(string, index) > -1;
317 |     }
318 | 319 | 320 | 321 |

Returns whether input is empty or only contains whitespace

322 | 323 | 324 |
    slang.isBlank = function isBlank(input) {
325 |         return /^\s*$/.test(input);
326 |     }
327 | 328 | 329 | 330 |

Returns the successor to str. The successor is calculated by incrementing characters starting 331 | from the rightmost alphanumeric (or the rightmost character if there are no alphanumerics) in the 332 | string. Incrementing a digit always results in another digit, and incrementing a letter results in 333 | another letter of the same case.

334 |

If the increment generates a carry, the character to the left of it is incremented. This 335 | process repeats until there is no carry, adding an additional character if necessary.

336 |
slang.successor("abcd")      == "abce"
337 | slang.successor("THX1138")   == "THX1139"
338 | slang.successor("<<koala>>") == "<<koalb>>"
339 | slang.successor("1999zzz")   == "2000aaa"
340 | slang.successor("ZZZ9999")   == "AAAA0000"
341 | 
342 | 343 |
    slang.successor = function successor(input) {
344 |         var alphabet = 'abcdefghijklmnopqrstuvwxyz',
345 |             length = alphabet.length,
346 |             result = input,
347 |             i = input.length;
348 | 
349 |         while(i >= 0) {
350 |             var last = input.charAt(--i),
351 |                 next = '',
352 |                 carry = false;
353 | 
354 |             if (isNaN(last)) {
355 |                 index = alphabet.indexOf(last.toLowerCase());
356 | 
357 |                 if (index === -1) {
358 |                     next = last;
359 |                     carry = true;
360 |                 }
361 |                 else {
362 |                     var isUpperCase = last === last.toUpperCase();
363 |                     next = alphabet.charAt((index + 1) % length);
364 |                     if (isUpperCase) {
365 |                         next = next.toUpperCase();
366 |                     }
367 | 
368 |                     carry = index + 1 >= length;
369 |                     if (carry && i === 0) {
370 |                         var added = isUpperCase ? 'A' : 'a';
371 |                         result = added + next + result.slice(1);
372 |                         break;
373 |                     }
374 |                 }
375 |             }
376 |             else {
377 |                 next = +last + 1;
378 |                 if(next > 9) {
379 |                     next = 0;
380 |                     carry = true
381 |                 }
382 | 
383 |                 if (carry && i === 0) {
384 |                     result = '1' + next + result.slice(1);
385 |                     break;
386 |                 }
387 |             }
388 | 
389 |             result = result.slice(0, i) + next + result.slice(i + 1);
390 |             if (!carry) {
391 |                 break;
392 |             }
393 |         }
394 |         return result;
395 |     }
396 | 397 | 398 | 399 |

Returns a unique guid of the specified length, or 32 by default

400 | 401 | 402 |
    slang.guid = function guid(length) {
403 |         var buf = [],
404 |             chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
405 |             charlen = chars.length,
406 |             length = length || 32;
407 |             
408 |         for (var i = 0; i < length; i++) {
409 |             buf[i] = chars.charAt(Math.floor(Math.random() * charlen));
410 |         }
411 |         
412 |         return buf.join('');
413 |     }
414 | 415 | 416 | 417 |

Inflection

418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 |

Set default language for inflection methods

427 | 428 | 429 |
    slang.lang = 'en';
430 | 431 | 432 | 433 |

Object to hold languages for inflection 434 | Add slang.Language objects to this

435 | 436 | 437 |
    slang.languages = {};
438 | 439 | 440 | 441 |

Define object to hold information about a language

442 | 443 | 444 |
    function Language() {
445 |         this.plural = [];
446 |         this.singular = [];
447 |         this.uncountable = [];
448 |         this.irregular = {
449 |             plural: {},
450 |             singular: {}
451 |         };
452 |     }
453 |     
454 |     slang.Language = Language;
455 | 456 | 457 | 458 |

Adds an array of irregular words to the language. 459 | Provide an array of arrays containing the singular 460 | and plural versions of the word

461 | 462 | 463 |
    Language.prototype.addIrregular = function(irregular) {
464 |         for (var i = 0, len = irregular.length; i < len; i++) {
465 |             var item = irregular[i];
466 |             this.irregular.plural[item[0]] = item[1];
467 |             this.irregular.singular[item[1]] = item[0];
468 |         }
469 |     }
470 | 471 | 472 | 473 |

Inflects a word by the specified type ('singular' or 'plural')

474 | 475 | 476 |
    Language.prototype.inflect = function(word, type) {
477 | 478 | 479 | 480 |

Check if this word is uncountable

481 | 482 | 483 |
        if (~this.uncountable.indexOf(word.toLowerCase()))
484 |             return word;
485 | 486 | 487 | 488 |

Check if this word is irregular

489 | 490 | 491 |
        var irregular = this.irregular[type][word];
492 |         if (irregular)
493 |             return irregular;
494 | 495 | 496 | 497 |

Check rules until a match is found

498 | 499 | 500 |
        var rules = this[type];
501 |         for (var i = 0, len = rules.length; i < len; i++) {
502 |             var regexp = rules[i][0];
503 |             if (regexp.test(word))
504 |                 return word.replace(regexp, rules[i][1]);
505 |         }
506 |         
507 |         return word;
508 |     }
509 | 510 | 511 | 512 |

Pluralize a word in the specified language 513 | or slang.lang by default

514 | 515 | 516 |
    slang.pluralize = function(word, lang) {
517 |         lang || (lang = slang.lang);
518 |         lang = slang.languages[lang];
519 |         
520 |         if (!lang)
521 |             return word;
522 |         
523 |         return lang.inflect(word, 'plural');
524 |     }
525 | 526 | 527 | 528 |

Singularize a word in the specified language 529 | or slang.lang by default

530 | 531 | 532 |
    slang.singularize = function(word, lang) {
533 |         lang || (lang = slang.lang);
534 |         lang = slang.languages[lang];
535 |         
536 |         if (!lang)
537 |             return word;
538 |             
539 |         return lang.inflect(word, 'singular');
540 |     }
541 | 542 | 543 | 544 |

Adds the methods from the slang object to String.prototype

545 | 546 | 547 |
    slang.addToPrototype = function addToPrototype() {
548 |         for (key in slang) {
549 |             if (key === 'guid' || 
550 |                 key === 'lang' ||
551 |                 key === 'languages' ||
552 |                 key === 'Language' ||
553 |                 key === 'humanize' ||
554 |                 key === 'isString' || 
555 |                 key === 'version' || 
556 |                 key === 'addToPrototype') {
557 |                     continue;
558 |             }
559 |             
560 |             (function(key) {
561 |                 String.prototype[key] = function() {
562 |                     var args = Array.prototype.slice.call(arguments)
563 |                     return slang[key].apply(slang, [this].concat(args));
564 |                 }
565 |             })(key);
566 |         }
567 |     }
568 | 569 | 570 | 571 |

English Inflector

572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 |

Define language for English

581 | 582 | 583 |
    var en = slang.languages['en'] = new slang.Language();
584 |     
585 |     en.plural = [
586 |         [/(todo)$/i, "$1s"],
587 |         [/(matr|vert|ind)(?:ix|ex)$/i, "$1ices"],
588 |         [/(octop|vir)us$/i, "$1i"],
589 |         [/(alias|status)$/i, "$1es"],
590 |         [/(cris|ax|test)is$/i, "$1es"],
591 |         [/(s|ss|sh|ch|x|o)$/i, "$1es"],
592 |         [/y$/i, "ies"],
593 |         [/(o|e)y$/i, "$1ys"],
594 |         [/([ti])um$/i, "$1a"],
595 |         [/sis$/i, "ses"],
596 |         [/(?:([^f])fe|([lr])f)$/i, "$1$2ves"],
597 |         [/([^aeiouy]|qu)y$/i, "$1ies"],
598 |         [/([m|l])ouse$/i, "$1ice"],
599 |         [/^(ox)$/i, "$1en"],
600 |         [/(quiz)$/i, "$1zes"],
601 |         [/$/, "s"]
602 |     ];
603 | 
604 |     en.singular = [
605 |         [/(bu|mis|kis)s$/i, "$1s"],
606 |         [/([ti])a$/i, "$1um"],
607 |         [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, "$1$2sis"],
608 |         [/(^analy)ses$/i, "$1sis"],
609 |         [/([^f])ves$/i, "$1fe"],
610 |         [/([lr])ves$/i, "$1f"],
611 |         [/([^aeiouy]|qu)ies$/i, "$1y"],
612 |         [/ies$/i, "ie"],
613 |         [/(x|ch|ss|sh)es$/i, "$1"],
614 |         [/([m|l])ice$/i, "$1ouse"],
615 |         [/(bus)es$/i, "$1"],
616 |         [/(shoe)s$/i, "$1"],
617 |         [/(o)es$/i, "$1"],
618 |         [/(cris|ax|test)es$/i, "$1is"],
619 |         [/(octop|vir)i$/i, "$1us"],
620 |         [/(alias|status)es$/i, "$1"],
621 |         [/^(ox)en/i, "$1"],
622 |         [/(vert|ind)ices$/i, "$1ex"],
623 |         [/(matr)ices$/i, "$1ix"],
624 |         [/(quiz)zes$/i, "$1"],
625 |         [/s$/i, ""]
626 |     ];
627 | 
628 |     en.addIrregular([
629 |         ['i', 'we'],
630 |         ['person', 'people'],
631 |         ['man', 'men'],
632 |         ['child', 'children'],
633 |         ['move', 'moves'],
634 |         ['she', 'they'],
635 |         ['he', 'they'],
636 |         ['myself', 'ourselves'],
637 |         ['yourself', 'ourselves'],
638 |         ['himself', 'themselves'],
639 |         ['herself', 'themselves'],
640 |         ['themself', 'themselves'],
641 |         ['mine', 'ours'],
642 |         ['hers', 'theirs'],
643 |         ['his', 'theirs'],
644 |         ['its', 'theirs'],
645 |         ['theirs', 'theirs'],
646 |         ['sex', 'sexes'],
647 |         ['this', 'that']
648 |     ]);
649 | 
650 |     en.uncountable = [
651 |         'advice',
652 |         'enegery',
653 |         'excretion',
654 |         'digestion',
655 |         'cooperation',
656 |         'health',
657 |         'justice',
658 |         'jeans',
659 |         'labour',
660 |         'machinery',
661 |         'equipment',
662 |         'information',
663 |         'pollution',
664 |         'sewage',
665 |         'paper',
666 |         'money',
667 |         'species',
668 |         'series',
669 |         'rain',
670 |         'rice',
671 |         'fish',
672 |         'sheep',
673 |         'moose',
674 |         'deer',
675 |         'bison',
676 |         'proceedings',
677 |         'shears',
678 |         'pincers',
679 |         'breeches',
680 |         'hijinks',
681 |         'clippers',
682 |         'chassis',
683 |         'innings',
684 |         'elk',
685 |         'rhinoceros',
686 |         'swine',
687 |         'you',
688 |         'news'
689 |     ];
690 | 
691 | })();
692 | 693 | 694 |
h
695 |
696 |
697 | 698 | 699 | --------------------------------------------------------------------------------