├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── array │ ├── binarySearch.js │ ├── binarySearch.spec.js │ ├── pickRandom.js │ ├── pickRandom.spec.js │ ├── shuffle.js │ └── shuffle.spec.js ├── async │ ├── queue.js │ ├── queue.spec.js │ └── sleep.js ├── canvas │ └── sprite.js ├── index.js ├── number │ ├── clamp.js │ ├── clamp.spec.js │ ├── random.js │ ├── random.spec.js │ ├── seedRandom.js │ └── seedRandom.spec.js ├── scale │ ├── linear.js │ └── linear.spec.js └── string │ ├── commas.js │ ├── commas.spec.js │ ├── padLeft.js │ └── padLeft.spec.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /types -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true 4 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # yootils changelog 2 | 3 | ## 0.3.1 4 | 5 | - Appease Safari 6 | 7 | ## 0.3.0 8 | 9 | - Pass width and height back to `createSprite` callback 10 | 11 | ## 0.2.2 12 | 13 | - Expose package.json via `pkg.exports` 14 | 15 | ## 0.2.1 16 | 17 | - Add `seedRandom` function 18 | - Add `padLeft` function 19 | 20 | ## 0.2.0 21 | 22 | - Rewrite in JS 23 | - Add `pkg.exports` 24 | 25 | ## 0.0.17 26 | 27 | - Add `sleep` function 28 | 29 | ## 0.0.16 30 | 31 | - Add `binarySearch` function 32 | 33 | ## 0.0.15 34 | 35 | - Add `createSprite` function 36 | 37 | ## 0.0.14 38 | 39 | - Fix another bug 40 | 41 | ## 0.0.13 42 | 43 | - Fix `q.close()` 44 | 45 | ## 0.0.12 46 | 47 | - Add a `q.close()` method 48 | 49 | ## 0.0.11 50 | 51 | - Fix `pkg.files` 52 | 53 | ## 0.0.10 54 | 55 | - Admin 56 | 57 | ## 0.0.9 58 | 59 | - Add `shuffle` function 60 | 61 | ## 0.0.8 62 | 63 | - Add `random` function 64 | 65 | ## 0.0.7 66 | 67 | - Add `pickRandom` function 68 | 69 | ## 0.0.6 70 | 71 | - Add `inverse()` method to scales 72 | 73 | ## 0.0.5 74 | 75 | - Add UMD build 76 | 77 | ## 0.0.4 78 | 79 | - Add `queue` 80 | 81 | ## 0.0.3 82 | 83 | - Add `commas` helper 84 | 85 | ## 0.0.2 86 | 87 | - Generate declaration files automatically 88 | 89 | ## 0.0.1 90 | 91 | - First release 92 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2021 [these people](https://github.com/Rich-Harris/yootils/graphs/contributors) 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yootils 2 | 3 | Stuff I often need. Work in progress -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yootils", 3 | "version": "0.3.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "version": "0.3.1", 9 | "license": "MIT", 10 | "devDependencies": { 11 | "agadoo": "^2.0.0", 12 | "esbuild": "^0.12.8", 13 | "prettier": "^2.3.1", 14 | "typescript": "^4.3.2", 15 | "uvu": "^0.5.1" 16 | } 17 | }, 18 | "node_modules/@types/estree": { 19 | "version": "0.0.48", 20 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz", 21 | "integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==", 22 | "dev": true 23 | }, 24 | "node_modules/@types/node": { 25 | "version": "15.12.2", 26 | "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", 27 | "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==", 28 | "dev": true 29 | }, 30 | "node_modules/acorn": { 31 | "version": "7.4.1", 32 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", 33 | "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", 34 | "dev": true, 35 | "bin": { 36 | "acorn": "bin/acorn" 37 | }, 38 | "engines": { 39 | "node": ">=0.4.0" 40 | } 41 | }, 42 | "node_modules/agadoo": { 43 | "version": "2.0.0", 44 | "resolved": "https://registry.npmjs.org/agadoo/-/agadoo-2.0.0.tgz", 45 | "integrity": "sha512-68aFhseH51ZBKYKkQNxwDi1hJwTnywBjHWg068qFnMkpXShhOazNzJUPRvaLQjrqhT3EOUth5G9mt1A0/dGhOw==", 46 | "dev": true, 47 | "dependencies": { 48 | "acorn": "^7.1.0", 49 | "rollup": "^1", 50 | "rollup-plugin-virtual": "^1.0.1" 51 | }, 52 | "bin": { 53 | "agadoo": "agadoo" 54 | } 55 | }, 56 | "node_modules/dequal": { 57 | "version": "2.0.2", 58 | "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", 59 | "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==", 60 | "dev": true, 61 | "engines": { 62 | "node": ">=6" 63 | } 64 | }, 65 | "node_modules/diff": { 66 | "version": "5.0.0", 67 | "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", 68 | "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", 69 | "dev": true, 70 | "engines": { 71 | "node": ">=0.3.1" 72 | } 73 | }, 74 | "node_modules/esbuild": { 75 | "version": "0.12.8", 76 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.8.tgz", 77 | "integrity": "sha512-sx/LwlP/SWTGsd9G4RlOPrXnIihAJ2xwBUmzoqe2nWwbXORMQWtAGNJNYLBJJqa3e9PWvVzxdrtyFZJcr7D87g==", 78 | "dev": true, 79 | "hasInstallScript": true, 80 | "bin": { 81 | "esbuild": "bin/esbuild" 82 | } 83 | }, 84 | "node_modules/kleur": { 85 | "version": "4.1.4", 86 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", 87 | "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", 88 | "dev": true, 89 | "engines": { 90 | "node": ">=6" 91 | } 92 | }, 93 | "node_modules/mri": { 94 | "version": "1.1.6", 95 | "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz", 96 | "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==", 97 | "dev": true, 98 | "engines": { 99 | "node": ">=4" 100 | } 101 | }, 102 | "node_modules/prettier": { 103 | "version": "2.3.1", 104 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", 105 | "integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", 106 | "dev": true, 107 | "bin": { 108 | "prettier": "bin-prettier.js" 109 | }, 110 | "engines": { 111 | "node": ">=10.13.0" 112 | } 113 | }, 114 | "node_modules/rollup": { 115 | "version": "1.32.1", 116 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz", 117 | "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==", 118 | "dev": true, 119 | "dependencies": { 120 | "@types/estree": "*", 121 | "@types/node": "*", 122 | "acorn": "^7.1.0" 123 | }, 124 | "bin": { 125 | "rollup": "dist/bin/rollup" 126 | } 127 | }, 128 | "node_modules/rollup-plugin-virtual": { 129 | "version": "1.0.1", 130 | "resolved": "https://registry.npmjs.org/rollup-plugin-virtual/-/rollup-plugin-virtual-1.0.1.tgz", 131 | "integrity": "sha512-HCTBpV8MwP5lNzZrHD2moVxHIToHU1EkzkKGVj6Z0DcgUfxrxrZmeQirQeLz2yhnkJqRjwiVywK9CS8jDYakrw==", 132 | "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-virtual.", 133 | "dev": true 134 | }, 135 | "node_modules/sade": { 136 | "version": "1.7.4", 137 | "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz", 138 | "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==", 139 | "dev": true, 140 | "dependencies": { 141 | "mri": "^1.1.0" 142 | }, 143 | "engines": { 144 | "node": ">= 6" 145 | } 146 | }, 147 | "node_modules/totalist": { 148 | "version": "2.0.0", 149 | "resolved": "https://registry.npmjs.org/totalist/-/totalist-2.0.0.tgz", 150 | "integrity": "sha512-+Y17F0YzxfACxTyjfhnJQEe7afPA0GSpYlFkl2VFMxYP7jshQf9gXV7cH47EfToBumFThfKBvfAcoUn6fdNeRQ==", 151 | "dev": true, 152 | "engines": { 153 | "node": ">=6" 154 | } 155 | }, 156 | "node_modules/typescript": { 157 | "version": "4.3.2", 158 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", 159 | "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", 160 | "dev": true, 161 | "bin": { 162 | "tsc": "bin/tsc", 163 | "tsserver": "bin/tsserver" 164 | }, 165 | "engines": { 166 | "node": ">=4.2.0" 167 | } 168 | }, 169 | "node_modules/uvu": { 170 | "version": "0.5.1", 171 | "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.1.tgz", 172 | "integrity": "sha512-JGxttnOGDFs77FaZ0yMUHIzczzQ5R1IlDeNW6Wymw6gAscwMdAffVOP6TlxLIfReZyK8tahoGwWZaTCJzNFDkg==", 173 | "dev": true, 174 | "dependencies": { 175 | "dequal": "^2.0.0", 176 | "diff": "^5.0.0", 177 | "kleur": "^4.0.3", 178 | "sade": "^1.7.3", 179 | "totalist": "^2.0.0" 180 | }, 181 | "bin": { 182 | "uvu": "bin.js" 183 | }, 184 | "engines": { 185 | "node": ">=8" 186 | } 187 | } 188 | }, 189 | "dependencies": { 190 | "@types/estree": { 191 | "version": "0.0.48", 192 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz", 193 | "integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==", 194 | "dev": true 195 | }, 196 | "@types/node": { 197 | "version": "15.12.2", 198 | "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", 199 | "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==", 200 | "dev": true 201 | }, 202 | "acorn": { 203 | "version": "7.4.1", 204 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", 205 | "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", 206 | "dev": true 207 | }, 208 | "agadoo": { 209 | "version": "2.0.0", 210 | "resolved": "https://registry.npmjs.org/agadoo/-/agadoo-2.0.0.tgz", 211 | "integrity": "sha512-68aFhseH51ZBKYKkQNxwDi1hJwTnywBjHWg068qFnMkpXShhOazNzJUPRvaLQjrqhT3EOUth5G9mt1A0/dGhOw==", 212 | "dev": true, 213 | "requires": { 214 | "acorn": "^7.1.0", 215 | "rollup": "^1", 216 | "rollup-plugin-virtual": "^1.0.1" 217 | } 218 | }, 219 | "dequal": { 220 | "version": "2.0.2", 221 | "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", 222 | "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==", 223 | "dev": true 224 | }, 225 | "diff": { 226 | "version": "5.0.0", 227 | "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", 228 | "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", 229 | "dev": true 230 | }, 231 | "esbuild": { 232 | "version": "0.12.8", 233 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.8.tgz", 234 | "integrity": "sha512-sx/LwlP/SWTGsd9G4RlOPrXnIihAJ2xwBUmzoqe2nWwbXORMQWtAGNJNYLBJJqa3e9PWvVzxdrtyFZJcr7D87g==", 235 | "dev": true 236 | }, 237 | "kleur": { 238 | "version": "4.1.4", 239 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", 240 | "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", 241 | "dev": true 242 | }, 243 | "mri": { 244 | "version": "1.1.6", 245 | "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz", 246 | "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==", 247 | "dev": true 248 | }, 249 | "prettier": { 250 | "version": "2.3.1", 251 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", 252 | "integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", 253 | "dev": true 254 | }, 255 | "rollup": { 256 | "version": "1.32.1", 257 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.32.1.tgz", 258 | "integrity": "sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==", 259 | "dev": true, 260 | "requires": { 261 | "@types/estree": "*", 262 | "@types/node": "*", 263 | "acorn": "^7.1.0" 264 | } 265 | }, 266 | "rollup-plugin-virtual": { 267 | "version": "1.0.1", 268 | "resolved": "https://registry.npmjs.org/rollup-plugin-virtual/-/rollup-plugin-virtual-1.0.1.tgz", 269 | "integrity": "sha512-HCTBpV8MwP5lNzZrHD2moVxHIToHU1EkzkKGVj6Z0DcgUfxrxrZmeQirQeLz2yhnkJqRjwiVywK9CS8jDYakrw==", 270 | "dev": true 271 | }, 272 | "sade": { 273 | "version": "1.7.4", 274 | "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz", 275 | "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==", 276 | "dev": true, 277 | "requires": { 278 | "mri": "^1.1.0" 279 | } 280 | }, 281 | "totalist": { 282 | "version": "2.0.0", 283 | "resolved": "https://registry.npmjs.org/totalist/-/totalist-2.0.0.tgz", 284 | "integrity": "sha512-+Y17F0YzxfACxTyjfhnJQEe7afPA0GSpYlFkl2VFMxYP7jshQf9gXV7cH47EfToBumFThfKBvfAcoUn6fdNeRQ==", 285 | "dev": true 286 | }, 287 | "typescript": { 288 | "version": "4.3.2", 289 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", 290 | "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", 291 | "dev": true 292 | }, 293 | "uvu": { 294 | "version": "0.5.1", 295 | "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.1.tgz", 296 | "integrity": "sha512-JGxttnOGDFs77FaZ0yMUHIzczzQ5R1IlDeNW6Wymw6gAscwMdAffVOP6TlxLIfReZyK8tahoGwWZaTCJzNFDkg==", 297 | "dev": true, 298 | "requires": { 299 | "dequal": "^2.0.0", 300 | "diff": "^5.0.0", 301 | "kleur": "^4.0.3", 302 | "sade": "^1.7.3", 303 | "totalist": "^2.0.0" 304 | } 305 | } 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yootils", 3 | "description": "Stuff I often need", 4 | "version": "0.3.1", 5 | "type": "module", 6 | "main": "dist/yootils.cjs", 7 | "module": "src/index.js", 8 | "exports": { 9 | "./package.json": "./package.json", 10 | ".": { 11 | "import": "./src/index.js", 12 | "require": "./dist/yootils.cjs" 13 | } 14 | }, 15 | "types": "types/index.d.ts", 16 | "files": [ 17 | "src", 18 | "dist", 19 | "types" 20 | ], 21 | "devDependencies": { 22 | "agadoo": "^2.0.0", 23 | "esbuild": "^0.12.8", 24 | "prettier": "^2.3.1", 25 | "typescript": "^4.3.2", 26 | "uvu": "^0.5.1" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/Rich-Harris/yootils.git" 31 | }, 32 | "scripts": { 33 | "bundle": "esbuild --bundle src/index.js --format=cjs --outfile=dist/yootils.cjs", 34 | "types": "tsc", 35 | "build": "prettier -c src && npm run types && npm run bundle && agadoo src/index.js", 36 | "format": "prettier -w src", 37 | "test": "uvu src \".\\.spec\\.js\"", 38 | "prepublishOnly": "npm test && npm run build" 39 | }, 40 | "license": "MIT" 41 | } 42 | -------------------------------------------------------------------------------- /src/array/binarySearch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @template T 3 | * @typedef {(item: T, needle?: number) => number} Comparator 4 | */ 5 | 6 | /** @type {Comparator} */ 7 | const default_sort = (item, needle) => item - needle; 8 | 9 | /** 10 | * @template T 11 | * @param {T[]} array 12 | * @param {number} search 13 | * @param {Comparator} [fn] 14 | */ 15 | export default function binarySearch(array, search, fn = default_sort) { 16 | let low = 0; 17 | let high = array.length - 1; 18 | 19 | /** @type {Comparator} */ 20 | const sort = 21 | fn.length === 1 22 | ? /** @type {Comparator} */ ((item, needle) => fn(item) - search) 23 | : fn; 24 | 25 | while (low <= high) { 26 | const i = (high + low) >> 1; 27 | 28 | const d = sort(array[i], search); 29 | 30 | if (d < 0) { 31 | low = i + 1; 32 | } else if (d > 0) { 33 | high = i - 1; 34 | } else { 35 | return i; 36 | } 37 | } 38 | 39 | return -low - 1; 40 | } 41 | -------------------------------------------------------------------------------- /src/array/binarySearch.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as assert from 'uvu/assert'; 3 | import binarySearch from './binarySearch.js'; 4 | 5 | test('finds an exact match', () => { 6 | const index = binarySearch([1, 2, 3, 4, 5], 4); 7 | assert.equal(index, 3); 8 | }); 9 | 10 | test(`returns two's complement of insertion index if not found`, () => { 11 | const index = binarySearch([1, 2, 3, 4, 5], 4.5); 12 | assert.equal(index, ~4); 13 | }); 14 | 15 | test('supports custom sort function', () => { 16 | const values = [ 17 | { value: 1 }, 18 | { value: 2 }, 19 | { value: 3 }, 20 | { value: 4 }, 21 | { value: 5 }, 22 | ]; 23 | 24 | const index = binarySearch(values, 4, (item) => item.value); 25 | 26 | assert.equal(index, 3); 27 | }); 28 | 29 | test('supports custom sort function with single argument', () => { 30 | const values = [ 31 | { value: 1 }, 32 | { value: 2 }, 33 | { value: 3 }, 34 | { value: 4 }, 35 | { value: 5 }, 36 | ]; 37 | 38 | const index = binarySearch(values, 3.5, (item) => item.value); 39 | 40 | assert.equal(index, ~3); 41 | }); 42 | 43 | test.run(); 44 | -------------------------------------------------------------------------------- /src/array/pickRandom.js: -------------------------------------------------------------------------------- 1 | /** @param {any[]} array */ 2 | export default function pickRandom(array) { 3 | const i = ~~(Math.random() * array.length); 4 | return array[i]; 5 | } 6 | -------------------------------------------------------------------------------- /src/array/pickRandom.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as assert from 'uvu/assert'; 3 | import pickRandom from './pickRandom.js'; 4 | 5 | test('picks an item from an array', () => { 6 | const item = pickRandom(['a', 'b', 'c']); 7 | 8 | assert.ok(item === 'a' || item === 'b' || item === 'c'); 9 | }); 10 | 11 | test('does not mutate the array', () => { 12 | const array = ['a', 'b', 'c']; 13 | pickRandom(array); 14 | 15 | assert.equal(array, ['a', 'b', 'c']); 16 | }); 17 | 18 | test.run(); 19 | -------------------------------------------------------------------------------- /src/array/shuffle.js: -------------------------------------------------------------------------------- 1 | // http://bost.ocks.org/mike/shuffle/ 2 | 3 | /** @param {any[]} array */ 4 | export default function shuffle(array) { 5 | let m = array.length; 6 | 7 | // While there remain elements to shuffle… 8 | while (m > 0) { 9 | // Pick a remaining element… 10 | const i = Math.floor(Math.random() * m--); 11 | 12 | // And swap it with the current element. 13 | const t = array[m]; 14 | array[m] = array[i]; 15 | array[i] = t; 16 | } 17 | 18 | return array; 19 | } 20 | -------------------------------------------------------------------------------- /src/array/shuffle.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as assert from 'uvu/assert'; 3 | import shuffle from './shuffle.js'; 4 | 5 | test('shuffles an array in place', () => { 6 | const arr = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l']; 7 | 8 | const shuffled = shuffle(arr); 9 | 10 | assert.is(shuffled, arr); 11 | 12 | // this *could* happen, but would be vanishingly unlikely 13 | assert.not.equal(shuffled, [ 14 | 'a', 15 | 'b', 16 | 'c', 17 | 'd', 18 | 'e', 19 | 'f', 20 | 'g', 21 | 'h', 22 | 'i', 23 | 'j', 24 | 'k', 25 | 'l', 26 | ]); 27 | }); 28 | 29 | test.run(); 30 | -------------------------------------------------------------------------------- /src/async/queue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {{ 3 | * fulfil: (value?: any) => void; 4 | * reject: (error?: Error) => void; 5 | * promise: Promise; 6 | * }} Deferred 7 | * 8 | * @typedef {{ 9 | * fn: () => Promise; 10 | * fulfil: (value: any) => void; 11 | * reject: (error: Error) => void; 12 | * }} Item 13 | */ 14 | 15 | /** 16 | * Create a queue for running promise-returning functions in sequence, with concurrency=`max` 17 | * @param {number} max 18 | */ 19 | export default function queue(max = 4) { 20 | /** @type {Item[]} */ 21 | const items = []; // TODO 22 | 23 | let pending = 0; 24 | 25 | let closed = false; 26 | 27 | /** @type {(value: any) => void} */ 28 | let fulfil_closed; 29 | 30 | function dequeue() { 31 | if (pending === 0 && items.length === 0) { 32 | if (fulfil_closed) fulfil_closed(); 33 | } 34 | 35 | if (pending >= max) return; 36 | if (items.length === 0) return; 37 | 38 | pending += 1; 39 | 40 | const { fn, fulfil, reject } = items.shift(); 41 | const promise = fn(); 42 | 43 | try { 44 | promise.then(fulfil, reject).then(() => { 45 | pending -= 1; 46 | dequeue(); 47 | }); 48 | } catch (err) { 49 | reject(err); 50 | pending -= 1; 51 | dequeue(); 52 | } 53 | 54 | dequeue(); 55 | } 56 | 57 | return { 58 | /** @param {() => Promise} fn */ 59 | add(fn) { 60 | if (closed) { 61 | throw new Error(`Cannot add to a closed queue`); 62 | } 63 | 64 | return new Promise((fulfil, reject) => { 65 | items.push({ fn, fulfil, reject }); 66 | dequeue(); 67 | }); 68 | }, 69 | 70 | close() { 71 | closed = true; 72 | 73 | return new Promise((fulfil, reject) => { 74 | if (pending === 0) { 75 | fulfil(); 76 | } else { 77 | fulfil_closed = fulfil; 78 | } 79 | }); 80 | }, 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /src/async/queue.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as assert from 'uvu/assert'; 3 | import queue from './queue.js'; 4 | 5 | /** 6 | * @typedef {import('./queue').Deferred} Deferred 7 | */ 8 | 9 | // TODO more and better tests 10 | test('max parallel check', async () => { 11 | const max = 4; 12 | const q = queue(max); 13 | 14 | /** @type {number[]} */ 15 | const executing = []; 16 | 17 | /** @param {number} delay */ 18 | function sleep(delay) { 19 | return new Promise(function (resolve) { 20 | executing.push(1); 21 | setTimeout(() => { 22 | executing.push(-1); 23 | resolve(); 24 | }, delay); 25 | }); 26 | } 27 | 28 | for (let i = 0; i < 10; i++) { 29 | q.add(() => sleep(10)); 30 | } 31 | 32 | q.close(); 33 | 34 | let concurrent = 0; 35 | while ((concurrent += executing.shift())) { 36 | assert.ok(concurrent <= max); 37 | } 38 | }); 39 | 40 | test('queues tasks', async () => { 41 | const q = queue(); 42 | 43 | const letters = []; 44 | 45 | letters.push( 46 | await q.add(() => { 47 | return Promise.resolve('a'); 48 | }) 49 | ); 50 | 51 | letters.push( 52 | await q.add(() => { 53 | return Promise.resolve('b'); 54 | }) 55 | ); 56 | 57 | letters.push( 58 | await q.add(() => { 59 | return Promise.resolve('c'); 60 | }) 61 | ); 62 | 63 | await letters.push( 64 | await q.add(() => { 65 | return Promise.resolve('d'); 66 | }) 67 | ); 68 | 69 | assert.equal(letters, ['a', 'b', 'c', 'd']); 70 | }); 71 | 72 | test('queue.close returns a promise that resolves and closes when all items are completed', async () => { 73 | const deferred = () => { 74 | let fulfil; 75 | let reject; 76 | 77 | const promise = new Promise((f, r) => { 78 | fulfil = f; 79 | reject = r; 80 | }); 81 | 82 | return { promise, fulfil, reject }; 83 | }; 84 | 85 | const q = queue(); 86 | 87 | /** @type {Deferred[]} */ 88 | const deferreds = []; 89 | 90 | /** @type {number[]} */ 91 | const values = []; 92 | 93 | for (let i = 0; i < 5; i += 1) { 94 | const d = deferred(); 95 | d.promise.then((value) => { 96 | values.push(value); 97 | }); 98 | 99 | deferreds.push(d); 100 | q.add(() => d.promise); 101 | } 102 | 103 | for (let i = 5; i < 10; i += 1) { 104 | const d = deferred(); 105 | d.promise.then((value) => { 106 | values.push(value); 107 | }); 108 | 109 | deferreds.push(d); 110 | q.add(() => d.promise); 111 | } 112 | 113 | const promise = q.close(); 114 | 115 | deferreds.forEach((d, i) => d.fulfil(i * 2)); 116 | 117 | await promise; 118 | assert.equal(values, [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]); 119 | }); 120 | 121 | test('queue.close returns a promise that resolves once all items are completed', async () => { 122 | const deferred = () => { 123 | let fulfil; 124 | let reject; 125 | 126 | const promise = new Promise((f, r) => { 127 | fulfil = f; 128 | reject = r; 129 | }); 130 | 131 | return { promise, fulfil, reject }; 132 | }; 133 | 134 | const q = queue(); 135 | 136 | /** @type {Deferred[]} */ 137 | const deferreds = []; 138 | 139 | /** @type {number[]} */ 140 | const values = []; 141 | 142 | for (let i = 0; i < 10; i += 1) { 143 | const d = deferred(); 144 | d.promise.then((value) => { 145 | values.push(value); 146 | }); 147 | 148 | deferreds.push(d); 149 | q.add(() => d.promise); 150 | } 151 | 152 | const promise = q.close(); 153 | 154 | deferreds.forEach((d, i) => d.fulfil(i * 2)); 155 | 156 | await promise; 157 | assert.equal(values, [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]); 158 | }); 159 | 160 | test('queue.close throws if a task is subsequently added', async () => { 161 | const q = queue(); 162 | 163 | q.close(); 164 | 165 | assert.throws(() => { 166 | q.add(() => Promise.resolve(42)); 167 | }, /Cannot add to a closed queue/); 168 | }); 169 | 170 | test.run(); 171 | -------------------------------------------------------------------------------- /src/async/sleep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wait for `ms` milliseconds 3 | * @param {number} ms 4 | */ 5 | export default function sleep(ms) { 6 | return new Promise((fulfil) => { 7 | setTimeout(fulfil, ms); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /src/canvas/sprite.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate a sprite using the canvas API 3 | * @param {number} width 4 | * @param {number} height 5 | * @param {(ctx: CanvasRenderingContext2D, w: number, h: number) => void} fn 6 | */ 7 | export default function createSprite(width, height, fn) { 8 | const canvas = document.createElement('canvas'); 9 | canvas.width = width; 10 | canvas.height = height; 11 | const ctx = canvas.getContext('2d'); 12 | 13 | fn(ctx, canvas.width, canvas.height); 14 | 15 | return canvas; 16 | } 17 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // array 2 | export { default as binarySearch } from './array/binarySearch.js'; 3 | export { default as pickRandom } from './array/pickRandom.js'; 4 | export { default as shuffle } from './array/shuffle.js'; 5 | 6 | // async 7 | export { default as queue } from './async/queue.js'; 8 | export { default as sleep } from './async/sleep.js'; 9 | 10 | // canvas 11 | export { default as createSprite } from './canvas/sprite.js'; 12 | 13 | // number 14 | export { default as clamp } from './number/clamp.js'; 15 | export { default as random } from './number/random.js'; 16 | export { default as seedRandom } from './number/seedRandom.js'; 17 | 18 | // scale 19 | export { default as linearScale } from './scale/linear.js'; 20 | 21 | // string 22 | export { default as commas } from './string/commas.js'; 23 | export { default as padLeft } from './string/padLeft.js'; 24 | -------------------------------------------------------------------------------- /src/number/clamp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Clamp `num` to the range `[min, max]` 3 | * @param {number} num 4 | * @param {number} min 5 | * @param {number} max 6 | */ 7 | export default function clamp(num, min, max) { 8 | return num < min ? min : num > max ? max : num; 9 | } 10 | -------------------------------------------------------------------------------- /src/number/clamp.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as assert from 'uvu/assert'; 3 | import clamp from './clamp.js'; 4 | 5 | test('clamps a number', () => { 6 | assert.equal(clamp(10, 20, 30), 20); 7 | assert.equal(clamp(25, 20, 30), 25); 8 | assert.equal(clamp(40, 20, 30), 30); 9 | }); 10 | 11 | test.run(); 12 | -------------------------------------------------------------------------------- /src/number/random.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate a random number between `a` and `b`, or between 0 and `a` if `b` is unspecified 3 | * @param {number} a 4 | * @param {number} [b] 5 | */ 6 | export default function random(a, b) { 7 | if (b === undefined) return Math.random() * a; 8 | return a + Math.random() * (b - a); 9 | } 10 | -------------------------------------------------------------------------------- /src/number/random.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as assert from 'uvu/assert'; 3 | import random from './random.js'; 4 | 5 | test('generates a random number', () => { 6 | const n = random(10); 7 | assert.ok(n >= 0); 8 | assert.ok(n <= 10); 9 | }); 10 | 11 | test('generates a random number between a and b', () => { 12 | const n = random(50, 60); 13 | assert.ok(n >= 50); 14 | assert.ok(n <= 60); 15 | }); 16 | 17 | test.run(); 18 | -------------------------------------------------------------------------------- /src/number/seedRandom.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Adapted from http://davidbau.com/encode/seedrandom.js 4 | 5 | LICENSE (MIT) 6 | ------------- 7 | 8 | Copyright 2014 David Bau. 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining 11 | a copy of this software and associated documentation files (the 12 | "Software"), to deal in the Software without restriction, including 13 | without limitation the rights to use, copy, modify, merge, publish, 14 | distribute, sublicense, and/or sell copies of the Software, and to 15 | permit persons to whom the Software is furnished to do so, subject to 16 | the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | */ 30 | 31 | const width = 256; // width: each RC4 output is 0 <= x < 256 32 | const chunks = 6; // chunks: at least six RC4 outputs for each double 33 | const digits = 52; // digits: there are 52 significant digits in a double 34 | 35 | // 36 | // The following constants are related to IEEE 754 limits. 37 | // 38 | const startdenom = Math.pow(width, chunks); 39 | const significance = Math.pow(2, digits); 40 | const overflow = significance * 2; 41 | const mask = width - 1; 42 | 43 | // 44 | // ARC4 45 | // 46 | // An ARC4 implementation. The constructor takes a key in the form of 47 | // an array of at most (width) integers that should be 0 <= x < (width). 48 | // 49 | // The g(count) method returns a pseudorandom integer that concatenates 50 | // the next (count) outputs from ARC4. Its return value is a number x 51 | // that is in the range 0 <= x < (width ^ count). 52 | // 53 | class ARC4 { 54 | /** @param {number[]} key */ 55 | constructor(key) { 56 | this.i = 0; 57 | this.j = 0; 58 | 59 | /** @type {number[]} */ 60 | this.S = []; 61 | 62 | // Set up S using the standard key scheduling algorithm. 63 | for (let i = 0; i < width; i += 1) { 64 | this.S[i] = i; 65 | } 66 | 67 | const len = key.length; 68 | let t; 69 | let j = 0; 70 | 71 | for (let i = 0; i < width; i += 1) { 72 | this.S[i] = this.S[(j = mask & (j + key[i % len] + (t = this.S[i])))]; 73 | this.S[j] = t; 74 | } 75 | 76 | // For robust unpredictability, the function call below automatically 77 | // discards an initial batch of values. This is called RC4-drop[256]. 78 | // See http://google.com/search?q=rsa+fluhrer+response&btnI 79 | this.g(width); 80 | } 81 | 82 | /** @param {number} count */ 83 | g(count) { 84 | const s = this.S; 85 | let r = 0; 86 | 87 | while (count--) { 88 | const t = s[(this.i = mask & (this.i + 1))]; 89 | r = 90 | r * width + 91 | s[ 92 | mask & 93 | ((s[this.i] = s[(this.j = mask & (this.j + t))]) + (s[this.j] = t)) 94 | ]; 95 | } 96 | 97 | return r; 98 | } 99 | } 100 | 101 | /** 102 | * Create a seeded random number generator that returns a random number between `a` and `b`, or between 0 and `a` if `b` is unspecified 103 | * @param {string} seed 104 | */ 105 | export default function seedRandom(seed) { 106 | if (!seed) seed = '\0'; 107 | 108 | /** @type {number[]} */ 109 | const key = []; 110 | 111 | /** @type {number} */ 112 | let smear; 113 | 114 | for (let i = 0; i < seed.length; i += 1) { 115 | key[mask & i] = mask & ((smear ^= key[mask & i] * 19) + seed.charCodeAt(i)); 116 | } 117 | 118 | // Use the seed to initialize an ARC4 generator. 119 | const arc4 = new ARC4(key); 120 | 121 | function prng() { 122 | let n = arc4.g(chunks); // Start with a numerator n < 2 ^ 48 123 | let d = startdenom; // and denominator d = 2 ^ 48. 124 | let x = 0; // and no 'extra last byte'. 125 | 126 | while (n < significance) { 127 | // Fill up all significant digits by 128 | n = (n + x) * width; // shifting numerator and 129 | d *= width; // denominator and generating a 130 | x = arc4.g(1); // new least-significant-byte. 131 | } 132 | 133 | while (n >= overflow) { 134 | // To avoid rounding up, before adding 135 | n /= 2; // last byte, shift everything 136 | d /= 2; // right using integer math until 137 | x >>>= 1; // we have exactly the desired bits. 138 | } 139 | 140 | return (n + x) / d; // Form the number within [0, 1). 141 | } 142 | 143 | /** 144 | * Generate a random number between `a` and `b`, or between 0 and `a` if `b` is unspecified 145 | * @param {number} a 146 | * @param {number} [b] 147 | */ 148 | function random(a, b) { 149 | if (b === undefined) return prng() * a; 150 | return a + prng() * (b - a); 151 | } 152 | 153 | return random; 154 | } 155 | -------------------------------------------------------------------------------- /src/number/seedRandom.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as assert from 'uvu/assert'; 3 | import seedRandom from './seedRandom.js'; 4 | 5 | test('generates a predictable random number', () => { 6 | const prng = seedRandom('hello.'); 7 | assert.equal(prng(0, 1), 0.9282578795792454); 8 | assert.equal(prng(0, 1), 0.3752569768646784); 9 | }); 10 | 11 | test.run(); 12 | -------------------------------------------------------------------------------- /src/scale/linear.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates a `scale` function that maps from `domain` to `range`. 3 | * `scale.inverse()` returns a function that maps from `range` to `domain` 4 | * @param {[number, number]} domain 5 | * @param {[number, number]} range 6 | */ 7 | export default function linear(domain, range) { 8 | const d0 = domain[0]; 9 | const r0 = range[0]; 10 | const m = (range[1] - r0) / (domain[1] - d0); 11 | 12 | /** @param {number} num */ 13 | function scale(num) { 14 | return r0 + (num - d0) * m; 15 | } 16 | 17 | scale.inverse = () => linear(range, domain); 18 | 19 | return scale; 20 | } 21 | -------------------------------------------------------------------------------- /src/scale/linear.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as assert from 'uvu/assert'; 3 | import linearScale from './linear.js'; 4 | 5 | test('scales a number', () => { 6 | const scale = linearScale([10, 20], [50, 100]); 7 | assert.equal(scale(15), 75); 8 | assert.equal(scale(5), 25); 9 | }); 10 | 11 | test('provides an inverse() method', () => { 12 | const scale = linearScale([10, 20], [50, 100]); 13 | const inverse = scale.inverse(); 14 | assert.equal(inverse(75), 15); 15 | assert.equal(inverse(25), 5); 16 | }); 17 | 18 | test.run(); 19 | -------------------------------------------------------------------------------- /src/string/commas.js: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript 2 | 3 | /** 4 | * Format a number with comma separators 5 | * @param {number} num 6 | */ 7 | export default function commas(num) { 8 | const parts = String(num).split('.'); 9 | parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); 10 | return parts.join('.'); 11 | } 12 | -------------------------------------------------------------------------------- /src/string/commas.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as assert from 'uvu/assert'; 3 | import commas from './commas.js'; 4 | 5 | test('adds commas to a number', () => { 6 | assert.equal(commas(1234), '1,234'); 7 | }); 8 | 9 | test('adds commas to a large number', () => { 10 | assert.equal(commas(1234567890), '1,234,567,890'); 11 | }); 12 | 13 | test('adds commas to a number with decimal point', () => { 14 | assert.equal(commas(1234.5678), '1,234.5678'); 15 | }); 16 | 17 | test.run(); 18 | -------------------------------------------------------------------------------- /src/string/padLeft.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Pad a number or string 3 | * @param {string | number} input 4 | * @param {number} [length] 5 | * @param {string} [char] 6 | */ 7 | export default function padLeft(input, length = 2, char = '0') { 8 | let output = String(input); 9 | while (output.length < length) output = char + output; 10 | return output; 11 | } 12 | -------------------------------------------------------------------------------- /src/string/padLeft.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as assert from 'uvu/assert'; 3 | import padLeft from './padLeft.js'; 4 | 5 | test('pads a number with zeroes, defaulting to 2', () => { 6 | assert.equal(padLeft(1), '01'); 7 | assert.equal(padLeft(1, 3), '001'); 8 | }); 9 | 10 | test('pads a string with zeroes, defaulting to 2', () => { 11 | assert.equal(padLeft('1'), '01'); 12 | assert.equal(padLeft('1', 3), '001'); 13 | }); 14 | 15 | test('pads a string with spaces', () => { 16 | assert.equal(padLeft('1', 2, ' '), ' 1'); 17 | assert.equal(padLeft('1', 3, ' '), ' 1'); 18 | }); 19 | 20 | test.run(); 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "checkJs": true, 5 | "noImplicitAny": true, 6 | "noImplicitThis": true, 7 | "lib": ["dom"], 8 | "declaration": true, 9 | "emitDeclarationOnly": true, 10 | "outDir": "types" 11 | }, 12 | "include": [ 13 | "src" 14 | ] 15 | } --------------------------------------------------------------------------------