├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bench └── bench.js ├── example.js ├── index.js ├── package-lock.json ├── package.json ├── tea.yaml └── test ├── bash-comparison.js ├── bash-results.txt ├── cases.txt ├── dollar.js ├── empty-option.js ├── generate.sh ├── negative-increment.js ├── nested.js ├── order.js ├── pad.js ├── same-type.js └── sequence.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | tidelift: "npm/brace-expansion" 2 | patreon: juliangruber 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | node: ['18', '20'] 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v2 12 | with: 13 | node-version: ${{ matrix.node }} 14 | - run: npm ci 15 | - run: npm test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.sw* 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | .gitignore 3 | .travis.yml 4 | example.js 5 | .npmignore 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013 Julian Gruber 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # brace-expansion 2 | 3 | [Brace expansion](https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html), 4 | as known from sh/bash, in JavaScript. 5 | 6 | [![CI](https://github.com/juliangruber/brace-expansion/actions/workflows/ci.yml/badge.svg)](https://github.com/juliangruber/brace-expansion/actions/workflows/ci.yml) 7 | [![downloads](https://img.shields.io/npm/dm/brace-expansion.svg)](https://www.npmjs.org/package/brace-expansion) 8 | 9 | ## Example 10 | 11 | ```js 12 | import expand from 'brace-expansion' 13 | 14 | expand('file-{a,b,c}.jpg') 15 | // => ['file-a.jpg', 'file-b.jpg', 'file-c.jpg'] 16 | 17 | expand('-v{,,}') 18 | // => ['-v', '-v', '-v'] 19 | 20 | expand('file{0..2}.jpg') 21 | // => ['file0.jpg', 'file1.jpg', 'file2.jpg'] 22 | 23 | expand('file-{a..c}.jpg') 24 | // => ['file-a.jpg', 'file-b.jpg', 'file-c.jpg'] 25 | 26 | expand('file{2..0}.jpg') 27 | // => ['file2.jpg', 'file1.jpg', 'file0.jpg'] 28 | 29 | expand('file{0..4..2}.jpg') 30 | // => ['file0.jpg', 'file2.jpg', 'file4.jpg'] 31 | 32 | expand('file-{a..e..2}.jpg') 33 | // => ['file-a.jpg', 'file-c.jpg', 'file-e.jpg'] 34 | 35 | expand('file{00..10..5}.jpg') 36 | // => ['file00.jpg', 'file05.jpg', 'file10.jpg'] 37 | 38 | expand('{{A..C},{a..c}}') 39 | // => ['A', 'B', 'C', 'a', 'b', 'c'] 40 | 41 | expand('ppp{,config,oe{,conf}}') 42 | // => ['ppp', 'pppconfig', 'pppoe', 'pppoeconf'] 43 | ``` 44 | 45 | ## API 46 | 47 | ```js 48 | import expand from 'brace-expansion' 49 | ``` 50 | 51 | ### const expanded = expand(str) 52 | 53 | Return an array of all possible and valid expansions of `str`. If none are 54 | found, `[str]` is returned. 55 | 56 | Valid expansions are: 57 | 58 | ```js 59 | /^(.*,)+(.+)?$/ 60 | // {a,b,...} 61 | ``` 62 | 63 | A comma separated list of options, like `{a,b}` or `{a,{b,c}}` or `{,a,}`. 64 | 65 | ```js 66 | /^-?\d+\.\.-?\d+(\.\.-?\d+)?$/ 67 | // {x..y[..incr]} 68 | ``` 69 | 70 | A numeric sequence from `x` to `y` inclusive, with optional increment. 71 | If `x` or `y` start with a leading `0`, all the numbers will be padded 72 | to have equal length. Negative numbers and backwards iteration work too. 73 | 74 | ```js 75 | /^-?\d+\.\.-?\d+(\.\.-?\d+)?$/ 76 | // {x..y[..incr]} 77 | ``` 78 | 79 | An alphabetic sequence from `x` to `y` inclusive, with optional increment. 80 | `x` and `y` must be exactly one character, and if given, `incr` must be a 81 | number. 82 | 83 | For compatibility reasons, the string `${` is not eligible for brace expansion. 84 | 85 | ## Installation 86 | 87 | With [npm](https://npmjs.org) do: 88 | 89 | ```bash 90 | npm install brace-expansion 91 | ``` 92 | 93 | ## Contributors 94 | 95 | - [Julian Gruber](https://github.com/juliangruber) 96 | - [Isaac Z. Schlueter](https://github.com/isaacs) 97 | - [Haelwenn Monnier](https://github.com/lanodan) 98 | 99 | ## Sponsors 100 | 101 | This module is proudly supported by my [Sponsors](https://github.com/juliangruber/sponsors)! 102 | 103 | Do you want to support modules like this to improve their quality, stability and weigh in on new features? Then please consider donating to my [Patreon](https://www.patreon.com/juliangruber). Not sure how much of my modules you're using? Try [feross/thanks](https://github.com/feross/thanks)! 104 | 105 | ## Security contact information 106 | 107 | To report a security vulnerability, please use the 108 | [Tidelift security contact](https://tidelift.com/security). 109 | Tidelift will coordinate the fix and disclosure. 110 | 111 | ## License 112 | 113 | (MIT) 114 | 115 | Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> 116 | 117 | Permission is hereby granted, free of charge, to any person obtaining a copy of 118 | this software and associated documentation files (the "Software"), to deal in 119 | the Software without restriction, including without limitation the rights to 120 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 121 | of the Software, and to permit persons to whom the Software is furnished to do 122 | so, subject to the following conditions: 123 | 124 | The above copyright notice and this permission notice shall be included in all 125 | copies or substantial portions of the Software. 126 | 127 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 128 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 129 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 130 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 131 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 132 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 133 | SOFTWARE. 134 | -------------------------------------------------------------------------------- /bench/bench.js: -------------------------------------------------------------------------------- 1 | /* global bench */ 2 | 3 | import expand from '..' 4 | import fs from 'fs' 5 | 6 | const resfile = new URL('../test/cases.txt', import.meta.url) 7 | const cases = fs.readFileSync(resfile, 'utf8').split('\n') 8 | 9 | bench('Average', function () { 10 | cases.forEach(function (testcase) { 11 | expand(testcase) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | import expand from './index.js' 2 | 3 | console.log(expand('http://any.org/archive{1996..1999}/vol{1..4}/part{a,b,c}.html')) 4 | console.log(expand('http://www.numericals.com/file{1..100..10}.txt')) 5 | console.log(expand('http://www.letters.com/file{a..z..2}.txt')) 6 | console.log(expand('mkdir /usr/local/src/bash/{old,new,dist,bugs}')) 7 | console.log(expand('chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}')) 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import balanced from 'balanced-match' 2 | 3 | const escSlash = '\0SLASH' + Math.random() + '\0' 4 | const escOpen = '\0OPEN' + Math.random() + '\0' 5 | const escClose = '\0CLOSE' + Math.random() + '\0' 6 | const escComma = '\0COMMA' + Math.random() + '\0' 7 | const escPeriod = '\0PERIOD' + Math.random() + '\0' 8 | const escSlashPattern = new RegExp(escSlash, 'g') 9 | const escOpenPattern = new RegExp(escOpen, 'g') 10 | const escClosePattern = new RegExp(escClose, 'g') 11 | const escCommaPattern = new RegExp(escComma, 'g') 12 | const escPeriodPattern = new RegExp(escPeriod, 'g') 13 | const slashPattern = /\\\\/g 14 | const openPattern = /\\{/g 15 | const closePattern = /\\}/g 16 | const commaPattern = /\\,/g 17 | const periodPattern = /\\./g 18 | 19 | /** 20 | * @return {number} 21 | */ 22 | function numeric (str) { 23 | return !isNaN(str) 24 | ? parseInt(str, 10) 25 | : str.charCodeAt(0) 26 | } 27 | 28 | /** 29 | * @param {string} str 30 | */ 31 | function escapeBraces (str) { 32 | return str.replace(slashPattern, escSlash) 33 | .replace(openPattern, escOpen) 34 | .replace(closePattern, escClose) 35 | .replace(commaPattern, escComma) 36 | .replace(periodPattern, escPeriod) 37 | } 38 | 39 | /** 40 | * @param {string} str 41 | */ 42 | function unescapeBraces (str) { 43 | return str.replace(escSlashPattern, '\\') 44 | .replace(escOpenPattern, '{') 45 | .replace(escClosePattern, '}') 46 | .replace(escCommaPattern, ',') 47 | .replace(escPeriodPattern, '.') 48 | } 49 | 50 | /** 51 | * Basically just str.split(","), but handling cases 52 | * where we have nested braced sections, which should be 53 | * treated as individual members, like {a,{b,c},d} 54 | * @param {string} str 55 | */ 56 | function parseCommaParts (str) { 57 | if (!str) { return [''] } 58 | 59 | const parts = [] 60 | const m = balanced('{', '}', str) 61 | 62 | if (!m) { return str.split(',') } 63 | 64 | const { pre, body, post } = m 65 | const p = pre.split(',') 66 | 67 | p[p.length - 1] += '{' + body + '}' 68 | const postParts = parseCommaParts(post) 69 | if (post.length) { 70 | p[p.length - 1] += postParts.shift() 71 | p.push.apply(p, postParts) 72 | } 73 | 74 | parts.push.apply(parts, p) 75 | 76 | return parts 77 | } 78 | 79 | /** 80 | * @param {string} str 81 | */ 82 | export default function expandTop (str) { 83 | if (!str) { return [] } 84 | 85 | // I don't know why Bash 4.3 does this, but it does. 86 | // Anything starting with {} will have the first two bytes preserved 87 | // but *only* at the top level, so {},a}b will not expand to anything, 88 | // but a{},b}c will be expanded to [a}c,abc]. 89 | // One could argue that this is a bug in Bash, but since the goal of 90 | // this module is to match Bash's rules, we escape a leading {} 91 | if (str.slice(0, 2) === '{}') { 92 | str = '\\{\\}' + str.slice(2) 93 | } 94 | 95 | return expand(escapeBraces(str), true).map(unescapeBraces) 96 | } 97 | 98 | /** 99 | * @param {string} str 100 | */ 101 | function embrace (str) { 102 | return '{' + str + '}' 103 | } 104 | 105 | /** 106 | * @param {string} el 107 | */ 108 | function isPadded (el) { 109 | return /^-?0\d/.test(el) 110 | } 111 | 112 | /** 113 | * @param {number} i 114 | * @param {number} y 115 | */ 116 | function lte (i, y) { 117 | return i <= y 118 | } 119 | 120 | /** 121 | * @param {number} i 122 | * @param {number} y 123 | */ 124 | function gte (i, y) { 125 | return i >= y 126 | } 127 | 128 | /** 129 | * @param {string} str 130 | * @param {boolean} [isTop] 131 | */ 132 | function expand (str, isTop) { 133 | /** @type {string[]} */ 134 | const expansions = [] 135 | 136 | const m = balanced('{', '}', str) 137 | if (!m) return [str] 138 | 139 | // no need to expand pre, since it is guaranteed to be free of brace-sets 140 | const pre = m.pre 141 | const post = m.post.length 142 | ? expand(m.post, false) 143 | : [''] 144 | 145 | if (/\$$/.test(m.pre)) { 146 | for (let k = 0; k < post.length; k++) { 147 | const expansion = pre + '{' + m.body + '}' + post[k] 148 | expansions.push(expansion) 149 | } 150 | } else { 151 | const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body) 152 | const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body) 153 | const isSequence = isNumericSequence || isAlphaSequence 154 | const isOptions = m.body.indexOf(',') >= 0 155 | if (!isSequence && !isOptions) { 156 | // {a},b} 157 | if (m.post.match(/,.*\}/)) { 158 | str = m.pre + '{' + m.body + escClose + m.post 159 | return expand(str) 160 | } 161 | return [str] 162 | } 163 | 164 | let n 165 | if (isSequence) { 166 | n = m.body.split(/\.\./) 167 | } else { 168 | n = parseCommaParts(m.body) 169 | if (n.length === 1) { 170 | // x{{a,b}}y ==> x{a}y x{b}y 171 | n = expand(n[0], false).map(embrace) 172 | if (n.length === 1) { 173 | return post.map(function (p) { 174 | return m.pre + n[0] + p 175 | }) 176 | } 177 | } 178 | } 179 | 180 | // at this point, n is the parts, and we know it's not a comma set 181 | // with a single entry. 182 | let N 183 | 184 | if (isSequence) { 185 | const x = numeric(n[0]) 186 | const y = numeric(n[1]) 187 | const width = Math.max(n[0].length, n[1].length) 188 | let incr = n.length === 3 189 | ? Math.abs(numeric(n[2])) 190 | : 1 191 | let test = lte 192 | const reverse = y < x 193 | if (reverse) { 194 | incr *= -1 195 | test = gte 196 | } 197 | const pad = n.some(isPadded) 198 | 199 | N = [] 200 | 201 | for (let i = x; test(i, y); i += incr) { 202 | let c 203 | if (isAlphaSequence) { 204 | c = String.fromCharCode(i) 205 | if (c === '\\') { c = '' } 206 | } else { 207 | c = String(i) 208 | if (pad) { 209 | const need = width - c.length 210 | if (need > 0) { 211 | const z = new Array(need + 1).join('0') 212 | if (i < 0) { c = '-' + z + c.slice(1) } else { c = z + c } 213 | } 214 | } 215 | } 216 | N.push(c) 217 | } 218 | } else { 219 | N = [] 220 | 221 | for (let j = 0; j < n.length; j++) { 222 | N.push.apply(N, expand(n[j], false)) 223 | } 224 | } 225 | 226 | for (let j = 0; j < N.length; j++) { 227 | for (let k = 0; k < post.length; k++) { 228 | const expansion = pre + N[j] + post[k] 229 | if (!isTop || isSequence || expansion) { expansions.push(expansion) } 230 | } 231 | } 232 | } 233 | 234 | return expansions 235 | } 236 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "brace-expansion", 3 | "description": "Brace expansion as known from sh/bash", 4 | "version": "4.0.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/juliangruber/brace-expansion.git" 8 | }, 9 | "homepage": "https://github.com/juliangruber/brace-expansion", 10 | "exports": "./index.js", 11 | "type": "module", 12 | "scripts": { 13 | "test": "standard --fix && node --test", 14 | "gentest": "bash test/generate.sh", 15 | "bench": "matcha bench/bench.js" 16 | }, 17 | "dependencies": { 18 | "balanced-match": "^3.0.0" 19 | }, 20 | "devDependencies": { 21 | "@c4312/matcha": "^1.3.1", 22 | "standard": "^17.1.0" 23 | }, 24 | "keywords": [], 25 | "author": { 26 | "name": "Julian Gruber", 27 | "email": "mail@juliangruber.com", 28 | "url": "http://juliangruber.com" 29 | }, 30 | "license": "MIT", 31 | "testling": { 32 | "files": "test/*.js", 33 | "browsers": [ 34 | "ie/8..latest", 35 | "firefox/20..latest", 36 | "firefox/nightly", 37 | "chrome/25..latest", 38 | "chrome/canary", 39 | "opera/12..latest", 40 | "opera/next", 41 | "safari/5.1..latest", 42 | "ipad/6.0..latest", 43 | "iphone/6.0..latest", 44 | "android-browser/4.2..latest" 45 | ] 46 | }, 47 | "engines": { 48 | "node": ">= 18" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | --- 3 | version: 1.0.0 4 | codeOwners: 5 | - '0xE7DEE1B8Bb97C3065850Cf582D6DED57C6009587' 6 | quorum: 1 7 | -------------------------------------------------------------------------------- /test/bash-comparison.js: -------------------------------------------------------------------------------- 1 | import test from 'node:test' 2 | import assert from 'assert' 3 | import expand from '../index.js' 4 | import fs from 'fs' 5 | 6 | const resfile = new URL('./bash-results.txt', import.meta.url) 7 | const cases = fs.readFileSync(resfile, 'utf8').split('><><><><') 8 | 9 | // throw away the EOF marker 10 | cases.pop() 11 | 12 | test('matches bash expansions', function () { 13 | cases.forEach(function (testcase) { 14 | let set = testcase.split('\n') 15 | const pattern = set.shift() 16 | const actual = expand(pattern) 17 | 18 | // If it expands to the empty string, then it's actually 19 | // just nothing, but Bash is a singly typed language, so 20 | // "nothing" is the same as "". 21 | if (set.length === 1 && set[0] === '') { 22 | set = [] 23 | } else { 24 | // otherwise, strip off the [] that were added so that 25 | // "" expansions would be preserved properly. 26 | set = set.map(function (s) { 27 | return s.replace(/^\[|\]$/g, '') 28 | }) 29 | } 30 | 31 | assert.deepStrictEqual(actual, set, pattern) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /test/bash-results.txt: -------------------------------------------------------------------------------- 1 | A{b,{d,e},{f,g}}Z 2 | [AbZ] 3 | [AdZ] 4 | [AeZ] 5 | [AfZ] 6 | [AgZ]><><><><><><><\{a,b}{{a,b},a,b} 15 | [{a,b}a] 16 | [{a,b}b] 17 | [{a,b}a] 18 | [{a,b}b]><><><><{{a,b} 19 | [{a] 20 | [{b]><><><><{a,b}} 21 | [a}] 22 | [b}]><><><><{,} 23 | ><><><><><><><{,}b 26 | [b] 27 | [b]><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><{-01..5} 111 | [-01] 112 | [000] 113 | [001] 114 | [002] 115 | [003] 116 | [004] 117 | [005]><><><><{-05..100..5} 118 | [-05] 119 | [000] 120 | [005] 121 | [010] 122 | [015] 123 | [020] 124 | [025] 125 | [030] 126 | [035] 127 | [040] 128 | [045] 129 | [050] 130 | [055] 131 | [060] 132 | [065] 133 | [070] 134 | [075] 135 | [080] 136 | [085] 137 | [090] 138 | [095] 139 | [100]><><><><{-05..100} 140 | [-05] 141 | [-04] 142 | [-03] 143 | [-02] 144 | [-01] 145 | [000] 146 | [001] 147 | [002] 148 | [003] 149 | [004] 150 | [005] 151 | [006] 152 | [007] 153 | [008] 154 | [009] 155 | [010] 156 | [011] 157 | [012] 158 | [013] 159 | [014] 160 | [015] 161 | [016] 162 | [017] 163 | [018] 164 | [019] 165 | [020] 166 | [021] 167 | [022] 168 | [023] 169 | [024] 170 | [025] 171 | [026] 172 | [027] 173 | [028] 174 | [029] 175 | [030] 176 | [031] 177 | [032] 178 | [033] 179 | [034] 180 | [035] 181 | [036] 182 | [037] 183 | [038] 184 | [039] 185 | [040] 186 | [041] 187 | [042] 188 | [043] 189 | [044] 190 | [045] 191 | [046] 192 | [047] 193 | [048] 194 | [049] 195 | [050] 196 | [051] 197 | [052] 198 | [053] 199 | [054] 200 | [055] 201 | [056] 202 | [057] 203 | [058] 204 | [059] 205 | [060] 206 | [061] 207 | [062] 208 | [063] 209 | [064] 210 | [065] 211 | [066] 212 | [067] 213 | [068] 214 | [069] 215 | [070] 216 | [071] 217 | [072] 218 | [073] 219 | [074] 220 | [075] 221 | [076] 222 | [077] 223 | [078] 224 | [079] 225 | [080] 226 | [081] 227 | [082] 228 | [083] 229 | [084] 230 | [085] 231 | [086] 232 | [087] 233 | [088] 234 | [089] 235 | [090] 236 | [091] 237 | [092] 238 | [093] 239 | [094] 240 | [095] 241 | [096] 242 | [097] 243 | [098] 244 | [099] 245 | [100]><><><><{0..5..2} 246 | [0] 247 | [2] 248 | [4]><><><><{0001..05..2} 249 | [0001] 250 | [0003] 251 | [0005]><><><><{0001..-5..2} 252 | [0001] 253 | [-001] 254 | [-003] 255 | [-005]><><><><{0001..-5..-2} 256 | [0001] 257 | [-001] 258 | [-003] 259 | [-005]><><><><{0001..5..-2} 260 | [0001] 261 | [0003] 262 | [0005]><><><><{01..5} 263 | [01] 264 | [02] 265 | [03] 266 | [04] 267 | [05]><><><><{1..05} 268 | [01] 269 | [02] 270 | [03] 271 | [04] 272 | [05]><><><><{1..05..3} 273 | [01] 274 | [04]><><><><{05..100} 275 | [005] 276 | [006] 277 | [007] 278 | [008] 279 | [009] 280 | [010] 281 | [011] 282 | [012] 283 | [013] 284 | [014] 285 | [015] 286 | [016] 287 | [017] 288 | [018] 289 | [019] 290 | [020] 291 | [021] 292 | [022] 293 | [023] 294 | [024] 295 | [025] 296 | [026] 297 | [027] 298 | [028] 299 | [029] 300 | [030] 301 | [031] 302 | [032] 303 | [033] 304 | [034] 305 | [035] 306 | [036] 307 | [037] 308 | [038] 309 | [039] 310 | [040] 311 | [041] 312 | [042] 313 | [043] 314 | [044] 315 | [045] 316 | [046] 317 | [047] 318 | [048] 319 | [049] 320 | [050] 321 | [051] 322 | [052] 323 | [053] 324 | [054] 325 | [055] 326 | [056] 327 | [057] 328 | [058] 329 | [059] 330 | [060] 331 | [061] 332 | [062] 333 | [063] 334 | [064] 335 | [065] 336 | [066] 337 | [067] 338 | [068] 339 | [069] 340 | [070] 341 | [071] 342 | [072] 343 | [073] 344 | [074] 345 | [075] 346 | [076] 347 | [077] 348 | [078] 349 | [079] 350 | [080] 351 | [081] 352 | [082] 353 | [083] 354 | [084] 355 | [085] 356 | [086] 357 | [087] 358 | [088] 359 | [089] 360 | [090] 361 | [091] 362 | [092] 363 | [093] 364 | [094] 365 | [095] 366 | [096] 367 | [097] 368 | [098] 369 | [099] 370 | [100]><><><><{0a..0z} 371 | [{0a..0z}]><><><><{a,b\}c,d} 372 | [a] 373 | [b}c] 374 | [d]><><><><{a,b{c,d} 375 | [{a,bc] 376 | [{a,bd]><><><><{a,b}c,d} 377 | [ac,d}] 378 | [bc,d}]><><><><{a..F} 379 | [a] 380 | [`] 381 | [_] 382 | [^] 383 | []] 384 | [] 385 | [[] 386 | [Z] 387 | [Y] 388 | [X] 389 | [W] 390 | [V] 391 | [U] 392 | [T] 393 | [S] 394 | [R] 395 | [Q] 396 | [P] 397 | [O] 398 | [N] 399 | [M] 400 | [L] 401 | [K] 402 | [J] 403 | [I] 404 | [H] 405 | [G] 406 | [F]><><><><{A..f} 407 | [A] 408 | [B] 409 | [C] 410 | [D] 411 | [E] 412 | [F] 413 | [G] 414 | [H] 415 | [I] 416 | [J] 417 | [K] 418 | [L] 419 | [M] 420 | [N] 421 | [O] 422 | [P] 423 | [Q] 424 | [R] 425 | [S] 426 | [T] 427 | [U] 428 | [V] 429 | [W] 430 | [X] 431 | [Y] 432 | [Z] 433 | [[] 434 | [] 435 | []] 436 | [^] 437 | [_] 438 | [`] 439 | [a] 440 | [b] 441 | [c] 442 | [d] 443 | [e] 444 | [f]><><><><{a..Z} 445 | [a] 446 | [`] 447 | [_] 448 | [^] 449 | []] 450 | [] 451 | [[] 452 | [Z]><><><><{A..z} 453 | [A] 454 | [B] 455 | [C] 456 | [D] 457 | [E] 458 | [F] 459 | [G] 460 | [H] 461 | [I] 462 | [J] 463 | [K] 464 | [L] 465 | [M] 466 | [N] 467 | [O] 468 | [P] 469 | [Q] 470 | [R] 471 | [S] 472 | [T] 473 | [U] 474 | [V] 475 | [W] 476 | [X] 477 | [Y] 478 | [Z] 479 | [[] 480 | [] 481 | []] 482 | [^] 483 | [_] 484 | [`] 485 | [a] 486 | [b] 487 | [c] 488 | [d] 489 | [e] 490 | [f] 491 | [g] 492 | [h] 493 | [i] 494 | [j] 495 | [k] 496 | [l] 497 | [m] 498 | [n] 499 | [o] 500 | [p] 501 | [q] 502 | [r] 503 | [s] 504 | [t] 505 | [u] 506 | [v] 507 | [w] 508 | [x] 509 | [y] 510 | [z]><><><><{z..A} 511 | [z] 512 | [y] 513 | [x] 514 | [w] 515 | [v] 516 | [u] 517 | [t] 518 | [s] 519 | [r] 520 | [q] 521 | [p] 522 | [o] 523 | [n] 524 | [m] 525 | [l] 526 | [k] 527 | [j] 528 | [i] 529 | [h] 530 | [g] 531 | [f] 532 | [e] 533 | [d] 534 | [c] 535 | [b] 536 | [a] 537 | [`] 538 | [_] 539 | [^] 540 | []] 541 | [] 542 | [[] 543 | [Z] 544 | [Y] 545 | [X] 546 | [W] 547 | [V] 548 | [U] 549 | [T] 550 | [S] 551 | [R] 552 | [Q] 553 | [P] 554 | [O] 555 | [N] 556 | [M] 557 | [L] 558 | [K] 559 | [J] 560 | [I] 561 | [H] 562 | [G] 563 | [F] 564 | [E] 565 | [D] 566 | [C] 567 | [B] 568 | [A]><><><><{Z..a} 569 | [Z] 570 | [[] 571 | [] 572 | []] 573 | [^] 574 | [_] 575 | [`] 576 | [a]><><><><{a..F..2} 577 | [a] 578 | [_] 579 | []] 580 | [[] 581 | [Y] 582 | [W] 583 | [U] 584 | [S] 585 | [Q] 586 | [O] 587 | [M] 588 | [K] 589 | [I] 590 | [G]><><><><{A..f..02} 591 | [A] 592 | [C] 593 | [E] 594 | [G] 595 | [I] 596 | [K] 597 | [M] 598 | [O] 599 | [Q] 600 | [S] 601 | [U] 602 | [W] 603 | [Y] 604 | [[] 605 | []] 606 | [_] 607 | [a] 608 | [c] 609 | [e]><><><><{a..Z..5} 610 | [a] 611 | []><><><><><><><{A..z..10} 614 | [A] 615 | [K] 616 | [U] 617 | [_] 618 | [i] 619 | [s]><><><><{z..A..-2} 620 | [z] 621 | [x] 622 | [v] 623 | [t] 624 | [r] 625 | [p] 626 | [n] 627 | [l] 628 | [j] 629 | [h] 630 | [f] 631 | [d] 632 | [b] 633 | [`] 634 | [^] 635 | [] 636 | [Z] 637 | [X] 638 | [V] 639 | [T] 640 | [R] 641 | [P] 642 | [N] 643 | [L] 644 | [J] 645 | [H] 646 | [F] 647 | [D] 648 | [B]><><><><{Z..a..20} 649 | [Z]><><><><{a{,b} 650 | [{a] 651 | [{ab]><><><><{a},b} 652 | [a}] 653 | [b]><><><><{x,y{,}g} 654 | [x] 655 | [yg] 656 | [yg]><><><><{x,y{}g} 657 | [x] 658 | [y{}g]><><><><{{a,b} 659 | [{a] 660 | [{b]><><><><{{a,b},c} 661 | [a] 662 | [b] 663 | [c]><><><><{{a,b}c} 664 | [{ac}] 665 | [{bc}]><><><><{{a,b},} 666 | [a] 667 | [b]><><><><><><><{{a,b},}c 671 | [ac] 672 | [bc] 673 | [c]><><><><{{a,b}.} 674 | [{a.}] 675 | [{b.}]><><><><{{a,b}} 676 | [{a}] 677 | [{b}]><><><><><><>< 679 | ><><><><{-10..00} 680 | [-10] 681 | [-09] 682 | [-08] 683 | [-07] 684 | [-06] 685 | [-05] 686 | [-04] 687 | [-03] 688 | [-02] 689 | [-01] 690 | [000]><><><><{a,\\{a,b}c} 691 | [a] 692 | [\ac] 693 | [\bc]><><><><{a,\{a,b}c} 694 | [ac}] 695 | [{ac}] 696 | [bc}]><><><><><><><{-10.\.00} 698 | [{-10..00}]><><><><><><><><><><{l,n,m}xyz 705 | [lxyz] 706 | [nxyz] 707 | [mxyz]><><><><{abc\,def} 708 | [{abc,def}]><><><><{abc} 709 | [{abc}]><><><><{x\,y,\{abc\},trie} 710 | [x,y] 711 | [{abc}] 712 | [trie]><><><><{} 713 | [{}]><><><><} 714 | [}]><><><><{ 715 | [{]><><><><><><><{1..10} 717 | [1] 718 | [2] 719 | [3] 720 | [4] 721 | [5] 722 | [6] 723 | [7] 724 | [8] 725 | [9] 726 | [10]><><><><{0..10,braces} 727 | [0..10] 728 | [braces]><><><><{{0..10},braces} 729 | [0] 730 | [1] 731 | [2] 732 | [3] 733 | [4] 734 | [5] 735 | [6] 736 | [7] 737 | [8] 738 | [9] 739 | [10] 740 | [braces]><><><><><><><{3..3} 753 | [3]><><><><><><><{10..1} 755 | [10] 756 | [9] 757 | [8] 758 | [7] 759 | [6] 760 | [5] 761 | [4] 762 | [3] 763 | [2] 764 | [1]><><><><{10..1}y 765 | [10y] 766 | [9y] 767 | [8y] 768 | [7y] 769 | [6y] 770 | [5y] 771 | [4y] 772 | [3y] 773 | [2y] 774 | [1y]><><><><><><><{a..f} 785 | [a] 786 | [b] 787 | [c] 788 | [d] 789 | [e] 790 | [f]><><><><{f..a} 791 | [f] 792 | [e] 793 | [d] 794 | [c] 795 | [b] 796 | [a]><><><><{a..A} 797 | [a] 798 | [`] 799 | [_] 800 | [^] 801 | []] 802 | [] 803 | [[] 804 | [Z] 805 | [Y] 806 | [X] 807 | [W] 808 | [V] 809 | [U] 810 | [T] 811 | [S] 812 | [R] 813 | [Q] 814 | [P] 815 | [O] 816 | [N] 817 | [M] 818 | [L] 819 | [K] 820 | [J] 821 | [I] 822 | [H] 823 | [G] 824 | [F] 825 | [E] 826 | [D] 827 | [C] 828 | [B] 829 | [A]><><><><{A..a} 830 | [A] 831 | [B] 832 | [C] 833 | [D] 834 | [E] 835 | [F] 836 | [G] 837 | [H] 838 | [I] 839 | [J] 840 | [K] 841 | [L] 842 | [M] 843 | [N] 844 | [O] 845 | [P] 846 | [Q] 847 | [R] 848 | [S] 849 | [T] 850 | [U] 851 | [V] 852 | [W] 853 | [X] 854 | [Y] 855 | [Z] 856 | [[] 857 | [] 858 | []] 859 | [^] 860 | [_] 861 | [`] 862 | [a]><><><><{f..f} 863 | [f]><><><><{1..f} 864 | [{1..f}]><><><><{f..1} 865 | [{f..1}]><><><><{-1..-10} 866 | [-1] 867 | [-2] 868 | [-3] 869 | [-4] 870 | [-5] 871 | [-6] 872 | [-7] 873 | [-8] 874 | [-9] 875 | [-10]><><><><{-20..0} 876 | [-20] 877 | [-19] 878 | [-18] 879 | [-17] 880 | [-16] 881 | [-15] 882 | [-14] 883 | [-13] 884 | [-12] 885 | [-11] 886 | [-10] 887 | [-9] 888 | [-8] 889 | [-7] 890 | [-6] 891 | [-5] 892 | [-4] 893 | [-3] 894 | [-2] 895 | [-1] 896 | [0]><><><><><><><><><><{klklkl}{1,2,3} 901 | [{klklkl}1] 902 | [{klklkl}2] 903 | [{klklkl}3]><><><><{1..10..2} 904 | [1] 905 | [3] 906 | [5] 907 | [7] 908 | [9]><><><><{-1..-10..2} 909 | [-1] 910 | [-3] 911 | [-5] 912 | [-7] 913 | [-9]><><><><{-1..-10..-2} 914 | [-1] 915 | [-3] 916 | [-5] 917 | [-7] 918 | [-9]><><><><{10..1..-2} 919 | [10] 920 | [8] 921 | [6] 922 | [4] 923 | [2]><><><><{10..1..2} 924 | [10] 925 | [8] 926 | [6] 927 | [4] 928 | [2]><><><><{1..20..2} 929 | [1] 930 | [3] 931 | [5] 932 | [7] 933 | [9] 934 | [11] 935 | [13] 936 | [15] 937 | [17] 938 | [19]><><><><{1..20..20} 939 | [1]><><><><{100..0..5} 940 | [100] 941 | [95] 942 | [90] 943 | [85] 944 | [80] 945 | [75] 946 | [70] 947 | [65] 948 | [60] 949 | [55] 950 | [50] 951 | [45] 952 | [40] 953 | [35] 954 | [30] 955 | [25] 956 | [20] 957 | [15] 958 | [10] 959 | [5] 960 | [0]><><><><{100..0..-5} 961 | [100] 962 | [95] 963 | [90] 964 | [85] 965 | [80] 966 | [75] 967 | [70] 968 | [65] 969 | [60] 970 | [55] 971 | [50] 972 | [45] 973 | [40] 974 | [35] 975 | [30] 976 | [25] 977 | [20] 978 | [15] 979 | [10] 980 | [5] 981 | [0]><><><><{a..z} 982 | [a] 983 | [b] 984 | [c] 985 | [d] 986 | [e] 987 | [f] 988 | [g] 989 | [h] 990 | [i] 991 | [j] 992 | [k] 993 | [l] 994 | [m] 995 | [n] 996 | [o] 997 | [p] 998 | [q] 999 | [r] 1000 | [s] 1001 | [t] 1002 | [u] 1003 | [v] 1004 | [w] 1005 | [x] 1006 | [y] 1007 | [z]><><><><{a..z..2} 1008 | [a] 1009 | [c] 1010 | [e] 1011 | [g] 1012 | [i] 1013 | [k] 1014 | [m] 1015 | [o] 1016 | [q] 1017 | [s] 1018 | [u] 1019 | [w] 1020 | [y]><><><><{z..a..-2} 1021 | [z] 1022 | [x] 1023 | [v] 1024 | [t] 1025 | [r] 1026 | [p] 1027 | [n] 1028 | [l] 1029 | [j] 1030 | [h] 1031 | [f] 1032 | [d] 1033 | [b]><><><><{2147483645..2147483649} 1034 | [2147483645] 1035 | [2147483646] 1036 | [2147483647] 1037 | [2147483648] 1038 | [2147483649]><><><><{10..0..2} 1039 | [10] 1040 | [8] 1041 | [6] 1042 | [4] 1043 | [2] 1044 | [0]><><><><{10..0..-2} 1045 | [10] 1046 | [8] 1047 | [6] 1048 | [4] 1049 | [2] 1050 | [0]><><><><{-50..-0..5} 1051 | [-50] 1052 | [-45] 1053 | [-40] 1054 | [-35] 1055 | [-30] 1056 | [-25] 1057 | [-20] 1058 | [-15] 1059 | [-10] 1060 | [-5] 1061 | [0]><><><><{1..10.f} 1062 | [{1..10.f}]><><><><{1..ff} 1063 | [{1..ff}]><><><><{1..10..ff} 1064 | [{1..10..ff}]><><><><{1.20..2} 1065 | [{1.20..2}]><><><><{1..20..f2} 1066 | [{1..20..f2}]><><><><{1..20..2f} 1067 | [{1..20..2f}]><><><><{1..2f..2} 1068 | [{1..2f..2}]><><><><{1..ff..2} 1069 | [{1..ff..2}]><><><><{1..ff} 1070 | [{1..ff}]><><><><{1..f} 1071 | [{1..f}]><><><><{1..0f} 1072 | [{1..0f}]><><><><{1..10f} 1073 | [{1..10f}]><><><><{1..10.f} 1074 | [{1..10.f}]><><><><{},b}.h 1075 | [{},b}.h]><><><><><><><{}{},a}b 1078 | [{}}b] 1079 | [{}ab]><><><><{{},a}}b 1080 | [{}}b] 1081 | [a}b]><><><><{}{{},a}}b 1082 | [{}{}}b] 1083 | [{}a}b]><><><><{}a,b}c 1084 | [{}a,b}c]><><><>< -------------------------------------------------------------------------------- /test/cases.txt: -------------------------------------------------------------------------------- 1 | # skip quotes for now 2 | # "{x,x}" 3 | # {"x,x"} 4 | # {x","x} 5 | # '{a,b}{{a,b},a,b}' 6 | A{b,{d,e},{f,g}}Z 7 | PRE-{a,b}{{a,b},a,b}-POST 8 | \\{a,b}{{a,b},a,b} 9 | {{a,b} 10 | {a,b}} 11 | {,} 12 | a{,} 13 | {,}b 14 | a{,}b 15 | a{b}c 16 | a{1..5}b 17 | a{01..5}b 18 | a{-01..5}b 19 | a{-01..5..3}b 20 | a{001..9}b 21 | a{b,c{d,e},{f,g}h}x{y,z 22 | a{b,c{d,e},{f,g}h}x{y,z\\} 23 | a{b,c{d,e},{f,g}h}x{y,z} 24 | a{b{c{d,e}f{x,y{{g}h 25 | a{b{c{d,e}f{x,y{}g}h 26 | a{b{c{d,e}f{x,y}}g}h 27 | a{b{c{d,e}f}g}h 28 | a{{x,y},z}b 29 | f{x,y{g,z}}h 30 | f{x,y{{g,z}}h 31 | f{x,y{{g,z}}h} 32 | f{x,y{{g}h 33 | f{x,y{{g}}h 34 | f{x,y{}g}h 35 | z{a,b{,c}d 36 | z{a,b},c}d 37 | {-01..5} 38 | {-05..100..5} 39 | {-05..100} 40 | {0..5..2} 41 | {0001..05..2} 42 | {0001..-5..2} 43 | {0001..-5..-2} 44 | {0001..5..-2} 45 | {01..5} 46 | {1..05} 47 | {1..05..3} 48 | {05..100} 49 | {0a..0z} 50 | {a,b\\}c,d} 51 | {a,b{c,d} 52 | {a,b}c,d} 53 | {a..F} 54 | {A..f} 55 | {a..Z} 56 | {A..z} 57 | {z..A} 58 | {Z..a} 59 | {a..F..2} 60 | {A..f..02} 61 | {a..Z..5} 62 | d{a..Z..5}b 63 | {A..z..10} 64 | {z..A..-2} 65 | {Z..a..20} 66 | {a{,b} 67 | {a},b} 68 | {x,y{,}g} 69 | {x,y{}g} 70 | {{a,b} 71 | {{a,b},c} 72 | {{a,b}c} 73 | {{a,b},} 74 | X{{a,b},}X 75 | {{a,b},}c 76 | {{a,b}.} 77 | {{a,b}} 78 | X{a..#}X 79 | # this next one is an empty string 80 | 81 | {-10..00} 82 | # Need to escape slashes in here for reasons i guess. 83 | {a,\\\\{a,b}c} 84 | {a,\\{a,b}c} 85 | a,\\{b,c} 86 | {-10.\\.00} 87 | #### bash tests/braces.tests 88 | # Note that some tests are edited out because some features of 89 | # bash are intentionally not supported in this brace expander. 90 | ff{c,b,a} 91 | f{d,e,f}g 92 | {l,n,m}xyz 93 | {abc\\,def} 94 | {abc} 95 | {x\\,y,\\{abc\\},trie} 96 | # not impementing back-ticks obviously 97 | # XXXX\\{`echo a b c | tr ' ' ','`\\} 98 | {} 99 | # We only ever have to worry about parsing a single argument, 100 | # not a command line, so spaces have a different meaning than bash. 101 | # { } 102 | } 103 | { 104 | abcd{efgh 105 | # spaces 106 | # foo {1,2} bar 107 | # not impementing back-ticks obviously 108 | # `zecho foo {1,2} bar` 109 | # $(zecho foo {1,2} bar) 110 | # ${var} is not a variable here, like it is in bash. omit. 111 | # foo{bar,${var}.} 112 | # foo{bar,${var}} 113 | # isaacs: skip quotes for now 114 | # "${var}"{x,y} 115 | # $var{x,y} 116 | # ${var}{x,y} 117 | # new sequence brace operators 118 | {1..10} 119 | # this doesn't work yet 120 | {0..10,braces} 121 | # but this does 122 | {{0..10},braces} 123 | x{{0..10},braces}y 124 | {3..3} 125 | x{3..3}y 126 | {10..1} 127 | {10..1}y 128 | x{10..1}y 129 | {a..f} 130 | {f..a} 131 | {a..A} 132 | {A..a} 133 | {f..f} 134 | # mixes are incorrectly-formed brace expansions 135 | {1..f} 136 | {f..1} 137 | # spaces 138 | # 0{1..9} {10..20} 139 | # do negative numbers work? 140 | {-1..-10} 141 | {-20..0} 142 | # weirdly-formed brace expansions -- fixed in post-bash-3.1 143 | a-{b{d,e}}-c 144 | a-{bdef-{g,i}-c 145 | # isaacs: skip quotes for now 146 | # {"klklkl"}{1,2,3} 147 | # isaacs: this is a valid test, though 148 | {klklkl}{1,2,3} 149 | # {"x,x"} 150 | {1..10..2} 151 | {-1..-10..2} 152 | {-1..-10..-2} 153 | {10..1..-2} 154 | {10..1..2} 155 | {1..20..2} 156 | {1..20..20} 157 | {100..0..5} 158 | {100..0..-5} 159 | {a..z} 160 | {a..z..2} 161 | {z..a..-2} 162 | # make sure brace expansion handles ints > 2**31 - 1 using intmax_t 163 | {2147483645..2147483649} 164 | # unwanted zero-padding -- fixed post-bash-4.0 165 | {10..0..2} 166 | {10..0..-2} 167 | {-50..-0..5} 168 | # bad 169 | {1..10.f} 170 | {1..ff} 171 | {1..10..ff} 172 | {1.20..2} 173 | {1..20..f2} 174 | {1..20..2f} 175 | {1..2f..2} 176 | {1..ff..2} 177 | {1..ff} 178 | {1..f} 179 | {1..0f} 180 | {1..10f} 181 | {1..10.f} 182 | {},b}.h 183 | y{},a}x 184 | {}{},a}b 185 | {{},a}}b 186 | {}{{},a}}b 187 | {}a,b}c 188 | -------------------------------------------------------------------------------- /test/dollar.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-template-curly-in-string */ 2 | 3 | import test from 'node:test' 4 | import assert from 'assert' 5 | import expand from '../index.js' 6 | 7 | test('ignores ${', function () { 8 | assert.deepStrictEqual(expand('${1..3}'), ['${1..3}']) 9 | assert.deepStrictEqual(expand('${a,b}${c,d}'), ['${a,b}${c,d}']) 10 | assert.deepStrictEqual(expand('${a,b}${c,d}{e,f}'), ['${a,b}${c,d}e', '${a,b}${c,d}f']) 11 | assert.deepStrictEqual(expand('{a,b}${c,d}${e,f}'), ['a${c,d}${e,f}', 'b${c,d}${e,f}']) 12 | assert.deepStrictEqual(expand('${a,b}${c,d}{1..3}'), ['${a,b}${c,d}1', '${a,b}${c,d}2', '${a,b}${c,d}3']) 13 | assert.deepStrictEqual(expand('x${a,b}x${c,d}x'), ['x${a,b}x${c,d}x']) 14 | }) 15 | -------------------------------------------------------------------------------- /test/empty-option.js: -------------------------------------------------------------------------------- 1 | import test from 'node:test' 2 | import assert from 'assert' 3 | import expand from '../index.js' 4 | 5 | test('empty option', function () { 6 | assert.deepStrictEqual(expand('-v{,,,,}'), [ 7 | '-v', '-v', '-v', '-v', '-v' 8 | ]) 9 | }) 10 | -------------------------------------------------------------------------------- /test/generate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # Bash 4.3 because of arbitrary need to pick a single standard. 6 | 7 | if [ "${BASH_VERSINFO[0]}" != "4" ] || [ "${BASH_VERSINFO[1]}" != "3" ]; then 8 | echo "this script requires bash 4.3" >&2 9 | exit 1 10 | fi 11 | 12 | CDPATH= cd "$(dirname "$0")" 13 | 14 | js='require("./")(process.argv[1]).join(" ")' 15 | 16 | cat cases.txt | \ 17 | while read case; do 18 | if [ "${case:0:1}" = "#" ]; then 19 | continue; 20 | fi; 21 | b="$($BASH -c 'for c in '"$case"'; do echo ["$c"]; done')" 22 | echo "$case" 23 | echo -n "$b><><><><"; 24 | done > bash-results.txt 25 | -------------------------------------------------------------------------------- /test/negative-increment.js: -------------------------------------------------------------------------------- 1 | import test from 'node:test' 2 | import assert from 'assert' 3 | import expand from '../index.js' 4 | 5 | test('negative increment', function () { 6 | assert.deepStrictEqual(expand('{3..1}'), ['3', '2', '1']) 7 | assert.deepStrictEqual(expand('{10..8}'), ['10', '9', '8']) 8 | assert.deepStrictEqual(expand('{10..08}'), ['10', '09', '08']) 9 | assert.deepStrictEqual(expand('{c..a}'), ['c', 'b', 'a']) 10 | 11 | assert.deepStrictEqual(expand('{4..0..2}'), ['4', '2', '0']) 12 | assert.deepStrictEqual(expand('{4..0..-2}'), ['4', '2', '0']) 13 | assert.deepStrictEqual(expand('{e..a..2}'), ['e', 'c', 'a']) 14 | }) 15 | -------------------------------------------------------------------------------- /test/nested.js: -------------------------------------------------------------------------------- 1 | import test from 'node:test' 2 | import assert from 'assert' 3 | import expand from '../index.js' 4 | 5 | test('nested', function () { 6 | assert.deepStrictEqual(expand('{a,b{1..3},c}'), [ 7 | 'a', 'b1', 'b2', 'b3', 'c' 8 | ]) 9 | assert.deepStrictEqual(expand('{{A..Z},{a..z}}'), 10 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('') 11 | ) 12 | assert.deepStrictEqual(expand('ppp{,config,oe{,conf}}'), [ 13 | 'ppp', 'pppconfig', 'pppoe', 'pppoeconf' 14 | ]) 15 | }) 16 | -------------------------------------------------------------------------------- /test/order.js: -------------------------------------------------------------------------------- 1 | import test from 'node:test' 2 | import assert from 'assert' 3 | import expand from '../index.js' 4 | 5 | test('order', function () { 6 | assert.deepStrictEqual(expand('a{d,c,b}e'), [ 7 | 'ade', 'ace', 'abe' 8 | ]) 9 | }) 10 | -------------------------------------------------------------------------------- /test/pad.js: -------------------------------------------------------------------------------- 1 | import test from 'node:test' 2 | import assert from 'assert' 3 | import expand from '../index.js' 4 | 5 | test('pad', function () { 6 | assert.deepStrictEqual(expand('{9..11}'), [ 7 | '9', '10', '11' 8 | ]) 9 | assert.deepStrictEqual(expand('{09..11}'), [ 10 | '09', '10', '11' 11 | ]) 12 | }) 13 | -------------------------------------------------------------------------------- /test/same-type.js: -------------------------------------------------------------------------------- 1 | import test from 'node:test' 2 | import assert from 'assert' 3 | import expand from '../index.js' 4 | 5 | test('x and y of same type', function () { 6 | assert.deepStrictEqual(expand('{a..9}'), ['{a..9}']) 7 | }) 8 | -------------------------------------------------------------------------------- /test/sequence.js: -------------------------------------------------------------------------------- 1 | import test from 'node:test' 2 | import assert from 'assert' 3 | import expand from '../index.js' 4 | 5 | test('numeric sequences', function () { 6 | assert.deepStrictEqual(expand('a{1..2}b{2..3}c'), [ 7 | 'a1b2c', 'a1b3c', 'a2b2c', 'a2b3c' 8 | ]) 9 | assert.deepStrictEqual(expand('{1..2}{2..3}'), [ 10 | '12', '13', '22', '23' 11 | ]) 12 | }) 13 | 14 | test('numeric sequences with step count', function () { 15 | assert.deepStrictEqual(expand('{0..8..2}'), [ 16 | '0', '2', '4', '6', '8' 17 | ]) 18 | assert.deepStrictEqual(expand('{1..8..2}'), [ 19 | '1', '3', '5', '7' 20 | ]) 21 | }) 22 | 23 | test('numeric sequence with negative x / y', function () { 24 | assert.deepStrictEqual(expand('{3..-2}'), [ 25 | '3', '2', '1', '0', '-1', '-2' 26 | ]) 27 | }) 28 | 29 | test('alphabetic sequences', function () { 30 | assert.deepStrictEqual(expand('1{a..b}2{b..c}3'), [ 31 | '1a2b3', '1a2c3', '1b2b3', '1b2c3' 32 | ]) 33 | assert.deepStrictEqual(expand('{a..b}{b..c}'), [ 34 | 'ab', 'ac', 'bb', 'bc' 35 | ]) 36 | }) 37 | 38 | test('alphabetic sequences with step count', function () { 39 | assert.deepStrictEqual(expand('{a..k..2}'), [ 40 | 'a', 'c', 'e', 'g', 'i', 'k' 41 | ]) 42 | assert.deepStrictEqual(expand('{b..k..2}'), [ 43 | 'b', 'd', 'f', 'h', 'j' 44 | ]) 45 | }) 46 | --------------------------------------------------------------------------------