├── .hsdoc ├── .babelrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── __tests__ ├── runner.js └── humanize.spec.js ├── bin ├── build └── header ├── bower.json ├── LICENSE ├── CHANGELOG.md ├── package.json ├── dist ├── humanize.min.js └── humanize.js ├── README.md └── src └── humanize.js /.hsdoc: -------------------------------------------------------------------------------- 1 | source: "coffee/src/*.coffee" 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015-riot"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules/ 3 | /build/ 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .npmignore 2 | npm-debug.log 3 | node_modules/ 4 | build/ 5 | coffee/ 6 | public/dist 7 | public/test 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5" 4 | - "4" 5 | - "0.12" 6 | - "0.10" 7 | - "iojs" 8 | notifications: 9 | email: false 10 | -------------------------------------------------------------------------------- /__tests__/runner.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | require('babel-core/register'); 4 | 5 | var Jasmine = require('jasmine'); 6 | var jasmine = new Jasmine(); 7 | 8 | jasmine.loadConfig({ 9 | spec_dir: '__tests__', 10 | spec_files: [ 11 | '**/*.spec.js' 12 | ], 13 | stopSpecOnExpectationFailure: true 14 | }); 15 | 16 | jasmine.execute(); 17 | -------------------------------------------------------------------------------- /bin/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | RESET='\033[0m' 4 | RED='\033[0;31m' 5 | BLUE='\033[0;34m' 6 | CYAN='\033[0;36m' 7 | 8 | FIND_ROOT="git rev-parse --show-toplevel" 9 | $($FIND_ROOT &>/dev/null) 10 | if [[ $? -eq 0 ]]; then 11 | PROJECT_ROOT=$($FIND_ROOT) 12 | cd $PROJECT_ROOT 13 | fi 14 | 15 | printf "\n${BLUE}[Humanize]${RESET} Wiping build directory...\n" 16 | npm run clean 17 | 18 | printf "\n${BLUE}[Humanize]${RESET} Compiling ES6 files...\n" 19 | npm run babel 20 | 21 | printf "\n${BLUE}[Humanize]${RESET} Compressing compiled files...\n" 22 | npm run uglify 23 | 24 | printf "\n${BLUE}[Humanize]${RESET} Appending headers...\n" 25 | npm run header 26 | 27 | printf "\n${CYAN}(づ。◕‿‿◕。)づ All done!${RESET}\n" 28 | -------------------------------------------------------------------------------- /bin/header: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* eslint-disable */ 4 | 5 | var fs = require('fs') 6 | var path = require('path'); 7 | var glob = require('glob'); 8 | var pkg = require('../package.json'); 9 | 10 | function header(file) { 11 | return '/* ' + path.basename(file) + ' - v' + pkg.version + ' */'; 12 | } 13 | 14 | glob( 15 | path.join(__dirname, '../dist/**/*.js'), 16 | function (err, files) { 17 | if (err) throw err; 18 | files.forEach(function(file) { 19 | fs.readFile(file, function(err, content) { 20 | if (err) throw err; 21 | var prepended = [header(file), content].join('\n'); 22 | fs.writeFile(file, prepended, function(err) { 23 | if (err) throw err; 24 | }); 25 | }) 26 | }); 27 | } 28 | ); 29 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "humanize-plus", 3 | "version": "1.8.1", 4 | "homepage": "https://github.com/HubSpot/humanize", 5 | "authors": [ 6 | "HubSpot (http://dev.hubspot.com)", 7 | "Jonathan Kim (http://jonathan-kim.com)", 8 | "Bryan Ash (http://github.com/b-ash)", 9 | "Andy Aylward (http://github.com/aaylward)", 10 | "Nicholas Hwang (http://github.com/geekjuice)" 11 | ], 12 | "description": "A simple utility library for making the web more humane.", 13 | "main": "./src/humanize.js", 14 | "keywords": [ 15 | "humanize", 16 | "format" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2013-2016 HubSpot 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. 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 1.8.2 2 | - Close Humanize in UMD wrapper to prevent global variables 3 | 4 | ### 1.8.1 5 | - Compress minified file with mangling 6 | 7 | ### 1.8.0 8 | - Add precision parameter to fileSize [#70] 9 | - Update tests to use source file and not distribution 10 | 11 | ### 1.7.1 12 | - deprecate node v0.8 13 | - fix undefined recursive function for older node versions 14 | 15 | ### 1.7.0 16 | - replace coffeescript with ES2016 17 | - UMD wrap library 18 | 19 | ### 1.6.0 20 | - update build process 21 | 22 | ### 1.5.0 23 | 24 | - fix [#52](https://github.com/HubSpot/humanize/issues/52) 25 | - remove support for node 0.6.x 26 | 27 | ### 1.4.2 28 | 29 | - fix [#41](https://github.com/HubSpot/humanize/issues/41) 30 | 31 | ### 1.4.1 32 | 33 | - documentation update for npm 34 | 35 | ### 1.4.0 36 | 37 | - add optional `downCaseTail` argument to [Humanize.capitalize](https://github.com/HubSpot/humanize#capitalize) 38 | - add camelCase aliases 39 | - `intComma` -> `intcomma` 40 | - `fileSize` -> `filesize` 41 | - `truncateWords` -> `truncatewords` 42 | - `boundedNumber` -> `truncatenumber` 43 | - `titleCase` -> `titlecase` 44 | 45 | - optimize internal `doTitleCase` method 46 | - remove unused helper methods 47 | - add default arguments for `truncate` 48 | 49 | ### 1.3.5 50 | - [Release Notes](https://github.com/HubSpot/humanize/tree/master#release-notes) added to README 51 | 52 | ### 1.3.4 53 | - fix [#33](https://github.com/HubSpot/humanize/issues/33) 54 | 55 | ### 1.3.3 56 | 57 | - fix [#27](https://github.com/HubSpot/humanize/issues/27) 58 | 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "humanize-plus", 3 | "title": "Humanize", 4 | "description": "A simple utility library for making the web more humane.", 5 | "version": "1.8.2", 6 | "license": "MIT", 7 | "homepage": "https://github.com/HubSpot/humanize", 8 | "main": "./dist/humanize.js", 9 | "author": { 10 | "name": "HubSpotDev", 11 | "email": "devteam@hubspot.com", 12 | "url": "dev.hubspot.com" 13 | }, 14 | "maintainers": [ 15 | { 16 | "name": "Jonathan Kim", 17 | "email": "me@jonathan-kim.com", 18 | "web": "http://jonathan-kim.com" 19 | }, 20 | { 21 | "name": "Bryan Ash", 22 | "email": "ashbryanct@gmail.com", 23 | "web": "http://github.com/b-ash" 24 | }, 25 | { 26 | "name": "Andy Aylward", 27 | "email": "aaylward@gmail.com", 28 | "web": "http://github.com/aaylward" 29 | }, 30 | { 31 | "name": "Nicholas Hwang", 32 | "email": "geek@nicholashwang.com", 33 | "web": "http://github.com/geekjuice" 34 | } 35 | ], 36 | "repository": { 37 | "type": "git", 38 | "url": "git://github.com/HubSpot/humanize.git" 39 | }, 40 | "bugs": { 41 | "url": "https://github.com/HubSpot/humanize/issues" 42 | }, 43 | "engines": { 44 | "node": ">= 0.8.0" 45 | }, 46 | "scripts": { 47 | "reinstall": "del node_modules && npm install", 48 | "clean": "del dist", 49 | "preuglify": "mkdir -p dist", 50 | "uglify": "uglifyjs -cmo dist/humanize.min.js dist/humanize.js", 51 | "watch": "babel --watch src -d dist", 52 | "babel": "babel src -d dist", 53 | "header": "./bin/header", 54 | "build": "./bin/build", 55 | "test": "node __tests__/runner.js" 56 | }, 57 | "devDependencies": { 58 | "babel-cli": "^6.6.5", 59 | "babel-core": "^6.7.2", 60 | "babel-preset-es2015-riot": "^1.0.3", 61 | "del-cli": "^0.2.0", 62 | "glob": "^7.0.3", 63 | "jasmine": "^2.4.1", 64 | "uglify-js": "^2.6.2" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /dist/humanize.min.js: -------------------------------------------------------------------------------- 1 | /* humanize.min.js - v1.8.2 */ 2 | "use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(n){return typeof n}:function(n){return n&&"function"==typeof Symbol&&n.constructor===Symbol?"symbol":typeof n};!function(n,e){"object"===("undefined"==typeof exports?"undefined":_typeof(exports))?module.exports=e():"function"==typeof define&&define.amd?define([],function(){return n.Humanize=e()}):n.Humanize=e()}(this,function(){var n=[{name:"second",value:1e3},{name:"minute",value:6e4},{name:"hour",value:36e5},{name:"day",value:864e5},{name:"week",value:6048e5}],e={P:Math.pow(2,50),T:Math.pow(2,40),G:Math.pow(2,30),M:Math.pow(2,20)},t=function(n){return"undefined"!=typeof n&&null!==n},r=function(n){return n!==n},i=function(n){return isFinite(n)&&!r(parseFloat(n))},o=function(n){var e=Object.prototype.toString.call(n);return"[object Array]"===e},a={intword:function(n,e){var t=arguments.length<=2||void 0===arguments[2]?2:arguments[2];return a.compactInteger(n,t)},compactInteger:function(n){var e=arguments.length<=1||void 0===arguments[1]?0:arguments[1];e=Math.max(e,0);var t=parseInt(n,10),r=0>t?"-":"",i=Math.abs(t),o=String(i),a=o.length,u=[13,10,7,4],l=["T","B","M","k"];if(1e3>i)return""+r+o;if(a>u[0]+3)return t.toExponential(e).replace("e+","x10^");for(var f=void 0,c=0;c=v){f=v;break}}var s=a-f+1,d=o.split(""),p=d.slice(0,s),h=d.slice(s,s+e+1),g=p.join(""),m=h.join("");m.length=i)return a.formatNumber(n/i,t,"")+" "+r+"B"}return n>=1024?a.formatNumber(n/1024,0)+" KB":a.formatNumber(n,0)+a.pluralize(n," byte")},filesize:function(){return a.fileSize.apply(a,arguments)},formatNumber:function(n){var e=arguments.length<=1||void 0===arguments[1]?0:arguments[1],t=arguments.length<=2||void 0===arguments[2]?",":arguments[2],r=arguments.length<=3||void 0===arguments[3]?".":arguments[3],i=function(n,e,t){return t?n.substr(0,t)+e:""},o=function(n,e,t){return n.substr(t).replace(/(\d{3})(?=\d)/g,"$1"+e)},u=function(n,e,t){return t?e+a.toFixed(Math.abs(n),t).split(".")[1]:""},l=a.normalizePrecision(e),f=0>n&&"-"||"",c=String(parseInt(a.toFixed(Math.abs(n||0),l),10)),v=c.length>3?c.length%3:0;return f+i(c,t,v)+o(c,t,v)+u(n,r,l)},toFixed:function(n,e){e=t(e)?e:a.normalizePrecision(e,0);var r=Math.pow(10,e);return(Math.round(n*r)/r).toFixed(e)},normalizePrecision:function(n,e){return n=Math.round(Math.abs(n)),r(n)?e:n},ordinal:function(n){var e=parseInt(n,10);if(0===e)return n;var t=e%100;if([11,12,13].indexOf(t)>=0)return e+"th";var r=e%10,i=void 0;switch(r){case 1:i="st";break;case 2:i="nd";break;case 3:i="rd";break;default:i="th"}return""+e+i},times:function(n){var e=arguments.length<=1||void 0===arguments[1]?{}:arguments[1];if(i(n)&&n>=0){var r=parseFloat(n),o=["never","once","twice"];if(t(e[r]))return String(e[r]);var a=t(o[r])&&o[r].toString();return a||r.toString()+" times"}return null},pluralize:function(n,e,r){return t(n)&&t(e)?(r=t(r)?r:e+"s",1===parseInt(n,10)?e:r):null},truncate:function(n){var e=arguments.length<=1||void 0===arguments[1]?100:arguments[1],t=arguments.length<=2||void 0===arguments[2]?"...":arguments[2];return n.length>e?n.substring(0,e-t.length)+t:n},truncateWords:function(n,e){for(var r=n.split(" "),i="",o=0;e>o;)t(r[o])&&(i+=r[o]+" "),o++;return r.length>e?i+"...":null},truncatewords:function(){return a.truncateWords.apply(a,arguments)},boundedNumber:function(n){var e=arguments.length<=1||void 0===arguments[1]?100:arguments[1],t=arguments.length<=2||void 0===arguments[2]?"+":arguments[2],r=void 0;return i(n)&&i(e)&&n>e&&(r=e+t),(r||n).toString()},truncatenumber:function(){return a.boundedNumber.apply(a,arguments)},oxford:function(n,e,r){var i=n.length,o=void 0;if(2>i)return String(n);if(2===i)return n.join(" and ");if(t(e)&&i>e){var u=i-e;o=e,r=t(r)?r:", and "+u+" "+a.pluralize(u,"other")}else o=-1,r=", and "+n[i-1];return n.slice(0,o).join(", ")+r},dictionary:function(n){var e=arguments.length<=1||void 0===arguments[1]?" is ":arguments[1],r=arguments.length<=2||void 0===arguments[2]?", ":arguments[2],i="";if(t(n)&&"object"===("undefined"==typeof n?"undefined":_typeof(n))&&!o(n)){var a=[];for(var u in n)if(n.hasOwnProperty(u)){var l=n[u];a.push(""+u+e+l)}return a.join(r)}return i},frequency:function(n,e){if(!o(n))return null;var t=n.length,r=a.times(t);return 0===t?r+" "+e:e+" "+r},pace:function(e,t){var r=arguments.length<=2||void 0===arguments[2]?"time":arguments[2];if(0===e||0===t)return"No "+a.pluralize(0,r);for(var i="Approximately",o=void 0,u=void 0,l=e/t,f=0;f1){o=c.name;break}}o||(i="Less than",u=1,o=n[n.length-1].name);var v=Math.round(u);return r=a.pluralize(v,r),i+" "+v+" "+r+" per "+o},nl2br:function(n){var e=arguments.length<=1||void 0===arguments[1]?"
":arguments[1];return n.replace(/\n/g,e)},br2nl:function(n){var e=arguments.length<=1||void 0===arguments[1]?"\r\n":arguments[1];return n.replace(/\/g,e)},capitalize:function(n){var e=arguments.length<=1||void 0===arguments[1]?!1:arguments[1];return""+n.charAt(0).toUpperCase()+(e?n.slice(1).toLowerCase():n.slice(1))},capitalizeAll:function(n){return n.replace(/(?:^|\s)\S/g,function(n){return n.toUpperCase()})},titleCase:function(n){var e=/\b(a|an|and|at|but|by|de|en|for|if|in|of|on|or|the|to|via|vs?\.?)\b/i,t=/\S+[A-Z]+\S*/,r=/\s+/,i=/-/,o=void 0;return(o=function(n){for(var u=arguments.length<=1||void 0===arguments[1]?!1:arguments[1],l=arguments.length<=2||void 0===arguments[2]?!0:arguments[2],f=[],c=n.split(u?i:r),v=0;v 25 | 29 | ``` 30 | 31 | In your node package.json: 32 | ```javascript 33 | "dependencies": { 34 | "humanize-plus": "^1.7.0" 35 | } 36 | ``` 37 | 38 | For recent changes, see the [changelog](https://github.com/HubSpot/humanize/blob/master/CHANGELOG.md). 39 | 40 | ## API Methods 41 | 42 | ### Numbers 43 | 44 | ##### formatNumber 45 | Formats a number to a human-readable string. Localize by overriding the precision, thousand and decimal arguments. 46 | 47 | ```javascript 48 | Humanize.formatNumber(123456789, 2) 49 | // "123,456,789.00" 50 | ``` 51 | 52 | ##### intComma 53 | Converts an integer to a string containing commas every three digits. 54 | 55 | ```javascript 56 | Humanize.intComma(123456789) 57 | // "123,456,789" 58 | ``` 59 | ##### intcomma - DEPRECATED - This method will not be present in the next major version. 60 | Alias for `intComma` 61 | 62 | 63 | ##### intword - DEPRECATED - This method will not be present in the next major version. 64 | Converts a large integer to a friendly text representation. 65 | This method is now a thin wrapper around compactInteger 66 | 67 | `Humanize.intword(num, ch, de) === Humanize.compactInteger(num, de)` 68 | 69 | ```javascript 70 | Humanize.intword(123456789, 'nopnopnopnop', 1) 71 | // "123.5M" 72 | 73 | Humanize.intword(123456789, 'this is a nop', 3) 74 | // "123.457M" 75 | 76 | Humanize.intword(10, 'still a nop', 1) 77 | // "10" 78 | ``` 79 | 80 | ##### compactInteger 81 | Converts an integer into its most compact representation. Decimal precision is ignored for all integers, n, such that abs(n) < 1000. 82 | 83 | ```javascript 84 | Humanize.compactInteger(123456789, 1) 85 | // "123.5M" 86 | 87 | // Switch to scientific notation for trillons, because no one knows those abbreviations. 88 | Humanize.compactInteger(-7832186132456328967, 4) 89 | // "-7.8322x10^18" 90 | 91 | Humanize.compactInteger(-100, 2) 92 | // "-100" 93 | ``` 94 | 95 | ##### boundedNumber 96 | Bounds a value from above. Modified values have customizable ending strings ('+' by default) 97 | 98 | ```javascript 99 | Humanize.boundedNumber(110, 100) 100 | // "100+" 101 | 102 | Humanize.boundedNumber(50, 100) 103 | // "50" 104 | ``` 105 | 106 | ##### truncatenumber - DEPRECATED - This method will not be present in the next major version. 107 | Alias for `boundedNumber` 108 | 109 | ##### ordinal 110 | Converts an integer to its ordinal as a string. 111 | 112 | ```javascript 113 | Humanize.ordinal(22) 114 | // "22nd" 115 | ``` 116 | 117 | ##### times 118 | Interprets numbers as occurences. Also accepts an optional array/map of overrides. 119 | 120 | ```javascript 121 | for (i=0; i<5; i++) { 122 | Humanize.times(i, {"4": "too many"}); 123 | // Bonus! 124 | if (i === 1) { 125 | Humanize.times(1.1); 126 | } 127 | } 128 | // never 129 | // once 130 | // 1.1 times 131 | // twice 132 | // 3 times 133 | // too many times 134 | ``` 135 | 136 | ##### pace 137 | Matches a pace (value and interval) with a logical time frame. Very useful for slow paces. 138 | 139 | ```javascript 140 | second = 1000 141 | week = 6.048e8 142 | decade = 3.156e11 143 | 144 | Humanize.pace(1.5, second, "heartbeat") 145 | // Approximately 2 heartbeats per second 146 | 147 | Humanize.pace(4, week) 148 | // Approximately 4 times per week 149 | 150 | Humanize.pace(1, decade, "life crisis") 151 | // Less than 1 life crisis per week 152 | ``` 153 | 154 | ##### fileSize 155 | Formats the value like a 'human-readable' file size (i.e. '13 KB', '4.1 MB', '102 bytes', etc). 156 | 157 | ```javascript 158 | Humanize.fileSize(1024 * 20) 159 | // "20 Kb" 160 | 161 | Humanize.fileSize(1024 * 2000) 162 | // "1.95 Mb" 163 | 164 | Humanize.fileSize(Math.pow(1000, 4)) 165 | // "931.32 Gb" 166 | ``` 167 | ##### filesize - DEPRECATED - This method will not be present in the next major version. 168 | Alias for `fileSize` 169 | 170 | 171 | ##### pluralize 172 | Returns the plural version of a given word if the value is not 1. The default suffix is 's'. 173 | 174 | ```javascript 175 | Humanize.pluralize(1, "duck") 176 | // "duck" 177 | 178 | Humanize.pluralize(3, "duck") 179 | // "ducks" 180 | 181 | Humanize.pluralize(3, "duck", "duckies") 182 | // "duckies" 183 | ``` 184 | 185 | ### Strings 186 | 187 | ##### truncate 188 | Truncates a string if it is longer than the specified number of characters. Truncated strings will end with a translatable ellipsis sequence ("…"). 189 | 190 | ```javascript 191 | Humanize.truncate('long text is good for you') 192 | // "long text is good for you" 193 | 194 | Humanize.truncate('long text is good for you', 19) 195 | // "long text is goo..." 196 | 197 | Humanize.truncate('long text is good for you', 19, '... etc') 198 | // "long text is... etc" 199 | ``` 200 | 201 | ##### truncateWords 202 | Truncates a string after a certain number of words. 203 | 204 | ```javascript 205 | Humanize.truncateWords('long text is good for you', 5) 206 | // "long text is good for ..." 207 | ``` 208 | 209 | ##### truncatewords - DEPRECATED - This method will not be present in the next major version. 210 | Alias for `truncateWords` 211 | 212 | ##### nl2br and br2nl 213 | Flexible conversion of `
` tags to newlines and vice versa. 214 | 215 | ```javascript 216 | // Use your imagination 217 | ``` 218 | 219 | ##### capitalize 220 | Capitalizes the first letter in a string, optionally downcasing the tail. 221 | 222 | ```javascript 223 | Humanize.capitalize("some boring string") 224 | // "Some boring string" 225 | 226 | Humanize.capitalize("wHoOaA!") 227 | // "WHoOaA!" 228 | 229 | Humanize.capitalize("wHoOaA!", true) 230 | // "Whooaa!" 231 | ``` 232 | 233 | ##### capitalizeAll 234 | Captializes the first letter of every word in a string. 235 | 236 | ```javascript 237 | Humanize.capitalizeAll("some boring string") 238 | // "Some Boring String" 239 | ``` 240 | 241 | ##### titleCase 242 | Intelligently capitalizes eligible words in a string and normalizes internal whitespace. 243 | 244 | ```javascript 245 | Humanize.titleCase("some of a boring string") 246 | // "Some of a Boring String" 247 | 248 | Humanize.titleCase("cool the iTunes cake, O'Malley!") 249 | // "Cool the iTunes Cake, O'Malley!" 250 | ``` 251 | 252 | ##### titlecase - DEPRECATED - This method will not be present in the next major version. 253 | Alias for `titleCase` 254 | 255 | 256 | ### Arrays 257 | 258 | ##### oxford 259 | Converts a list of items to a human readable string with an optional limit. 260 | 261 | ```javascript 262 | items = ['apple', 'orange', 'banana', 'pear', 'pineapple'] 263 | 264 | Humanize.oxford(items) 265 | // "apple, orange, banana, pear, and pineapple" 266 | 267 | Humanize.oxford(items, 3) 268 | // "apple, orange, banana, and 2 others" 269 | 270 | // Pluralizes properly too! 271 | Humanize.oxford(items, 4) 272 | // "apple, orange, banana, pear, and 1 other" 273 | 274 | Humanize.oxford(items, 3, "and some other fruits") 275 | // "apple, orange, banana, and some other fruits" 276 | ``` 277 | 278 | ##### frequency 279 | Describes how many times an item appears in a list 280 | 281 | ```javascript 282 | catPics = [ 283 | 'https://media2.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif', 284 | 'https://media3.giphy.com/media/uzglgIsyY1Cgg/giphy.gif' 285 | ] 286 | dogPics = [] 287 | 288 | "Cats " + Humanize.frequency(catPics, "typed on keyboards") 289 | // "Cats typed on keyboards 3 times" 290 | 291 | "Dogs " + Humanize.frequency(docPics, "typed on keyboards") 292 | // "Dogs never typed on keyboards" 293 | ``` 294 | 295 | 296 | ### Utility methods 297 | 298 | ##### toFixed 299 | Fixes binary rounding issues (eg. (0.615).toFixed(2) === "0.61"). 300 | 301 | ```javascript 302 | Humanize.toFixed(0.615, 2) 303 | // "0.62" 304 | ``` 305 | 306 | ##### normalizePrecision 307 | Ensures precision value is a positive integer. 308 | 309 | ```javascript 310 | Humanize.normalizePrecision(-232.231) 311 | // 232 312 | ``` 313 | 314 | ## Important notes 315 | Please don't edit files in the `dist` subdirectory as they are generated through compilation. You'll find source code in the `src` subdirectory! 316 | 317 | ## Compiling 318 | 319 | `npm run install && npm run build` 320 | 321 | And that's it! 322 | 323 | The project will compile the CoffeeScript files into the `dist` subdirectory. 324 | 325 | ## Testing 326 | 327 | `npm run test` 328 | 329 | 330 | ## License 331 | Copyright (c) 2013-2016 HubSpotDev 332 | Licensed under the MIT license. 333 | -------------------------------------------------------------------------------- /__tests__/humanize.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | 3 | import Humanize from '../src/humanize'; 4 | 5 | describe('When using method via destructured assignment', () => { 6 | it('should properly reference other Humanize methods', () => { 7 | spyOn(Humanize, 'capitalize').and.callThrough(); 8 | expect(Humanize.titlecase('ship it')).toEqual('Ship It'); 9 | expect(Humanize.capitalize).toHaveBeenCalled(); 10 | }); 11 | }); 12 | 13 | describe('Millions as word', () => { 14 | it('should pass', () => { 15 | expect(Humanize).toBeDefined(); 16 | expect(Humanize.intword(123456789, 'this is a nop', 2)).toEqual('123.46M'); 17 | expect(Humanize.intword(123456789, 'this is a nop', 1)).toEqual('123.5M'); 18 | expect(Humanize.intword(100)).toEqual('100'); 19 | expect(Humanize.intword(100, 'this is a nop', 0)).toEqual('100'); 20 | }); 21 | }); 22 | 23 | describe('compactInteger tests', () => { 24 | it('should string small numbers', () => { 25 | expect(Humanize).toBeDefined(); 26 | expect(Humanize.compactInteger(999)).toEqual('999'); 27 | expect(Humanize.compactInteger(999, 2)).toEqual('999'); 28 | expect(Humanize.compactInteger(-999)).toEqual('-999'); 29 | expect(Humanize.compactInteger(-0, 1)).toEqual('0'); 30 | expect(Humanize.compactInteger(15, 0)).toEqual('15'); 31 | expect(Humanize.compactInteger(7832186132456328967, 2)).toEqual('7.83x10^18'); 32 | expect(Humanize.compactInteger(-7832186132456328967, 4)).toEqual('-7.8322x10^18'); 33 | expect(Humanize.compactInteger(1000, 0)).toEqual('1k'); 34 | expect(Humanize.compactInteger(-99321, 2)).toEqual('-99.32k'); 35 | expect(Humanize.compactInteger(3199321, 1)).toEqual('3.2M'); 36 | expect(Humanize.compactInteger(-37123436321, 5)).toEqual('-37.12344B'); 37 | expect(Humanize.compactInteger(-9900432253321, 1)).toEqual('-9.9T'); 38 | expect(Humanize.compactInteger(-9960432253321, 1)).toEqual('-10.0T'); 39 | expect(Humanize.compactInteger(9990432253, 1)).toEqual('10.0B'); 40 | expect(Humanize.compactInteger(100)).toEqual('100'); 41 | expect(Humanize.compactInteger(123456789, 1)).toEqual('123.5M'); 42 | }); 43 | }); 44 | 45 | 46 | describe('Ordinal value of numbers Test Suite', () => { 47 | describe('Ordinal value for numbers ending in zero', () => { 48 | it('should return 0 if the number is 0 (cos 0th doesnt read very well)', () => { 49 | expect(Humanize.ordinal(0)).toEqual(0); 50 | }); 51 | 52 | it('should return the number with suffix th', () => { 53 | expect(Humanize.ordinal(10)).toEqual('10th'); 54 | }); 55 | }); 56 | 57 | describe('Ordinal value for numbers ending in one', () => { 58 | it('should end in st for numbers not ending in 11', () => { 59 | expect(Humanize.ordinal(1)).toEqual('1st'); 60 | expect(Humanize.ordinal(11)).not.toEqual('11st'); 61 | expect(Humanize.ordinal(21)).toEqual('21st'); 62 | }); 63 | 64 | it('should be 11th for numbers ending in 11', () => { 65 | expect(Humanize.ordinal(11)).toEqual('11th'); 66 | expect(Humanize.ordinal(111)).toEqual('111th'); 67 | }); 68 | }); 69 | 70 | describe('Ordinal value for numbers ending in two', () => { 71 | it('should end in nd for numbers not ending in 12', () => { 72 | expect(Humanize.ordinal(2)).toEqual('2nd'); 73 | expect(Humanize.ordinal(12)).not.toEqual('12nd'); 74 | expect(Humanize.ordinal(22)).toEqual('22nd'); 75 | }); 76 | 77 | it('should be 12th for numbers ending in 12', () => { 78 | expect(Humanize.ordinal(12)).toEqual('12th'); 79 | expect(Humanize.ordinal(112)).toEqual('112th'); 80 | }); 81 | }); 82 | 83 | describe('Ordinal value for numbers ending in three', () => { 84 | it('should end in rd for numbers not ending in 13', () => { 85 | expect(Humanize.ordinal(3)).toEqual('3rd'); 86 | expect(Humanize.ordinal(13)).not.toEqual('13rd'); 87 | expect(Humanize.ordinal(23)).toEqual('23rd'); 88 | }); 89 | 90 | it('should be 13th for numbers ending in 13', () => { 91 | expect(Humanize.ordinal(13)).toEqual('13th'); 92 | expect(Humanize.ordinal(113)).toEqual('113th'); 93 | }); 94 | }); 95 | 96 | describe('Ordinal value for numbers ending in four', () => { 97 | it('should end in th for numbers', () => { 98 | expect(Humanize.ordinal(4)).toEqual('4th'); 99 | expect(Humanize.ordinal(14)).toEqual('14th'); 100 | expect(Humanize.ordinal(24)).toEqual('24th'); 101 | }); 102 | }) 103 | ; 104 | describe('Times tests', () => { 105 | it('should say never', () => { 106 | expect(Humanize.times(0)).toEqual('never'); 107 | }); 108 | 109 | it('should say once', () => { 110 | expect(Humanize.times(1)).toEqual('once'); 111 | }); 112 | 113 | it('should say twice', () => { 114 | expect(Humanize.times(2)).toEqual('twice'); 115 | expect(Humanize.times(2, {2: 'dos times'})).toEqual('dos times'); 116 | }); 117 | 118 | it('should say thrice or three times', () => { 119 | expect(Humanize.times(3)).toEqual('3 times'); 120 | expect(Humanize.times(3, {3: 'thrice'})).toEqual('thrice'); 121 | }); 122 | 123 | it('should say 12 times', () => { 124 | expect(Humanize.times(12)).toEqual('12 times'); 125 | expect(Humanize.times(12, {12: 'douze times'})).toEqual('douze times'); 126 | }); 127 | 128 | it('should allow number overrides for specified values', () => { 129 | expect(Humanize.times(12, {12: 'too many times'})).toEqual('too many times'); 130 | }); 131 | }); 132 | }); 133 | 134 | describe('Pluralize tests', () => { 135 | it('should append an s as the default', () => { 136 | expect(Humanize.pluralize(1, 'cupcake')).toEqual('cupcake'); 137 | expect(Humanize.pluralize(2, 'cupcake')).toEqual('cupcakes'); 138 | }); 139 | 140 | it('should return provided value for special cases', () => { 141 | expect(Humanize.pluralize(1, 'person', 'people')).toEqual('person'); 142 | expect(Humanize.pluralize(2, 'person', 'people')).toEqual('people'); 143 | expect(Humanize.pluralize(1, 'child', 'children')).toEqual('child'); 144 | expect(Humanize.pluralize(2, 'child', 'children')).toEqual('children'); 145 | }); 146 | }); 147 | 148 | describe('Filesize tests for nerds', () => { 149 | it('should append byte if it is exactly 1 byte', () => { 150 | expect(Humanize.fileSize(0)).toEqual('0 bytes'); 151 | expect(Humanize.filesize(1)).toEqual('1 byte'); 152 | expect(Humanize.filesize(2)).toEqual('2 bytes'); 153 | }); 154 | 155 | it('should append bytes if it is less than 1024 bytes', () => { 156 | expect(Humanize.filesize(512)).toEqual('512 bytes'); 157 | }); 158 | 159 | it('should return a file in KB if it is more than 1024 bytes', () => { 160 | expect(Humanize.filesize(1080)).toEqual('1 KB'); 161 | }); 162 | 163 | it('should return a file in MB if it is more than a 1024 * 1024 bytes', () => { 164 | expect(Humanize.filesize(2.22 * 1024 * 1024)).toEqual('2.22 MB'); 165 | }); 166 | 167 | it('should return a file in GB if it is more than a 1024 * 1024 * 1024 bytes', () => { 168 | expect(Humanize.filesize(2.22 * 1024 * 1024 * 1024)).toEqual('2.22 GB'); 169 | }); 170 | 171 | it('should return a file in TB if it is more than a 2^40 bytes', () => { 172 | expect(Humanize.filesize(2.22 * 1024 * 1024 * 1024 * 1024)).toEqual('2.22 TB'); 173 | }); 174 | 175 | it('should return a file in PB if it is more than a 2^50 bytes', () => { 176 | expect(Humanize.filesize(2.22 * 1024 * 1024 * 1024 * 1024 * 1024)).toEqual('2.22 PB'); 177 | }); 178 | 179 | it('should ignore precision when filesize is less than units', () => { 180 | expect(Humanize.filesize(512, 4)).toEqual('512 bytes'); 181 | }); 182 | 183 | it('should respect precision with filesize is greater than units', () => { 184 | expect(Humanize.filesize(2.22 * 1024 * 1024, 0)).toEqual('2 MB'); 185 | }); 186 | 187 | it('should respect greater precision when specified', () => { 188 | expect(Humanize.filesize(2.2222 * 1024 * 1024, 3)).toEqual('2.222 MB'); 189 | }); 190 | }); 191 | 192 | describe('Truncating objects to shorter versions', () => { 193 | const objs = { 194 | str: 'abcdefghijklmnopqrstuvwxyz', 195 | num: 1234567890, 196 | arr: [1, 2, 3, 4, 5] 197 | }; 198 | 199 | it('should truncate a long string with ellipsis', () => { 200 | expect(Humanize.truncate(objs.str, 14)).toEqual('abcdefghijk...'); 201 | expect(Humanize.truncate(objs.str, 14, '...kidding')).toEqual('abcd...kidding'); 202 | }); 203 | 204 | it('should truncate a number to an upper bound', () => { 205 | expect(Humanize.truncatenumber(objs.num, 500)).toEqual('500+'); 206 | expect(Humanize.boundedNumber(objs.num, 500)).toEqual('500+'); 207 | }); 208 | 209 | it('should not trucate things that are too short', () => { 210 | expect(Humanize.truncate(objs.str, objs.str.length + 1)).toEqual(objs.str); 211 | expect(Humanize.truncatenumber(objs.num, objs.num + 1)).toEqual(String(objs.num)); 212 | expect(Humanize.boundedNumber(objs.num, objs.num + 1)).toEqual(String(objs.num)); 213 | }); 214 | }); 215 | 216 | describe('Converting a list to a readable, oxford commafied string', () => { 217 | const items = ['apple', 'orange', 'banana', 'pear', 'pineapple']; 218 | 219 | it('should return an empty string when given an empty list', () => { 220 | expect(Humanize.oxford(items.slice(0, 0))).toEqual(''); 221 | }); 222 | 223 | it('should return a string version of a list that has only one value', () => { 224 | expect(Humanize.oxford(items.slice(0, 1))).toEqual('apple'); 225 | }); 226 | 227 | it("should return items separated by 'and' when given a list of two values", () => { 228 | expect(Humanize.oxford(items.slice(0, 2))).toEqual('apple and orange'); 229 | }); 230 | 231 | it('should convert a list to an oxford commafied string', () => { 232 | expect(Humanize.oxford(items.slice(0))).toEqual('apple, orange, banana, pear, and pineapple'); 233 | }); 234 | 235 | it('should truncate a large list of items with proper pluralization', () => { 236 | expect(Humanize.oxford(items.slice(0), 3)).toEqual('apple, orange, banana, and 2 others'); 237 | expect(Humanize.oxford(items.slice(0), 4)).toEqual('apple, orange, banana, pear, and 1 other'); 238 | }); 239 | 240 | it('should accept custom trucation strings', () => { 241 | const limitStr = ', and some other fruits'; 242 | 243 | expect(Humanize.oxford(items.slice(0), 3, limitStr)).toEqual(`apple, orange, banana${limitStr}`); 244 | expect(Humanize.oxford(items.slice(0, 3), 3, limitStr)).toEqual('apple, orange, and banana'); 245 | }); 246 | }); 247 | 248 | describe('Converting a hashmap to a dictionary-like string', () => { 249 | const hash = {'Jonathan': 24, 'Bash': 23, 'Matt': 26}; 250 | 251 | it('should not accept non-objects', () => { 252 | expect(Humanize.dictionary('String')).toEqual(''); 253 | expect(Humanize.dictionary(() => 'Function')).toEqual(''); 254 | expect(Humanize.dictionary([1, 2, 3])).toEqual(''); 255 | }); 256 | 257 | it('should convert a hash to a key-value string', () => { 258 | expect(Humanize.dictionary(hash)).toEqual('Jonathan is 24, Bash is 23, Matt is 26'); 259 | }); 260 | }); 261 | 262 | describe('Converting pace arguments into strings', () => { 263 | it('should convert two pace arguments to a string', () => { 264 | const second = 1000; 265 | const week = 6.048e8; 266 | const decade = 3.156e11; 267 | expect(Humanize.pace(4, week)).toEqual('Approximately 4 times per week'); 268 | expect(Humanize.pace(1.5, second, 'heartbeat')).toEqual('Approximately 2 heartbeats per second'); 269 | expect(Humanize.pace(1, decade, 'life crisis')).toEqual('Less than 1 life crisis per week'); 270 | }); 271 | }); 272 | 273 | describe('Converting line breaks', () => { 274 | it('should convert /\n to a
tag', () => { 275 | expect(Humanize.nl2br('\n')).toEqual('
'); 276 | }); 277 | 278 | it('should convert a
tag to /\r/\n (new line)', () => { 279 | expect(Humanize.br2nl('
')).toEqual('\r\n'); 280 | }); 281 | 282 | it('should convert a
tag to /\r/\n (new line)', () => { 283 | expect(Humanize.br2nl('
')).toEqual('\r\n'); 284 | }); 285 | 286 | it('should convert a malformed
tag to /\r/\n (new line)', () => { 287 | expect(Humanize.br2nl('
')).toEqual('\r\n'); 288 | }); 289 | 290 | it('should convert a malformed
tag to /\r/\n (new line)', () => { 291 | expect(Humanize.br2nl('
')).toEqual('\r\n'); 292 | }); 293 | }); 294 | 295 | describe('Capitalizing words appropriately', () => { 296 | it("should convert 'ship it' to 'Ship it'", () => { 297 | expect(Humanize.capitalize('ship it')).toEqual('Ship it'); 298 | expect(Humanize.capitalize('wHoOaA!')).toEqual('WHoOaA!'); 299 | expect(Humanize.capitalize('wHoOaA!', true)).toEqual('Whooaa!'); 300 | }); 301 | 302 | it("should convert 'ship it' to 'Ship It'", () => { 303 | expect(Humanize.titlecase('ship it')).toEqual('Ship It'); 304 | expect(Humanize.titleCase('ship it')).toEqual('Ship It'); 305 | }); 306 | 307 | it("should convert '' to ''", () => { 308 | expect(Humanize.titlecase('')).toEqual(''); 309 | expect(Humanize.titleCase('')).toEqual(''); 310 | }); 311 | 312 | it("should convert 'the boss is O\'Mally\'s brother.' to 'The Boss is O\'Mally\'s Brother.'", () => { 313 | expect(Humanize.titlecase('the boss likes O\'Mally\'s little brother a lot.')).toEqual('The Boss Likes O\'Mally\'s Little Brother a Lot.'); 314 | expect(Humanize.titleCase('the boss likes O\'Mally\'s little brother a lot.')).toEqual('The Boss Likes O\'Mally\'s Little Brother a Lot.'); 315 | }); 316 | 317 | it("should convert 'you get the cake an iTunes hat is West wacky?' to 'You Get the Cake an iTunes Hat Is West Wacky?'", () => { 318 | expect(Humanize.titlecase('you get the cake an iTunes hat is West wacky?')).toEqual('You Get the Cake an iTunes Hat Is West Wacky?'); 319 | expect(Humanize.titleCase('you get the cake an iTunes hat is West wacky?')).toEqual('You Get the Cake an iTunes Hat Is West Wacky?'); 320 | }); 321 | 322 | it("should convert 'cool the iTunes cake, O\'Malley!' to 'Cool the iTunes Cake, O\'Malley!'", () => { 323 | expect(Humanize.titlecase('cool the iTunes cake, O\'Malley!')).toEqual('Cool the iTunes Cake, O\'Malley!'); 324 | expect(Humanize.titleCase('cool the iTunes cake, O\'Malley!')).toEqual('Cool the iTunes Cake, O\'Malley!'); 325 | }); 326 | 327 | it("should convert 'cul-de-sac drive-by' to 'Cul-de-Sac Drive-By'", () => { 328 | expect(Humanize.titlecase('cul-de-sac drive-by')).toEqual('Cul-de-Sac Drive-By'); 329 | expect(Humanize.titleCase('cul-de-sac drive-by')).toEqual('Cul-de-Sac Drive-By'); 330 | }); 331 | 332 | it("should convert 'ultra-book By iTunes' to 'Ultra-Book by iTunes'", () => { 333 | expect(Humanize.titlecase('ultra-book By iTunes')).toEqual('Ultra-Book by iTunes'); 334 | expect(Humanize.titleCase('ultra-book By iTunes')).toEqual('Ultra-Book by iTunes'); 335 | }); 336 | 337 | it("should convert 'by-the-book ultra-book By iTunes' to 'By-the-Book Ultra-Book by iTunes'", () => { 338 | expect(Humanize.titlecase('by-the-book ultra-book By iTunes')).toEqual('By-the-Book Ultra-Book by iTunes'); 339 | expect(Humanize.titleCase('by-the-book ultra-book By iTunes')).toEqual('By-the-Book Ultra-Book by iTunes'); 340 | }); 341 | 342 | it("should convert 'by-the-book ultra-book by-the-by iTunes' to 'By-the-Book Ultra-Book by-the-by iTunes'", () => { 343 | expect(Humanize.titlecase('by-the-book ultra-book by-the-by iTunes')).toEqual('By-the-Book Ultra-Book by-the-by iTunes'); 344 | expect(Humanize.titleCase('by-the-book ultra-book by-the-by iTunes')).toEqual('By-the-Book Ultra-Book by-the-by iTunes'); 345 | }); 346 | 347 | it("should convert 'by-the-by is not iTunes-O\'Malley\'s favorite of the new-on-a-book' to 'By-the-by Is Not iTunes-O\'Malley\'s Favorite of the New-on-a-Book'", () => { 348 | expect(Humanize.titlecase('by-the-by is not iTunes-O\'Malley\'s favorite of the new-on-a-book')).toEqual('By-the-By Is Not iTunes-O\'Malley\'s Favorite of the New-on-a-Book'); 349 | expect(Humanize.titleCase('by-the-by is not iTunes-O\'Malley\'s favorite of the new-on-a-book')).toEqual('By-the-By Is Not iTunes-O\'Malley\'s Favorite of the New-on-a-Book'); 350 | }); 351 | }); 352 | -------------------------------------------------------------------------------- /src/humanize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2016 HubSpotDev 3 | * MIT Licensed 4 | * 5 | * @module humanize.js 6 | */ 7 | 8 | ((root, factory) => { 9 | if (typeof exports === 'object') { 10 | module.exports = factory(); 11 | } else if (typeof define === 'function' && define.amd) { 12 | define([], () => (root.Humanize = factory())); 13 | } else { 14 | root.Humanize = factory(); 15 | } 16 | })(this, () => { 17 | //------------------------------------------------------------------------------ 18 | // Constants 19 | //------------------------------------------------------------------------------ 20 | 21 | const TIME_FORMATS = [ 22 | { 23 | name: 'second', 24 | value: 1e3 25 | }, 26 | { 27 | name: 'minute', 28 | value: 6e4 29 | }, 30 | { 31 | name: 'hour', 32 | value: 36e5 33 | }, 34 | { 35 | name: 'day', 36 | value: 864e5 37 | }, 38 | { 39 | name: 'week', 40 | value: 6048e5 41 | } 42 | ]; 43 | 44 | const LABELS_FOR_POWERS_OF_KILO = { 45 | P: Math.pow(2, 50), 46 | T: Math.pow(2, 40), 47 | G: Math.pow(2, 30), 48 | M: Math.pow(2, 20) 49 | }; 50 | 51 | //------------------------------------------------------------------------------ 52 | // Helpers 53 | //------------------------------------------------------------------------------ 54 | 55 | const exists = maybe => typeof maybe !== 'undefined' && maybe !== null; 56 | 57 | const isNaN = value => value !== value; // eslint-disable-line 58 | 59 | const isFiniteNumber = value => { 60 | return isFinite(value) && !isNaN(parseFloat(value)); 61 | }; 62 | 63 | const isArray = value => { 64 | const type = Object.prototype.toString.call(value); 65 | return type === '[object Array]'; 66 | }; 67 | 68 | //------------------------------------------------------------------------------ 69 | // Humanize 70 | //------------------------------------------------------------------------------ 71 | 72 | const Humanize = { 73 | 74 | // Converts a large integer to a friendly text representation. 75 | intword(number, charWidth, decimals = 2) { 76 | /* 77 | * This method is deprecated. Please use compactInteger instead. 78 | * intword will be going away in the next major version. 79 | */ 80 | return Humanize.compactInteger(number, decimals); 81 | }, 82 | 83 | // Converts an integer into its most compact representation 84 | compactInteger(input, decimals = 0) { 85 | decimals = Math.max(decimals, 0); 86 | const number = parseInt(input, 10); 87 | const signString = number < 0 ? '-' : ''; 88 | const unsignedNumber = Math.abs(number); 89 | const unsignedNumberString = String(unsignedNumber); 90 | const numberLength = unsignedNumberString.length; 91 | const numberLengths = [13, 10, 7, 4]; 92 | const bigNumPrefixes = ['T', 'B', 'M', 'k']; 93 | 94 | // small numbers 95 | if (unsignedNumber < 1000) { 96 | return `${ signString }${ unsignedNumberString }`; 97 | } 98 | 99 | // really big numbers 100 | if (numberLength > numberLengths[0] + 3) { 101 | return number.toExponential(decimals).replace('e+', 'x10^'); 102 | } 103 | 104 | // 999 < unsignedNumber < 999,999,999,999,999 105 | let length; 106 | for (let i = 0; i < numberLengths.length; i++) { 107 | const _length = numberLengths[i]; 108 | if (numberLength >= _length) { 109 | length = _length; 110 | break; 111 | } 112 | } 113 | 114 | const decimalIndex = numberLength - length + 1; 115 | const unsignedNumberCharacterArray = unsignedNumberString.split(''); 116 | 117 | const wholePartArray = unsignedNumberCharacterArray.slice(0, decimalIndex); 118 | const decimalPartArray = unsignedNumberCharacterArray.slice(decimalIndex, decimalIndex + decimals + 1); 119 | 120 | const wholePart = wholePartArray.join(''); 121 | 122 | // pad decimalPart if necessary 123 | let decimalPart = decimalPartArray.join(''); 124 | if (decimalPart.length < decimals) { 125 | decimalPart += `${ Array(decimals - decimalPart.length + 1).join('0') }`; 126 | } 127 | 128 | let output; 129 | if (decimals === 0) { 130 | output = `${ signString }${ wholePart }${ bigNumPrefixes[numberLengths.indexOf(length)] }`; 131 | } else { 132 | const outputNumber = Number(`${ wholePart }.${ decimalPart }`).toFixed(decimals); 133 | output = `${ signString }${ outputNumber }${ bigNumPrefixes[numberLengths.indexOf(length)] }`; 134 | } 135 | 136 | return output; 137 | }, 138 | 139 | // Converts an integer to a string containing commas every three digits. 140 | intComma(number, decimals = 0) { 141 | return Humanize.formatNumber(number, decimals); 142 | }, 143 | 144 | intcomma(...args) { 145 | return Humanize.intComma(...args); 146 | }, 147 | 148 | // Formats the value like a 'human-readable' file size (i.e. '13 KB', '4.1 MB', '102 bytes', etc). 149 | fileSize(filesize, precision = 2) { 150 | for (const label in LABELS_FOR_POWERS_OF_KILO) { 151 | if (LABELS_FOR_POWERS_OF_KILO.hasOwnProperty(label)) { 152 | const minnum = LABELS_FOR_POWERS_OF_KILO[label]; 153 | if (filesize >= minnum) { 154 | return `${Humanize.formatNumber(filesize / minnum, precision, '')} ${label}B`; 155 | } 156 | } 157 | } 158 | if (filesize >= 1024) { 159 | return `${Humanize.formatNumber(filesize / 1024, 0)} KB`; 160 | } 161 | 162 | return Humanize.formatNumber(filesize, 0) + Humanize.pluralize(filesize, ' byte'); 163 | }, 164 | 165 | filesize(...args) { 166 | return Humanize.fileSize(...args); 167 | }, 168 | 169 | // Formats a number to a human-readable string. 170 | // Localize by overriding the precision, thousand and decimal arguments. 171 | formatNumber(number, precision = 0, thousand = ',', decimal = '.') { 172 | // Create some private utility functions to make the computational 173 | // code that follows much easier to read. 174 | const firstComma = (_number, _thousand, _position) => { 175 | return _position ? _number.substr(0, _position) + _thousand : ''; 176 | }; 177 | 178 | const commas = (_number, _thousand, _position) => { 179 | return _number.substr(_position).replace(/(\d{3})(?=\d)/g, `$1${_thousand}`); 180 | }; 181 | 182 | const decimals = (_number, _decimal, usePrecision) => { 183 | return usePrecision 184 | ? _decimal + Humanize.toFixed(Math.abs(_number), usePrecision).split('.')[1] 185 | : ''; 186 | }; 187 | 188 | const usePrecision = Humanize.normalizePrecision(precision); 189 | 190 | // Do some calc 191 | const negative = number < 0 && '-' || ''; 192 | const base = String(parseInt(Humanize.toFixed(Math.abs(number || 0), usePrecision), 10)); 193 | const mod = base.length > 3 ? base.length % 3 : 0; 194 | 195 | // Format the number 196 | return ( 197 | negative + 198 | firstComma(base, thousand, mod) + 199 | commas(base, thousand, mod) + 200 | decimals(number, decimal, usePrecision) 201 | ); 202 | }, 203 | 204 | // Fixes binary rounding issues (eg. (0.615).toFixed(2) === '0.61') 205 | toFixed(value, precision) { 206 | precision = exists(precision) ? precision : Humanize.normalizePrecision(precision, 0); 207 | const power = Math.pow(10, precision); 208 | 209 | // Multiply up by precision, round accurately, then divide and use native toFixed() 210 | return (Math.round(value * power) / power).toFixed(precision); 211 | }, 212 | 213 | // Ensures precision value is a positive integer 214 | normalizePrecision(value, base) { 215 | value = Math.round(Math.abs(value)); 216 | return isNaN(value) ? base : value; 217 | }, 218 | 219 | // Converts an integer to its ordinal as a string. 220 | ordinal(value) { 221 | const number = parseInt(value, 10); 222 | 223 | if (number === 0) { 224 | return value; 225 | } 226 | 227 | const specialCase = number % 100; 228 | if ([11, 12, 13].indexOf(specialCase) >= 0) { 229 | return `${ number }th`; 230 | } 231 | 232 | const leastSignificant = number % 10; 233 | 234 | let end; 235 | switch (leastSignificant) { 236 | case 1: 237 | end = 'st'; 238 | break; 239 | case 2: 240 | end = 'nd'; 241 | break; 242 | case 3: 243 | end = 'rd'; 244 | break; 245 | default: 246 | end = 'th'; 247 | } 248 | 249 | return `${ number }${ end }`; 250 | }, 251 | 252 | // Interprets numbers as occurences. Also accepts an optional array/map of overrides. 253 | times(value, overrides = {}) { 254 | if (isFiniteNumber(value) && value >= 0) { 255 | const number = parseFloat(value); 256 | const smallTimes = ['never', 'once', 'twice']; 257 | if (exists(overrides[number])) { 258 | return String(overrides[number]); 259 | } 260 | 261 | const numberString = exists(smallTimes[number]) && smallTimes[number].toString(); 262 | return numberString || `${number.toString()} times`; 263 | } 264 | return null; 265 | }, 266 | 267 | // Returns the plural version of a given word if the value is not 1. The default suffix is 's'. 268 | pluralize(number, singular, plural) { 269 | if (!(exists(number) && exists(singular))) { 270 | return null; 271 | } 272 | 273 | plural = exists(plural) ? plural : `${singular}s`; 274 | 275 | return parseInt(number, 10) === 1 ? singular : plural; 276 | }, 277 | 278 | // Truncates a string if it is longer than the specified number of characters (inclusive). 279 | // Truncated strings will end with a translatable ellipsis sequence ("…"). 280 | truncate(str, length = 100, ending = '...') { 281 | if (str.length > length) { 282 | return str.substring(0, length - ending.length) + ending; 283 | } 284 | return str; 285 | }, 286 | 287 | // Truncates a string after a certain number of words. 288 | truncateWords(string, length) { 289 | const array = string.split(' '); 290 | let result = ''; 291 | let i = 0; 292 | 293 | while (i < length) { 294 | if (exists(array[i])) { 295 | result += `${array[i]} `; 296 | } 297 | i++; 298 | } 299 | 300 | if (array.length > length) { 301 | return `${result}...`; 302 | } 303 | 304 | return null; 305 | }, 306 | 307 | truncatewords(...args) { 308 | return Humanize.truncateWords(...args); 309 | }, 310 | 311 | // Truncates a number to an upper bound. 312 | boundedNumber(num, bound = 100, ending = '+') { 313 | let result; 314 | 315 | if (isFiniteNumber(num) && isFiniteNumber(bound)) { 316 | if (num > bound) { 317 | result = bound + ending; 318 | } 319 | } 320 | 321 | return (result || num).toString(); 322 | }, 323 | 324 | truncatenumber(...args) { 325 | return Humanize.boundedNumber(...args); 326 | }, 327 | 328 | // Converts a list of items to a human readable string with an optional limit. 329 | oxford(items, limit, limitStr) { 330 | const numItems = items.length; 331 | 332 | let limitIndex; 333 | if (numItems < 2) { 334 | return String(items); 335 | } else if (numItems === 2) { 336 | return items.join(' and '); 337 | } else if (exists(limit) && numItems > limit) { 338 | const extra = numItems - limit; 339 | limitIndex = limit; 340 | limitStr = exists(limitStr) ? limitStr : `, and ${extra} ${Humanize.pluralize(extra, 'other')}`; 341 | } else { 342 | limitIndex = -1; 343 | limitStr = `, and ${items[numItems - 1]}`; 344 | } 345 | 346 | return items.slice(0, limitIndex).join(', ') + limitStr; 347 | }, 348 | 349 | // Converts an object to a definition-like string 350 | dictionary(object, joiner = ' is ', separator = ', ') { 351 | const result = ''; 352 | 353 | if (exists(object) && typeof object === 'object' && !isArray(object)) { 354 | const defs = []; 355 | for (const key in object) { 356 | if (object.hasOwnProperty(key)) { 357 | const val = object[key]; 358 | defs.push(`${ key }${ joiner }${ val }`); 359 | } 360 | } 361 | 362 | return defs.join(separator); 363 | } 364 | 365 | return result; 366 | }, 367 | 368 | // Describes how many times an item appears in a list 369 | frequency(list, verb) { 370 | if (!isArray(list)) { 371 | return null; 372 | } 373 | 374 | const len = list.length; 375 | const times = Humanize.times(len); 376 | 377 | if (len === 0) { 378 | return `${times} ${verb}`; 379 | } 380 | 381 | return `${verb} ${times}`; 382 | }, 383 | 384 | pace(value, intervalMs, unit = 'time') { 385 | if (value === 0 || intervalMs === 0) { 386 | // Needs a better string than this... 387 | return `No ${Humanize.pluralize(0, unit)}`; 388 | } 389 | 390 | // Expose these as overridables? 391 | let prefix = 'Approximately'; 392 | let timeUnit; 393 | let relativePace; 394 | 395 | const rate = value / intervalMs; 396 | for (let i = 0; i < TIME_FORMATS.length; ++i) { // assumes sorted list 397 | const f = TIME_FORMATS[i]; 398 | relativePace = rate * f.value; 399 | if (relativePace > 1) { 400 | timeUnit = f.name; 401 | break; 402 | } 403 | } 404 | 405 | // Use the last time unit if there is nothing smaller 406 | if (!timeUnit) { 407 | prefix = 'Less than'; 408 | relativePace = 1; 409 | timeUnit = TIME_FORMATS[TIME_FORMATS.length - 1].name; 410 | } 411 | 412 | const roundedPace = Math.round(relativePace); 413 | unit = Humanize.pluralize(roundedPace, unit); 414 | 415 | return `${prefix} ${roundedPace} ${unit} per ${timeUnit}`; 416 | }, 417 | 418 | // Converts newlines to
tags 419 | nl2br(string, replacement = '
') { 420 | return string.replace(/\n/g, replacement); 421 | }, 422 | 423 | // Converts
tags to newlines 424 | br2nl(string, replacement = '\r\n') { 425 | return string.replace(/\/g, replacement); 426 | }, 427 | 428 | // Capitalizes first letter in a string 429 | capitalize(string, downCaseTail = false) { 430 | return `${ string.charAt(0).toUpperCase() }${ downCaseTail ? string.slice(1).toLowerCase() : string.slice(1) }`; 431 | }, 432 | 433 | // Capitalizes the first letter of each word in a string 434 | capitalizeAll(string) { 435 | return string.replace(/(?:^|\s)\S/g, (a) => a.toUpperCase()); 436 | }, 437 | 438 | // Titlecase words in a string. 439 | titleCase(string) { 440 | const smallWords = /\b(a|an|and|at|but|by|de|en|for|if|in|of|on|or|the|to|via|vs?\.?)\b/i; 441 | const internalCaps = /\S+[A-Z]+\S*/; 442 | const splitOnWhiteSpaceRegex = /\s+/; 443 | const splitOnHyphensRegex = /-/; 444 | 445 | let doTitleCase; 446 | doTitleCase = (_string, hyphenated = false, firstOrLast = true) => { 447 | const titleCasedArray = []; 448 | const stringArray = _string.split(hyphenated ? splitOnHyphensRegex : splitOnWhiteSpaceRegex); 449 | 450 | for (let index = 0; index < stringArray.length; ++index) { 451 | const word = stringArray[index]; 452 | if (word.indexOf('-') !== -1) { 453 | titleCasedArray.push(doTitleCase(word, true, (index === 0 || index === stringArray.length - 1))); 454 | continue; 455 | } 456 | 457 | if (firstOrLast && (index === 0 || index === stringArray.length - 1)) { 458 | titleCasedArray.push(internalCaps.test(word) ? word : Humanize.capitalize(word)); 459 | continue; 460 | } 461 | 462 | if (internalCaps.test(word)) { 463 | titleCasedArray.push(word); 464 | } else if (smallWords.test(word)) { 465 | titleCasedArray.push(word.toLowerCase()); 466 | } else { 467 | titleCasedArray.push(Humanize.capitalize(word)); 468 | } 469 | } 470 | 471 | return titleCasedArray.join(hyphenated ? '-' : ' '); 472 | }; 473 | 474 | return doTitleCase(string); 475 | }, 476 | 477 | titlecase(...args) { 478 | return Humanize.titleCase(...args); 479 | } 480 | }; 481 | 482 | return Humanize; 483 | }); 484 | -------------------------------------------------------------------------------- /dist/humanize.js: -------------------------------------------------------------------------------- 1 | /* humanize.js - v1.8.2 */ 2 | 'use strict'; 3 | 4 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; 5 | 6 | /** 7 | * Copyright 2013-2016 HubSpotDev 8 | * MIT Licensed 9 | * 10 | * @module humanize.js 11 | */ 12 | 13 | (function (root, factory) { 14 | if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object') { 15 | module.exports = factory(); 16 | } else if (typeof define === 'function' && define.amd) { 17 | define([], function () { 18 | return root.Humanize = factory(); 19 | }); 20 | } else { 21 | root.Humanize = factory(); 22 | } 23 | })(this, function () { 24 | //------------------------------------------------------------------------------ 25 | // Constants 26 | //------------------------------------------------------------------------------ 27 | 28 | var TIME_FORMATS = [{ 29 | name: 'second', 30 | value: 1e3 31 | }, { 32 | name: 'minute', 33 | value: 6e4 34 | }, { 35 | name: 'hour', 36 | value: 36e5 37 | }, { 38 | name: 'day', 39 | value: 864e5 40 | }, { 41 | name: 'week', 42 | value: 6048e5 43 | }]; 44 | 45 | var LABELS_FOR_POWERS_OF_KILO = { 46 | P: Math.pow(2, 50), 47 | T: Math.pow(2, 40), 48 | G: Math.pow(2, 30), 49 | M: Math.pow(2, 20) 50 | }; 51 | 52 | //------------------------------------------------------------------------------ 53 | // Helpers 54 | //------------------------------------------------------------------------------ 55 | 56 | var exists = function exists(maybe) { 57 | return typeof maybe !== 'undefined' && maybe !== null; 58 | }; 59 | 60 | var isNaN = function isNaN(value) { 61 | return value !== value; 62 | }; // eslint-disable-line 63 | 64 | var isFiniteNumber = function isFiniteNumber(value) { 65 | return isFinite(value) && !isNaN(parseFloat(value)); 66 | }; 67 | 68 | var isArray = function isArray(value) { 69 | var type = Object.prototype.toString.call(value); 70 | return type === '[object Array]'; 71 | }; 72 | 73 | //------------------------------------------------------------------------------ 74 | // Humanize 75 | //------------------------------------------------------------------------------ 76 | 77 | var Humanize = { 78 | 79 | // Converts a large integer to a friendly text representation. 80 | 81 | intword: function intword(number, charWidth) { 82 | var decimals = arguments.length <= 2 || arguments[2] === undefined ? 2 : arguments[2]; 83 | 84 | /* 85 | * This method is deprecated. Please use compactInteger instead. 86 | * intword will be going away in the next major version. 87 | */ 88 | return Humanize.compactInteger(number, decimals); 89 | }, 90 | 91 | 92 | // Converts an integer into its most compact representation 93 | compactInteger: function compactInteger(input) { 94 | var decimals = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; 95 | 96 | decimals = Math.max(decimals, 0); 97 | var number = parseInt(input, 10); 98 | var signString = number < 0 ? '-' : ''; 99 | var unsignedNumber = Math.abs(number); 100 | var unsignedNumberString = String(unsignedNumber); 101 | var numberLength = unsignedNumberString.length; 102 | var numberLengths = [13, 10, 7, 4]; 103 | var bigNumPrefixes = ['T', 'B', 'M', 'k']; 104 | 105 | // small numbers 106 | if (unsignedNumber < 1000) { 107 | return '' + signString + unsignedNumberString; 108 | } 109 | 110 | // really big numbers 111 | if (numberLength > numberLengths[0] + 3) { 112 | return number.toExponential(decimals).replace('e+', 'x10^'); 113 | } 114 | 115 | // 999 < unsignedNumber < 999,999,999,999,999 116 | var length = void 0; 117 | for (var i = 0; i < numberLengths.length; i++) { 118 | var _length = numberLengths[i]; 119 | if (numberLength >= _length) { 120 | length = _length; 121 | break; 122 | } 123 | } 124 | 125 | var decimalIndex = numberLength - length + 1; 126 | var unsignedNumberCharacterArray = unsignedNumberString.split(''); 127 | 128 | var wholePartArray = unsignedNumberCharacterArray.slice(0, decimalIndex); 129 | var decimalPartArray = unsignedNumberCharacterArray.slice(decimalIndex, decimalIndex + decimals + 1); 130 | 131 | var wholePart = wholePartArray.join(''); 132 | 133 | // pad decimalPart if necessary 134 | var decimalPart = decimalPartArray.join(''); 135 | if (decimalPart.length < decimals) { 136 | decimalPart += '' + Array(decimals - decimalPart.length + 1).join('0'); 137 | } 138 | 139 | var output = void 0; 140 | if (decimals === 0) { 141 | output = '' + signString + wholePart + bigNumPrefixes[numberLengths.indexOf(length)]; 142 | } else { 143 | var outputNumber = Number(wholePart + '.' + decimalPart).toFixed(decimals); 144 | output = '' + signString + outputNumber + bigNumPrefixes[numberLengths.indexOf(length)]; 145 | } 146 | 147 | return output; 148 | }, 149 | 150 | 151 | // Converts an integer to a string containing commas every three digits. 152 | intComma: function intComma(number) { 153 | var decimals = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; 154 | 155 | return Humanize.formatNumber(number, decimals); 156 | }, 157 | intcomma: function intcomma() { 158 | return Humanize.intComma.apply(Humanize, arguments); 159 | }, 160 | 161 | 162 | // Formats the value like a 'human-readable' file size (i.e. '13 KB', '4.1 MB', '102 bytes', etc). 163 | fileSize: function fileSize(filesize) { 164 | var precision = arguments.length <= 1 || arguments[1] === undefined ? 2 : arguments[1]; 165 | 166 | for (var label in LABELS_FOR_POWERS_OF_KILO) { 167 | if (LABELS_FOR_POWERS_OF_KILO.hasOwnProperty(label)) { 168 | var minnum = LABELS_FOR_POWERS_OF_KILO[label]; 169 | if (filesize >= minnum) { 170 | return Humanize.formatNumber(filesize / minnum, precision, '') + ' ' + label + 'B'; 171 | } 172 | } 173 | } 174 | if (filesize >= 1024) { 175 | return Humanize.formatNumber(filesize / 1024, 0) + ' KB'; 176 | } 177 | 178 | return Humanize.formatNumber(filesize, 0) + Humanize.pluralize(filesize, ' byte'); 179 | }, 180 | filesize: function filesize() { 181 | return Humanize.fileSize.apply(Humanize, arguments); 182 | }, 183 | 184 | 185 | // Formats a number to a human-readable string. 186 | // Localize by overriding the precision, thousand and decimal arguments. 187 | formatNumber: function formatNumber(number) { 188 | var precision = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; 189 | var thousand = arguments.length <= 2 || arguments[2] === undefined ? ',' : arguments[2]; 190 | var decimal = arguments.length <= 3 || arguments[3] === undefined ? '.' : arguments[3]; 191 | 192 | // Create some private utility functions to make the computational 193 | // code that follows much easier to read. 194 | var firstComma = function firstComma(_number, _thousand, _position) { 195 | return _position ? _number.substr(0, _position) + _thousand : ''; 196 | }; 197 | 198 | var commas = function commas(_number, _thousand, _position) { 199 | return _number.substr(_position).replace(/(\d{3})(?=\d)/g, '$1' + _thousand); 200 | }; 201 | 202 | var decimals = function decimals(_number, _decimal, usePrecision) { 203 | return usePrecision ? _decimal + Humanize.toFixed(Math.abs(_number), usePrecision).split('.')[1] : ''; 204 | }; 205 | 206 | var usePrecision = Humanize.normalizePrecision(precision); 207 | 208 | // Do some calc 209 | var negative = number < 0 && '-' || ''; 210 | var base = String(parseInt(Humanize.toFixed(Math.abs(number || 0), usePrecision), 10)); 211 | var mod = base.length > 3 ? base.length % 3 : 0; 212 | 213 | // Format the number 214 | return negative + firstComma(base, thousand, mod) + commas(base, thousand, mod) + decimals(number, decimal, usePrecision); 215 | }, 216 | 217 | 218 | // Fixes binary rounding issues (eg. (0.615).toFixed(2) === '0.61') 219 | toFixed: function toFixed(value, precision) { 220 | precision = exists(precision) ? precision : Humanize.normalizePrecision(precision, 0); 221 | var power = Math.pow(10, precision); 222 | 223 | // Multiply up by precision, round accurately, then divide and use native toFixed() 224 | return (Math.round(value * power) / power).toFixed(precision); 225 | }, 226 | 227 | 228 | // Ensures precision value is a positive integer 229 | normalizePrecision: function normalizePrecision(value, base) { 230 | value = Math.round(Math.abs(value)); 231 | return isNaN(value) ? base : value; 232 | }, 233 | 234 | 235 | // Converts an integer to its ordinal as a string. 236 | ordinal: function ordinal(value) { 237 | var number = parseInt(value, 10); 238 | 239 | if (number === 0) { 240 | return value; 241 | } 242 | 243 | var specialCase = number % 100; 244 | if ([11, 12, 13].indexOf(specialCase) >= 0) { 245 | return number + 'th'; 246 | } 247 | 248 | var leastSignificant = number % 10; 249 | 250 | var end = void 0; 251 | switch (leastSignificant) { 252 | case 1: 253 | end = 'st'; 254 | break; 255 | case 2: 256 | end = 'nd'; 257 | break; 258 | case 3: 259 | end = 'rd'; 260 | break; 261 | default: 262 | end = 'th'; 263 | } 264 | 265 | return '' + number + end; 266 | }, 267 | 268 | 269 | // Interprets numbers as occurences. Also accepts an optional array/map of overrides. 270 | times: function times(value) { 271 | var overrides = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; 272 | 273 | if (isFiniteNumber(value) && value >= 0) { 274 | var number = parseFloat(value); 275 | var smallTimes = ['never', 'once', 'twice']; 276 | if (exists(overrides[number])) { 277 | return String(overrides[number]); 278 | } 279 | 280 | var numberString = exists(smallTimes[number]) && smallTimes[number].toString(); 281 | return numberString || number.toString() + ' times'; 282 | } 283 | return null; 284 | }, 285 | 286 | 287 | // Returns the plural version of a given word if the value is not 1. The default suffix is 's'. 288 | pluralize: function pluralize(number, singular, plural) { 289 | if (!(exists(number) && exists(singular))) { 290 | return null; 291 | } 292 | 293 | plural = exists(plural) ? plural : singular + 's'; 294 | 295 | return parseInt(number, 10) === 1 ? singular : plural; 296 | }, 297 | 298 | 299 | // Truncates a string if it is longer than the specified number of characters (inclusive). 300 | // Truncated strings will end with a translatable ellipsis sequence ("…"). 301 | truncate: function truncate(str) { 302 | var length = arguments.length <= 1 || arguments[1] === undefined ? 100 : arguments[1]; 303 | var ending = arguments.length <= 2 || arguments[2] === undefined ? '...' : arguments[2]; 304 | 305 | if (str.length > length) { 306 | return str.substring(0, length - ending.length) + ending; 307 | } 308 | return str; 309 | }, 310 | 311 | 312 | // Truncates a string after a certain number of words. 313 | truncateWords: function truncateWords(string, length) { 314 | var array = string.split(' '); 315 | var result = ''; 316 | var i = 0; 317 | 318 | while (i < length) { 319 | if (exists(array[i])) { 320 | result += array[i] + ' '; 321 | } 322 | i++; 323 | } 324 | 325 | if (array.length > length) { 326 | return result + '...'; 327 | } 328 | 329 | return null; 330 | }, 331 | truncatewords: function truncatewords() { 332 | return Humanize.truncateWords.apply(Humanize, arguments); 333 | }, 334 | 335 | 336 | // Truncates a number to an upper bound. 337 | boundedNumber: function boundedNumber(num) { 338 | var bound = arguments.length <= 1 || arguments[1] === undefined ? 100 : arguments[1]; 339 | var ending = arguments.length <= 2 || arguments[2] === undefined ? '+' : arguments[2]; 340 | 341 | var result = void 0; 342 | 343 | if (isFiniteNumber(num) && isFiniteNumber(bound)) { 344 | if (num > bound) { 345 | result = bound + ending; 346 | } 347 | } 348 | 349 | return (result || num).toString(); 350 | }, 351 | truncatenumber: function truncatenumber() { 352 | return Humanize.boundedNumber.apply(Humanize, arguments); 353 | }, 354 | 355 | 356 | // Converts a list of items to a human readable string with an optional limit. 357 | oxford: function oxford(items, limit, limitStr) { 358 | var numItems = items.length; 359 | 360 | var limitIndex = void 0; 361 | if (numItems < 2) { 362 | return String(items); 363 | } else if (numItems === 2) { 364 | return items.join(' and '); 365 | } else if (exists(limit) && numItems > limit) { 366 | var extra = numItems - limit; 367 | limitIndex = limit; 368 | limitStr = exists(limitStr) ? limitStr : ', and ' + extra + ' ' + Humanize.pluralize(extra, 'other'); 369 | } else { 370 | limitIndex = -1; 371 | limitStr = ', and ' + items[numItems - 1]; 372 | } 373 | 374 | return items.slice(0, limitIndex).join(', ') + limitStr; 375 | }, 376 | 377 | 378 | // Converts an object to a definition-like string 379 | dictionary: function dictionary(object) { 380 | var joiner = arguments.length <= 1 || arguments[1] === undefined ? ' is ' : arguments[1]; 381 | var separator = arguments.length <= 2 || arguments[2] === undefined ? ', ' : arguments[2]; 382 | 383 | var result = ''; 384 | 385 | if (exists(object) && (typeof object === 'undefined' ? 'undefined' : _typeof(object)) === 'object' && !isArray(object)) { 386 | var defs = []; 387 | for (var key in object) { 388 | if (object.hasOwnProperty(key)) { 389 | var val = object[key]; 390 | defs.push('' + key + joiner + val); 391 | } 392 | } 393 | 394 | return defs.join(separator); 395 | } 396 | 397 | return result; 398 | }, 399 | 400 | 401 | // Describes how many times an item appears in a list 402 | frequency: function frequency(list, verb) { 403 | if (!isArray(list)) { 404 | return null; 405 | } 406 | 407 | var len = list.length; 408 | var times = Humanize.times(len); 409 | 410 | if (len === 0) { 411 | return times + ' ' + verb; 412 | } 413 | 414 | return verb + ' ' + times; 415 | }, 416 | pace: function pace(value, intervalMs) { 417 | var unit = arguments.length <= 2 || arguments[2] === undefined ? 'time' : arguments[2]; 418 | 419 | if (value === 0 || intervalMs === 0) { 420 | // Needs a better string than this... 421 | return 'No ' + Humanize.pluralize(0, unit); 422 | } 423 | 424 | // Expose these as overridables? 425 | var prefix = 'Approximately'; 426 | var timeUnit = void 0; 427 | var relativePace = void 0; 428 | 429 | var rate = value / intervalMs; 430 | for (var i = 0; i < TIME_FORMATS.length; ++i) { 431 | // assumes sorted list 432 | var f = TIME_FORMATS[i]; 433 | relativePace = rate * f.value; 434 | if (relativePace > 1) { 435 | timeUnit = f.name; 436 | break; 437 | } 438 | } 439 | 440 | // Use the last time unit if there is nothing smaller 441 | if (!timeUnit) { 442 | prefix = 'Less than'; 443 | relativePace = 1; 444 | timeUnit = TIME_FORMATS[TIME_FORMATS.length - 1].name; 445 | } 446 | 447 | var roundedPace = Math.round(relativePace); 448 | unit = Humanize.pluralize(roundedPace, unit); 449 | 450 | return prefix + ' ' + roundedPace + ' ' + unit + ' per ' + timeUnit; 451 | }, 452 | 453 | 454 | // Converts newlines to
tags 455 | nl2br: function nl2br(string) { 456 | var replacement = arguments.length <= 1 || arguments[1] === undefined ? '
' : arguments[1]; 457 | 458 | return string.replace(/\n/g, replacement); 459 | }, 460 | 461 | 462 | // Converts
tags to newlines 463 | br2nl: function br2nl(string) { 464 | var replacement = arguments.length <= 1 || arguments[1] === undefined ? '\r\n' : arguments[1]; 465 | 466 | return string.replace(/\/g, replacement); 467 | }, 468 | 469 | 470 | // Capitalizes first letter in a string 471 | capitalize: function capitalize(string) { 472 | var downCaseTail = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 473 | 474 | return '' + string.charAt(0).toUpperCase() + (downCaseTail ? string.slice(1).toLowerCase() : string.slice(1)); 475 | }, 476 | 477 | 478 | // Capitalizes the first letter of each word in a string 479 | capitalizeAll: function capitalizeAll(string) { 480 | return string.replace(/(?:^|\s)\S/g, function (a) { 481 | return a.toUpperCase(); 482 | }); 483 | }, 484 | 485 | 486 | // Titlecase words in a string. 487 | titleCase: function titleCase(string) { 488 | var smallWords = /\b(a|an|and|at|but|by|de|en|for|if|in|of|on|or|the|to|via|vs?\.?)\b/i; 489 | var internalCaps = /\S+[A-Z]+\S*/; 490 | var splitOnWhiteSpaceRegex = /\s+/; 491 | var splitOnHyphensRegex = /-/; 492 | 493 | var _doTitleCase = void 0; 494 | _doTitleCase = function doTitleCase(_string) { 495 | var hyphenated = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 496 | var firstOrLast = arguments.length <= 2 || arguments[2] === undefined ? true : arguments[2]; 497 | 498 | var titleCasedArray = []; 499 | var stringArray = _string.split(hyphenated ? splitOnHyphensRegex : splitOnWhiteSpaceRegex); 500 | 501 | for (var index = 0; index < stringArray.length; ++index) { 502 | var word = stringArray[index]; 503 | if (word.indexOf('-') !== -1) { 504 | titleCasedArray.push(_doTitleCase(word, true, index === 0 || index === stringArray.length - 1)); 505 | continue; 506 | } 507 | 508 | if (firstOrLast && (index === 0 || index === stringArray.length - 1)) { 509 | titleCasedArray.push(internalCaps.test(word) ? word : Humanize.capitalize(word)); 510 | continue; 511 | } 512 | 513 | if (internalCaps.test(word)) { 514 | titleCasedArray.push(word); 515 | } else if (smallWords.test(word)) { 516 | titleCasedArray.push(word.toLowerCase()); 517 | } else { 518 | titleCasedArray.push(Humanize.capitalize(word)); 519 | } 520 | } 521 | 522 | return titleCasedArray.join(hyphenated ? '-' : ' '); 523 | }; 524 | 525 | return _doTitleCase(string); 526 | }, 527 | titlecase: function titlecase() { 528 | return Humanize.titleCase.apply(Humanize, arguments); 529 | } 530 | }; 531 | 532 | return Humanize; 533 | }); --------------------------------------------------------------------------------