diff --git a/.eslintrc.json b/.eslintrc.json index 24b8984..cf4d887 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,13 +1,12 @@ { - "extends": [ - "eslint:recommended" - ], + "root": true, + "extends": "eslint:recommended", "env": { "browser": false, "es6": true, - "node": true, - "mocha": true + "mocha": true, + "node": true }, "parserOptions":{ @@ -27,29 +26,52 @@ "rules": { "accessor-pairs": 2, - "arrow-spacing": [2, { "before": true, "after": true }], - "block-spacing": [2, "always"], - "brace-style": [2, "1tbs", { "allowSingleLine": true }], - "comma-dangle": [2, "never"], - "comma-spacing": [2, { "before": false, "after": true }], - "comma-style": [2, "last"], + "array-bracket-newline": [1, "consistent"], + "array-bracket-spacing": [1, "never"], + "array-callback-return": 1, + "array-element-newline": [1, "consistent"], + "arrow-body-style": 0, + "arrow-parens": [1, "as-needed"], + "arrow-spacing": [1, { "before": true, "after": true }], + "block-scoped-var": 1, + "block-spacing": [1, "always"], + "brace-style": [1, "1tbs", { "allowSingleLine": true }], + "callback-return": 0, + "camelcase": [0, { "allow": [] }], + "capitalized-comments": 0, + "class-methods-use-this": 0, + "comma-dangle": [1, "never"], + "comma-spacing": [1, { "before": false, "after": true }], + "comma-style": [1, "last"], + "computed-property-spacing": 1, + "consistent-return": 0, + "consistent-this": 1, "constructor-super": 2, - "curly": [2, "multi-line"], - "dot-location": [2, "property"], - "eol-last": 2, - "eqeqeq": [2, "allow-null"], - "generator-star-spacing": [2, { "before": true, "after": true }], - "handle-callback-err": [2, "^(err|error)$" ], - "indent": [2, 2, { "SwitchCase": 1 }], - "key-spacing": [2, { "beforeColon": false, "afterColon": true }], - "keyword-spacing": [2, { "before": true, "after": true }], - "new-cap": [2, { "newIsCap": true, "capIsNew": false }], + "curly": [1, "multi-line", "consistent"], + "default-case": 1, + "dot-location": [1, "property"], + "dot-notation": 1, + "eol-last": 1, + "eqeqeq": [1, "allow-null"], + "for-direction": 1, + "func-call-spacing": 2, + "generator-star-spacing": [1, { "before": true, "after": true }], + "handle-callback-err": [2, "^(err|error)$"], + "indent": [1, 2, { "SwitchCase": 1 }], + "key-spacing": [1, { "beforeColon": false, "afterColon": true }], + "keyword-spacing": [1, { "before": true, "after": true }], + "linebreak-style": [1, "unix"], + "new-cap": [1, { "newIsCap": true, "capIsNew": false }], "new-parens": 2, - "no-array-constructor": 2, + "no-alert": 1, + "no-array-constructor": 1, + "no-async-promise-executor": 1, "no-caller": 2, + "no-case-declarations": 1, "no-class-assign": 2, "no-cond-assign": 2, "no-const-assign": 2, + "no-constant-condition": [1, { "checkLoops": false }], "no-control-regex": 2, "no-debugger": 2, "no-delete-var": 2, @@ -57,74 +79,137 @@ "no-dupe-class-members": 2, "no-dupe-keys": 2, "no-duplicate-case": 2, + "no-duplicate-imports": 0, + "no-else-return": 0, "no-empty-character-class": 2, - "no-eval": 2, + "no-empty-function": 0, + "no-empty-pattern": 0, + "no-empty": [1, { "allowEmptyCatch": true }], + "no-eval": 0, "no-ex-assign": 2, "no-extend-native": 2, - "no-extra-bind": 2, - "no-extra-boolean-cast": 2, - "no-extra-parens": [2, "functions"], + "no-extra-bind": 1, + "no-extra-boolean-cast": 1, + "no-extra-label": 1, + "no-extra-parens": [1, "all", + { + "conditionalAssign": false, + "returnAssign": false, + "nestedBinaryExpressions": false, + "ignoreJSX": "multi-line", + "enforceForArrowConditionals": false + } + ], + "no-extra-semi": 1, "no-fallthrough": 2, "no-floating-decimal": 2, "no-func-assign": 2, + "no-global-assign": 2, + "no-implicit-coercion": 2, + "no-implicit-globals": 1, "no-implied-eval": 2, - "no-inner-declarations": [2, "functions"], + "no-inner-declarations": [1, "functions"], "no-invalid-regexp": 2, + "no-invalid-this": 1, "no-irregular-whitespace": 2, "no-iterator": 2, "no-label-var": 2, "no-labels": 2, "no-lone-blocks": 2, + "no-lonely-if": 2, + "no-loop-func": 1, + "no-mixed-requires": 1, "no-mixed-spaces-and-tabs": 2, - "no-multi-spaces": 2, + "no-multi-assign": 0, + "no-multi-spaces": 1, "no-multi-str": 2, - "no-multiple-empty-lines": [2, { "max": 1 }], - "no-native-reassign": 0, + "no-multiple-empty-lines": [1, { "max": 1 }], + "no-native-reassign": 2, + "no-negated-condition": 0, "no-negated-in-lhs": 2, - "no-new": 2, "no-new-func": 2, "no-new-object": 2, "no-new-require": 2, + "no-new-symbol": 1, "no-new-wrappers": 2, + "no-new": 1, "no-obj-calls": 2, - "no-octal": 2, "no-octal-escape": 2, - "no-proto": 0, + "no-octal": 2, + "no-path-concat": 1, + "no-proto": 2, + "no-prototype-builtins": 0, "no-redeclare": 2, "no-regex-spaces": 2, - "no-return-assign": 2, - "no-self-compare": 2, + "no-restricted-globals": 2, + "no-return-assign": 1, + "no-return-await": 2, + "no-script-url": 1, + "no-self-assign": 1, + "no-self-compare": 1, "no-sequences": 2, "no-shadow-restricted-names": 2, + "no-shadow": 0, "no-spaced-func": 2, "no-sparse-arrays": 2, + "no-template-curly-in-string": 0, "no-this-before-super": 2, "no-throw-literal": 2, - "no-trailing-spaces": 0, - "no-undef": 2, + "no-trailing-spaces": 1, "no-undef-init": 2, + "no-undef": 2, "no-unexpected-multiline": 2, - "no-unneeded-ternary": [2, { "defaultAssignment": false }], + "no-unneeded-ternary": [1, { "defaultAssignment": false }], + "no-unreachable-loop": 1, "no-unreachable": 2, - "no-unused-vars": [2, { "vars": "all", "args": "none" }], - "no-useless-call": 0, + "no-unsafe-assignment": 0, + "no-unsafe-call": 0, + "no-unsafe-finally": 2, + "no-unsafe-member-access": 0, + "no-unsafe-negation": 2, + "no-unsafe-optional-chaining": 0, + "no-unsafe-return": 0, + "no-unused-expressions": 2, + "no-unused-vars": [1, { "vars": "all", "args": "after-used" }], + "no-use-before-define": 0, + "no-useless-call": 2, + "no-useless-catch": 0, + "no-useless-escape": 0, + "no-useless-rename": 1, + "no-useless-return": 1, + "no-var": 1, + "no-void": 1, + "no-warning-comments": 0, "no-with": 2, - "one-var": [0, { "initialized": "never" }], + "object-curly-spacing": [1, "always", { "objectsInObjects": true }], + "object-shorthand": 1, + "one-var": [1, { "initialized": "never" }], "operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }], - "padded-blocks": [0, "never"], - "quotes": [2, "single", "avoid-escape"], + "padded-blocks": [1, { "switches": "never" }], + "prefer-const": [1, { "destructuring": "all", "ignoreReadBeforeAssign": false }], + "prefer-promise-reject-errors": 1, + "quotes": [1, "single", "avoid-escape"], "radix": 2, - "semi": [2, "always"], - "semi-spacing": [2, { "before": false, "after": true }], - "space-before-blocks": [2, "always"], - "space-before-function-paren": [2, "never"], - "space-in-parens": [2, "never"], - "space-infix-ops": 2, - "space-unary-ops": [2, { "words": true, "nonwords": false }], + "rest-spread-spacing": 1, + "semi-spacing": [1, { "before": false, "after": true }], + "semi-style": 1, + "semi": [1, "always"], + "space-before-blocks": [1, "always"], + "space-before-function-paren": [1, { "anonymous": "never", "named": "never", "asyncArrow": "always" }], + "space-in-parens": [1, "never"], + "space-infix-ops": 1, + "space-unary-ops": [1, { "words": true, "nonwords": false }], "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], + "strict": 2, + "switch-colon-spacing": 1, + "symbol-description": 1, + "template-curly-spacing": [2, "never"], + "template-tag-spacing": [2, "never"], + "unicode-bom": 1, "use-isnan": 2, + "valid-jsdoc": 1, "valid-typeof": 2, - "wrap-iife": [2, "any"], - "yoda": [2, "never"] + "wrap-iife": [1, "any"], + "yoda": [1, "never"] } } diff --git a/.travis.yml b/.travis.yml index 1686664..47f9bcc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,9 @@ -sudo: false os: - linux - osx + - windows language: node_js node_js: - node - - '9' + - '10' - '8' - - '7' - - '6' - - '5' - - '4' - - '0.12' - - '0.10' diff --git a/.verb.md b/.verb.md index e21679b..2ee6767 100644 --- a/.verb.md +++ b/.verb.md @@ -1,64 +1,66 @@ + +## v3.0.0 Released!! + +See the [changelog](CHANGELOG.md) for details. + ## Why use braces? -Brace patterns are great for matching ranges. Users (and implementors) shouldn't have to think about whether or not they will break their application (or yours) from accidentally defining an aggressive brace pattern. _Braces is the only library that offers a [solution to this problem](#performance)_. +Brace patterns make globs more powerful by adding the ability to match specific ranges and sequences of characters. + +- **Accurate** - complete support for the [Bash 4.3 Brace Expansion][bash] specification (passes all of the Bash braces tests) +- **[fast and performant](#benchmarks)** - Starts fast, runs fast and [scales well](#performance) as patterns increase in complexity. +- **Organized code base** - The parser and compiler are easy to maintain and update when edge cases crop up. +- **Well-tested** - Thousands of test assertions, and passes all of the Bash, minimatch, and [brace-expansion][] unit tests (as of the date this was written). +- **Safer** - You shouldn't have to worry about users defining aggressive or malicious brace patterns that can break your application. Braces takes measures to prevent malicious regex that can be used for DDoS attacks (see [catastrophic backtracking](https://www.regular-expressions.info/catastrophic.html)). +- [Supports lists](#lists) - (aka "sets") `a/{b,c}/d` => `['a/b/d', 'a/c/d']` +- [Supports sequences](#sequences) - (aka "ranges") `{01..03}` => `['01', '02', '03']` +- [Supports steps](#steps) - (aka "increments") `{2..10..2}` => `['2', '4', '6', '8', '10']` +- [Supports escaping](#escaping) - To prevent evaluation of special characters. -- **Safe(r)**: Braces isn't vulnerable to DoS attacks like [brace-expansion][], [minimatch][] and [multimatch][] (a different bug than the [other regex DoS bug][bug]). -- **Accurate**: complete support for the [Bash 4.3 Brace Expansion][bash] specification (passes all of the Bash braces tests) -- **[fast and performant](#benchmarks)**: Starts fast, runs fast and [scales well](#performance) as patterns increase in complexity. -- **Organized code base**: with parser and compiler that are eas(y|ier) to maintain and update when edge cases crop up. -- **Well-tested**: thousands of test assertions. Passes 100% of the [minimatch][] and [brace-expansion][] unit tests as well (as of the writing of this). ## Usage The main export is a function that takes one or more brace `patterns` and `options`. ```js -var braces = require('braces'); -braces(pattern[, options]); -``` +const braces = require('braces'); +// braces(patterns[, options]); -By default, braces returns an optimized regex-source string. To get an array of brace patterns, use `brace.expand()`. +console.log(braces(['{01..05}', '{a..e}'])); +//=> ['(0[1-5])', '([a-e])'] -The following section explains the difference in more detail. _(If you're curious about "why" braces does this by default, see [brace matching pitfalls](#brace-matching-pitfalls)_. +console.log(braces(['{01..05}', '{a..e}'], { expand: true })); +//=> ['01', '02', '03', '04', '05', 'a', 'b', 'c', 'd', 'e'] +``` -### Optimized vs. expanded braces +### Brace Expansion vs. Compilation -**Optimized** +By default, brace patterns are compiled into strings that are optimized for creating regular expressions and matching. -By default, patterns are optimized for regex and matching: +**Compiled** ```js -console.log(braces('a/{x,y,z}/b')); +console.log(braces('a/{x,y,z}/b')); //=> ['a/(x|y|z)/b'] +console.log(braces(['a/{01..20}/b', 'a/{1..5}/b'])); +//=> [ 'a/(0[1-9]|1[0-9]|20)/b', 'a/([1-5])/b' ] ``` **Expanded** -To expand patterns the same way as Bash or [minimatch](https://github.com/isaacs/minimatch), use the [.expand](#expand) method: +Enable brace expansion by setting the `expand` option to true, or by using [braces.expand()](#expand) (returns an array similar to what you'd expect from Bash, or `echo {1..5}`, or [minimatch](https://github.com/isaacs/minimatch)): ```js -console.log(braces.expand('a/{x,y,z}/b')); +console.log(braces('a/{x,y,z}/b', { expand: true })); //=> ['a/x/b', 'a/y/b', 'a/z/b'] -``` - -Or use [options.expand](#optionsexpand): -```js -console.log(braces('a/{x,y,z}/b', {expand: true})); -//=> ['a/x/b', 'a/y/b', 'a/z/b'] +console.log(braces.expand('{01..10}')); +//=> ['01','02','03','04','05','06','07','08','09','10'] ``` -## Features - -* [lists](#lists): Supports "lists": `a/{b,c}/d` => `['a/b/d', 'a/c/d']` -* [sequences](#sequences): Supports alphabetical or numerical "sequences" (ranges): `{1..3}` => `['1', '2', '3']` -* [steps](#steps): Supports "steps" or increments: `{2..10..2}` => `['2', '4', '6', '8', '10']` -* [escaping](#escaping) -* [options](#options) - ### Lists -Uses [fill-range](https://github.com/jonschlinkert/fill-range) for expanding alphabetical or numeric lists: +Expand lists (like Bash "sets"): ```js console.log(braces('a/{foo,bar,baz}/*.js')); @@ -70,21 +72,23 @@ console.log(braces.expand('a/{foo,bar,baz}/*.js')); ### Sequences -Uses [fill-range](https://github.com/jonschlinkert/fill-range) for expanding alphabetical or numeric ranges (bash "sequences"): +Expand ranges of characters (like Bash "sequences"): ```js -console.log(braces.expand('{1..3}')); // ['1', '2', '3'] -console.log(braces.expand('a{01..03}b')); // ['a01b', 'a02b', 'a03b'] -console.log(braces.expand('a{1..3}b')); // ['a1b', 'a2b', 'a3b'] -console.log(braces.expand('{a..c}')); // ['a', 'b', 'c'] -console.log(braces.expand('foo/{a..c}')); // ['foo/a', 'foo/b', 'foo/c'] +console.log(braces.expand('{1..3}')); // ['1', '2', '3'] +console.log(braces.expand('a/{1..3}/b')); // ['a/1/b', 'a/2/b', 'a/3/b'] +console.log(braces('{a..c}', { expand: true })); // ['a', 'b', 'c'] +console.log(braces('foo/{a..c}', { expand: true })); // ['foo/a', 'foo/b', 'foo/c'] -// supports padded ranges -console.log(braces('a{01..03}b')); //=> [ 'a(0[1-3])b' ] -console.log(braces('a{001..300}b')); //=> [ 'a(0{2}[1-9]|0[1-9][0-9]|[12][0-9]{2}|300)b' ] +// supports zero-padded ranges +console.log(braces('a/{01..03}/b')); //=> ['a/(0[1-3])/b'] +console.log(braces('a/{001..300}/b')); //=> ['a/(0{2}[1-9]|0[1-9][0-9]|[12][0-9]{2}|300)/b'] ``` -### Steps +See [fill-range](https://github.com/jonschlinkert/fill-range) for all available range-expansion options. + + +### Steppped ranges Steps, or increments, may be used with ranges: @@ -163,7 +167,7 @@ console.log(braces.expand('a{b}c')); **Type**: `Number` -**Default**: `65,536` +**Default**: `10,000` **Description**: Limit the length of the input string. Useful when the input string is generated or your application allows users to pass a string, et cetera. @@ -177,43 +181,31 @@ console.log(braces('a/{b,c}/d', { maxLength: 3 })); //=> throws an error **Default**: `undefined` -**Description**: Generate an "expanded" brace pattern (this option is unncessary with the `.expand` method, which does the same thing). +**Description**: Generate an "expanded" brace pattern (alternatively you can use the `braces.expand()` method, which does the same thing). ```js -console.log(braces('a/{b,c}/d', {expand: true})); +console.log(braces('a/{b,c}/d', { expand: true })); //=> [ 'a/b/d', 'a/c/d' ] ``` -### options.optimize - -**Type**: `Boolean` - -**Default**: `true` - -**Description**: Enabled by default. - -```js -console.log(braces('a/{b,c}/d')); -//=> [ 'a/(b|c)/d' ] -``` - ### options.nodupes **Type**: `Boolean` -**Default**: `true` +**Default**: `undefined` + +**Description**: Remove duplicates from the returned array. -**Description**: Duplicates are removed by default. To keep duplicates, pass `{nodupes: false}` on the options ### options.rangeLimit **Type**: `Number` -**Default**: `250` +**Default**: `1000` -**Description**: When `braces.expand()` is used, or `options.expand` is true, brace patterns will automatically be [optimized](#optionsoptimize) when the difference between the range minimum and range maximum exceeds the `rangeLimit`. This is to prevent huge ranges from freezing your application. +**Description**: To prevent malicious patterns from being passed by users, an error is thrown when `braces.expand()` is used or `options.expand` is true and the generated range will exceed the `rangeLimit`. -You can set this to any number, or change `options.rangeLimit` to `Inifinity` to disable this altogether. +You can customize `options.rangeLimit` or set it to `Inifinity` to disable this altogether. **Examples** @@ -235,17 +227,33 @@ console.log(braces.expand('{1..100}')); **Description**: Customize range expansion. +**Example: Transforming non-numeric values** + ```js -var range = braces.expand('x{a..e}y', { - transform: function(str) { - return 'foo' + str; +const alpha = braces.expand('x/{a..e}/y', { + transform(value, index) { + // When non-numeric values are passed, "value" is a character code. + return 'foo/' + String.fromCharCode(value) + '-' + index; } }); +console.log(alpha); +//=> [ 'x/foo/a-0/y', 'x/foo/b-1/y', 'x/foo/c-2/y', 'x/foo/d-3/y', 'x/foo/e-4/y' ] +``` + +**Example: Transforming numeric values** -console.log(range); -//=> [ 'xfooay', 'xfooby', 'xfoocy', 'xfoody', 'xfooey' ] +```js +const numeric = braces.expand('{1..5}', { + transform(value) { + // when numeric values are passed, "value" is a number + return 'foo/' + value * 2; + } +}); +console.log(numeric); +//=> [ 'foo/2', 'foo/4', 'foo/6', 'foo/8', 'foo/10' ] ``` + ### options.quantifiers **Type**: `Boolean` @@ -258,10 +266,11 @@ Unfortunately, regex quantifiers happen to share the same syntax as [Bash lists] The `quantifiers` option tells braces to detect when [regex quantifiers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#quantifiers) are defined in the given pattern, and not to try to expand them as lists. + **Examples** ```js -var braces = require('braces'); +const braces = require('braces'); console.log(braces('a/b{1,3}/{x,y,z}')); //=> [ 'a/b(1|3)/(x|y|z)' ] console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: true})); @@ -482,72 +491,40 @@ npm i -d && npm benchmark ### Latest results -```bash -Benchmarking: (8 of 8) - · combination-nested - · combination - · escaped - · list-basic - · list-multiple - · no-braces - · sequence-basic - · sequence-multiple - -# benchmark/fixtures/combination-nested.js (52 bytes) - brace-expansion x 4,756 ops/sec ±1.09% (86 runs sampled) - braces x 11,202,303 ops/sec ±1.06% (88 runs sampled) - minimatch x 4,816 ops/sec ±0.99% (87 runs sampled) - - fastest is braces - -# benchmark/fixtures/combination.js (51 bytes) - brace-expansion x 625 ops/sec ±0.87% (87 runs sampled) - braces x 11,031,884 ops/sec ±0.72% (90 runs sampled) - minimatch x 637 ops/sec ±0.84% (88 runs sampled) - - fastest is braces +Braces is more accurate, without sacrificing performance. -# benchmark/fixtures/escaped.js (44 bytes) - brace-expansion x 163,325 ops/sec ±1.05% (87 runs sampled) - braces x 10,655,071 ops/sec ±1.22% (88 runs sampled) - minimatch x 147,495 ops/sec ±0.96% (88 runs sampled) - - fastest is braces - -# benchmark/fixtures/list-basic.js (40 bytes) - brace-expansion x 99,726 ops/sec ±1.07% (83 runs sampled) - braces x 10,596,584 ops/sec ±0.98% (88 runs sampled) - minimatch x 100,069 ops/sec ±1.17% (86 runs sampled) - - fastest is braces - -# benchmark/fixtures/list-multiple.js (52 bytes) - brace-expansion x 34,348 ops/sec ±1.08% (88 runs sampled) - braces x 9,264,131 ops/sec ±1.12% (88 runs sampled) - minimatch x 34,893 ops/sec ±0.87% (87 runs sampled) +```bash +# range (expanded) + braces x 29,040 ops/sec ±3.69% (91 runs sampled)) + minimatch x 4,735 ops/sec ±1.28% (90 runs sampled) - fastest is braces +# range (optimized for regex) + braces x 382,878 ops/sec ±0.56% (94 runs sampled) + minimatch x 1,040 ops/sec ±0.44% (93 runs sampled) -# benchmark/fixtures/no-braces.js (48 bytes) - brace-expansion x 275,368 ops/sec ±1.18% (89 runs sampled) - braces x 9,134,677 ops/sec ±0.95% (88 runs sampled) - minimatch x 3,755,954 ops/sec ±1.13% (89 runs sampled) +# nested ranges (expanded) + braces x 19,744 ops/sec ±2.27% (92 runs sampled)) + minimatch x 4,579 ops/sec ±0.50% (93 runs sampled) - fastest is braces +# nested ranges (optimized for regex) + braces x 246,019 ops/sec ±2.02% (93 runs sampled) + minimatch x 1,028 ops/sec ±0.39% (94 runs sampled) -# benchmark/fixtures/sequence-basic.js (41 bytes) - brace-expansion x 5,492 ops/sec ±1.35% (87 runs sampled) - braces x 8,485,034 ops/sec ±1.28% (89 runs sampled) - minimatch x 5,341 ops/sec ±1.17% (87 runs sampled) +# set (expanded) + braces x 138,641 ops/sec ±0.53% (95 runs sampled) + minimatch x 219,582 ops/sec ±0.98% (94 runs sampled) - fastest is braces +# set (optimized for regex) + braces x 388,408 ops/sec ±0.41% (95 runs sampled) + minimatch x 44,724 ops/sec ±0.91% (89 runs sampled) -# benchmark/fixtures/sequence-multiple.js (51 bytes) - brace-expansion x 116 ops/sec ±0.77% (77 runs sampled) - braces x 9,445,118 ops/sec ±1.32% (84 runs sampled) - minimatch x 109 ops/sec ±1.16% (76 runs sampled) +# nested sets (expanded) + braces x 84,966 ops/sec ±0.48% (94 runs sampled) + minimatch x 140,720 ops/sec ±0.37% (95 runs sampled) - fastest is braces +# nested sets (optimized for regex) + braces x 263,340 ops/sec ±2.06% (92 runs sampled) + minimatch x 28,714 ops/sec ±0.40% (90 runs sampled) ``` [^1]: this is the largest safe integer allowed in JavaScript. @@ -555,10 +532,9 @@ Benchmarking: (8 of 8) [bash]: www.gnu.org/software/bash/ [braces]: https://github.com/jonschlinkert/braces [brace-expansion]: https://github.com/juliangruber/brace-expansion -[expand-range]: https://github.com/jonschlinkert/expand-range [fill-range]: https://github.com/jonschlinkert/fill-range [micromatch]: https://github.com/jonschlinkert/micromatch [minimatch]: https://github.com/isaacs/minimatch [quantifiers]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#quantifiers [dos]: https://en.wikipedia.org/wiki/Denial-of-service_attack -[bug]: https://medium.com/node-security/minimatch-redos-vulnerability-590da24e6d3c#.jew0b6mpc +[bug]: https://medium.com/node-security/minimatch-redos-vulnerability-590da24e6d3c#.jew0b6mpc \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..36f798b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,184 @@ +# Release history + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +
+ Guiding Principles + +- Changelogs are for humans, not machines. +- There should be an entry for every single version. +- The same types of changes should be grouped. +- Versions and sections should be linkable. +- The latest version comes first. +- The release date of each versions is displayed. +- Mention whether you follow Semantic Versioning. + +
+ +
+ Types of changes + +Changelog entries are classified using the following labels _(from [keep-a-changelog](http://keepachangelog.com/)_): + +- `Added` for new features. +- `Changed` for changes in existing functionality. +- `Deprecated` for soon-to-be removed features. +- `Removed` for now removed features. +- `Fixed` for any bug fixes. +- `Security` in case of vulnerabilities. + +
+ +## [3.0.0] - 2018-04-08 + +v3.0 is a complete refactor, resulting in a faster, smaller codebase, with fewer deps, and a more accurate parser and compiler. + +**Breaking Changes** + +- The undocumented `.makeRe` method was removed + +**Non-breaking changes** + +- Caching was removed + +## [2.3.2] - 2018-04-08 + +- start refactoring +- cover sets +- better range handling + +## [2.3.1] - 2018-02-17 + +- Remove unnecessary escape in Regex. (#14) + +## [2.3.0] - 2017-10-19 + +- minor code reorganization +- optimize regex +- expose `maxLength` option + +## [2.2.1] - 2017-05-30 + +- don't condense when braces contain extglobs + +## [2.2.0] - 2017-05-28 + +- ensure word boundaries are preserved +- fixes edge case where extglob characters precede a brace pattern + +## [2.1.1] - 2017-04-27 + +- use snapdragon-node +- handle edge case +- optimizations, lint + +## [2.0.4] - 2017-04-11 + +- pass opts to compiler +- minor optimization in create method +- re-write parser handlers to remove negation regex + +## [2.0.3] - 2016-12-10 + +- use split-string +- clear queue at the end +- adds sequences example +- add unit tests + +## [2.0.2] - 2016-10-21 + +- fix comma handling in nested extglobs + +## [2.0.1] - 2016-10-20 + +- add comments +- more tests, ensure quotes are stripped + +## [2.0.0] - 2016-10-19 + +- don't expand braces inside character classes +- add quantifier pattern + +## [1.8.5] - 2016-05-21 + +- Refactor (#10) + +## [1.8.4] - 2016-04-20 + +- fixes https://github.com/jonschlinkert/micromatch/issues/66 + +## [1.8.0] - 2015-03-18 + +- adds exponent examples, tests +- fixes the first example in https://github.com/jonschlinkert/micromatch/issues/38 + +## [1.6.0] - 2015-01-30 + +- optimizations, `bash` mode: +- improve path escaping + +## [1.5.0] - 2015-01-28 + +- Merge pull request #5 from eush77/lib-files + +## [1.4.0] - 2015-01-24 + +- add extglob tests +- externalize exponent function +- better whitespace handling + +## [1.3.0] - 2015-01-24 + +- make regex patterns explicity + +## [1.1.0] - 2015-01-11 + +- don't create a match group with `makeRe` + +## [1.0.0] - 2014-12-23 + +- Merge commit '97b05f5544f8348736a8efaecf5c32bbe3e2ad6e' +- support empty brace syntax +- better bash coverage +- better support for regex strings + +## [0.1.4] - 2014-11-14 + +- improve recognition of bad args, recognize mismatched argument types +- support escaping +- remove pathname-expansion +- support whitespace in patterns + +## [0.1.0] + +- first commit + +[2.3.2]: https://github.com/micromatch/braces/compare/2.3.1...2.3.2 +[2.3.1]: https://github.com/micromatch/braces/compare/2.3.0...2.3.1 +[2.3.0]: https://github.com/micromatch/braces/compare/2.2.1...2.3.0 +[2.2.1]: https://github.com/micromatch/braces/compare/2.2.0...2.2.1 +[2.2.0]: https://github.com/micromatch/braces/compare/2.1.1...2.2.0 +[2.1.1]: https://github.com/micromatch/braces/compare/2.1.0...2.1.1 +[2.1.0]: https://github.com/micromatch/braces/compare/2.0.4...2.1.0 +[2.0.4]: https://github.com/micromatch/braces/compare/2.0.3...2.0.4 +[2.0.3]: https://github.com/micromatch/braces/compare/2.0.2...2.0.3 +[2.0.2]: https://github.com/micromatch/braces/compare/2.0.1...2.0.2 +[2.0.1]: https://github.com/micromatch/braces/compare/2.0.0...2.0.1 +[2.0.0]: https://github.com/micromatch/braces/compare/1.8.5...2.0.0 +[1.8.5]: https://github.com/micromatch/braces/compare/1.8.4...1.8.5 +[1.8.4]: https://github.com/micromatch/braces/compare/1.8.0...1.8.4 +[1.8.0]: https://github.com/micromatch/braces/compare/1.6.0...1.8.0 +[1.6.0]: https://github.com/micromatch/braces/compare/1.5.0...1.6.0 +[1.5.0]: https://github.com/micromatch/braces/compare/1.4.0...1.5.0 +[1.4.0]: https://github.com/micromatch/braces/compare/1.3.0...1.4.0 +[1.3.0]: https://github.com/micromatch/braces/compare/1.2.0...1.3.0 +[1.2.0]: https://github.com/micromatch/braces/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/micromatch/braces/compare/1.0.0...1.1.0 +[1.0.0]: https://github.com/micromatch/braces/compare/0.1.4...1.0.0 +[0.1.4]: https://github.com/micromatch/braces/compare/0.1.0...0.1.4 + +[Unreleased]: https://github.com/micromatch/braces/compare/0.1.0...HEAD +[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog \ No newline at end of file diff --git a/LICENSE b/LICENSE index d32ab44..9af4a67 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2018, Jon Schlinkert. +Copyright (c) 2014-present, Jon Schlinkert. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index f909bfb..f59dd60 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# braces [![NPM version](https://img.shields.io/npm/v/braces.svg?style=flat)](https://www.npmjs.com/package/braces) [![NPM monthly downloads](https://img.shields.io/npm/dm/braces.svg?style=flat)](https://npmjs.org/package/braces) [![NPM total downloads](https://img.shields.io/npm/dt/braces.svg?style=flat)](https://npmjs.org/package/braces) [![Linux Build Status](https://img.shields.io/travis/micromatch/braces.svg?style=flat&label=Travis)](https://travis-ci.org/micromatch/braces) [![Windows Build Status](https://img.shields.io/appveyor/ci/micromatch/braces.svg?style=flat&label=AppVeyor)](https://ci.appveyor.com/project/micromatch/braces) +# braces [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W8YFZ425KND68) [![NPM version](https://img.shields.io/npm/v/braces.svg?style=flat)](https://www.npmjs.com/package/braces) [![NPM monthly downloads](https://img.shields.io/npm/dm/braces.svg?style=flat)](https://npmjs.org/package/braces) [![NPM total downloads](https://img.shields.io/npm/dt/braces.svg?style=flat)](https://npmjs.org/package/braces) [![Linux Build Status](https://img.shields.io/travis/micromatch/braces.svg?style=flat&label=Travis)](https://travis-ci.org/micromatch/braces) > Bash-like brace expansion, implemented in JavaScript. Safer than other brace expansion libs, with complete support for the Bash 4.3 braces specification, without sacrificing speed. @@ -12,67 +12,67 @@ Install with [npm](https://www.npmjs.com/): $ npm install --save braces ``` +## v3.0.0 Released!! + +See the [changelog](CHANGELOG.md) for details. + ## Why use braces? -Brace patterns are great for matching ranges. Users (and implementors) shouldn't have to think about whether or not they will break their application (or yours) from accidentally defining an aggressive brace pattern. _Braces is the only library that offers a [solution to this problem](#performance)_. +Brace patterns make globs more powerful by adding the ability to match specific ranges and sequences of characters. -* **Safe(r)**: Braces isn't vulnerable to DoS attacks like [brace-expansion](https://github.com/juliangruber/brace-expansion), [minimatch](https://github.com/isaacs/minimatch) and [multimatch](https://github.com/sindresorhus/multimatch) (a different bug than the [other regex DoS bug](https://medium.com/node-security/minimatch-redos-vulnerability-590da24e6d3c#.jew0b6mpc)). -* **Accurate**: complete support for the [Bash 4.3 Brace Expansion](www.gnu.org/software/bash/) specification (passes all of the Bash braces tests) -* **[fast and performant](#benchmarks)**: Starts fast, runs fast and [scales well](#performance) as patterns increase in complexity. -* **Organized code base**: with parser and compiler that are eas(y|ier) to maintain and update when edge cases crop up. -* **Well-tested**: thousands of test assertions. Passes 100% of the [minimatch](https://github.com/isaacs/minimatch) and [brace-expansion](https://github.com/juliangruber/brace-expansion) unit tests as well (as of the writing of this). +- **Accurate** - complete support for the [Bash 4.3 Brace Expansion](www.gnu.org/software/bash/) specification (passes all of the Bash braces tests) +- **[fast and performant](#benchmarks)** - Starts fast, runs fast and [scales well](#performance) as patterns increase in complexity. +- **Organized code base** - The parser and compiler are easy to maintain and update when edge cases crop up. +- **Well-tested** - Thousands of test assertions, and passes all of the Bash, minimatch, and [brace-expansion](https://github.com/juliangruber/brace-expansion) unit tests (as of the date this was written). +- **Safer** - You shouldn't have to worry about users defining aggressive or malicious brace patterns that can break your application. Braces takes measures to prevent malicious regex that can be used for DDoS attacks (see [catastrophic backtracking](https://www.regular-expressions.info/catastrophic.html)). +- [Supports lists](#lists) - (aka "sets") `a/{b,c}/d` => `['a/b/d', 'a/c/d']` +- [Supports sequences](#sequences) - (aka "ranges") `{01..03}` => `['01', '02', '03']` +- [Supports steps](#steps) - (aka "increments") `{2..10..2}` => `['2', '4', '6', '8', '10']` +- [Supports escaping](#escaping) - To prevent evaluation of special characters. ## Usage The main export is a function that takes one or more brace `patterns` and `options`. ```js -var braces = require('braces'); -braces(pattern[, options]); -``` +const braces = require('braces'); +// braces(patterns[, options]); -By default, braces returns an optimized regex-source string. To get an array of brace patterns, use `brace.expand()`. +console.log(braces(['{01..05}', '{a..e}'])); +//=> ['(0[1-5])', '([a-e])'] -The following section explains the difference in more detail. _(If you're curious about "why" braces does this by default, see [brace matching pitfalls](#brace-matching-pitfalls)_. +console.log(braces(['{01..05}', '{a..e}'], { expand: true })); +//=> ['01', '02', '03', '04', '05', 'a', 'b', 'c', 'd', 'e'] +``` -### Optimized vs. expanded braces +### Brace Expansion vs. Compilation -**Optimized** +By default, brace patterns are compiled into strings that are optimized for creating regular expressions and matching. -By default, patterns are optimized for regex and matching: +**Compiled** ```js console.log(braces('a/{x,y,z}/b')); //=> ['a/(x|y|z)/b'] +console.log(braces(['a/{01..20}/b', 'a/{1..5}/b'])); +//=> [ 'a/(0[1-9]|1[0-9]|20)/b', 'a/([1-5])/b' ] ``` **Expanded** -To expand patterns the same way as Bash or [minimatch](https://github.com/isaacs/minimatch), use the [.expand](#expand) method: +Enable brace expansion by setting the `expand` option to true, or by using [braces.expand()](#expand) (returns an array similar to what you'd expect from Bash, or `echo {1..5}`, or [minimatch](https://github.com/isaacs/minimatch)): ```js -console.log(braces.expand('a/{x,y,z}/b')); +console.log(braces('a/{x,y,z}/b', { expand: true })); //=> ['a/x/b', 'a/y/b', 'a/z/b'] -``` - -Or use [options.expand](#optionsexpand): -```js -console.log(braces('a/{x,y,z}/b', {expand: true})); -//=> ['a/x/b', 'a/y/b', 'a/z/b'] +console.log(braces.expand('{01..10}')); +//=> ['01','02','03','04','05','06','07','08','09','10'] ``` -## Features - -* [lists](#lists): Supports "lists": `a/{b,c}/d` => `['a/b/d', 'a/c/d']` -* [sequences](#sequences): Supports alphabetical or numerical "sequences" (ranges): `{1..3}` => `['1', '2', '3']` -* [steps](#steps): Supports "steps" or increments: `{2..10..2}` => `['2', '4', '6', '8', '10']` -* [escaping](#escaping) -* [options](#options) - ### Lists -Uses [fill-range](https://github.com/jonschlinkert/fill-range) for expanding alphabetical or numeric lists: +Expand lists (like Bash "sets"): ```js console.log(braces('a/{foo,bar,baz}/*.js')); @@ -84,21 +84,22 @@ console.log(braces.expand('a/{foo,bar,baz}/*.js')); ### Sequences -Uses [fill-range](https://github.com/jonschlinkert/fill-range) for expanding alphabetical or numeric ranges (bash "sequences"): +Expand ranges of characters (like Bash "sequences"): ```js -console.log(braces.expand('{1..3}')); // ['1', '2', '3'] -console.log(braces.expand('a{01..03}b')); // ['a01b', 'a02b', 'a03b'] -console.log(braces.expand('a{1..3}b')); // ['a1b', 'a2b', 'a3b'] -console.log(braces.expand('{a..c}')); // ['a', 'b', 'c'] -console.log(braces.expand('foo/{a..c}')); // ['foo/a', 'foo/b', 'foo/c'] +console.log(braces.expand('{1..3}')); // ['1', '2', '3'] +console.log(braces.expand('a/{1..3}/b')); // ['a/1/b', 'a/2/b', 'a/3/b'] +console.log(braces('{a..c}', { expand: true })); // ['a', 'b', 'c'] +console.log(braces('foo/{a..c}', { expand: true })); // ['foo/a', 'foo/b', 'foo/c'] -// supports padded ranges -console.log(braces('a{01..03}b')); //=> [ 'a(0[1-3])b' ] -console.log(braces('a{001..300}b')); //=> [ 'a(0{2}[1-9]|0[1-9][0-9]|[12][0-9]{2}|300)b' ] +// supports zero-padded ranges +console.log(braces('a/{01..03}/b')); //=> ['a/(0[1-3])/b'] +console.log(braces('a/{001..300}/b')); //=> ['a/(0{2}[1-9]|0[1-9][0-9]|[12][0-9]{2}|300)/b'] ``` -### Steps +See [fill-range](https://github.com/jonschlinkert/fill-range) for all available range-expansion options. + +### Steppped ranges Steps, or increments, may be used with ranges: @@ -177,12 +178,12 @@ console.log(braces.expand('a{b}c')); **Type**: `Number` -**Default**: `65,536` +**Default**: `10,000` **Description**: Limit the length of the input string. Useful when the input string is generated or your application allows users to pass a string, et cetera. ```js -console.log(braces('a/{b,c}/d', { maxLength: 3 })); //=> throws an error +console.log(braces('a/{b,c}/d', { maxLength: 3 })); //=> throws an error ``` ### options.expand @@ -191,43 +192,30 @@ console.log(braces('a/{b,c}/d', { maxLength: 3 })); //=> throws an error **Default**: `undefined` -**Description**: Generate an "expanded" brace pattern (this option is unncessary with the `.expand` method, which does the same thing). +**Description**: Generate an "expanded" brace pattern (alternatively you can use the `braces.expand()` method, which does the same thing). ```js -console.log(braces('a/{b,c}/d', {expand: true})); +console.log(braces('a/{b,c}/d', { expand: true })); //=> [ 'a/b/d', 'a/c/d' ] ``` -### options.optimize - -**Type**: `Boolean` - -**Default**: `true` - -**Description**: Enabled by default. - -```js -console.log(braces('a/{b,c}/d')); -//=> [ 'a/(b|c)/d' ] -``` - ### options.nodupes **Type**: `Boolean` -**Default**: `true` +**Default**: `undefined` -**Description**: Duplicates are removed by default. To keep duplicates, pass `{nodupes: false}` on the options +**Description**: Remove duplicates from the returned array. ### options.rangeLimit **Type**: `Number` -**Default**: `250` +**Default**: `1000` -**Description**: When `braces.expand()` is used, or `options.expand` is true, brace patterns will automatically be [optimized](#optionsoptimize) when the difference between the range minimum and range maximum exceeds the `rangeLimit`. This is to prevent huge ranges from freezing your application. +**Description**: To prevent malicious patterns from being passed by users, an error is thrown when `braces.expand()` is used or `options.expand` is true and the generated range will exceed the `rangeLimit`. -You can set this to any number, or change `options.rangeLimit` to `Inifinity` to disable this altogether. +You can customize `options.rangeLimit` or set it to `Inifinity` to disable this altogether. **Examples** @@ -249,15 +237,30 @@ console.log(braces.expand('{1..100}')); **Description**: Customize range expansion. +**Example: Transforming non-numeric values** + ```js -var range = braces.expand('x{a..e}y', { - transform: function(str) { - return 'foo' + str; - } +const alpha = braces.expand('x/{a..e}/y', { + transform(value, index) { + // When non-numeric values are passed, "value" is a character code. + return 'foo/' + String.fromCharCode(value) + '-' + index; + }, }); +console.log(alpha); +//=> [ 'x/foo/a-0/y', 'x/foo/b-1/y', 'x/foo/c-2/y', 'x/foo/d-3/y', 'x/foo/e-4/y' ] +``` + +**Example: Transforming numeric values** -console.log(range); -//=> [ 'xfooay', 'xfooby', 'xfoocy', 'xfoody', 'xfooey' ] +```js +const numeric = braces.expand('{1..5}', { + transform(value) { + // when numeric values are passed, "value" is a number + return 'foo/' + value * 2; + }, +}); +console.log(numeric); +//=> [ 'foo/2', 'foo/4', 'foo/6', 'foo/8', 'foo/10' ] ``` ### options.quantifiers @@ -275,22 +278,22 @@ The `quantifiers` option tells braces to detect when [regex quantifiers](https:/ **Examples** ```js -var braces = require('braces'); +const braces = require('braces'); console.log(braces('a/b{1,3}/{x,y,z}')); //=> [ 'a/b(1|3)/(x|y|z)' ] -console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: true})); +console.log(braces('a/b{1,3}/{x,y,z}', { quantifiers: true })); //=> [ 'a/b{1,3}/(x|y|z)' ] -console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: true, expand: true})); +console.log(braces('a/b{1,3}/{x,y,z}', { quantifiers: true, expand: true })); //=> [ 'a/b{1,3}/x', 'a/b{1,3}/y', 'a/b{1,3}/z' ] ``` -### options.unescape +### options.keepEscaping **Type**: `Boolean` **Default**: `undefined` -**Description**: Strip backslashes that were used for escaping from the result. +**Description**: Do not strip backslashes that were used for escaping from the result. ## What is "brace expansion"? @@ -298,8 +301,8 @@ Brace expansion is a type of parameter expansion that was made popular by unix s In addition to "expansion", braces are also used for matching. In other words: -* [brace expansion](#brace-expansion) is for generating new lists -* [brace matching](#brace-matching) is for filtering existing lists +- [brace expansion](#brace-expansion) is for generating new lists +- [brace matching](#brace-matching) is for filtering existing lists
More about brace expansion (click to expand) @@ -379,9 +382,9 @@ Although brace patterns offer a user-friendly way of matching ranges or sets of **"brace bombs"** -* brace expansion can eat up a huge amount of processing resources -* as brace patterns increase _linearly in size_, the system resources required to expand the pattern increase exponentially -* users can accidentally (or intentially) exhaust your system's resources resulting in the equivalent of a DoS attack (bonus: no programming knowledge is required!) +- brace expansion can eat up a huge amount of processing resources +- as brace patterns increase _linearly in size_, the system resources required to expand the pattern increase exponentially +- users can accidentally (or intentially) exhaust your system's resources resulting in the equivalent of a DoS attack (bonus: no programming knowledge is required!) For a more detailed explanation with examples, see the [geometric complexity](#geometric-complexity) section. @@ -403,8 +406,8 @@ For example, the following sets demonstrate quadratic (`O(n^2)`) complexity: But add an element to a set, and we get a n-fold Cartesian product with `O(n^c)` complexity: ``` -{1,2,3}{4,5,6}{7,8,9} => (3X3X3) => 147 148 149 157 158 159 167 168 169 247 248 - 249 257 258 259 267 268 269 347 348 349 357 +{1,2,3}{4,5,6}{7,8,9} => (3X3X3) => 147 148 149 157 158 159 167 168 169 247 248 + 249 257 258 259 267 268 269 347 348 349 357 358 359 367 368 369 ``` @@ -421,9 +424,9 @@ Although these examples are clearly contrived, they demonstrate how brace patter Interested in learning more about brace expansion? -* [linuxjournal/bash-brace-expansion](http://www.linuxjournal.com/content/bash-brace-expansion) -* [rosettacode/Brace_expansion](https://rosettacode.org/wiki/Brace_expansion) -* [cartesian product](https://en.wikipedia.org/wiki/Cartesian_product) +- [linuxjournal/bash-brace-expansion](http://www.linuxjournal.com/content/bash-brace-expansion) +- [rosettacode/Brace_expansion](https://rosettacode.org/wiki/Brace_expansion) +- [cartesian product](https://en.wikipedia.org/wiki/Cartesian_product)
@@ -441,25 +444,25 @@ Instead, convert the pattern into an optimized regular expression. This is easie Minimatch gets exponentially slower as patterns increase in complexity, braces does not. The following results were generated using `braces()` and `minimatch.braceExpand()`, respectively. -| **Pattern** | **braces** | **[minimatch](https://github.com/isaacs/minimatch)** | -| --- | --- | --- | -| `{1..9007199254740991}`[1] | `298 B` (5ms 459μs) | N/A (freezes) | -| `{1..1000000000000000}` | `41 B` (1ms 15μs) | N/A (freezes) | -| `{1..100000000000000}` | `40 B` (890μs) | N/A (freezes) | -| `{1..10000000000000}` | `39 B` (2ms 49μs) | N/A (freezes) | -| `{1..1000000000000}` | `38 B` (608μs) | N/A (freezes) | -| `{1..100000000000}` | `37 B` (397μs) | N/A (freezes) | -| `{1..10000000000}` | `35 B` (983μs) | N/A (freezes) | -| `{1..1000000000}` | `34 B` (798μs) | N/A (freezes) | -| `{1..100000000}` | `33 B` (733μs) | N/A (freezes) | -| `{1..10000000}` | `32 B` (5ms 632μs) | `78.89 MB` (16s 388ms 569μs) | -| `{1..1000000}` | `31 B` (1ms 381μs) | `6.89 MB` (1s 496ms 887μs) | -| `{1..100000}` | `30 B` (950μs) | `588.89 kB` (146ms 921μs) | -| `{1..10000}` | `29 B` (1ms 114μs) | `48.89 kB` (14ms 187μs) | -| `{1..1000}` | `28 B` (760μs) | `3.89 kB` (1ms 453μs) | -| `{1..100}` | `22 B` (345μs) | `291 B` (196μs) | -| `{1..10}` | `10 B` (533μs) | `20 B` (37μs) | -| `{1..3}` | `7 B` (190μs) | `5 B` (27μs) | +| **Pattern** | **braces** | **[minimatch][]** | +| --------------------------- | ------------------- | ---------------------------- | +| `{1..9007199254740991}`[^1] | `298 B` (5ms 459μs) | N/A (freezes) | +| `{1..1000000000000000}` | `41 B` (1ms 15μs) | N/A (freezes) | +| `{1..100000000000000}` | `40 B` (890μs) | N/A (freezes) | +| `{1..10000000000000}` | `39 B` (2ms 49μs) | N/A (freezes) | +| `{1..1000000000000}` | `38 B` (608μs) | N/A (freezes) | +| `{1..100000000000}` | `37 B` (397μs) | N/A (freezes) | +| `{1..10000000000}` | `35 B` (983μs) | N/A (freezes) | +| `{1..1000000000}` | `34 B` (798μs) | N/A (freezes) | +| `{1..100000000}` | `33 B` (733μs) | N/A (freezes) | +| `{1..10000000}` | `32 B` (5ms 632μs) | `78.89 MB` (16s 388ms 569μs) | +| `{1..1000000}` | `31 B` (1ms 381μs) | `6.89 MB` (1s 496ms 887μs) | +| `{1..100000}` | `30 B` (950μs) | `588.89 kB` (146ms 921μs) | +| `{1..10000}` | `29 B` (1ms 114μs) | `48.89 kB` (14ms 187μs) | +| `{1..1000}` | `28 B` (760μs) | `3.89 kB` (1ms 453μs) | +| `{1..100}` | `22 B` (345μs) | `291 B` (196μs) | +| `{1..10}` | `10 B` (533μs) | `20 B` (37μs) | +| `{1..3}` | `7 B` (190μs) | `5 B` (27μs) | ### Faster algorithms @@ -467,16 +470,16 @@ When you need expansion, braces is still much faster. _(the following results were generated using `braces.expand()` and `minimatch.braceExpand()`, respectively)_ -| **Pattern** | **braces** | **[minimatch](https://github.com/isaacs/minimatch)** | -| --- | --- | --- | +| **Pattern** | **braces** | **[minimatch][]** | +| --------------- | --------------------------- | ---------------------------- | | `{1..10000000}` | `78.89 MB` (2s 698ms 642μs) | `78.89 MB` (18s 601ms 974μs) | -| `{1..1000000}` | `6.89 MB` (458ms 576μs) | `6.89 MB` (1s 491ms 621μs) | -| `{1..100000}` | `588.89 kB` (20ms 728μs) | `588.89 kB` (156ms 919μs) | -| `{1..10000}` | `48.89 kB` (2ms 202μs) | `48.89 kB` (13ms 641μs) | -| `{1..1000}` | `3.89 kB` (1ms 796μs) | `3.89 kB` (1ms 958μs) | -| `{1..100}` | `291 B` (424μs) | `291 B` (211μs) | -| `{1..10}` | `20 B` (487μs) | `20 B` (72μs) | -| `{1..3}` | `5 B` (166μs) | `5 B` (27μs) | +| `{1..1000000}` | `6.89 MB` (458ms 576μs) | `6.89 MB` (1s 491ms 621μs) | +| `{1..100000}` | `588.89 kB` (20ms 728μs) | `588.89 kB` (156ms 919μs) | +| `{1..10000}` | `48.89 kB` (2ms 202μs) | `48.89 kB` (13ms 641μs) | +| `{1..1000}` | `3.89 kB` (1ms 796μs) | `3.89 kB` (1ms 958μs) | +| `{1..100}` | `291 B` (424μs) | `291 B` (211μs) | +| `{1..10}` | `20 B` (487μs) | `20 B` (72μs) | +| `{1..3}` | `5 B` (166μs) | `5 B` (27μs) | If you'd like to run these comparisons yourself, see [test/support/generate.js](test/support/generate.js). @@ -492,72 +495,33 @@ npm i -d && npm benchmark ### Latest results -```bash -Benchmarking: (8 of 8) - · combination-nested - · combination - · escaped - · list-basic - · list-multiple - · no-braces - · sequence-basic - · sequence-multiple - -# benchmark/fixtures/combination-nested.js (52 bytes) - brace-expansion x 4,756 ops/sec ±1.09% (86 runs sampled) - braces x 11,202,303 ops/sec ±1.06% (88 runs sampled) - minimatch x 4,816 ops/sec ±0.99% (87 runs sampled) - - fastest is braces - -# benchmark/fixtures/combination.js (51 bytes) - brace-expansion x 625 ops/sec ±0.87% (87 runs sampled) - braces x 11,031,884 ops/sec ±0.72% (90 runs sampled) - minimatch x 637 ops/sec ±0.84% (88 runs sampled) - - fastest is braces - -# benchmark/fixtures/escaped.js (44 bytes) - brace-expansion x 163,325 ops/sec ±1.05% (87 runs sampled) - braces x 10,655,071 ops/sec ±1.22% (88 runs sampled) - minimatch x 147,495 ops/sec ±0.96% (88 runs sampled) - - fastest is braces - -# benchmark/fixtures/list-basic.js (40 bytes) - brace-expansion x 99,726 ops/sec ±1.07% (83 runs sampled) - braces x 10,596,584 ops/sec ±0.98% (88 runs sampled) - minimatch x 100,069 ops/sec ±1.17% (86 runs sampled) - - fastest is braces - -# benchmark/fixtures/list-multiple.js (52 bytes) - brace-expansion x 34,348 ops/sec ±1.08% (88 runs sampled) - braces x 9,264,131 ops/sec ±1.12% (88 runs sampled) - minimatch x 34,893 ops/sec ±0.87% (87 runs sampled) - - fastest is braces +Braces is more accurate, without sacrificing performance. -# benchmark/fixtures/no-braces.js (48 bytes) - brace-expansion x 275,368 ops/sec ±1.18% (89 runs sampled) - braces x 9,134,677 ops/sec ±0.95% (88 runs sampled) - minimatch x 3,755,954 ops/sec ±1.13% (89 runs sampled) - - fastest is braces - -# benchmark/fixtures/sequence-basic.js (41 bytes) - brace-expansion x 5,492 ops/sec ±1.35% (87 runs sampled) - braces x 8,485,034 ops/sec ±1.28% (89 runs sampled) - minimatch x 5,341 ops/sec ±1.17% (87 runs sampled) - - fastest is braces - -# benchmark/fixtures/sequence-multiple.js (51 bytes) - brace-expansion x 116 ops/sec ±0.77% (77 runs sampled) - braces x 9,445,118 ops/sec ±1.32% (84 runs sampled) - minimatch x 109 ops/sec ±1.16% (76 runs sampled) - - fastest is braces +```bash +● expand - range (expanded) + braces x 53,167 ops/sec ±0.12% (102 runs sampled) + minimatch x 11,378 ops/sec ±0.10% (102 runs sampled) +● expand - range (optimized for regex) + braces x 373,442 ops/sec ±0.04% (100 runs sampled) + minimatch x 3,262 ops/sec ±0.18% (100 runs sampled) +● expand - nested ranges (expanded) + braces x 33,921 ops/sec ±0.09% (99 runs sampled) + minimatch x 10,855 ops/sec ±0.28% (100 runs sampled) +● expand - nested ranges (optimized for regex) + braces x 287,479 ops/sec ±0.52% (98 runs sampled) + minimatch x 3,219 ops/sec ±0.28% (101 runs sampled) +● expand - set (expanded) + braces x 238,243 ops/sec ±0.19% (97 runs sampled) + minimatch x 538,268 ops/sec ±0.31% (96 runs sampled) +● expand - set (optimized for regex) + braces x 321,844 ops/sec ±0.10% (97 runs sampled) + minimatch x 140,600 ops/sec ±0.15% (100 runs sampled) +● expand - nested sets (expanded) + braces x 165,371 ops/sec ±0.42% (96 runs sampled) + minimatch x 337,720 ops/sec ±0.28% (100 runs sampled) +● expand - nested sets (optimized for regex) + braces x 242,948 ops/sec ±0.12% (99 runs sampled) + minimatch x 87,403 ops/sec ±0.79% (96 runs sampled) ``` ## About @@ -593,48 +557,30 @@ $ npm install -g verbose/verb#dev verb-generate-readme && verb -### Related projects - -You might also be interested in these projects: - -* [expand-brackets](https://www.npmjs.com/package/expand-brackets): Expand POSIX bracket expressions (character classes) in glob patterns. | [homepage](https://github.com/jonschlinkert/expand-brackets "Expand POSIX bracket expressions (character classes) in glob patterns.") -* [extglob](https://www.npmjs.com/package/extglob): Extended glob support for JavaScript. Adds (almost) the expressive power of regular expressions to glob… [more](https://github.com/micromatch/extglob) | [homepage](https://github.com/micromatch/extglob "Extended glob support for JavaScript. Adds (almost) the expressive power of regular expressions to glob patterns.") -* [fill-range](https://www.npmjs.com/package/fill-range): Fill in a range of numbers or letters, optionally passing an increment or `step` to… [more](https://github.com/jonschlinkert/fill-range) | [homepage](https://github.com/jonschlinkert/fill-range "Fill in a range of numbers or letters, optionally passing an increment or `step` to use, or create a regex-compatible range with `options.toRegex`") -* [micromatch](https://www.npmjs.com/package/micromatch): Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch. | [homepage](https://github.com/micromatch/micromatch "Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch.") -* [nanomatch](https://www.npmjs.com/package/nanomatch): Fast, minimal glob matcher for node.js. Similar to micromatch, minimatch and multimatch, but complete Bash… [more](https://github.com/micromatch/nanomatch) | [homepage](https://github.com/micromatch/nanomatch "Fast, minimal glob matcher for node.js. Similar to micromatch, minimatch and multimatch, but complete Bash 4.3 wildcard support only (no support for exglobs, posix brackets or braces)") - ### Contributors -| **Commits** | **Contributor** | -| --- | --- | -| 188 | [jonschlinkert](https://github.com/jonschlinkert) | -| 4 | [doowb](https://github.com/doowb) | -| 1 | [es128](https://github.com/es128) | -| 1 | [eush77](https://github.com/eush77) | -| 1 | [hemanth](https://github.com/hemanth) | +| **Commits** | **Contributor** | +| ----------- | ------------------------------------------------------------- | +| 197 | [jonschlinkert](https://github.com/jonschlinkert) | +| 4 | [doowb](https://github.com/doowb) | +| 1 | [es128](https://github.com/es128) | +| 1 | [eush77](https://github.com/eush77) | +| 1 | [hemanth](https://github.com/hemanth) | +| 1 | [wtgtybhertgeghgtwtg](https://github.com/wtgtybhertgeghgtwtg) | ### Author **Jon Schlinkert** -* [linkedin/in/jonschlinkert](https://linkedin.com/in/jonschlinkert) -* [github/jonschlinkert](https://github.com/jonschlinkert) -* [twitter/jonschlinkert](https://twitter.com/jonschlinkert) +- [GitHub Profile](https://github.com/jonschlinkert) +- [Twitter Profile](https://twitter.com/jonschlinkert) +- [LinkedIn Profile](https://linkedin.com/in/jonschlinkert) ### License -Copyright © 2018, [Jon Schlinkert](https://github.com/jonschlinkert). +Copyright © 2019, [Jon Schlinkert](https://github.com/jonschlinkert). Released under the [MIT License](LICENSE). -*** - -_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on February 17, 2018._ - -
-
-
    -
  1. this is the largest safe integer allowed in JavaScript. +--- -
  2. -
-
\ No newline at end of file +_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on April 08, 2019._ diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index be354d3..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Test against this version of Node.js -environment: - matrix: - # node.js - - nodejs_version: "7.0" - - nodejs_version: "6.0" - - nodejs_version: "5.0" - - nodejs_version: "0.12" - - nodejs_version: "0.10" - -# Install scripts. (runs after repo cloning) -install: - # Get the latest stable version of Node.js or io.js - - ps: Install-Product node $env:nodejs_version - # install modules - - npm install - -# Post-install test scripts. -test_script: - # Output useful info for debugging. - - node --version - - npm --version - # run tests - - npm test - -# Don't actually build. -build: off diff --git a/bench/index.js b/bench/index.js new file mode 100644 index 0000000..2a9cf76 --- /dev/null +++ b/bench/index.js @@ -0,0 +1,91 @@ +'use strict'; + +const { Suite } = require('benchmark'); +const colors = require('ansi-colors'); +const argv = require('minimist')(process.argv.slice(2)); +const minimatch = require('minimatch'); +const braces = require('..'); + +/** + * Setup + */ + +const cycle = (e, newline) => { + process.stdout.write(`\u001b[G ${e.target}${newline ? '\n' : ''}`); +}; + +const bench = (name, options) => { + const config = { name, ...options }; + const suite = new Suite(config); + const add = suite.add.bind(suite); + suite.on('error', console.error); + + if (argv.run && !new RegExp(argv.run).test(name)) { + suite.add = () => suite; + return suite; + } + + console.log(colors.green(`● ${config.name}`)); + + suite.add = (key, fn, opts) => { + if (typeof fn !== 'function') opts = fn; + + add(key, { + onCycle: e => cycle(e), + onComplete: e => cycle(e, true), + fn, + ...opts + }); + return suite; + }; + + return suite; +}; + +const skip = () => {}; +skip.add = () => skip; +skip.run = () => skip; +bench.skip = name => { + console.log(colors.cyan('● ' + colors.unstyle(name) + ' (skipped)')); + return skip; +}; + +bench('expand - range (expanded)') + .add(' braces', () => braces.expand('foo/{1..250}/bar')) + .add('minimatch', () => minimatch.braceExpand('foo/{1..250}/bar')) + .run(); + +bench('expand - range (optimized for regex)') + .add(' braces', () => braces.compile('foo/{1..250}/bar')) + .add('minimatch', () => minimatch.makeRe('foo/{1..250}/bar')) + .run(); + +bench('expand - nested ranges (expanded)') + .add(' braces', () => braces.expand('foo/{a,b,{1..250}}/bar')) + .add('minimatch', () => minimatch.braceExpand('foo/{a,b,{1..250}}/bar')) + .run(); + +bench('expand - nested ranges (optimized for regex)') + .add(' braces', () => braces.compile('foo/{a,b,{1..250}}/bar')) + .add('minimatch', () => minimatch.makeRe('foo/{a,b,{1..250}}/bar')) + .run(); + +bench('expand - set (expanded)') + .add(' braces', () => braces.expand('foo/{a,b,c}/bar')) + .add('minimatch', () => minimatch.braceExpand('foo/{a,b,c}/bar')) + .run(); + +bench('expand - set (optimized for regex)') + .add(' braces', () => braces.compile('foo/{a,b,c,d,e}/bar')) + .add('minimatch', () => minimatch.makeRe('foo/{a,b,c,d,e}/bar')) + .run(); + +bench('expand - nested sets (expanded)') + .add(' braces', () => braces.expand('foo/{a,b,{x,y,z}}/bar')) + .add('minimatch', () => minimatch.braceExpand('foo/{a,b,{x,y,z}}/bar')) + .run(); + +bench('expand - nested sets (optimized for regex)') + .add(' braces', () => braces.compile('foo/{a,b,c,d,e,{x,y,z}}/bar')) + .add('minimatch', () => minimatch.makeRe('foo/{a,b,c,d,e,{x,y,z}}/bar')) + .run(); diff --git a/bench/package.json b/bench/package.json new file mode 100644 index 0000000..28aaf2b --- /dev/null +++ b/bench/package.json @@ -0,0 +1,21 @@ +{ + "name": "picomatch-benchmarks", + "version": "0.0.0", + "private": true, + "main": "index.js", + "dependencies": { + "ansi-colors": "^3.0.3", + "benchmark": "^2.1.4", + "minimatch": "^3.0.4", + "minimist": "^1.2.0" + }, + "lintDeps": { + "devDependencies": { + "files": { + "patterns": [ + "*.js" + ] + } + } + } +} diff --git a/benchmark/code/brace-expansion.js b/benchmark/code/brace-expansion.js deleted file mode 100644 index 8f1599b..0000000 --- a/benchmark/code/brace-expansion.js +++ /dev/null @@ -1,4 +0,0 @@ -var braceExpansion = require('brace-expansion'); -module.exports = function(args) { - return braceExpansion.apply(null, Array.isArray(args) ? args : [args]); -}; diff --git a/benchmark/code/braces.js b/benchmark/code/braces.js deleted file mode 100644 index 2e93852..0000000 --- a/benchmark/code/braces.js +++ /dev/null @@ -1,4 +0,0 @@ -var braces = require('../..'); -module.exports = function(args) { - return braces.apply(null, Array.isArray(args) ? args : [args]); -}; diff --git a/benchmark/code/minimatch.js b/benchmark/code/minimatch.js deleted file mode 100644 index 5eea207..0000000 --- a/benchmark/code/minimatch.js +++ /dev/null @@ -1,4 +0,0 @@ -var braceExpand = require('minimatch').braceExpand; -module.exports = function(args) { - return braceExpand.apply(null, Array.isArray(args) ? args : [args]); -}; diff --git a/benchmark/fixtures/combination-nested.js b/benchmark/fixtures/combination-nested.js deleted file mode 100644 index bed3e8d..0000000 --- a/benchmark/fixtures/combination-nested.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = ['a{b,c{1..100}/{foo/bar},h}x/z']; diff --git a/benchmark/fixtures/combination.js b/benchmark/fixtures/combination.js deleted file mode 100644 index 699795a..0000000 --- a/benchmark/fixtures/combination.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = ['a/{b,c,d}/{e,f,g}/h/{1..100}']; diff --git a/benchmark/fixtures/escaped.js b/benchmark/fixtures/escaped.js deleted file mode 100644 index b5e69d5..0000000 --- a/benchmark/fixtures/escaped.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = ['a/\\{b,c}/{x\\,y}/d/e']; diff --git a/benchmark/fixtures/list-basic.js b/benchmark/fixtures/list-basic.js deleted file mode 100644 index 916273a..0000000 --- a/benchmark/fixtures/list-basic.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = ['a/b/c/{x,y,z}/d/e']; diff --git a/benchmark/fixtures/list-multiple.js b/benchmark/fixtures/list-multiple.js deleted file mode 100644 index 405aac2..0000000 --- a/benchmark/fixtures/list-multiple.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = ['a/b/c/{k,l,m}/d/{w,x,y,z}/d/e']; diff --git a/benchmark/fixtures/match.multiple.js b/benchmark/fixtures/match.multiple.js deleted file mode 100644 index 699795a..0000000 --- a/benchmark/fixtures/match.multiple.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = ['a/{b,c,d}/{e,f,g}/h/{1..100}']; diff --git a/benchmark/fixtures/match.sequence.js b/benchmark/fixtures/match.sequence.js deleted file mode 100644 index e94a78f..0000000 --- a/benchmark/fixtures/match.sequence.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = ['a/b/c/{1..100}/d/e']; diff --git a/benchmark/fixtures/no-braces.js b/benchmark/fixtures/no-braces.js deleted file mode 100644 index 3b4653e..0000000 --- a/benchmark/fixtures/no-braces.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = ['a/b/c/d/e/**/@(x|y|z)*.js']; diff --git a/benchmark/fixtures/sequence-basic.js b/benchmark/fixtures/sequence-basic.js deleted file mode 100644 index e94a78f..0000000 --- a/benchmark/fixtures/sequence-basic.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = ['a/b/c/{1..100}/d/e']; diff --git a/benchmark/fixtures/sequence-multiple.js b/benchmark/fixtures/sequence-multiple.js deleted file mode 100644 index ba55e69..0000000 --- a/benchmark/fixtures/sequence-multiple.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = ['a/b/c/{1..50}/d/{1..100}/d/e']; diff --git a/benchmark/index.js b/benchmark/index.js deleted file mode 100644 index 23b4d80..0000000 --- a/benchmark/index.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -var path = require('path'); -var util = require('util'); -var cyan = require('ansi-cyan'); -var argv = require('yargs-parser')(process.argv.slice(2)); -var Suite = require('benchmarked'); - -function run(type, fixtures) { - var suite = new Suite({ - cwd: __dirname, - fixtures: `fixtures/${fixtures}.js`, - code: `code/${type}.js` - }); - - if (argv.dry) { - suite.dryRun(function(code, fixture) { - console.log(cyan('%s > %s'), code.key, fixture.key); - var args = require(fixture.path); - var res = code.run(args); - console.log(util.inspect(res, null, 10)); - console.log(); - }); - } else { - suite.run(); - } -} - -run(argv.code || '*', argv._[0] || '!(match)*'); -// run('braces', 'no-*'); diff --git a/benchmark/last.md b/benchmark/last.md deleted file mode 100644 index e805f89..0000000 --- a/benchmark/last.md +++ /dev/null @@ -1,65 +0,0 @@ -Benchmarking: (8 of 8) - · combination-nested - · combination - · escaped - · list-basic - · list-multiple - · no-braces - · sequence-basic - · sequence-multiple - -# benchmark/fixtures/combination-nested.js (52 bytes) - brace-expansion x 5,605 ops/sec ±1.14% (83 runs sampled) - braces x 14,410,490 ops/sec ±1.15% (85 runs sampled) - minimatch x 5,977 ops/sec ±1.28% (85 runs sampled) - - fastest is braces - -# benchmark/fixtures/combination.js (51 bytes) - brace-expansion x 755 ops/sec ±1.18% (83 runs sampled) - braces x 10,759,364 ops/sec ±0.94% (85 runs sampled) - minimatch x 723 ops/sec ±0.98% (84 runs sampled) - - fastest is braces - -# benchmark/fixtures/escaped.js (44 bytes) - brace-expansion x 189,901 ops/sec ±1.23% (86 runs sampled) - braces x 10,832,036 ops/sec ±0.89% (85 runs sampled) - minimatch x 150,475 ops/sec ±1.29% (88 runs sampled) - - fastest is braces - -# benchmark/fixtures/list-basic.js (40 bytes) - brace-expansion x 126,961 ops/sec ±0.70% (85 runs sampled) - braces x 11,004,254 ops/sec ±1.29% (84 runs sampled) - minimatch x 111,199 ops/sec ±1.26% (85 runs sampled) - - fastest is braces - -# benchmark/fixtures/list-multiple.js (52 bytes) - brace-expansion x 36,894 ops/sec ±0.70% (86 runs sampled) - braces x 8,609,924 ops/sec ±1.03% (85 runs sampled) - minimatch x 41,010 ops/sec ±1.17% (88 runs sampled) - - fastest is braces - -# benchmark/fixtures/no-braces.js (48 bytes) - brace-expansion x 309,785 ops/sec ±0.82% (88 runs sampled) - braces x 8,709,136 ops/sec ±1.23% (88 runs sampled) - minimatch x 2,208,995 ops/sec ±1.03% (88 runs sampled) - - fastest is braces - -# benchmark/fixtures/sequence-basic.js (41 bytes) - brace-expansion x 6,236 ops/sec ±0.94% (83 runs sampled) - braces x 9,241,779 ops/sec ±1.26% (83 runs sampled) - minimatch x 7,230 ops/sec ±1.35% (85 runs sampled) - - fastest is braces - -# benchmark/fixtures/sequence-multiple.js (51 bytes) - brace-expansion x 133 ops/sec ±1.08% (73 runs sampled) - braces x 8,859,756 ops/sec ±1.31% (85 runs sampled) - minimatch x 135 ops/sec ±0.94% (73 runs sampled) - - fastest is braces diff --git a/examples/ast.js b/examples/ast.js deleted file mode 100644 index bfecbef..0000000 --- a/examples/ast.js +++ /dev/null @@ -1,20 +0,0 @@ -var braces = require('..'); - -var ast = braces.parse('a/{\\{b,c,d},z}/e', {unescape: false}); -var str = ''; -console.log(ast); - -visit(ast, function(node) { - if (node.val) str += node.val; -}); - -function visit(node, fn) { - return node.nodes ? mapVisit(node, fn) : fn(node); -} - -function mapVisit(node, fn) { - for (var i = 0; i < node.nodes.length; i++) { - visit(node.nodes[i], fn); - } - return node; -} diff --git a/examples/braces.js b/examples/braces.js deleted file mode 100644 index 9692522..0000000 --- a/examples/braces.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -var braces = require('..'); -// console.log(braces('some/path/{a,b,c}')); -// console.log(braces.expand('some/path/{a,b,c}')); - -var res = braces('{1..10000000}'); -console.log(res); -// console.log(format(res[0].length)); - -var res = braces('{1..100000000}'); -console.log(res); -// console.log(format(res[0].length)); diff --git a/examples/brackets.js b/examples/brackets.js deleted file mode 100644 index d37600b..0000000 --- a/examples/brackets.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -var braces = require('..'); -var list = ['foo.js', '(a).js', '(b).js', '(c).js']; -console.log(braces.match(list, '{([a-b]),foo}.js')); -console.log(braces.expand('{([a-b]),foo}.js')); -console.log(braces.expand('{[dec-1992],[dec-1993]}.js')); diff --git a/examples/comparison.js b/examples/comparison.js deleted file mode 100644 index c3d1819..0000000 --- a/examples/comparison.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -var braceExpansion = require('brace-expansion'); -var braces = require('..'); - -console.log('braces'); -console.log(braces('http://any.org/archive{1996..1999}/vol({1..4})/part{a,b,c}.html')); -console.log(braces('http://any.org/archive{1996..1999}/vol{1..4}/part{a,b,c}.html')); -console.log(braces('http://www.numericals.com/file{1..100..10}.txt')); -console.log(braces('http://www.letters.com/file{a..z..2}.txt')); -console.log(braces('mkdir /usr/local/src/bash/{old,new,dist,bugs}')); -console.log(braces('chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}')); -console.log(); -console.log(); -console.log('braces: {expand: true}'); -console.log(braces('http://any.org/archive{1996..1999}/vol{1..4}/part{a,b,c}.html', {expand: true})); -console.log(braces('http://www.numericals.com/file{1..100..10}.txt', {expand: true})); -console.log(braces('http://www.letters.com/file{a..z..2}.txt', {expand: true})); -console.log(braces('mkdir /usr/local/src/bash/{old,new,dist,bugs}', {expand: true})); -console.log(braces('chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}', {expand: true})); -console.log(); -console.log(); -console.log('brace-expansion'); -console.log(braceExpansion('http://any.org/archive{1996..1999}/vol{1..4}/part{a,b,c}.html')); -console.log(braceExpansion('http://www.numericals.com/file{1..100..10}.txt')); -console.log(braceExpansion('http://www.letters.com/file{a..z..2}.txt')); -console.log(braceExpansion('mkdir /usr/local/src/bash/{old,new,dist,bugs}')); -console.log(braceExpansion('chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}')); - - -console.log(braces('user-{200..300}/project-{a,b,c}-{1..10}')) -//=> 'user-(20[0-9]|2[1-9][0-9]|300)/project-(a|b|c)-([1-9]|10)' -console.log(braces.makeRe('user-{200..300}')) -//=> /^(?:user-(20[0-9]|2[1-9][0-9]|300))$/ diff --git a/examples/compile.js b/examples/compile.js new file mode 100644 index 0000000..710f221 --- /dev/null +++ b/examples/compile.js @@ -0,0 +1,6 @@ +'use strict'; + +const compile = require('../lib/compile'); +const parse = require('../lib/parse'); +console.log(compile(parse('{a,b,c}'))); +console.log(compile(parse('{01..09}'))); diff --git a/examples/escape.js b/examples/escape.js deleted file mode 100644 index c597ec2..0000000 --- a/examples/escape.js +++ /dev/null @@ -1,4 +0,0 @@ -'use strict'; - -var braces = require('..'); -console.log(braces.expand('{1\\.2}')); diff --git a/examples/expand.js b/examples/expand.js index 2acf235..5e458f1 100644 --- a/examples/expand.js +++ b/examples/expand.js @@ -1,14 +1,23 @@ -'use strict'; -var mm = require('minimatch'); -var braces = require('..'); +const colors = require('ansi-colors'); +const color = (arr, c) => arr.map(s => c(s)).join(', '); +const cp = require('child_process'); +const braces = input => { + return cp.execSync(`echo ${input}`).toString().trim().split(' '); +}; -console.log(braces.expand('a/{b,c}/d')); -//=> [ 'a/b/d', 'a/c/d' ] - -var ast = braces.parse('a{b{a,b}}c'); -var res = braces.compile(ast, {expand: true}); -console.log(res) - -console.log(braces.expand('{1..100}{1..100}').length); -console.log(braces.expand('{1..10}{1..10}{1..10}').length); +// const fixture = '{a,{b,c},d}'; +// const fixture = '{a,b}{c,d}{e,f}'; +// const fixture = 'a/{b,c{x,y}d,e}/f'; +// const fixture = '{{a,b}/i,j,k}'; +// const fixture = '{c,d{e,f}g,h}'; +// const fixture = '{{c,d{e,f}g,h}/i,j,k}'; +// const fixture = '{a,b}/{c,d{e,f}g,h}'; +// const fixture = '{{a,b}/{c,d{e,f}g,h}/i,j,k}'; +// const fixture = '{x,y,{a,b,c\\}}'; +const fixture = 'a{,b}c'; +console.log(); +console.log(' FIXTURE:', colors.magenta(fixture)); +// console.log(' ACTUAL:', color(expand(parse(fixture)), colors.yellow)); +console.log('EXPECTED:', color(braces(fixture), colors.blue)); +console.log(); diff --git a/examples/extglobs.js b/examples/extglobs.js deleted file mode 100644 index 800889c..0000000 --- a/examples/extglobs.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -var braces = require('..'); -console.log(braces.expand('{!(a|b),!(c|d)}')); -console.log(braces('{!(a|b),!(c|d)}')); diff --git a/examples/nested-regex.js b/examples/nested-regex.js deleted file mode 100644 index 6d6d532..0000000 --- a/examples/nested-regex.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -var minimatch = require('minimatch'); -var braces = require('..'); - -console.log('braces'); -console.log(braces.makeRe('a{b,c{1..100}/{foo/bar},h}x/z')); -console.log(); -console.log(); -console.log('brace-expansion'); -console.log(minimatch.makeRe('a{b,c{1..100}/{foo/bar},h}x/z')); - diff --git a/examples/nested.js b/examples/nested.js deleted file mode 100644 index 3677928..0000000 --- a/examples/nested.js +++ /dev/null @@ -1,49 +0,0 @@ -var util = require('util'); -var braces = require('..').expand; -var expand = require('brace-expansion'); -var bash = require('../test/support/bash'); - -function log(pattern) { - // console.log('---'); - console.log('equal(\'' + pattern + '\', ', util.inspect(braces(pattern).sort()).split('\n').join('') + ');'); - // console.log('equal(\'' + pattern + '\', ', util.inspect(bash(pattern).sort()).split('\n').join('') + ');'); - // console.log('equal(\'' + pattern + '\', ', util.inspect(expand(pattern).sort()).split('\n').join('') + ');'); - // console.log(expand(pattern)); - console.log(); -} - -log('{,eno,thro,ro}ugh') -log('{,{,eno,thro,ro}ugh}{,out}') -log('{{,eno,thro,ro}ugh,}{,out}') -log('{,{,a,b}z}{,c}') -log('{,{,a,b}z}{c,}') -log('{,{,a,b}z}{,c,}') -log('{,{,a,b}z}{c,d}') -log('{{,a,b}z,}{,c}') -log('{,a{,b}z,}{,c}') -log('{,a{,b},}{,c}') -log('{,a{,b}}{,c}') -log('{,b}{,d}') -log('{a,b}{,d}') -log('{,a}{z,c}') -log('{,{,a},}{z,c}') -log('{,,a,}{z,c}') -log('{{,a},}{z,c}') -log('{,{,a},}{z,c}') -log('{,{,a}}{z,c}') -log('{{a,},}{z,c}') -log('{{,a},}{z,c}') -log('{,,a}{z,c}') -log('{,a,}{z,c}') -log('{,{,}}{z,c}') -log('{,{a,b}}{,c}') -log('{,{a,}}{,c}') -log('{,{,b}}{,c}') -log('{,{,}}{,c}') -log('{,a}{,c}') -log('{,{,a}b}') -log('{,b}') -log('{,b{,a}}') -log('{b,{,a}}') -log('{,b}{,d}') -log('{a,b}{,d}') diff --git a/examples/option-transform.js b/examples/option-transform.js new file mode 100644 index 0000000..9c36bc0 --- /dev/null +++ b/examples/option-transform.js @@ -0,0 +1,18 @@ +'use strict'; + +const braces = require('..'); +const alpha = braces.expand('x/{a..e}/y', { + transform(code, index) { + // when non-numeric values are passed, "code" is a character code, + return 'foo/' + String.fromCharCode(code) + '-' + index; + } +}); +console.log(alpha); +//=> [ 'x/foo/a-0/y', 'x/foo/b-1/y', 'x/foo/c-2/y', 'x/foo/d-3/y', 'x/foo/e-4/y' ] + +const numeric = braces.expand('{1..5}', { + transform(value) { + return 'foo/' + value * 2; + } +}); +console.log(numeric); //=> [ 'foo/2', 'foo/4', 'foo/6', 'foo/8', 'foo/10' ] diff --git a/examples/options.quantifiers.js b/examples/options.quantifiers.js deleted file mode 100644 index ff79a57..0000000 --- a/examples/options.quantifiers.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -var mm = require('minimatch'); -var braces = require('..'); - -/** - * True - */ - -console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: true})); -//=> [ 'a/b(1|3)/(x|y|z)' ] -console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: true})); -//=> [ 'a/b{1,3}/(x|y|z)' ] -console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: true, expand: true})); -//=> [ 'a/b{1,3}/x', 'a/b{1,3}/y', 'a/b{1,3}/z' ] - -/** - * False - */ - -console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: false})); -//=> [ 'a/b(1|3)/(x|y|z)' ] -console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: false})); -//=> [ 'a/b{1,3}/(x|y|z)' ] -console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: false, expand: true})); -//=> [ 'a/b{1,3}/x', 'a/b{1,3}/y', 'a/b{1,3}/z' ] diff --git a/examples/parse.js b/examples/parse.js new file mode 100644 index 0000000..1adf666 --- /dev/null +++ b/examples/parse.js @@ -0,0 +1,25 @@ +'use strict'; + +// const input = 'foo/{a,bar/{b,c},d}'; +// const input = 'a/{b,c{x,y}}/d'; +// const input = '{{x,y},/{b,c{x,y}d,e}/f}'; +// const input = '{{a,b}/{b,c{x,y}d,e}/f,x,z}'; +// const input = 'a/{b,c}/d'; +// const ast = parse(input); +// console.log(ast) +// console.log(JSON.stringify(ast.queue)); +// console.log('EXPECTED:', [ 'a/b/f', 'a/cxd/f', 'a/cyd/f', 'a/e/f' ]); +// console.log(JSON.stringify(ast, null, 2)) +// console.log(expand(ast)); +// expand(ast); + +// const sets = parse('foo/{a/b,{c,d,{x..z},e},f}/bar'); +// const sets = parse('{a,{c,d}'); +// console.log(sets.nodes[2]); +// console.log(compile(sets)); + +// const range = parse(']{a..e,z}'); +// console.log(range.nodes[2]); +// console.log(braces.expand(']{a..e,z}')) +// console.log(compile(range)); +// console.log(parse('[abc]')) diff --git a/examples/paths.js b/examples/paths.js deleted file mode 100644 index b759bb8..0000000 --- a/examples/paths.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -var braces = require('..'); - -console.log(braces('foo*{/*,*}')); -console.log(braces('foo (abc, 1990)')); diff --git a/examples/readme.js b/examples/readme.js deleted file mode 100644 index 53131a7..0000000 --- a/examples/readme.js +++ /dev/null @@ -1,28 +0,0 @@ -var braces = require('..'); - -console.log(braces.expand('it{,{{em,alic}iz,erat}e{d,}}')); -console.log(braces.expand('I like {pizza,beer,money}.')); -// console.log(braces.expand('{bull,shoe}{,horn}')); - -console.log(braces.expand('{a,b}{1,2}')); -//=> [ 'a1', 'a2', 'b1', 'b2' ] - -console.log(braces.expand('{a,b}/{1,2}')); -//=> [ 'a/1', 'a/2', 'b/1', 'b/2' ] - -console.log(braces.expand('foo/{a,b,c}/bar').join(' ')); -console.log(braces('{a,b}{1,2}')); -//=> [ '(a|b)(1|2)' ] - -console.log(braces.expand('{a,b,c}{1,2}').join(' ')); -console.log(braces.expand('{4..-4}').join(' ')); -//=> [ '(a|b)/(1|2)' ] - -console.log(braces.expand('{a,b,c}{1..3}').join(' ')); -console.log(braces.expand('{a..j}').join(' ')); -console.log(braces.expand('{j..a}').join(' ')); -console.log(braces.expand('{1..20..3}').join(' ')); -console.log(braces.expand('{a..z..3}').join(' ')); -console.log(braces.expand('a{1..3}b').join(' ')); -console.log(braces.expand('{1..3}.{0..9}0')); -//=> [ 'a1b', 'a2b', 'a3b' ] diff --git a/examples/regex.js b/examples/regex.js deleted file mode 100644 index 711e8e2..0000000 --- a/examples/regex.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -var mm = require('minimatch'); -var braces = require('..'); - -// console.log(braces.makeRe('a/b/c/{k,l,m}/d/{w,x,y,z}/d/e', {expand: true})); -// console.log(mm.makeRe('a/b/c/{k,l,m}/d/{w,x,y,z}/d/e')); - -// console.log('braces.makeRe: "foo/{1..20000}/bar/{a..j}/baz"'); -// console.log(braces.makeRe('foo/{1..20000}/bar/{a..j}/baz')); -// console.log(); -// console.log('minimatch.makeRe: "foo/{1..20000}/bar/{a..j}/baz"'); -// console.log(mm.makeRe('foo/{1..20000}/bar/{a..j}/baz')); - -// var re = braces.makeRe('a/{foo/bar}/z'); -// console.log(re); -// console.log(re.test('a/{foo/bar}/z')); - -console.log(braces.makeRe('{00000001..99999999}')); diff --git a/examples/sequences-steps.js b/examples/sequences-steps.js deleted file mode 100644 index 6895aa9..0000000 --- a/examples/sequences-steps.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -var mm = require('minimatch'); -var braces = require('..'); - -console.log(braces('{0..10..2}', {expand: true})); -//=> [ '0', '2', '4', '6', '8', '10' ] diff --git a/examples/sequences.js b/examples/sequences.js deleted file mode 100644 index 88dcf88..0000000 --- a/examples/sequences.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -var mm = require('minimatch'); -var braces = require('..'); - -console.log(braces('{1..5}', {expand: true})); -//=> [ '1', '2', '3', '4', '5' ] - -console.log(braces('{"1..5"}', {expand: true})) diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index 0f8b221..0000000 --- a/gulpfile.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -var gulp = require('gulp'); -var mocha = require('gulp-mocha'); -var unused = require('gulp-unused'); -var istanbul = require('gulp-istanbul'); -var eslint = require('gulp-eslint'); - -gulp.task('coverage', function() { - return gulp.src(['index.js', 'lib/*.js']) - .pipe(istanbul()) - .pipe(istanbul.hookRequire()); -}); - -gulp.task('test', ['coverage'], function() { - return gulp.src('test/*.js') - .pipe(mocha({reporter: 'spec'})) - .pipe(istanbul.writeReports()); -}); - -gulp.task('eslint', function() { - return gulp.src(['*.js', 'lib/*.js', 'test/*.js']) - .pipe(eslint()) - .pipe(eslint.format()); -}); - -gulp.task('unused', function() { - return gulp.src(['index.js', 'lib/*.js']) - .pipe(unused({keys: Object.keys(require('./lib/utils.js'))})); -}); - -gulp.task('default', ['test', 'eslint']); diff --git a/index.js b/index.js index 048e1c2..d222c13 100644 --- a/index.js +++ b/index.js @@ -1,34 +1,17 @@ 'use strict'; -/** - * Module dependencies - */ - -var toRegex = require('to-regex'); -var unique = require('array-unique'); -var extend = require('extend-shallow'); - -/** - * Local dependencies - */ - -var compilers = require('./lib/compilers'); -var parsers = require('./lib/parsers'); -var Braces = require('./lib/braces'); -var utils = require('./lib/utils'); -var MAX_LENGTH = 1024 * 64; -var cache = {}; +const stringify = require('./lib/stringify'); +const compile = require('./lib/compile'); +const expand = require('./lib/expand'); +const parse = require('./lib/parse'); /** - * Convert the given `braces` pattern into a regex-compatible string. By default, only one string is generated for every input string. Set `options.expand` to true to return an array of patterns (similar to Bash or minimatch. Before using `options.expand`, it's recommended that you read the [performance notes](#performance)). + * Expand the given pattern or create a regex-compatible string. * * ```js - * var braces = require('braces'); - * console.log(braces('{a,b,c}')); - * //=> ['(a|b|c)'] - * - * console.log(braces('{a,b,c}', {expand: true})); - * //=> ['a', 'b', 'c'] + * const braces = require('braces'); + * console.log(braces('{a,b,c}', { compile: true })); //=> ['(a|b|c)'] + * console.log(braces('{a,b,c}')); //=> ['a', 'b', 'c'] * ``` * @param {String} `str` * @param {Object} `options` @@ -36,283 +19,152 @@ var cache = {}; * @api public */ -function braces(pattern, options) { - var key = utils.createKey(String(pattern), options); - var arr = []; - - var disabled = options && options.cache === false; - if (!disabled && cache.hasOwnProperty(key)) { - return cache[key]; - } - - if (Array.isArray(pattern)) { - for (var i = 0; i < pattern.length; i++) { - arr.push.apply(arr, braces.create(pattern[i], options)); +const braces = (input, options = {}) => { + let output = []; + + if (Array.isArray(input)) { + for (const pattern of input) { + const result = braces.create(pattern, options); + if (Array.isArray(result)) { + output.push(...result); + } else { + output.push(result); + } } } else { - arr = braces.create(pattern, options); - } - - if (options && options.nodupes === true) { - arr = unique(arr); + output = [].concat(braces.create(input, options)); } - if (!disabled) { - cache[key] = arr; + if (options && options.expand === true && options.nodupes === true) { + output = [...new Set(output)]; } - return arr; -} + return output; +}; /** - * Expands a brace pattern into an array. This method is called by the main [braces](#braces) function when `options.expand` is true. Before using this method it's recommended that you read the [performance notes](#performance)) and advantages of using [.optimize](#optimize) instead. + * Parse the given `str` with the given `options`. * * ```js - * var braces = require('braces'); - * console.log(braces.expand('a/{b,c}/d')); - * //=> ['a/b/d', 'a/c/d']; + * // braces.parse(pattern, [, options]); + * const ast = braces.parse('a/{b,c}/d'); + * console.log(ast); * ``` - * @param {String} `pattern` Brace pattern - * @param {Object} `options` - * @return {Array} Returns an array of expanded values. + * @param {String} pattern Brace pattern to parse + * @param {Object} options + * @return {Object} Returns an AST * @api public */ -braces.expand = function(pattern, options) { - return braces.create(pattern, extend({}, options, {expand: true})); -}; +braces.parse = (input, options = {}) => parse(input, options); /** - * Expands a brace pattern into a regex-compatible, optimized string. This method is called by the main [braces](#braces) function by default. + * Creates a braces string from an AST, or an AST node. * * ```js - * var braces = require('braces'); - * console.log(braces.expand('a/{b,c}/d')); - * //=> ['a/(b|c)/d'] + * const braces = require('braces'); + * let ast = braces.parse('foo/{a,b}/bar'); + * console.log(stringify(ast.nodes[2])); //=> '{a,b}' * ``` - * @param {String} `pattern` Brace pattern + * @param {String} `input` Brace pattern or AST. * @param {Object} `options` * @return {Array} Returns an array of expanded values. * @api public */ -braces.optimize = function(pattern, options) { - return braces.create(pattern, options); +braces.stringify = (input, options = {}) => { + if (typeof input === 'string') { + return stringify(braces.parse(input, options), options); + } + return stringify(input, options); }; /** - * Processes a brace pattern and returns either an expanded array (if `options.expand` is true), a highly optimized regex-compatible string. This method is called by the main [braces](#braces) function. + * Compiles a brace pattern into a regex-compatible, optimized string. + * This method is called by the main [braces](#braces) function by default. * * ```js - * var braces = require('braces'); - * console.log(braces.create('user-{200..300}/project-{a,b,c}-{1..10}')) - * //=> 'user-(20[0-9]|2[1-9][0-9]|300)/project-(a|b|c)-([1-9]|10)' + * const braces = require('braces'); + * console.log(braces.compile('a/{b,c}/d')); + * //=> ['a/(b|c)/d'] * ``` - * @param {String} `pattern` Brace pattern + * @param {String} `input` Brace pattern or AST. * @param {Object} `options` * @return {Array} Returns an array of expanded values. * @api public */ -braces.create = function(pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected a string'); - } - - var maxLength = (options && options.maxLength) || MAX_LENGTH; - if (pattern.length >= maxLength) { - throw new Error('expected pattern to be less than ' + maxLength + ' characters'); +braces.compile = (input, options = {}) => { + if (typeof input === 'string') { + input = braces.parse(input, options); } - - function create() { - if (pattern === '' || pattern.length < 3) { - return [pattern]; - } - - if (utils.isEmptySets(pattern)) { - return []; - } - - if (utils.isQuotedString(pattern)) { - return [pattern.slice(1, -1)]; - } - - var proto = new Braces(options); - var result = !options || options.expand !== true - ? proto.optimize(pattern, options) - : proto.expand(pattern, options); - - // get the generated pattern(s) - var arr = result.output; - - // filter out empty strings if specified - if (options && options.noempty === true) { - arr = arr.filter(Boolean); - } - - // filter out duplicates if specified - if (options && options.nodupes === true) { - arr = unique(arr); - } - - Object.defineProperty(arr, 'result', { - enumerable: false, - value: result - }); - - return arr; - } - - return memoize('create', pattern, options, create); + return compile(input, options); }; /** - * Create a regular expression from the given string `pattern`. + * Expands a brace pattern into an array. This method is called by the + * main [braces](#braces) function when `options.expand` is true. Before + * using this method it's recommended that you read the [performance notes](#performance)) + * and advantages of using [.compile](#compile) instead. * * ```js - * var braces = require('braces'); - * - * console.log(braces.makeRe('id-{200..300}')); - * //=> /^(?:id-(20[0-9]|2[1-9][0-9]|300))$/ + * const braces = require('braces'); + * console.log(braces.expand('a/{b,c}/d')); + * //=> ['a/b/d', 'a/c/d']; * ``` - * @param {String} `pattern` The pattern to convert to regex. + * @param {String} `pattern` Brace pattern * @param {Object} `options` - * @return {RegExp} + * @return {Array} Returns an array of expanded values. * @api public */ -braces.makeRe = function(pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected a string'); +braces.expand = (input, options = {}) => { + if (typeof input === 'string') { + input = braces.parse(input, options); } - var maxLength = (options && options.maxLength) || MAX_LENGTH; - if (pattern.length >= maxLength) { - throw new Error('expected pattern to be less than ' + maxLength + ' characters'); - } + let result = expand(input, options); - function makeRe() { - var arr = braces(pattern, options); - var opts = extend({strictErrors: false}, options); - return toRegex(arr, opts); + // filter out empty strings if specified + if (options.noempty === true) { + result = result.filter(Boolean); } - return memoize('makeRe', pattern, options, makeRe); -}; - -/** - * Parse the given `str` with the given `options`. - * - * ```js - * var braces = require('braces'); - * var ast = braces.parse('a/{b,c}/d'); - * console.log(ast); - * // { type: 'root', - * // errors: [], - * // input: 'a/{b,c}/d', - * // nodes: - * // [ { type: 'bos', val: '' }, - * // { type: 'text', val: 'a/' }, - * // { type: 'brace', - * // nodes: - * // [ { type: 'brace.open', val: '{' }, - * // { type: 'text', val: 'b,c' }, - * // { type: 'brace.close', val: '}' } ] }, - * // { type: 'text', val: '/d' }, - * // { type: 'eos', val: '' } ] } - * ``` - * @param {String} `pattern` Brace pattern to parse - * @param {Object} `options` - * @return {Object} Returns an AST - * @api public - */ + // filter out duplicates if specified + if (options.nodupes === true) { + result = [...new Set(result)]; + } -braces.parse = function(pattern, options) { - var proto = new Braces(options); - return proto.parse(pattern, options); + return result; }; /** - * Compile the given `ast` or string with the given `options`. + * Processes a brace pattern and returns either an expanded array + * (if `options.expand` is true), a highly optimized regex-compatible string. + * This method is called by the main [braces](#braces) function. * * ```js - * var braces = require('braces'); - * var ast = braces.parse('a/{b,c}/d'); - * console.log(braces.compile(ast)); - * // { options: { source: 'string' }, - * // state: {}, - * // compilers: - * // { eos: [Function], - * // noop: [Function], - * // bos: [Function], - * // brace: [Function], - * // 'brace.open': [Function], - * // text: [Function], - * // 'brace.close': [Function] }, - * // output: [ 'a/(b|c)/d' ], - * // ast: - * // { ... }, - * // parsingErrors: [] } + * const braces = require('braces'); + * console.log(braces.create('user-{200..300}/project-{a,b,c}-{1..10}')) + * //=> 'user-(20[0-9]|2[1-9][0-9]|300)/project-(a|b|c)-([1-9]|10)' * ``` - * @param {Object|String} `ast` AST from [.parse](#parse). If a string is passed it will be parsed first. + * @param {String} `pattern` Brace pattern * @param {Object} `options` - * @return {Object} Returns an object that has an `output` property with the compiled string. - * @api public - */ - -braces.compile = function(ast, options) { - var proto = new Braces(options); - return proto.compile(ast, options); -}; - -/** - * Clear the regex cache. - * - * ```js - * braces.clearCache(); - * ``` + * @return {Array} Returns an array of expanded values. * @api public */ -braces.clearCache = function() { - cache = braces.cache = {}; -}; - -/** - * Memoize a generated regex or function. A unique key is generated - * from the method name, pattern, and user-defined options. Set - * options.memoize to false to disable. - */ - -function memoize(type, pattern, options, fn) { - var key = utils.createKey(type + ':' + pattern, options); - var disabled = options && options.cache === false; - if (disabled) { - braces.clearCache(); - return fn(pattern, options); - } - - if (cache.hasOwnProperty(key)) { - return cache[key]; +braces.create = (input, options = {}) => { + if (input === '' || input.length < 3) { + return [input]; } - var res = fn(pattern, options); - cache[key] = res; - return res; -} - -/** - * Expose `Braces` constructor and methods - * @type {Function} - */ - -braces.Braces = Braces; -braces.compilers = compilers; -braces.parsers = parsers; -braces.cache = cache; + return options.expand !== true + ? braces.compile(input, options) + : braces.expand(input, options); +}; /** - * Expose `braces` - * @type {Function} + * Expose "braces" */ module.exports = braces; diff --git a/lib/braces.js b/lib/braces.js deleted file mode 100644 index baf6bf1..0000000 --- a/lib/braces.js +++ /dev/null @@ -1,104 +0,0 @@ -'use strict'; - -var extend = require('extend-shallow'); -var Snapdragon = require('snapdragon'); -var compilers = require('./compilers'); -var parsers = require('./parsers'); -var utils = require('./utils'); - -/** - * Customize Snapdragon parser and renderer - */ - -function Braces(options) { - this.options = extend({}, options); -} - -/** - * Initialize braces - */ - -Braces.prototype.init = function(options) { - if (this.isInitialized) return; - this.isInitialized = true; - var opts = utils.createOptions({}, this.options, options); - this.snapdragon = this.options.snapdragon || new Snapdragon(opts); - this.compiler = this.snapdragon.compiler; - this.parser = this.snapdragon.parser; - - compilers(this.snapdragon, opts); - parsers(this.snapdragon, opts); - - /** - * Call Snapdragon `.parse` method. When AST is returned, we check to - * see if any unclosed braces are left on the stack and, if so, we iterate - * over the stack and correct the AST so that compilers are called in the correct - * order and unbalance braces are properly escaped. - */ - - utils.define(this.snapdragon, 'parse', function(pattern, options) { - var parsed = Snapdragon.prototype.parse.apply(this, arguments); - this.parser.ast.input = pattern; - - var stack = this.parser.stack; - while (stack.length) { - addParent({type: 'brace.close', val: ''}, stack.pop()); - } - - function addParent(node, parent) { - utils.define(node, 'parent', parent); - parent.nodes.push(node); - } - - // add non-enumerable parser reference - utils.define(parsed, 'parser', this.parser); - return parsed; - }); -}; - -/** - * Decorate `.parse` method - */ - -Braces.prototype.parse = function(ast, options) { - if (ast && typeof ast === 'object' && ast.nodes) return ast; - this.init(options); - return this.snapdragon.parse(ast, options); -}; - -/** - * Decorate `.compile` method - */ - -Braces.prototype.compile = function(ast, options) { - if (typeof ast === 'string') { - ast = this.parse(ast, options); - } else { - this.init(options); - } - return this.snapdragon.compile(ast, options); -}; - -/** - * Expand - */ - -Braces.prototype.expand = function(pattern) { - var ast = this.parse(pattern, {expand: true}); - return this.compile(ast, {expand: true}); -}; - -/** - * Optimize - */ - -Braces.prototype.optimize = function(pattern) { - var ast = this.parse(pattern, {optimize: true}); - return this.compile(ast, {optimize: true}); -}; - -/** - * Expose `Braces` - */ - -module.exports = Braces; diff --git a/lib/compile.js b/lib/compile.js new file mode 100644 index 0000000..dce69be --- /dev/null +++ b/lib/compile.js @@ -0,0 +1,60 @@ +'use strict'; + +const fill = require('fill-range'); +const utils = require('./utils'); + +const compile = (ast, options = {}) => { + const walk = (node, parent = {}) => { + const invalidBlock = utils.isInvalidBrace(parent); + const invalidNode = node.invalid === true && options.escapeInvalid === true; + const invalid = invalidBlock === true || invalidNode === true; + const prefix = options.escapeInvalid === true ? '\\' : ''; + let output = ''; + + if (node.isOpen === true) { + return prefix + node.value; + } + + if (node.isClose === true) { + console.log('node.isClose', prefix, node.value); + return prefix + node.value; + } + + if (node.type === 'open') { + return invalid ? prefix + node.value : '('; + } + + if (node.type === 'close') { + return invalid ? prefix + node.value : ')'; + } + + if (node.type === 'comma') { + return node.prev.type === 'comma' ? '' : invalid ? node.value : '|'; + } + + if (node.value) { + return node.value; + } + + if (node.nodes && node.ranges > 0) { + const args = utils.reduce(node.nodes); + const range = fill(...args, { ...options, wrap: false, toRegex: true, strictZeros: true }); + + if (range.length !== 0) { + return args.length > 1 && range.length > 1 ? `(${range})` : range; + } + } + + if (node.nodes) { + for (const child of node.nodes) { + output += walk(child, node); + } + } + + return output; + }; + + return walk(ast); +}; + +module.exports = compile; diff --git a/lib/compilers.js b/lib/compilers.js deleted file mode 100644 index a3b820e..0000000 --- a/lib/compilers.js +++ /dev/null @@ -1,282 +0,0 @@ -'use strict'; - -var utils = require('./utils'); - -module.exports = function(braces, options) { - braces.compiler - - /** - * bos - */ - - .set('bos', function() { - if (this.output) return; - this.ast.queue = isEscaped(this.ast) ? [this.ast.val] : []; - this.ast.count = 1; - }) - - /** - * Square brackets - */ - - .set('bracket', function(node) { - var close = node.close; - var open = !node.escaped ? '[' : '\\['; - var negated = node.negated; - var inner = node.inner; - - inner = inner.replace(/\\(?=[\\\w]|$)/g, '\\\\'); - if (inner === ']-') { - inner = '\\]\\-'; - } - - if (negated && inner.indexOf('.') === -1) { - inner += '.'; - } - if (negated && inner.indexOf('/') === -1) { - inner += '/'; - } - - var val = open + negated + inner + close; - var queue = node.parent.queue; - var last = utils.arrayify(queue.pop()); - - queue.push(utils.join(last, val)); - queue.push.apply(queue, []); - }) - - /** - * Brace - */ - - .set('brace', function(node) { - node.queue = isEscaped(node) ? [node.val] : []; - node.count = 1; - return this.mapVisit(node.nodes); - }) - - /** - * Open - */ - - .set('brace.open', function(node) { - node.parent.open = node.val; - }) - - /** - * Inner - */ - - .set('text', function(node) { - var queue = node.parent.queue; - var escaped = node.escaped; - var segs = [node.val]; - - if (node.optimize === false) { - options = utils.extend({}, options, {optimize: false}); - } - - if (node.multiplier > 1) { - node.parent.count *= node.multiplier; - } - - if (options.quantifiers === true && utils.isQuantifier(node.val)) { - escaped = true; - - } else if (node.val.length > 1) { - if (isType(node.parent, 'brace') && !isEscaped(node)) { - var expanded = utils.expand(node.val, options); - segs = expanded.segs; - - if (expanded.isOptimized) { - node.parent.isOptimized = true; - } - - // if nothing was expanded, we probably have a literal brace - if (!segs.length) { - var val = (expanded.val || node.val); - if (options.unescape !== false) { - // unescape unexpanded brace sequence/set separators - val = val.replace(/\\([,.])/g, '$1'); - // strip quotes - val = val.replace(/["'`]/g, ''); - } - - segs = [val]; - escaped = true; - } - } - - } else if (node.val === ',') { - if (options.expand) { - node.parent.queue.push(['']); - segs = ['']; - } else { - segs = ['|']; - } - } else { - escaped = true; - } - - if (escaped && isType(node.parent, 'brace')) { - if (node.parent.nodes.length <= 4 && node.parent.count === 1) { - node.parent.escaped = true; - } else if (node.parent.length <= 3) { - node.parent.escaped = true; - } - } - - if (!hasQueue(node.parent)) { - node.parent.queue = segs; - return; - } - - var last = utils.arrayify(queue.pop()); - if (node.parent.count > 1 && options.expand) { - last = multiply(last, node.parent.count); - node.parent.count = 1; - } - - queue.push(utils.join(utils.flatten(last), segs.shift())); - queue.push.apply(queue, segs); - }) - - /** - * Close - */ - - .set('brace.close', function(node) { - var queue = node.parent.queue; - var prev = node.parent.parent; - var last = prev.queue.pop(); - var open = node.parent.open; - var close = node.val; - - if (open && close && isOptimized(node, options)) { - open = '('; - close = ')'; - } - - // if a close brace exists, and the previous segment is one character - // don't wrap the result in braces or parens - var ele = utils.last(queue); - if (node.parent.count > 1 && options.expand) { - ele = multiply(queue.pop(), node.parent.count); - node.parent.count = 1; - queue.push(ele); - } - - if (close && typeof ele === 'string' && ele.length === 1) { - open = ''; - close = ''; - } - - if ((isLiteralBrace(node, options) || noInner(node)) && !node.parent.hasEmpty) { - queue.push(utils.join(open, queue.pop() || '')); - queue = utils.flatten(utils.join(queue, close)); - } - - if (typeof last === 'undefined') { - prev.queue = [queue]; - } else { - prev.queue.push(utils.flatten(utils.join(last, queue))); - } - }) - - /** - * eos - */ - - .set('eos', function(node) { - if (this.input) return; - - if (options.optimize !== false) { - this.output = utils.last(utils.flatten(this.ast.queue)); - } else if (Array.isArray(utils.last(this.ast.queue))) { - this.output = utils.flatten(this.ast.queue.pop()); - } else { - this.output = utils.flatten(this.ast.queue); - } - - if (node.parent.count > 1 && options.expand) { - this.output = multiply(this.output, node.parent.count); - } - - this.output = utils.arrayify(this.output); - this.ast.queue = []; - }); - -}; - -/** - * Multiply the segments in the current brace level - */ - -function multiply(queue, n, options) { - return utils.flatten(utils.repeat(utils.arrayify(queue), n)); -} - -/** - * Return true if `node` is escaped - */ - -function isEscaped(node) { - return node.escaped === true; -} - -/** - * Returns true if regex parens should be used for sets. If the parent `type` - * is not `brace`, then we're on a root node, which means we should never - * expand segments and open/close braces should be `{}` (since this indicates - * a brace is missing from the set) - */ - -function isOptimized(node, options) { - if (node.parent.isOptimized) return true; - return isType(node.parent, 'brace') - && !isEscaped(node.parent) - && options.expand !== true; -} - -/** - * Returns true if the value in `node` should be wrapped in a literal brace. - * @return {Boolean} - */ - -function isLiteralBrace(node, options) { - return isEscaped(node.parent) || options.optimize !== false; -} - -/** - * Returns true if the given `node` does not have an inner value. - * @return {Boolean} - */ - -function noInner(node, type) { - if (node.parent.queue.length === 1) { - return true; - } - var nodes = node.parent.nodes; - return nodes.length === 3 - && isType(nodes[0], 'brace.open') - && !isType(nodes[1], 'text') - && isType(nodes[2], 'brace.close'); -} - -/** - * Returns true if the given `node` is the given `type` - * @return {Boolean} - */ - -function isType(node, type) { - return typeof node !== 'undefined' && node.type === type; -} - -/** - * Returns true if the given `node` has a non-empty queue. - * @return {Boolean} - */ - -function hasQueue(node) { - return Array.isArray(node.queue) && node.queue.length; -} diff --git a/lib/constants.js b/lib/constants.js new file mode 100644 index 0000000..2bb3b88 --- /dev/null +++ b/lib/constants.js @@ -0,0 +1,57 @@ +'use strict'; + +module.exports = { + MAX_LENGTH: 10000, + + // Digits + CHAR_0: '0', /* 0 */ + CHAR_9: '9', /* 9 */ + + // Alphabet chars. + CHAR_UPPERCASE_A: 'A', /* A */ + CHAR_LOWERCASE_A: 'a', /* a */ + CHAR_UPPERCASE_Z: 'Z', /* Z */ + CHAR_LOWERCASE_Z: 'z', /* z */ + + CHAR_LEFT_PARENTHESES: '(', /* ( */ + CHAR_RIGHT_PARENTHESES: ')', /* ) */ + + CHAR_ASTERISK: '*', /* * */ + + // Non-alphabetic chars. + CHAR_AMPERSAND: '&', /* & */ + CHAR_AT: '@', /* @ */ + CHAR_BACKSLASH: '\\', /* \ */ + CHAR_BACKTICK: '`', /* ` */ + CHAR_CARRIAGE_RETURN: '\r', /* \r */ + CHAR_CIRCUMFLEX_ACCENT: '^', /* ^ */ + CHAR_COLON: ':', /* : */ + CHAR_COMMA: ',', /* , */ + CHAR_DOLLAR: '$', /* . */ + CHAR_DOT: '.', /* . */ + CHAR_DOUBLE_QUOTE: '"', /* " */ + CHAR_EQUAL: '=', /* = */ + CHAR_EXCLAMATION_MARK: '!', /* ! */ + CHAR_FORM_FEED: '\f', /* \f */ + CHAR_FORWARD_SLASH: '/', /* / */ + CHAR_HASH: '#', /* # */ + CHAR_HYPHEN_MINUS: '-', /* - */ + CHAR_LEFT_ANGLE_BRACKET: '<', /* < */ + CHAR_LEFT_CURLY_BRACE: '{', /* { */ + CHAR_LEFT_SQUARE_BRACKET: '[', /* [ */ + CHAR_LINE_FEED: '\n', /* \n */ + CHAR_NO_BREAK_SPACE: '\u00A0', /* \u00A0 */ + CHAR_PERCENT: '%', /* % */ + CHAR_PLUS: '+', /* + */ + CHAR_QUESTION_MARK: '?', /* ? */ + CHAR_RIGHT_ANGLE_BRACKET: '>', /* > */ + CHAR_RIGHT_CURLY_BRACE: '}', /* } */ + CHAR_RIGHT_SQUARE_BRACKET: ']', /* ] */ + CHAR_SEMICOLON: ';', /* ; */ + CHAR_SINGLE_QUOTE: '\'', /* ' */ + CHAR_SPACE: ' ', /* */ + CHAR_TAB: '\t', /* \t */ + CHAR_UNDERSCORE: '_', /* _ */ + CHAR_VERTICAL_LINE: '|', /* | */ + CHAR_ZERO_WIDTH_NOBREAK_SPACE: '\uFEFF' /* \uFEFF */ +}; diff --git a/lib/expand.js b/lib/expand.js new file mode 100644 index 0000000..35b2c41 --- /dev/null +++ b/lib/expand.js @@ -0,0 +1,113 @@ +'use strict'; + +const fill = require('fill-range'); +const stringify = require('./stringify'); +const utils = require('./utils'); + +const append = (queue = '', stash = '', enclose = false) => { + const result = []; + + queue = [].concat(queue); + stash = [].concat(stash); + + if (!stash.length) return queue; + if (!queue.length) { + return enclose ? utils.flatten(stash).map(ele => `{${ele}}`) : stash; + } + + for (const item of queue) { + if (Array.isArray(item)) { + for (const value of item) { + result.push(append(value, stash, enclose)); + } + } else { + for (let ele of stash) { + if (enclose === true && typeof ele === 'string') ele = `{${ele}}`; + result.push(Array.isArray(ele) ? append(item, ele, enclose) : item + ele); + } + } + } + return utils.flatten(result); +}; + +const expand = (ast, options = {}) => { + const rangeLimit = options.rangeLimit === undefined ? 1000 : options.rangeLimit; + + const walk = (node, parent = {}) => { + node.queue = []; + + let p = parent; + let q = parent.queue; + + while (p.type !== 'brace' && p.type !== 'root' && p.parent) { + p = p.parent; + q = p.queue; + } + + if (node.invalid || node.dollar) { + q.push(append(q.pop(), stringify(node, options))); + return; + } + + if (node.type === 'brace' && node.invalid !== true && node.nodes.length === 2) { + q.push(append(q.pop(), ['{}'])); + return; + } + + if (node.nodes && node.ranges > 0) { + const args = utils.reduce(node.nodes); + + if (utils.exceedsLimit(...args, options.step, rangeLimit)) { + throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.'); + } + + let range = fill(...args, options); + if (range.length === 0) { + range = stringify(node, options); + } + + q.push(append(q.pop(), range)); + node.nodes = []; + return; + } + + const enclose = utils.encloseBrace(node); + let queue = node.queue; + let block = node; + + while (block.type !== 'brace' && block.type !== 'root' && block.parent) { + block = block.parent; + queue = block.queue; + } + + for (let i = 0; i < node.nodes.length; i++) { + const child = node.nodes[i]; + + if (child.type === 'comma' && node.type === 'brace') { + if (i === 1) queue.push(''); + queue.push(''); + continue; + } + + if (child.type === 'close') { + q.push(append(q.pop(), queue, enclose)); + continue; + } + + if (child.value && child.type !== 'open') { + queue.push(append(queue.pop(), child.value)); + continue; + } + + if (child.nodes) { + walk(child, node); + } + } + + return queue; + }; + + return utils.flatten(walk(ast)); +}; + +module.exports = expand; diff --git a/lib/parse.js b/lib/parse.js new file mode 100644 index 0000000..3a6988e --- /dev/null +++ b/lib/parse.js @@ -0,0 +1,331 @@ +'use strict'; + +const stringify = require('./stringify'); + +/** + * Constants + */ + +const { + MAX_LENGTH, + CHAR_BACKSLASH, /* \ */ + CHAR_BACKTICK, /* ` */ + CHAR_COMMA, /* , */ + CHAR_DOT, /* . */ + CHAR_LEFT_PARENTHESES, /* ( */ + CHAR_RIGHT_PARENTHESES, /* ) */ + CHAR_LEFT_CURLY_BRACE, /* { */ + CHAR_RIGHT_CURLY_BRACE, /* } */ + CHAR_LEFT_SQUARE_BRACKET, /* [ */ + CHAR_RIGHT_SQUARE_BRACKET, /* ] */ + CHAR_DOUBLE_QUOTE, /* " */ + CHAR_SINGLE_QUOTE, /* ' */ + CHAR_NO_BREAK_SPACE, + CHAR_ZERO_WIDTH_NOBREAK_SPACE +} = require('./constants'); + +/** + * parse + */ + +const parse = (input, options = {}) => { + if (typeof input !== 'string') { + throw new TypeError('Expected a string'); + } + + const opts = options || {}; + const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + if (input.length > max) { + throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`); + } + + const ast = { type: 'root', input, nodes: [] }; + const stack = [ast]; + let block = ast; + let prev = ast; + let brackets = 0; + const length = input.length; + let index = 0; + let depth = 0; + let value; + + /** + * Helpers + */ + + const advance = () => input[index++]; + const push = node => { + if (node.type === 'text' && prev.type === 'dot') { + prev.type = 'text'; + } + + if (prev && prev.type === 'text' && node.type === 'text') { + prev.value += node.value; + return; + } + + block.nodes.push(node); + node.parent = block; + node.prev = prev; + prev = node; + return node; + }; + + push({ type: 'bos' }); + + while (index < length) { + block = stack[stack.length - 1]; + value = advance(); + + /** + * Invalid chars + */ + + if (value === CHAR_ZERO_WIDTH_NOBREAK_SPACE || value === CHAR_NO_BREAK_SPACE) { + continue; + } + + /** + * Escaped chars + */ + + if (value === CHAR_BACKSLASH) { + push({ type: 'text', value: (options.keepEscaping ? value : '') + advance() }); + continue; + } + + /** + * Right square bracket (literal): ']' + */ + + if (value === CHAR_RIGHT_SQUARE_BRACKET) { + push({ type: 'text', value: '\\' + value }); + continue; + } + + /** + * Left square bracket: '[' + */ + + if (value === CHAR_LEFT_SQUARE_BRACKET) { + brackets++; + + let next; + + while (index < length && (next = advance())) { + value += next; + + if (next === CHAR_LEFT_SQUARE_BRACKET) { + brackets++; + continue; + } + + if (next === CHAR_BACKSLASH) { + value += advance(); + continue; + } + + if (next === CHAR_RIGHT_SQUARE_BRACKET) { + brackets--; + + if (brackets === 0) { + break; + } + } + } + + push({ type: 'text', value }); + continue; + } + + /** + * Parentheses + */ + + if (value === CHAR_LEFT_PARENTHESES) { + block = push({ type: 'paren', nodes: [] }); + stack.push(block); + push({ type: 'text', value }); + continue; + } + + if (value === CHAR_RIGHT_PARENTHESES) { + if (block.type !== 'paren') { + push({ type: 'text', value }); + continue; + } + block = stack.pop(); + push({ type: 'text', value }); + block = stack[stack.length - 1]; + continue; + } + + /** + * Quotes: '|"|` + */ + + if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) { + const open = value; + let next; + + if (options.keepQuotes !== true) { + value = ''; + } + + while (index < length && (next = advance())) { + if (next === CHAR_BACKSLASH) { + value += next + advance(); + continue; + } + + if (next === open) { + if (options.keepQuotes === true) value += next; + break; + } + + value += next; + } + + push({ type: 'text', value }); + continue; + } + + /** + * Left curly brace: '{' + */ + + if (value === CHAR_LEFT_CURLY_BRACE) { + depth++; + + const dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true; + const brace = { + type: 'brace', + open: true, + close: false, + dollar, + depth, + commas: 0, + ranges: 0, + nodes: [] + }; + + block = push(brace); + stack.push(block); + push({ type: 'open', value }); + continue; + } + + /** + * Right curly brace: '}' + */ + + if (value === CHAR_RIGHT_CURLY_BRACE) { + if (block.type !== 'brace') { + push({ type: 'text', value }); + continue; + } + + const type = 'close'; + block = stack.pop(); + block.close = true; + + push({ type, value }); + depth--; + + block = stack[stack.length - 1]; + continue; + } + + /** + * Comma: ',' + */ + + if (value === CHAR_COMMA && depth > 0) { + if (block.ranges > 0) { + block.ranges = 0; + const open = block.nodes.shift(); + block.nodes = [open, { type: 'text', value: stringify(block) }]; + } + + push({ type: 'comma', value }); + block.commas++; + continue; + } + + /** + * Dot: '.' + */ + + if (value === CHAR_DOT && depth > 0 && block.commas === 0) { + const siblings = block.nodes; + + if (depth === 0 || siblings.length === 0) { + push({ type: 'text', value }); + continue; + } + + if (prev.type === 'dot') { + block.range = []; + prev.value += value; + prev.type = 'range'; + + if (block.nodes.length !== 3 && block.nodes.length !== 5) { + block.invalid = true; + block.ranges = 0; + prev.type = 'text'; + continue; + } + + block.ranges++; + block.args = []; + continue; + } + + if (prev.type === 'range') { + siblings.pop(); + + const before = siblings[siblings.length - 1]; + before.value += prev.value + value; + prev = before; + block.ranges--; + continue; + } + + push({ type: 'dot', value }); + continue; + } + + /** + * Text + */ + + push({ type: 'text', value }); + } + + // Mark imbalanced braces and brackets as invalid + do { + block = stack.pop(); + + if (block.type !== 'root') { + block.nodes.forEach(node => { + if (!node.nodes) { + if (node.type === 'open') node.isOpen = true; + if (node.type === 'close') node.isClose = true; + if (!node.nodes) node.type = 'text'; + node.invalid = true; + } + }); + + // get the location of the block on parent.nodes (block's siblings) + const parent = stack[stack.length - 1]; + const index = parent.nodes.indexOf(block); + // replace the (invalid) block with it's nodes + parent.nodes.splice(index, 1, ...block.nodes); + } + } while (stack.length > 0); + + push({ type: 'eos' }); + return ast; +}; + +module.exports = parse; diff --git a/lib/parsers.js b/lib/parsers.js deleted file mode 100644 index 8bf3e92..0000000 --- a/lib/parsers.js +++ /dev/null @@ -1,360 +0,0 @@ -'use strict'; - -var Node = require('snapdragon-node'); -var utils = require('./utils'); - -/** - * Braces parsers - */ - -module.exports = function(braces, options) { - braces.parser - .set('bos', function() { - if (!this.parsed) { - this.ast = this.nodes[0] = new Node(this.ast); - } - }) - - /** - * Character parsers - */ - - .set('escape', function() { - var pos = this.position(); - var m = this.match(/^(?:\\(.)|\$\{)/); - if (!m) return; - - var prev = this.prev(); - var last = utils.last(prev.nodes); - - var node = pos(new Node({ - type: 'text', - multiplier: 1, - val: m[0] - })); - - if (node.val === '\\\\') { - return node; - } - - if (node.val === '${') { - var str = this.input; - var idx = -1; - var ch; - - while ((ch = str[++idx])) { - this.consume(1); - node.val += ch; - if (ch === '\\') { - node.val += str[++idx]; - continue; - } - if (ch === '}') { - break; - } - } - } - - if (this.options.unescape !== false) { - node.val = node.val.replace(/\\([{}])/g, '$1'); - } - - if (last.val === '"' && this.input.charAt(0) === '"') { - last.val = node.val; - this.consume(1); - return; - } - - return concatNodes.call(this, pos, node, prev, options); - }) - - /** - * Brackets: "[...]" (basic, this is overridden by - * other parsers in more advanced implementations) - */ - - .set('bracket', function() { - var isInside = this.isInside('brace'); - var pos = this.position(); - var m = this.match(/^(?:\[([!^]?)([^\]]{2,}|\]-)(\]|[^*+?]+)|\[)/); - if (!m) return; - - var prev = this.prev(); - var val = m[0]; - var negated = m[1] ? '^' : ''; - var inner = m[2] || ''; - var close = m[3] || ''; - - if (isInside && prev.type === 'brace') { - prev.text = prev.text || ''; - prev.text += val; - } - - var esc = this.input.slice(0, 2); - if (inner === '' && esc === '\\]') { - inner += esc; - this.consume(2); - - var str = this.input; - var idx = -1; - var ch; - - while ((ch = str[++idx])) { - this.consume(1); - if (ch === ']') { - close = ch; - break; - } - inner += ch; - } - } - - return pos(new Node({ - type: 'bracket', - val: val, - escaped: close !== ']', - negated: negated, - inner: inner, - close: close - })); - }) - - /** - * Empty braces (we capture these early to - * speed up processing in the compiler) - */ - - .set('multiplier', function() { - var isInside = this.isInside('brace'); - var pos = this.position(); - var m = this.match(/^\{((?:,|\{,+\})+)\}/); - if (!m) return; - - this.multiplier = true; - var prev = this.prev(); - var val = m[0]; - - if (isInside && prev.type === 'brace') { - prev.text = prev.text || ''; - prev.text += val; - } - - var node = pos(new Node({ - type: 'text', - multiplier: 1, - match: m, - val: val - })); - - return concatNodes.call(this, pos, node, prev, options); - }) - - /** - * Open - */ - - .set('brace.open', function() { - var pos = this.position(); - var m = this.match(/^\{(?!(?:[^\\}]?|,+)\})/); - if (!m) return; - - var prev = this.prev(); - var last = utils.last(prev.nodes); - - // if the last parsed character was an extglob character - // we need to _not optimize_ the brace pattern because - // it might be mistaken for an extglob by a downstream parser - if (last && last.val && isExtglobChar(last.val.slice(-1))) { - last.optimize = false; - } - - var open = pos(new Node({ - type: 'brace.open', - val: m[0] - })); - - var node = pos(new Node({ - type: 'brace', - nodes: [] - })); - - node.push(open); - prev.push(node); - this.push('brace', node); - }) - - /** - * Close - */ - - .set('brace.close', function() { - var pos = this.position(); - var m = this.match(/^\}/); - if (!m || !m[0]) return; - - var brace = this.pop('brace'); - var node = pos(new Node({ - type: 'brace.close', - val: m[0] - })); - - if (!this.isType(brace, 'brace')) { - if (this.options.strict) { - throw new Error('missing opening "{"'); - } - node.type = 'text'; - node.multiplier = 0; - node.escaped = true; - return node; - } - - var prev = this.prev(); - var last = utils.last(prev.nodes); - if (last.text) { - var lastNode = utils.last(last.nodes); - if (lastNode.val === ')' && /[!@*?+]\(/.test(last.text)) { - var open = last.nodes[0]; - var text = last.nodes[1]; - if (open.type === 'brace.open' && text && text.type === 'text') { - text.optimize = false; - } - } - } - - if (brace.nodes.length > 2) { - var first = brace.nodes[1]; - if (first.type === 'text' && first.val === ',') { - brace.nodes.splice(1, 1); - brace.nodes.push(first); - } - } - - brace.push(node); - }) - - /** - * Capture boundary characters - */ - - .set('boundary', function() { - var pos = this.position(); - var m = this.match(/^[$^](?!\{)/); - if (!m) return; - return pos(new Node({ - type: 'text', - val: m[0] - })); - }) - - /** - * One or zero, non-comma characters wrapped in braces - */ - - .set('nobrace', function() { - var isInside = this.isInside('brace'); - var pos = this.position(); - var m = this.match(/^\{[^,]?\}/); - if (!m) return; - - var prev = this.prev(); - var val = m[0]; - - if (isInside && prev.type === 'brace') { - prev.text = prev.text || ''; - prev.text += val; - } - - return pos(new Node({ - type: 'text', - multiplier: 0, - val: val - })); - }) - - /** - * Text - */ - - .set('text', function() { - var isInside = this.isInside('brace'); - var pos = this.position(); - var m = this.match(/^((?!\\)[^${}[\]])+/); - if (!m) return; - - var prev = this.prev(); - var val = m[0]; - - if (isInside && prev.type === 'brace') { - prev.text = prev.text || ''; - prev.text += val; - } - - var node = pos(new Node({ - type: 'text', - multiplier: 1, - val: val - })); - - return concatNodes.call(this, pos, node, prev, options); - }); -}; - -/** - * Returns true if the character is an extglob character. - */ - -function isExtglobChar(ch) { - return ch === '!' || ch === '@' || ch === '*' || ch === '?' || ch === '+'; -} - -/** - * Combine text nodes, and calculate empty sets (`{,,}`) - * @param {Function} `pos` Function to calculate node position - * @param {Object} `node` AST node - * @return {Object} - */ - -function concatNodes(pos, node, parent, options) { - node.orig = node.val; - var prev = this.prev(); - var last = utils.last(prev.nodes); - var isEscaped = false; - - if (node.val.length > 1) { - var a = node.val.charAt(0); - var b = node.val.slice(-1); - - isEscaped = (a === '"' && b === '"') - || (a === "'" && b === "'") - || (a === '`' && b === '`'); - } - - if (isEscaped && options.unescape !== false) { - node.val = node.val.slice(1, node.val.length - 1); - node.escaped = true; - } - - if (node.match) { - var match = node.match[1]; - if (!match || match.indexOf('}') === -1) { - match = node.match[0]; - } - - // replace each set with a single "," - var val = match.replace(/\{/g, ',').replace(/\}/g, ''); - node.multiplier *= val.length; - node.val = ''; - } - - var simpleText = last.type === 'text' - && last.multiplier === 1 - && node.multiplier === 1 - && node.val; - - if (simpleText) { - last.val += node.val; - return; - } - - prev.push(node); -} diff --git a/lib/stringify.js b/lib/stringify.js new file mode 100644 index 0000000..8bcf872 --- /dev/null +++ b/lib/stringify.js @@ -0,0 +1,32 @@ +'use strict'; + +const utils = require('./utils'); + +module.exports = (ast, options = {}) => { + const stringify = (node, parent = {}) => { + const invalidBlock = options.escapeInvalid && utils.isInvalidBrace(parent); + const invalidNode = node.invalid === true && options.escapeInvalid === true; + let output = ''; + + if (node.value) { + if ((invalidBlock || invalidNode) && utils.isOpenOrClose(node)) { + return '\\' + node.value; + } + return node.value; + } + + if (node.value) { + return node.value; + } + + if (node.nodes) { + for (const child of node.nodes) { + output += stringify(child); + } + } + return output; + }; + + return stringify(ast); +}; + diff --git a/lib/utils.js b/lib/utils.js index 4716671..d19311f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,343 +1,122 @@ 'use strict'; -var splitString = require('split-string'); -var utils = module.exports; - -/** - * Module dependencies - */ - -utils.extend = require('extend-shallow'); -utils.flatten = require('arr-flatten'); -utils.isObject = require('isobject'); -utils.fillRange = require('fill-range'); -utils.repeat = require('repeat-element'); -utils.unique = require('array-unique'); - -utils.define = function(obj, key, val) { - Object.defineProperty(obj, key, { - writable: true, - configurable: true, - enumerable: false, - value: val - }); +exports.isInteger = num => { + if (typeof num === 'number') { + return Number.isInteger(num); + } + if (typeof num === 'string' && num.trim() !== '') { + return Number.isInteger(Number(num)); + } + return false; }; /** - * Returns true if the given string contains only empty brace sets. + * Find a node of the given type */ -utils.isEmptySets = function(str) { - return /^(?:\{,\})+$/.test(str); -}; +exports.find = (node, type) => node.nodes.find(node => node.type === type); /** - * Returns true if the given string contains only empty brace sets. + * Find a node of the given type */ -utils.isQuotedString = function(str) { - var open = str.charAt(0); - if (open === '\'' || open === '"' || open === '`') { - return str.slice(-1) === open; - } - return false; +exports.exceedsLimit = (min, max, step = 1, limit) => { + if (limit === false) return false; + if (!exports.isInteger(min) || !exports.isInteger(max)) return false; + return ((Number(max) - Number(min)) / Number(step)) >= limit; }; /** - * Create the key to use for memoization. The unique key is generated - * by iterating over the options and concatenating key-value pairs - * to the pattern string. + * Escape the given node with '\\' before node.value */ -utils.createKey = function(pattern, options) { - var id = pattern; - if (typeof options === 'undefined') { - return id; - } - var keys = Object.keys(options); - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - id += ';' + key + '=' + String(options[key]); +exports.escapeNode = (block, n = 0, type) => { + const node = block.nodes[n]; + if (!node) return; + + if ((type && node.type === type) || node.type === 'open' || node.type === 'close') { + if (node.escaped !== true) { + node.value = '\\' + node.value; + node.escaped = true; + } } - return id; }; /** - * Normalize options + * Returns true if the given brace node should be enclosed in literal braces */ -utils.createOptions = function(options) { - var opts = utils.extend.apply(null, arguments); - if (typeof opts.expand === 'boolean') { - opts.optimize = !opts.expand; - } - if (typeof opts.optimize === 'boolean') { - opts.expand = !opts.optimize; +exports.encloseBrace = node => { + if (node.type !== 'brace') return false; + if ((node.commas >> 0 + node.ranges >> 0) === 0) { + node.invalid = true; + return true; } - if (opts.optimize === true) { - opts.makeRe = true; - } - return opts; + return false; }; /** - * Join patterns in `a` to patterns in `b` + * Returns true if a brace node is invalid. */ -utils.join = function(a, b, options) { - options = options || {}; - a = utils.arrayify(a); - b = utils.arrayify(b); - - if (!a.length) return b; - if (!b.length) return a; - - var len = a.length; - var idx = -1; - var arr = []; - - while (++idx < len) { - var val = a[idx]; - if (Array.isArray(val)) { - for (var i = 0; i < val.length; i++) { - val[i] = utils.join(val[i], b, options); - } - arr.push(val); - continue; - } - - for (var j = 0; j < b.length; j++) { - var bval = b[j]; - - if (Array.isArray(bval)) { - arr.push(utils.join(val, bval, options)); - } else { - arr.push(val + bval); - } - } +exports.isInvalidBrace = block => { + if (block.type !== 'brace') return false; + if (block.invalid === true || block.dollar) return true; + if ((block.commas >> 0 + block.ranges >> 0) === 0) { + block.invalid = true; + return true; } - return arr; + if (block.open !== true || block.close !== true) { + block.invalid = true; + return true; + } + return false; }; /** - * Split the given string on `,` if not escaped. + * Returns true if a node is an open or close node */ -utils.split = function(str, options) { - var opts = utils.extend({sep: ','}, options); - if (typeof opts.keepQuotes !== 'boolean') { - opts.keepQuotes = true; - } - if (opts.unescape === false) { - opts.keepEscaping = true; +exports.isOpenOrClose = node => { + if (node.type === 'open' || node.type === 'close') { + return true; } - return splitString(str, opts, utils.escapeBrackets(opts)); + return node.open === true || node.close === true; }; /** - * Expand ranges or sets in the given `pattern`. - * - * @param {String} `str` - * @param {Object} `options` - * @return {Object} + * Reduce an array of text nodes. */ -utils.expand = function(str, options) { - var opts = utils.extend({rangeLimit: 10000}, options); - var segs = utils.split(str, opts); - var tok = { segs: segs }; - - if (utils.isQuotedString(str)) { - return tok; - } - - if (opts.rangeLimit === true) { - opts.rangeLimit = 10000; - } - - if (segs.length > 1) { - if (opts.optimize === false) { - tok.val = segs[0]; - return tok; - } - - tok.segs = utils.stringifyArray(tok.segs); - } else if (segs.length === 1) { - var arr = str.split('..'); - - if (arr.length === 1) { - tok.val = tok.segs[tok.segs.length - 1] || tok.val || str; - tok.segs = []; - return tok; - } - - if (arr.length === 2 && arr[0] === arr[1]) { - tok.escaped = true; - tok.val = arr[0]; - tok.segs = []; - return tok; - } - - if (arr.length > 1) { - if (opts.optimize !== false) { - opts.optimize = true; - delete opts.expand; - } - - if (opts.optimize !== true) { - var min = Math.min(arr[0], arr[1]); - var max = Math.max(arr[0], arr[1]); - var step = arr[2] || 1; - - if (opts.rangeLimit !== false && ((max - min) / step >= opts.rangeLimit)) { - throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.'); - } - } - - arr.push(opts); - tok.segs = utils.fillRange.apply(null, arr); - - if (!tok.segs.length) { - tok.escaped = true; - tok.val = str; - return tok; - } - - if (opts.optimize === true) { - tok.segs = utils.stringifyArray(tok.segs); - } - - if (tok.segs === '') { - tok.val = str; - } else { - tok.val = tok.segs[0]; - } - return tok; - } - } else { - tok.val = str; - } - return tok; -}; +exports.reduce = nodes => nodes.reduce((acc, node) => { + if (node.type === 'text') acc.push(node.value); + if (node.type === 'range') node.type = 'text'; + return acc; +}, []); /** - * Ensure commas inside brackets and parens are not split. - * @param {Object} `tok` Token from the `split-string` module - * @return {undefined} + * Flatten an array */ -utils.escapeBrackets = function(options) { - return function(tok) { - if (tok.escaped && tok.val === 'b') { - tok.val = '\\b'; - return; - } - - if (tok.val !== '(' && tok.val !== '[') return; - var opts = utils.extend({}, options); - var brackets = []; - var parens = []; - var stack = []; - var val = tok.val; - var str = tok.str; - var i = tok.idx - 1; +exports.flatten = (...args) => { + const result = []; - while (++i < str.length) { - var ch = str[i]; + const flat = arr => { + for (let i = 0; i < arr.length; i++) { + const ele = arr[i]; - if (ch === '\\') { - val += (opts.keepEscaping === false ? '' : ch) + str[++i]; + if (Array.isArray(ele)) { + flat(ele); continue; } - if (ch === '(') { - parens.push(ch); - stack.push(ch); + if (ele !== undefined) { + result.push(ele); } - - if (ch === '[') { - brackets.push(ch); - stack.push(ch); - } - - if (ch === ')') { - parens.pop(); - stack.pop(); - if (!stack.length) { - val += ch; - break; - } - } - - if (ch === ']') { - brackets.pop(); - stack.pop(); - if (!stack.length) { - val += ch; - break; - } - } - val += ch; } - - tok.split = false; - tok.val = val.slice(1); - tok.idx = i; + return result; }; -}; - -/** - * Returns true if the given string looks like a regex quantifier - * @return {Boolean} - */ - -utils.isQuantifier = function(str) { - return /^(?:[0-9]?,[0-9]|[0-9],)$/.test(str); -}; - -/** - * Cast `val` to an array. - * @param {*} `val` - */ - -utils.stringifyArray = function(arr) { - return [utils.arrayify(arr).join('|')]; -}; - -/** - * Cast `val` to an array. - * @param {*} `val` - */ - -utils.arrayify = function(arr) { - if (typeof arr === 'undefined') { - return []; - } - if (typeof arr === 'string') { - return [arr]; - } - return arr; -}; - -/** - * Returns true if the given `str` is a non-empty string - * @return {Boolean} - */ - -utils.isString = function(str) { - return str != null && typeof str === 'string'; -}; - -/** - * Get the last element from `array` - * @param {Array} `array` - * @return {*} - */ - -utils.last = function(arr, n) { - return arr[arr.length - (n || 1)]; -}; -utils.escapeRegex = function(str) { - return str.replace(/\\?([!^*?()[\]{}+?/])/g, '\\$1'); + flat(args); + return result; }; diff --git a/package.json b/package.json index 8aa90c8..c3c056e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "braces", "description": "Bash-like brace expansion, implemented in JavaScript. Safer than other brace expansion libs, with complete support for the Bash 4.3 braces specification, without sacrificing speed.", - "version": "2.3.1", + "version": "3.0.3", "homepage": "https://github.com/micromatch/braces", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "contributors": [ @@ -22,42 +22,20 @@ ], "main": "index.js", "engines": { - "node": ">=0.10.0" + "node": ">=8" }, "scripts": { "test": "mocha", "benchmark": "node benchmark" }, "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "fill-range": "^7.1.1" }, "devDependencies": { - "ansi-cyan": "^0.1.1", - "benchmarked": "^2.0.0", - "brace-expansion": "^1.1.8", - "cross-spawn": "^5.1.0", - "gulp": "^3.9.1", - "gulp-eslint": "^4.0.0", - "gulp-format-md": "^1.0.0", - "gulp-istanbul": "^1.1.2", - "gulp-mocha": "^3.0.1", - "gulp-unused": "^0.2.1", - "is-windows": "^1.0.1", - "minimatch": "^3.0.4", - "mocha": "^3.2.0", - "noncharacters": "^1.1.0", - "text-table": "^0.2.0", - "time-diff": "^0.3.1", - "yargs-parser": "^8.0.0" + "ansi-colors": "^3.2.4", + "bash-path": "^2.0.1", + "gulp-format-md": "^2.0.0", + "mocha": "^6.1.1" }, "keywords": [ "alpha", @@ -94,15 +72,6 @@ }, "plugins": [ "gulp-format-md" - ], - "related": { - "list": [ - "expand-brackets", - "extglob", - "fill-range", - "micromatch", - "nanomatch" - ] - } + ] } } diff --git a/test/bash-compiled-ranges.js b/test/bash-compiled-ranges.js new file mode 100644 index 0000000..8181a35 --- /dev/null +++ b/test/bash-compiled-ranges.js @@ -0,0 +1,203 @@ +'use strict'; + +require('mocha'); +const assert = require('assert').strict; +const bashPath = require('bash-path'); +const cp = require('child_process'); +const braces = require('..'); + +const bash = input => { + return cp.spawnSync(bashPath(), ['-c', `echo ${input}`]) + .stdout.toString() + .split(/\s+/) + .filter(Boolean); +}; + +const equal = (input, expected = bash(input), options) => { + assert.deepEqual(braces.compile(input, options), expected); +}; + +/** + * Bash 4.3 unit tests with `braces.compile()` + */ + +describe('bash ranges - braces.compile()', () => { + const fixtures = [ + ['a{b,c{1..100}/{foo,bar}/,h}x/z', {}, 'a(b|c([1-9]|[1-9][0-9]|100)/(foo|bar)/|h)x/z'], + ['0{1..9} {10..20}', {}, '0([1-9]) (1[0-9]|20)'], + + // should not try to expand ranges with decimals + ['{1.1..2.1}', {}, '{1.1..2.1}'], + ['{1.1..~2.1}', {}, '{1.1..~2.1}'], + + // should escape invalid ranges + ['{1..0f}', {}, '{1..0f}'], + ['{1..10..ff}', {}, '{1..10..ff}'], + ['{1..10.f}', {}, '{1..10.f}'], + ['{1..10f}', {}, '{1..10f}'], + ['{1..20..2f}', {}, '{1..20..2f}'], + ['{1..20..f2}', {}, '{1..20..f2}'], + ['{1..2f..2}', {}, '{1..2f..2}'], + ['{1..ff..2}', {}, '{1..ff..2}'], + ['{1..ff}', {}, '{1..ff}'], + ['{1.20..2}', {}, '{1.20..2}'], + + // should handle weirdly-formed brace expansions (fixed in post-bash-3.1) + ['a-{b{d,e}}-c', {}, 'a-{b(d|e)}-c'], + ['a-{bdef-{g,i}-c', {}, 'a-{bdef-(g|i)-c'], + + // should not expand quoted strings + ['{"klklkl"}{1,2,3}', {}, '{klklkl}(1|2|3)'], + ['{"x,x"}', {}, '{x,x}'], + + // should escaped outer braces in nested non-sets + ['{a-{b,c,d}}', {}, '{a-(b|c|d)}'], + ['{a,{a-{b,c,d}}}', {}, '(a|{a-(b|c|d)})'], + + // should escape imbalanced braces + ['abc{', {}, 'abc{'], + ['{abc{', {}, '{abc{'], + ['{abc', {}, '{abc'], + ['}abc', {}, '}abc'], + ['ab{c', {}, 'ab{c'], + ['{{a,b}', {}, '{(a|b)'], + ['{a,b}}', {}, '(a|b)}'], + ['abcd{efgh', {}, 'abcd{efgh'], + ['a{b{c{d,e}f}gh', {}, 'a{b{c(d|e)f}gh'], + ['a{b{c{d,e}f}g}h', {}, 'a{b{c(d|e)f}g}h'], + ['f{x,y{{g,z}}h}', {}, 'f(x|y{(g|z)}h)'], + ['z{a,b},c}d', {}, 'z(a|b),c}d'], + ['a{b{c{d,e}f{x,y{{g}h', {}, 'a{b{c(d|e)f{x,y{{g}h'], + ['f{x,y{{g}h', {}, 'f{x,y{{g}h'], + ['f{x,y{{g}}h', {}, 'f{x,y{{g}}h'], + ['a{b{c{d,e}f{x,y{}g}h', {}, 'a{b{c(d|e)f(x|y{}g)h'], + ['f{x,y{}g}h', {}, 'f(x|y{}g)h'], + ['z{a,b{,c}d', {}, 'z{a,b(|c)d'], + + // should expand numeric ranges + ['a{0..3}d', {}, 'a([0-3])d'], + ['x{10..1}y', {}, 'x([1-9]|10)y'], + ['x{3..3}y', {}, 'x3y'], + ['{1..10}', {}, '([1-9]|10)'], + ['{1..3}', {}, '([1-3])'], + ['{1..9}', {}, '([1-9])'], + ['{10..1}', {}, '([1-9]|10)'], + ['{10..1}y', {}, '([1-9]|10)y'], + ['{3..3}', {}, '3'], + ['{5..8}', {}, '([5-8])'], + + // should expand ranges with negative numbers + ['{-10..-1}', {}, '(-[1-9]|-10)'], + ['{-20..0}', {}, '(-[1-9]|-1[0-9]|-20|0)'], + ['{0..-5}', {}, '(-[1-5]|0)'], + ['{9..-4}', {}, '(-[1-4]|[0-9])'], + + // should expand alphabetical ranges + ['0{1..9}/{10..20}', {}, '0([1-9])/(1[0-9]|20)'], + ['0{a..d}0', {}, '0([a-d])0'], + ['a/{b..d}/e', {}, 'a/([b-d])/e'], + ['{1..f}', {}, '([1-f])'], + ['{a..A}', {}, '([A-a])'], + ['{A..a}', {}, '([A-a])'], + ['{a..e}', {}, '([a-e])'], + ['{A..E}', {}, '([A-E])'], + ['{a..f}', {}, '([a-f])'], + ['{a..z}', {}, '([a-z])'], + ['{E..A}', {}, '([A-E])'], + ['{f..1}', {}, '([1-f])'], + ['{f..a}', {}, '([a-f])'], + ['{f..f}', {}, 'f'], + + // should expand multiple ranges + ['a/{b..d}/e/{f..h}', {}, 'a/([b-d])/e/([f-h])'], + + // should expand numerical ranges - positive and negative + ['{-10..10}', {}, '(-[1-9]|-?10|[0-9])'], + + // HEADS UP! If you're using the `--mm` flag minimatch freezes on these + // should expand large numbers + ['{2147483645..2147483649}', {}, '(214748364[5-9])'], + ['{214748364..2147483649}', {}, '(21474836[4-9]|2147483[7-9][0-9]|214748[4-9][0-9]{2}|214749[0-9]{3}|2147[5-9][0-9]{4}|214[89][0-9]{5}|21[5-9][0-9]{6}|2[2-9][0-9]{7}|[3-9][0-9]{8}|1[0-9]{9}|20[0-9]{8}|21[0-3][0-9]{7}|214[0-6][0-9]{6}|2147[0-3][0-9]{5}|21474[0-7][0-9]{4}|214748[0-2][0-9]{3}|2147483[0-5][0-9]{2}|21474836[0-4][0-9])'], + + // should expand ranges using steps + ['{1..10..1}', { bash: false }, '([1-9]|10)'], + ['{1..10..2}', { bash: false }, '(1|3|5|7|9)'], + ['{1..20..20}', { bash: false }, '1'], + ['{1..20..2}', { bash: false }, '(1|3|5|7|9|11|13|15|17|19)'], + ['{10..1..2}', { bash: false }, '(2|4|6|8|10)'], + ['{100..0..5}', { bash: false }, '(0|5|10|15|20|25|30|35|40|45|50|55|60|65|70|75|80|85|90|95|100)'], + ['{2..10..1}', { bash: false }, '([2-9]|10)'], + ['{2..10..2}', { bash: false }, '(2|4|6|8|10)'], + ['{2..10..3}', { bash: false }, '(2|5|8)'], + + // should expand negative ranges using steps + ['{-1..-10..-2}', { bash: false }, '(-(?:1|3|5|7|9))'], + ['{-1..-10..2}', { bash: false }, '(-(?:1|3|5|7|9))'], + ['{-10..-2..2}', { bash: false }, '(-(?:2|4|6|8|10))'], + ['{-2..-10..1}', { bash: false }, '(-[2-9]|-10)'], + ['{-2..-10..2}', { bash: false }, '(-(?:2|4|6|8|10))'], + ['{-2..-10..3}', { bash: false }, '(-(?:2|5|8))'], + ['{-9..9..3}', { bash: false }, '(0|3|6|9|-(?:3|6|9))'], + ['{10..1..-2}', { bash: false }, '(2|4|6|8|10)'], + ['{100..0..-5}', { bash: false }, '(0|5|10|15|20|25|30|35|40|45|50|55|60|65|70|75|80|85|90|95|100)'], + + // should expand alpha ranges with steps + ['{a..e..2}', { bash: false }, '(a|c|e)'], + ['{E..A..2}', { bash: false }, '(E|C|A)'], + ['{a..z..2}', { bash: false }, '(a|c|e|g|i|k|m|o|q|s|u|w|y)'], + + // should expand alpha ranges with negative steps + ['{z..a..-2}', { bash: false }, '(z|x|v|t|r|p|n|l|j|h|f|d|b)'], + + // unwanted zero-padding (fixed post-bash-4.0) + ['{10..0..2}', { bash: false }, '(0|2|4|6|8|10)'], + ['{10..0..-2}', { bash: false }, '(0|2|4|6|8|10)'], + ['{-50..-0..5}', { bash: false }, '(0|-(?:5|10|15|20|25|30|35|40|45|50))'], + + // should work with dots in file paths + ['../{1..3}/../foo', {}, '../([1-3])/../foo'], + ['../{2..10..2}/../foo', {}, '../(2|4|6|8|10)/../foo'], + ['../{1..3}/../{a,b,c}/foo', {}, '../([1-3])/../(a|b|c)/foo'], + ['./{a..z..3}/', {}, './(a|d|g|j|m|p|s|v|y)/'], + ['./{"x,y"}/{a..z..3}/', { keepQuotes: true }, './{"x,y"}/(a|d|g|j|m|p|s|v|y)/'], + + // should expand a complex combination of ranges and sets + ['a/{x,y}/{1..5}c{d,e}f.{md,txt}', {}, 'a/(x|y)/([1-5])c(d|e)f.(md|txt)'], + + // should expand complex sets and ranges in `bash` mode + ['a/{x,{1..5},y}/c{d}e', {}, 'a/(x|([1-5])|y)/c{d}e'], + + // should treat glob characters as literal + ['**/{1..5}/a.js', {}, '**/([1-5])/a.js'], + ['x{{0..10},braces}y', {}, 'x(([0-9]|10)|braces)y'], + + // should handle sets with invalid ranges + ['{0..10,braces}', {}, '(0..10|braces)'], + ['{1..10,braces}', {}, '(1..10|braces)'], + + ['./\\{x,y}/{a..z..3}/', {}, './{x,y}/(a|d|g|j|m|p|s|v|y)/'], + ['{braces,{0..10}}', {}, '(braces|([0-9]|10))'], + ['{{0..10},braces}', {}, '(([0-9]|10)|braces)'], + ['{{1..10..2},braces}', { bash: false }, '((1|3|5|7|9)|braces)'], + ['{{1..10..2},braces}', {}, '((1|3|5|7|9)|braces)'] + ]; + + fixtures.forEach(arr => { + if (typeof arr === 'string') { + return; + } + + const options = { ...arr[1] }; + const pattern = arr[0]; + const expected = arr[2]; + + if (options.skip === true) { + return; + } + + it('should compile: ' + pattern, () => { + equal(pattern, expected, options); + }); + }); +}); + diff --git a/test/bash-compiled-sets.js b/test/bash-compiled-sets.js new file mode 100644 index 0000000..1a95c19 --- /dev/null +++ b/test/bash-compiled-sets.js @@ -0,0 +1,199 @@ +'use strict'; + +require('mocha'); +const assert = require('assert').strict; +const bashPath = require('bash-path'); +const cp = require('child_process'); +const braces = require('..'); + +const bash = input => { + return cp.spawnSync(bashPath(), ['-c', `echo ${input}`]) + .stdout.toString() + .split(/\s+/) + .filter(Boolean); +}; + +const equal = (input, expected = bash(input), options) => { + assert.deepEqual(braces.compile(input, options), expected); +}; + +/** + * Bash 4.3 unit tests with `braces.expand()` + */ + +describe('bash sets - braces.compile()', () => { + const fixtures = [ + ['{a,b,c,d,e}', {}, '(a|b|c|d|e)'], + ['a/\\{b,c,d,{x,y}}{e,f\\}/g', {}, 'a/{b,c,d,(x|y)}{e,f}/g'], + ['a/\\{b,c,d\\}\\{e,f\\}/g', {}, 'a/{b,c,d}{e,f}/g'], + ['a/\\{b,c,d\\}\\{e,f}/g', {}, 'a/{b,c,d}{e,f}/g'], + ['a/\\{b,c,d\\}{e,f}/g', {}, 'a/{b,c,d}(e|f)/g'], + ['a/\\{b,c,d{x,y}}{e,f\\}/g', {}, 'a/{b,c,d(x|y)}{e,f}/g'], + ['a/\\{b,c,d}{e,f\\}/g', {}, 'a/{b,c,d}{e,f}/g'], + ['a/\\{b,c,d}{e,f}/g', {}, 'a/{b,c,d}(e|f)/g'], + ['a/\\{{b,c}{e,f}/g', {}, 'a/{(b|c)(e|f)/g'], + ['a/\\{{b,c}{e,f}\\}/g', {}, 'a/{(b|c)(e|f)}/g'], + ['a/\\{{b,c}{e,f}}/g', {}, 'a/{(b|c)(e|f)}/g'], + ['a/b/{b,c,{d,e{f,g},{w,x}/{y,z}}}/h/i', {}, 'a/b/(b|c|(d|e(f|g)|(w|x)/(y|z)))/h/i'], + ['a/{b,c}}{e,f}/g', {}, 'a/(b|c)}(e|f)/g'], + ['a/{b,c\\,d}{e,f}/g', {}, 'a/(b|c,d)(e|f)/g'], + ['a/{b,c\\}}{e,f}/g', {}, 'a/(b|c})(e|f)/g'], + ['a/{b,c}', {}, 'a/(b|c)'], + ['a/{b,c}d{e,f}/g', {}, 'a/(b|c)d(e|f)/g'], + ['a/{b,c}{e,f}/g', {}, 'a/(b|c)(e|f)/g'], + ['a/{b,c}{e,f}{g,h,i}/k', {}, 'a/(b|c)(e|f)(g|h|i)/k'], + ['a/{b,{c,d},e}/f', {}, 'a/(b|(c|d)|e)/f'], + ['a/{b,{c,d}/{e,f},g}/h', {}, 'a/(b|(c|d)/(e|f)|g)/h'], + ['a/{b{c,d},e{f,g}h{i,j}}/k', {}, 'a/(b(c|d)|e(f|g)h(i|j))/k'], + ['a/{b{c,d},e}/f', {}, 'a/(b(c|d)|e)/f'], + ['a/{b{c,d}e{f,g}h{i,j}}/k', {}, 'a/{b(c|d)e(f|g)h(i|j)}/k'], + ['a/{b{c,d}e{f,g},h{i,j}}/k', {}, 'a/(b(c|d)e(f|g)|h(i|j))/k'], + ['a/{x,z}{b,{c,d}/{e,f},g}/h', {}, 'a/(x|z)(b|(c|d)/(e|f)|g)/h'], + ['a/{{a,b}/{c,d}}/z', {}, 'a/{(a|b)/(c|d)}/z'], + ['a/{{b,c}/{d,e}}', {}, 'a/{(b|c)/(d|e)}'], + ['a/{{b,c}/{d,e}}/f', {}, 'a/{(b|c)/(d|e)}/f'], + ['a{b{c{d,e}f{x,y{{g}h', {}, 'a{b{c(d|e)f{x,y{{g}h'], + ['{a,b,{c,d},e}', {}, '(a|b|(c|d)|e)'], + ['{a,b,{c,d}e}', {}, '(a|b|(c|d)e)'], + ['{a,b,{c,d}}', {}, '(a|b|(c|d))'], + ['{a,b{c,d}}', {}, '(a|b(c|d))'], + ['{a,b}/{c,d}', {}, '(a|b)/(c|d)'], + ['{a,b}{c,d}', {}, '(a|b)(c|d)'], + + ['{{a,b},{c,d}}', {}, '((a|b)|(c|d))'], + ['{{a,b}/{c,d}}', {}, '{(a|b)/(c|d)}'], + ['{{a,b}/{c,d}}/z', {}, '{(a|b)/(c|d)}/z'], + + // should not process glob characters + ['{generate,{assemble,update,verb}{file,-generate-*},generator}.js', {}, '(generate|(assemble|update|verb)(file|-generate-*)|generator).js'], + ['**/{foo,bar}.js', {}, '**/(foo|bar).js'], + ['**/{a,b,c}/*.js', {}, '**/(a|b|c)/*.js'], + ['**/{a,b,*}/*.js', {}, '**/(a|b|*)/*.js'], + ['**/{**,b,*}/*.js', {}, '**/(**|b|*)/*.js'], + + // should not expand escaped braces + ['\\{a,b,c,d,e}', {}, '{a,b,c,d,e}'], + ['a/b/c/{x,y\\}', {}, 'a/b/c/{x,y}'], + ['a/\\{x,y}/cde', {}, 'a/{x,y}/cde'], + ['abcd{efgh', {}, 'abcd{efgh'], + ['\\{abc\\}', {}, '{abc}'], + ['{x,y,\\{a,b,c\\}}', {}, '(x|y|{a|b|c})'], + ['{x,y,{a,b,c\\}}', {}, '{x,y,(a|b|c})'], + ['{x,y,{abc},trie}', {}, '(x|y|{abc}|trie)'], + ['x,y,{abc},trie', {}, 'x,y,{abc},trie'], + ['{b{c,d},e}', {}, '(b(c|d)|e)'], + ['{b{c,d},e}/f', {}, '(b(c|d)|e)/f'], + ['{abc}', {}, '{abc}'], + + // should handle empty braces + ['{ }', {}, '{ }'], + ['{', {}, '{'], + ['{}', {}, '{}'], + ['}', {}, '}'], + + // should escape braces when only one value is defined + ['a{b}c', {}, 'a{b}c'], + ['a/b/c{d}e', {}, 'a/b/c{d}e'], + + // should escape closing braces when open is not defined + ['{a,b}c,d}', {}, '(a|b)c,d}'], + ['a,b,c,d}', {}, 'a,b,c,d}'], + + // should not expand braces in sets with es6/bash-like variables + ['abc/${ddd}/xyz', {}, 'abc/${ddd}/xyz'], + ['a${b}c', {}, 'a${b}c'], + ['a${b{a,b}}c', {}, 'a${b{a,b}}c'], + ['a/{${b},c}/d', {}, 'a/(${b}|c)/d'], + ['a${b,d}/{foo,bar}c', {}, 'a${b,d}/(foo|bar)c'], + + // should not expand escaped commas + ['a{b\\,c\\,d}e', {}, 'a{b,c,d}e'], + ['a{b\\,c}d', {}, 'a{b,c}d'], + ['{abc\\,def}', {}, '{abc,def}'], + ['{abc\\,def,ghi}', {}, '(abc,def|ghi)'], + ['a/{b,c}/{x\\,y}/d/e', {}, 'a/(b|c)/{x,y}/d/e'], + + // should not expand escaped braces + ['{a,b\\}c,d}', {}, '(a|b}c|d)'], + ['a/{z,\\{a,b,c,d,e}/d', {}, 'a/(z|{a|b|c|d|e)/d'], + ['a/\\{b,c}/{d,e}/f', {}, 'a/{b,c}/(d|e)/f'], + + // should not expand escaped braces or commas + ['{x\\,y,\\{abc\\},trie}', {}, '(x,y|{abc}|trie)'], + + // should support sequence brace operators + ['ff{c,b,a}', {}, 'ff(c|b|a)'], + ['f{d,e,f}g', {}, 'f(d|e|f)g'], + ['{a,b,c}', {}, '(a|b|c)'], + ['{l,n,m}xyz', {}, '(l|n|m)xyz'], + + // should expand multiple sets + ['a/{a,b}/{c,d}/e', {}, 'a/(a|b)/(c|d)/e'], + ['a{b,c}d{e,f}g', {}, 'a(b|c)d(e|f)g'], + ['a/{x,y}/c{d,e}f.{md,txt}', {}, 'a/(x|y)/c(d|e)f.(md|txt)'], + + // should expand nested sets + ['{a,b}{{a,b},a,b}', {}, '(a|b)((a|b)|a|b)'], + ['/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', {}, '/usr/(ucb/(ex|edit)|lib/(ex|how_ex))'], + ['a{b,c{d,e}f}g', {}, 'a(b|c(d|e)f)g'], + ['a{{x,y},z}b', {}, 'a((x|y)|z)b'], + ['f{x,y{g,z}}h', {}, 'f(x|y(g|z))h'], + ['a{b,c{d,e},h}x/z', {}, 'a(b|c(d|e)|h)x/z'], + ['a{b,c{d,e},h}x{y,z}', {}, 'a(b|c(d|e)|h)x(y|z)'], + ['a{b,c{d,e},{f,g}h}x{y,z}', {}, 'a(b|c(d|e)|(f|g)h)x(y|z)'], + ['a-{b{d,e}}-c', {}, 'a-{b(d|e)}-c'], + + // should expand not modify non-brace characters + ['a/b/{d,e}/*.js', {}, 'a/b/(d|e)/*.js'], + ['a/**/c/{d,e}/f*.js', {}, 'a/**/c/(d|e)/f*.js'], + ['a/**/c/{d,e}/f*.{md,txt}', {}, 'a/**/c/(d|e)/f*.(md|txt)'], + + // should work with leading and trailing commas + ['a{b,}c', {}, 'a(b|)c'], + ['a{,b}c', {}, 'a(|b)c'], + + // should handle spaces + // Bash 4.3 says the this first one should be equivalent to `foo|(1|2)|bar + // That makes sense in Bash, since ' ' is a separator, but not here. + ['foo {1,2} bar', {}, 'foo (1|2) bar'], + ['a{ ,c{d, },h}x', {}, 'a( |c(d| )|h)x'], + ['a{ ,c{d, },h} ', {}, 'a( |c(d| )|h) '], + + // see https://github.com/jonschlinkert/microequal/issues/66 + ['/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.{html,ejs}', {}, '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.(html|ejs)'] + ]; + + const seen = new Map(); + const dupes = []; + + for (let i = 0; i < fixtures.length; i++) { + const fixture = fixtures[i]; + + const key = fixture[0] + String(fixture[1].bash); + if (seen.has(key)) { + dupes.push(i + 21, fixture[0]); + } else { + + seen.set(key, i + 21); + } + } + + fixtures.forEach(arr => { + if (typeof arr === 'string') { + return; + } + + const options = { ...arr[1] }; + const pattern = arr[0]; + const expected = arr[2]; + + if (options.skip === true) { + return; + } + + it('should compile: ' + pattern, () => { + equal(pattern, expected, options); + }); + }); +}); + diff --git a/test/bash-expanded-ranges.js b/test/bash-expanded-ranges.js new file mode 100644 index 0000000..21c1db2 --- /dev/null +++ b/test/bash-expanded-ranges.js @@ -0,0 +1,317 @@ +'use strict'; + +require('mocha'); +const assert = require('assert').strict; +const bashPath = require('bash-path'); +const cp = require('child_process'); +const braces = require('..'); + +const bash = input => { + return cp.spawnSync(bashPath(), ['-c', `echo ${input}`]) + .stdout.toString() + .split(/\s+/) + .filter(Boolean); +}; + +const equal = (input, expected = bash(input), options) => { + assert.deepEqual(braces.expand(input, options), expected); +}; + +/** + * Bash 4.3 unit tests with `braces.expand()` + */ + +describe('bash - expanded brace ranges', () => { + describe('large numbers', () => { + it('should expand large numbers', () => { + equal('{2147483645..2147483649}', ['2147483645', '2147483646', '2147483647', '2147483648', '2147483649']); + }); + + it('should throw an error when range exceeds rangeLimit', () => { + assert.throws(() => braces.expand('{214748364..2147483649}')); + }); + }); + + describe('escaping / invalid ranges', () => { + it('should not try to expand ranges with decimals', () => { + equal('{1.1..2.1}', ['{1.1..2.1}']); + equal('{1.1..~2.1}', ['{1.1..~2.1}']); + }); + + it('should escape invalid ranges:', () => { + equal('{1..0f}', ['{1..0f}']); + equal('{1..10..ff}', ['{1..10..ff}']); + equal('{1..10.f}', ['{1..10.f}']); + equal('{1..10f}', ['{1..10f}']); + equal('{1..20..2f}', ['{1..20..2f}']); + equal('{1..20..f2}', ['{1..20..f2}']); + equal('{1..2f..2}', ['{1..2f..2}']); + equal('{1..ff..2}', ['{1..ff..2}']); + equal('{1..ff}', ['{1..ff}']); + equal('{1.20..2}', ['{1.20..2}']); + }); + + it('weirdly-formed brace expansions -- fixed in post-bash-3.1', () => { + equal('a-{b{d,e}}-c', ['a-{bd}-c', 'a-{be}-c']); + equal('a-{bdef-{g,i}-c', ['a-{bdef-g-c', 'a-{bdef-i-c']); + }); + + it('should not expand quoted strings.', () => { + equal('{"klklkl"}{1,2,3}', ['{klklkl}1', '{klklkl}2', '{klklkl}3']); + equal('{"x,x"}', ['{x,x}']); + }); + + it('should escaped outer braces in nested non-sets', () => { + equal('{a-{b,c,d}}', ['{a-b}', '{a-c}', '{a-d}']); + equal('{a,{a-{b,c,d}}}', ['a', '{a-b}', '{a-c}', '{a-d}']); + }); + + it('should escape imbalanced braces', () => { + equal('a-{bdef-{g,i}-c', ['a-{bdef-g-c', 'a-{bdef-i-c']); + equal('abc{', ['abc{']); + equal('{abc{', ['{abc{']); + equal('{abc', ['{abc']); + equal('}abc', ['}abc']); + equal('ab{c', ['ab{c']); + equal('ab{c', ['ab{c']); + equal('{{a,b}', ['{a', '{b']); + equal('{a,b}}', ['a}', 'b}']); + equal('abcd{efgh', ['abcd{efgh']); + equal('a{b{c{d,e}f}g}h', ['a{b{cdf}g}h', 'a{b{cef}g}h']); + equal('f{x,y{{g,z}}h}', ['fx', 'fy{g}h', 'fy{z}h']); + equal('z{a,b},c}d', ['za,c}d', 'zb,c}d']); + equal('a{b{c{d,e}f{x,y{{g}h', ['a{b{cdf{x,y{{g}h', 'a{b{cef{x,y{{g}h']); + equal('f{x,y{{g}h', ['f{x,y{{g}h']); + equal('f{x,y{{g}}h', ['f{x,y{{g}}h']); + equal('a{b{c{d,e}f{x,y{}g}h', ['a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh']); + equal('f{x,y{}g}h', ['fxh', 'fy{}gh']); + equal('z{a,b{,c}d', ['z{a,bd', 'z{a,bcd']); + }); + }); + + describe('positive numeric ranges', () => { + it('should expand numeric ranges', () => { + equal('a{0..3}d', ['a0d', 'a1d', 'a2d', 'a3d']); + equal('x{10..1}y', ['x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y']); + equal('x{3..3}y', ['x3y']); + equal('{1..10}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{1..3}', ['1', '2', '3']); + equal('{1..9}', ['1', '2', '3', '4', '5', '6', '7', '8', '9']); + equal('{10..1}', ['10', '9', '8', '7', '6', '5', '4', '3', '2', '1']); + equal('{10..1}y', ['10y', '9y', '8y', '7y', '6y', '5y', '4y', '3y', '2y', '1y']); + equal('{3..3}', ['3']); + equal('{5..8}', ['5', '6', '7', '8']); + }); + }); + + describe('negative ranges', () => { + it('should expand ranges with negative numbers', () => { + equal('{-10..-1}', ['-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1']); + equal('{-20..0}', ['-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0']); + equal('{0..-5}', ['0', '-1', '-2', '-3', '-4', '-5']); + equal('{9..-4}', ['9', '8', '7', '6', '5', '4', '3', '2', '1', '0', '-1', '-2', '-3', '-4']); + }); + }); + + describe('alphabetical ranges', () => { + it('should expand alphabetical ranges', () => { + equal('{a..F}', ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F']); + equal('0{a..d}0', ['0a0', '0b0', '0c0', '0d0']); + equal('a/{b..d}/e', ['a/b/e', 'a/c/e', 'a/d/e']); + equal('{1..f}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f']); + equal('{a..A}', ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A']); + equal('{A..a}', ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a']); + equal('{a..e}', ['a', 'b', 'c', 'd', 'e']); + equal('{A..E}', ['A', 'B', 'C', 'D', 'E']); + equal('{a..f}', ['a', 'b', 'c', 'd', 'e', 'f']); + equal('{a..z}', ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']); + equal('{E..A}', ['E', 'D', 'C', 'B', 'A']); + equal('{f..1}', ['f', 'e', 'd', 'c', 'b', 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A', '@', '?', '>', '=', '<', ';', ':', '9', '8', '7', '6', '5', '4', '3', '2', '1']); + equal('{f..a}', ['f', 'e', 'd', 'c', 'b', 'a']); + equal('{f..f}', ['f']); + }); + + it('should expand multiple ranges:', () => { + equal('a/{b..d}/e/{f..h}', ['a/b/e/f', 'a/b/e/g', 'a/b/e/h', 'a/c/e/f', 'a/c/e/g', 'a/c/e/h', 'a/d/e/f', 'a/d/e/g', 'a/d/e/h']); + }); + }); + + describe('combo', () => { + it('should expand numerical ranges - positive and negative', () => { + equal('a{01..05}b', ['a01b', 'a02b', 'a03b', 'a04b', 'a05b']); + equal('0{1..9}/{10..20}', ['01/10', '01/11', '01/12', '01/13', '01/14', '01/15', '01/16', '01/17', '01/18', '01/19', '01/20', '02/10', '02/11', '02/12', '02/13', '02/14', '02/15', '02/16', '02/17', '02/18', '02/19', '02/20', '03/10', '03/11', '03/12', '03/13', '03/14', '03/15', '03/16', '03/17', '03/18', '03/19', '03/20', '04/10', '04/11', '04/12', '04/13', '04/14', '04/15', '04/16', '04/17', '04/18', '04/19', '04/20', '05/10', '05/11', '05/12', '05/13', '05/14', '05/15', '05/16', '05/17', '05/18', '05/19', '05/20', '06/10', '06/11', '06/12', '06/13', '06/14', '06/15', '06/16', '06/17', '06/18', '06/19', '06/20', '07/10', '07/11', '07/12', '07/13', '07/14', '07/15', '07/16', '07/17', '07/18', '07/19', '07/20', '08/10', '08/11', '08/12', '08/13', '08/14', '08/15', '08/16', '08/17', '08/18', '08/19', '08/20', '09/10', '09/11', '09/12', '09/13', '09/14', '09/15', '09/16', '09/17', '09/18', '09/19', '09/20']); + equal('{-10..10}', ['-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); + }); + }); + + describe('steps > positive ranges', () => { + it('should expand ranges using steps:', () => { + equal('{1..10..1}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{1..10..2}', ['1', '3', '5', '7', '9']); + equal('{1..20..20}', ['1']); + equal('{1..20..2}', ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19']); + equal('{10..0..2}', ['10', '8', '6', '4', '2', '0']); + equal('{10..1..2}', ['10', '8', '6', '4', '2']); + equal('{100..0..5}', ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']); + equal('{2..10..1}', ['2', '3', '4', '5', '6', '7', '8', '9', '10']); + equal('{2..10..2}', ['2', '4', '6', '8', '10']); + equal('{2..10..3}', ['2', '5', '8']); + equal('{a..z..2}', ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']); + }); + + it('should expand positive ranges with negative steps:', () => { + equal('{10..0..-2}', ['10', '8', '6', '4', '2', '0']); + }); + }); + + describe('steps > negative ranges', () => { + it('should expand negative ranges using steps:', () => { + equal('{-1..-10..-2}', ['-1', '-3', '-5', '-7', '-9']); + equal('{-1..-10..2}', ['-1', '-3', '-5', '-7', '-9']); + equal('{-10..-2..2}', ['-10', '-8', '-6', '-4', '-2']); + equal('{-2..-10..1}', ['-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10']); + equal('{-2..-10..2}', ['-2', '-4', '-6', '-8', '-10']); + equal('{-2..-10..3}', ['-2', '-5', '-8']); + equal('{-50..-0..5}', ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); + equal('{10..1..-2}', ['10', '8', '6', '4', '2']); + equal('{100..0..-5}', ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']); + }); + }); + + describe('steps > alphabetical ranges', () => { + it('should expand alpha ranges with steps', () => { + equal('{a..e..2}', ['a', 'c', 'e']); + equal('{E..A..2}', ['E', 'C', 'A']); + equal('{a..z}', ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']); + equal('{a..z..2}', ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']); + equal('{z..a..-2}', ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']); + }); + + it('should expand alpha ranges with negative steps', () => { + equal('{z..a..-2}', ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']); + }); + }); + + describe('padding', () => { + it('unwanted zero-padding -- fixed post-bash-4.0', () => { + equal('{10..0..2}', ['10', '8', '6', '4', '2', '0']); + equal('{10..0..-2}', ['10', '8', '6', '4', '2', '0']); + equal('{-50..-0..5}', ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); + }); + }); + + describe('ranges', () => { + const fixtures = [ + 'should expand ranges', + + ['a{b,c{1..50}/{d,e,f}/,g}h/i', {}, ['abh/i', 'ac1/d/h/i', 'ac1/e/h/i', 'ac1/f/h/i', 'ac2/d/h/i', 'ac2/e/h/i', 'ac2/f/h/i', 'ac3/d/h/i', 'ac3/e/h/i', 'ac3/f/h/i', 'ac4/d/h/i', 'ac4/e/h/i', 'ac4/f/h/i', 'ac5/d/h/i', 'ac5/e/h/i', 'ac5/f/h/i', 'ac6/d/h/i', 'ac6/e/h/i', 'ac6/f/h/i', 'ac7/d/h/i', 'ac7/e/h/i', 'ac7/f/h/i', 'ac8/d/h/i', 'ac8/e/h/i', 'ac8/f/h/i', 'ac9/d/h/i', 'ac9/e/h/i', 'ac9/f/h/i', 'ac10/d/h/i', 'ac10/e/h/i', 'ac10/f/h/i', 'ac11/d/h/i', 'ac11/e/h/i', 'ac11/f/h/i', 'ac12/d/h/i', 'ac12/e/h/i', 'ac12/f/h/i', 'ac13/d/h/i', 'ac13/e/h/i', 'ac13/f/h/i', 'ac14/d/h/i', 'ac14/e/h/i', 'ac14/f/h/i', 'ac15/d/h/i', 'ac15/e/h/i', 'ac15/f/h/i', 'ac16/d/h/i', 'ac16/e/h/i', 'ac16/f/h/i', 'ac17/d/h/i', 'ac17/e/h/i', 'ac17/f/h/i', 'ac18/d/h/i', 'ac18/e/h/i', 'ac18/f/h/i', 'ac19/d/h/i', 'ac19/e/h/i', 'ac19/f/h/i', 'ac20/d/h/i', 'ac20/e/h/i', 'ac20/f/h/i', 'ac21/d/h/i', 'ac21/e/h/i', 'ac21/f/h/i', 'ac22/d/h/i', 'ac22/e/h/i', 'ac22/f/h/i', 'ac23/d/h/i', 'ac23/e/h/i', 'ac23/f/h/i', 'ac24/d/h/i', 'ac24/e/h/i', 'ac24/f/h/i', 'ac25/d/h/i', 'ac25/e/h/i', 'ac25/f/h/i', 'ac26/d/h/i', 'ac26/e/h/i', 'ac26/f/h/i', 'ac27/d/h/i', 'ac27/e/h/i', 'ac27/f/h/i', 'ac28/d/h/i', 'ac28/e/h/i', 'ac28/f/h/i', 'ac29/d/h/i', 'ac29/e/h/i', 'ac29/f/h/i', 'ac30/d/h/i', 'ac30/e/h/i', 'ac30/f/h/i', 'ac31/d/h/i', 'ac31/e/h/i', 'ac31/f/h/i', 'ac32/d/h/i', 'ac32/e/h/i', 'ac32/f/h/i', 'ac33/d/h/i', 'ac33/e/h/i', 'ac33/f/h/i', 'ac34/d/h/i', 'ac34/e/h/i', 'ac34/f/h/i', 'ac35/d/h/i', 'ac35/e/h/i', 'ac35/f/h/i', 'ac36/d/h/i', 'ac36/e/h/i', 'ac36/f/h/i', 'ac37/d/h/i', 'ac37/e/h/i', 'ac37/f/h/i', 'ac38/d/h/i', 'ac38/e/h/i', 'ac38/f/h/i', 'ac39/d/h/i', 'ac39/e/h/i', 'ac39/f/h/i', 'ac40/d/h/i', 'ac40/e/h/i', 'ac40/f/h/i', 'ac41/d/h/i', 'ac41/e/h/i', 'ac41/f/h/i', 'ac42/d/h/i', 'ac42/e/h/i', 'ac42/f/h/i', 'ac43/d/h/i', 'ac43/e/h/i', 'ac43/f/h/i', 'ac44/d/h/i', 'ac44/e/h/i', 'ac44/f/h/i', 'ac45/d/h/i', 'ac45/e/h/i', 'ac45/f/h/i', 'ac46/d/h/i', 'ac46/e/h/i', 'ac46/f/h/i', 'ac47/d/h/i', 'ac47/e/h/i', 'ac47/f/h/i', 'ac48/d/h/i', 'ac48/e/h/i', 'ac48/f/h/i', 'ac49/d/h/i', 'ac49/e/h/i', 'ac49/f/h/i', 'ac50/d/h/i', 'ac50/e/h/i', 'ac50/f/h/i', 'agh/i']], + ['0{1..9} {10..20}', {}, ['01 10', '01 11', '01 12', '01 13', '01 14', '01 15', '01 16', '01 17', '01 18', '01 19', '01 20', '02 10', '02 11', '02 12', '02 13', '02 14', '02 15', '02 16', '02 17', '02 18', '02 19', '02 20', '03 10', '03 11', '03 12', '03 13', '03 14', '03 15', '03 16', '03 17', '03 18', '03 19', '03 20', '04 10', '04 11', '04 12', '04 13', '04 14', '04 15', '04 16', '04 17', '04 18', '04 19', '04 20', '05 10', '05 11', '05 12', '05 13', '05 14', '05 15', '05 16', '05 17', '05 18', '05 19', '05 20', '06 10', '06 11', '06 12', '06 13', '06 14', '06 15', '06 16', '06 17', '06 18', '06 19', '06 20', '07 10', '07 11', '07 12', '07 13', '07 14', '07 15', '07 16', '07 17', '07 18', '07 19', '07 20', '08 10', '08 11', '08 12', '08 13', '08 14', '08 15', '08 16', '08 17', '08 18', '08 19', '08 20', '09 10', '09 11', '09 12', '09 13', '09 14', '09 15', '09 16', '09 17', '09 18', '09 19', '09 20']], + ['a{0..3}d', {}, ['a0d', 'a1d', 'a2d', 'a3d']], + ['x{10..1}y', {}, ['x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y']], + ['x{3..3}y', {}, ['x3y']], + ['{0..10,braces}', {}, ['0..10', 'braces']], + ['{3..3}', {}, ['3']], + ['{5..8}', {}, ['5', '6', '7', '8']], + ['**/{1..5}/a.js', {}, ['**/1/a.js', '**/2/a.js', '**/3/a.js', '**/4/a.js', '**/5/a.js']], + ['{braces,{0..10}}', {}, ['braces', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']], + ['./\\{x,y}/{a..z..3}/', {}, ['./{x,y}/a/', './{x,y}/d/', './{x,y}/g/', './{x,y}/j/', './{x,y}/m/', './{x,y}/p/', './{x,y}/s/', './{x,y}/v/', './{x,y}/y/']], + ['x{{0..10},braces}y', {}, ['x0y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'x10y', 'xbracesy']], + ['{braces,{0..10}}', {}, ['braces', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']], + ['{{0..10},braces}', {}, ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces']], + ['{{1..10..2},braces}', {}, ['1', '3', '5', '7', '9', 'braces']], + ['{{1..10},braces}', {}, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces']], + ['{1.1..2.1}', {}, ['{1.1..2.1}']], + ['{1.1..~2.1}', {}, ['{1.1..~2.1}']], + ['{1..0f}', {}, ['{1..0f}']], + ['{1..10..ff}', {}, ['{1..10..ff}']], + ['{1..10.f}', {}, ['{1..10.f}']], + ['{1..10f}', {}, ['{1..10f}']], + ['{1..20..2f}', {}, ['{1..20..2f}']], + ['{1..20..f2}', {}, ['{1..20..f2}']], + ['{1..2f..2}', {}, ['{1..2f..2}']], + ['{1..ff..2}', {}, ['{1..ff..2}']], + ['{1..ff}', {}, ['{1..ff}']], + ['{1.20..2}', {}, ['{1.20..2}']], + ['a{0..3}d', {}, ['a0d', 'a1d', 'a2d', 'a3d']], + ['x{10..1}y', {}, ['x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y']], + ['x{3..3}y', {}, ['x3y']], + ['{1..10}', {}, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']], + ['{1..3}', {}, ['1', '2', '3']], + ['{1..9}', {}, ['1', '2', '3', '4', '5', '6', '7', '8', '9']], + ['{10..1}y', {}, ['10y', '9y', '8y', '7y', '6y', '5y', '4y', '3y', '2y', '1y']], + ['{3..3}', {}, ['3']], + ['{5..8}', {}, ['5', '6', '7', '8']], + ['{-10..-1}', {}, ['-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1']], + ['{-20..0}', {}, ['-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0']], + ['{0..-5}', {}, ['0', '-1', '-2', '-3', '-4', '-5']], + ['{9..-4}', {}, ['9', '8', '7', '6', '5', '4', '3', '2', '1', '0', '-1', '-2', '-3', '-4']], + ['0{1..9}/{10..20}', {}, ['01/10', '01/11', '01/12', '01/13', '01/14', '01/15', '01/16', '01/17', '01/18', '01/19', '01/20', '02/10', '02/11', '02/12', '02/13', '02/14', '02/15', '02/16', '02/17', '02/18', '02/19', '02/20', '03/10', '03/11', '03/12', '03/13', '03/14', '03/15', '03/16', '03/17', '03/18', '03/19', '03/20', '04/10', '04/11', '04/12', '04/13', '04/14', '04/15', '04/16', '04/17', '04/18', '04/19', '04/20', '05/10', '05/11', '05/12', '05/13', '05/14', '05/15', '05/16', '05/17', '05/18', '05/19', '05/20', '06/10', '06/11', '06/12', '06/13', '06/14', '06/15', '06/16', '06/17', '06/18', '06/19', '06/20', '07/10', '07/11', '07/12', '07/13', '07/14', '07/15', '07/16', '07/17', '07/18', '07/19', '07/20', '08/10', '08/11', '08/12', '08/13', '08/14', '08/15', '08/16', '08/17', '08/18', '08/19', '08/20', '09/10', '09/11', '09/12', '09/13', '09/14', '09/15', '09/16', '09/17', '09/18', '09/19', '09/20']], + ['0{a..d}0', {}, ['0a0', '0b0', '0c0', '0d0']], + ['a/{b..d}/e', {}, ['a/b/e', 'a/c/e', 'a/d/e']], + ['{1..f}', { minimatch: false }, ['1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f']], + ['{a..A}', {}, ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A']], + ['{A..a}', {}, ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a']], + ['{a..e}', {}, ['a', 'b', 'c', 'd', 'e']], + ['{A..E}', {}, ['A', 'B', 'C', 'D', 'E']], + ['{a..f}', {}, ['a', 'b', 'c', 'd', 'e', 'f']], + ['{a..z}', {}, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']], + ['{E..A}', {}, ['E', 'D', 'C', 'B', 'A']], + ['{f..1}', { minimatch: false }, ['f', 'e', 'd', 'c', 'b', 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A', '@', '?', '>', '=', '<', ';', ':', '9', '8', '7', '6', '5', '4', '3', '2', '1']], + ['{f..a}', {}, ['f', 'e', 'd', 'c', 'b', 'a']], + ['{f..f}', {}, ['f']], + ['a/{b..d}/e/{f..h}', {}, ['a/b/e/f', 'a/b/e/g', 'a/b/e/h', 'a/c/e/f', 'a/c/e/g', 'a/c/e/h', 'a/d/e/f', 'a/d/e/g', 'a/d/e/h']], + ['{-10..10}', {}, ['-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']], + ['{1..10..1}', { optimize: false }, ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']], + ['{1..10..2}', { optimize: false }, ['1', '3', '5', '7', '9']], + ['{1..20..20}', { optimize: false }, ['1']], + ['{1..20..2}', { optimize: false }, ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19']], + ['{10..0..2}', { optimize: false }, ['10', '8', '6', '4', '2', '0']], + ['{10..1..2}', { optimize: false }, ['10', '8', '6', '4', '2']], + ['{100..0..5}', { optimize: false }, ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']], + ['{2..10..1}', { optimize: false }, ['2', '3', '4', '5', '6', '7', '8', '9', '10']], + ['{2..10..2}', { optimize: false }, ['2', '4', '6', '8', '10']], + ['{2..10..3}', { optimize: false }, ['2', '5', '8']], + ['{a..z..2}', { optimize: false }, ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']], + ['{10..0..-2}', { optimize: false }, ['10', '8', '6', '4', '2', '0']], + ['{-1..-10..-2}', { optimize: false }, ['-1', '-3', '-5', '-7', '-9']], + ['{-1..-10..2}', { optimize: false }, ['-1', '-3', '-5', '-7', '-9']], + ['{-10..-2..2}', { optimize: false }, ['-10', '-8', '-6', '-4', '-2']], + ['{-2..-10..1}', { optimize: false }, ['-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10']], + ['{-2..-10..2}', { optimize: false }, ['-2', '-4', '-6', '-8', '-10']], + ['{-2..-10..3}', { optimize: false }, ['-2', '-5', '-8']], + ['{-50..-0..5}', { optimize: false }, ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']], + ['{-9..9..3}', { optimize: false }, ['-9', '-6', '-3', '0', '3', '6', '9']], + ['{10..1..-2}', { optimize: false }, ['10', '8', '6', '4', '2']], + ['{100..0..-5}', { optimize: false }, ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']], + ['{a..e..2}', { optimize: false }, ['a', 'c', 'e']], + ['{E..A..2}', { optimize: false }, ['E', 'C', 'A']], + ['{a..z..2}', { optimize: false }, ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']], + ['{z..a..-2}', { optimize: false }, ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']], + ['{z..a..-2}', { optimize: false }, ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']], + ['{10..0..2}', { optimize: false }, ['10', '8', '6', '4', '2', '0']], + ['{10..0..-2}', { optimize: false }, ['10', '8', '6', '4', '2', '0']], + ['{-50..-0..5}', { optimize: false }, ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']], + ['../{1..3}/../foo', {}, ['../1/../foo', '../2/../foo', '../3/../foo']], + ['../{2..10..2}/../foo', { optimize: false }, ['../2/../foo', '../4/../foo', '../6/../foo', '../8/../foo', '../10/../foo']], + ['../{1..3}/../{a,b,c}/foo', {}, ['../1/../a/foo', '../1/../b/foo', '../1/../c/foo', '../2/../a/foo', '../2/../b/foo', '../2/../c/foo', '../3/../a/foo', '../3/../b/foo', '../3/../c/foo']], + ['./{a..z..3}/', { optimize: false }, ['./a/', './d/', './g/', './j/', './m/', './p/', './s/', './v/', './y/']], + ['./{"x,y"}/{a..z..3}/', { minimatch: false, optimize: false }, ['./{x,y}/a/', './{x,y}/d/', './{x,y}/g/', './{x,y}/j/', './{x,y}/m/', './{x,y}/p/', './{x,y}/s/', './{x,y}/v/', './{x,y}/y/']], + ['a/{x,y}/{1..5}c{d,e}f.{md,txt}', {}, ['a/x/1cdf.md', 'a/x/1cdf.txt', 'a/x/1cef.md', 'a/x/1cef.txt', 'a/x/2cdf.md', 'a/x/2cdf.txt', 'a/x/2cef.md', 'a/x/2cef.txt', 'a/x/3cdf.md', 'a/x/3cdf.txt', 'a/x/3cef.md', 'a/x/3cef.txt', 'a/x/4cdf.md', 'a/x/4cdf.txt', 'a/x/4cef.md', 'a/x/4cef.txt', 'a/x/5cdf.md', 'a/x/5cdf.txt', 'a/x/5cef.md', 'a/x/5cef.txt', 'a/y/1cdf.md', 'a/y/1cdf.txt', 'a/y/1cef.md', 'a/y/1cef.txt', 'a/y/2cdf.md', 'a/y/2cdf.txt', 'a/y/2cef.md', 'a/y/2cef.txt', 'a/y/3cdf.md', 'a/y/3cdf.txt', 'a/y/3cef.md', 'a/y/3cef.txt', 'a/y/4cdf.md', 'a/y/4cdf.txt', 'a/y/4cef.md', 'a/y/4cef.txt', 'a/y/5cdf.md', 'a/y/5cdf.txt', 'a/y/5cef.md', 'a/y/5cef.txt']], + ['a/{x,{1..5},y}/c{d}e', {}, ['a/x/c{d}e', 'a/1/c{d}e', 'a/2/c{d}e', 'a/3/c{d}e', 'a/4/c{d}e', 'a/5/c{d}e', 'a/y/c{d}e']] + ]; + + fixtures.forEach(arr => { + if (typeof arr === 'string') { + return; + } + + const options = { ...arr[1] }; + const pattern = arr[0]; + const expected = arr[2]; + + if (options.skip !== true) { + it('should compile: ' + pattern, () => equal(pattern, expected, options)); + } + }); + }); +}); diff --git a/test/bash-expanded-sets.js b/test/bash-expanded-sets.js new file mode 100644 index 0000000..b5a40cc --- /dev/null +++ b/test/bash-expanded-sets.js @@ -0,0 +1,241 @@ +'use strict'; + +require('mocha'); +const assert = require('assert').strict; +const bashPath = require('bash-path'); +const cp = require('child_process'); +const braces = require('..'); + +const bash = input => { + return cp.spawnSync(bashPath(), ['-c', `echo ${input}`]) + .stdout.toString() + .split(/\s+/) + .filter(Boolean); +}; + +const equal = (input, expected = bash(input), options) => { + assert.deepEqual(braces.expand(input, options), expected); +}; + +/** + * Bash 4.3 unit tests with `braces.expand()` + */ + +describe('bash - expanded brace sets', () => { + const fixtures = [ + ['a/\\{b,c,d,{x,y}}{e,f\\}/g', {}, ['a/{b,c,d,x}{e,f}/g', 'a/{b,c,d,y}{e,f}/g']], + ['a/\\{b,c,d\\}\\{e,f\\}/g', {}, ['a/{b,c,d}{e,f}/g']], + ['a/\\{b,c,d\\}\\{e,f}/g', {}, ['a/{b,c,d}{e,f}/g']], + ['a/\\{b,c,d\\}{e,f}/g', {}, ['a/{b,c,d}e/g', 'a/{b,c,d}f/g']], + ['a/\\{b,c,d{x,y}}{e,f\\}/g', {}, ['a/{b,c,dx}{e,f}/g', 'a/{b,c,dy}{e,f}/g']], + ['a/\\{b,c,d}{e,f\\}/g', {}, ['a/{b,c,d}{e,f}/g']], + ['a/\\{b,c,d}{e,f}/g', {}, ['a/{b,c,d}e/g', 'a/{b,c,d}f/g']], + ['a/\\{x,y}/cde', {}, ['a/{x,y}/cde']], + ['a/\\{{b,c}{e,f}/g', {}, ['a/{be/g', 'a/{bf/g', 'a/{ce/g', 'a/{cf/g']], + ['a/\\{{b,c}{e,f}\\}/g', {}, ['a/{be}/g', 'a/{bf}/g', 'a/{ce}/g', 'a/{cf}/g']], + ['a/\\{{b,c}{e,f}}/g', {}, ['a/{be}/g', 'a/{bf}/g', 'a/{ce}/g', 'a/{cf}/g']], + ['a/b/{b,c,{d,e{f,g},{w,x}/{y,z}}}/h/i', {}, ['a/b/b/h/i', 'a/b/c/h/i', 'a/b/d/h/i', 'a/b/ef/h/i', 'a/b/eg/h/i', 'a/b/w/y/h/i', 'a/b/w/z/h/i', 'a/b/x/y/h/i', 'a/b/x/z/h/i']], + ['a/{b,c,d}{e,f}/g', {}, ['a/be/g', 'a/bf/g', 'a/ce/g', 'a/cf/g', 'a/de/g', 'a/df/g']], + ['a/{b,c\\,d}{e,f}/g', {}, ['a/be/g', 'a/bf/g', 'a/c,de/g', 'a/c,df/g']], + ['a/{b,c\\}}{e,f}/g', {}, ['a/be/g', 'a/bf/g', 'a/c}e/g', 'a/c}f/g']], + ['a/{b,c}', {}, ['a/b', 'a/c']], + ['a/{b,c}d{e,f}/g', {}, ['a/bde/g', 'a/bdf/g', 'a/cde/g', 'a/cdf/g']], + ['a/{b,c}{e,f}/g', {}, ['a/be/g', 'a/bf/g', 'a/ce/g', 'a/cf/g']], + ['a/{b,c}{e,f}{g,h,i}/k', {}, ['a/beg/k', 'a/beh/k', 'a/bei/k', 'a/bfg/k', 'a/bfh/k', 'a/bfi/k', 'a/ceg/k', 'a/ceh/k', 'a/cei/k', 'a/cfg/k', 'a/cfh/k', 'a/cfi/k']], + ['a/{b,{c,d},e}/f', {}, ['a/b/f', 'a/c/f', 'a/d/f', 'a/e/f']], + ['a/{b,{c,d}/{e,f},g}/h', {}, ['a/b/h', 'a/c/e/h', 'a/c/f/h', 'a/d/e/h', 'a/d/f/h', 'a/g/h']], + ['a/{b{c,d},e{f,g}h{i,j}}/k', {}, ['a/bc/k', 'a/bd/k', 'a/efhi/k', 'a/efhj/k', 'a/eghi/k', 'a/eghj/k']], + ['a/{b{c,d},e}/f', {}, ['a/bc/f', 'a/bd/f', 'a/e/f']], + ['a/{b{c,d}e{f,g}h{i,j}}/k', {}, ['a/{bcefhi}/k', 'a/{bcefhj}/k', 'a/{bceghi}/k', 'a/{bceghj}/k', 'a/{bdefhi}/k', 'a/{bdefhj}/k', 'a/{bdeghi}/k', 'a/{bdeghj}/k']], + ['a/{b{c,d}e{f,g},h{i,j}}/k', {}, ['a/bcef/k', 'a/bceg/k', 'a/bdef/k', 'a/bdeg/k', 'a/hi/k', 'a/hj/k']], + ['a/{x,z}{b,{c,d}/{e,f},g}/h', {}, ['a/xb/h', 'a/xc/e/h', 'a/xc/f/h', 'a/xd/e/h', 'a/xd/f/h', 'a/xg/h', 'a/zb/h', 'a/zc/e/h', 'a/zc/f/h', 'a/zd/e/h', 'a/zd/f/h', 'a/zg/h']], + ['a/{{a,b}/{c,d}}/z', {}, ['a/{a/c}/z', 'a/{a/d}/z', 'a/{b/c}/z', 'a/{b/d}/z']], + ['a/{{b,c}/{d,e}}', {}, ['a/{b/d}', 'a/{b/e}', 'a/{c/d}', 'a/{c/e}']], + ['a/{{b,c}/{d,e}}/f', {}, ['a/{b/d}/f', 'a/{b/e}/f', 'a/{c/d}/f', 'a/{c/e}/f']], + ['a{b}c', {}, ['a{b}c']], + ['foo {1,2} bar', {}, ['foo 1 bar', 'foo 2 bar']], + ['{ }', {}, ['{ }']], + ['{', {}, ['{']], + ['{a,b,{c,d},e}', {}, ['a', 'b', 'c', 'd', 'e']], + ['{a,b,{c,d}e}', {}, ['a', 'b', 'ce', 'de']], + ['{a,b,{c,d}}', {}, ['a', 'b', 'c', 'd']], + ['{a,b{c,d}}', {}, ['a', 'bc', 'bd']], + ['{a,b}/{c,d}', {}, ['a/c', 'a/d', 'b/c', 'b/d']], + ['{a,b}c,d\\}', {}, ['ac,d}', 'bc,d}']], + ['{a,b\\}c,d}', {}, ['a', 'b}c', 'd']], + ['{a,b}{c,d}', {}, ['ac', 'ad', 'bc', 'bd']], + ['{abc}', {}, ['{abc}']], + ['{b{c,d},e}', {}, ['bc', 'bd', 'e']], + ['{b{c,d},e}/f', {}, ['bc/f', 'bd/f', 'e/f']], + ['x,y,{abc},trie', {}, ['x,y,{abc},trie']], + ['{{a,b},{c,d}}', {}, ['a', 'b', 'c', 'd']], + ['{{a,b}/{c,d}}', {}, ['{a/c}', '{a/d}', '{b/c}', '{b/d}']], + ['{{a,b}/{c,d}}/z', {}, ['{a/c}/z', '{a/d}/z', '{b/c}/z', '{b/d}/z']], + ['{}', {}, ['{}']], + + // // should ignore globs + ['}', {}, ['}']], + + // 'should ignore globs', + + ['{generate,{assemble,update,verb}{file,-generate-*},generator}.js', {}, ['generate.js', 'assemblefile.js', 'assemble-generate-*.js', 'updatefile.js', 'update-generate-*.js', 'verbfile.js', 'verb-generate-*.js', 'generator.js']], + ['**/{foo,bar}.js', {}, ['**/foo.js', '**/bar.js']], + ['**/{a,b,c}/*.js', {}, ['**/a/*.js', '**/b/*.js', '**/c/*.js']], + ['**/{a,b,*}/*.js', {}, ['**/a/*.js', '**/b/*.js', '**/*/*.js']], + ['**/{**,b,*}/*.js', {}, ['**/**/*.js', '**/b/*.js', '**/*/*.js']], + ['/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', {}, ['/usr/ucb/ex', '/usr/ucb/edit', '/usr/lib/ex', '/usr/lib/how_ex']], + ['ff{c,b,a}', {}, ['ffc', 'ffb', 'ffa']], + ['f{d,e,f}g', {}, ['fdg', 'feg', 'ffg']], + ['{a,b,c}', {}, ['a', 'b', 'c']], + ['{l,m,n}xyz', {}, ['lxyz', 'mxyz', 'nxyz']], + ['a/{a,b}/{c,d}/e', {}, ['a/a/c/e', 'a/a/d/e', 'a/b/c/e', 'a/b/d/e']], + ['a{b,c}d{e,f}g', {}, ['abdeg', 'abdfg', 'acdeg', 'acdfg']], + ['a/{x,y}/c{d,e}f.{md,txt}', {}, ['a/x/cdf.md', 'a/x/cdf.txt', 'a/x/cef.md', 'a/x/cef.txt', 'a/y/cdf.md', 'a/y/cdf.txt', 'a/y/cef.md', 'a/y/cef.txt']], + ['{a,b}{{a,b},a,b}', {}, ['aa', 'ab', 'aa', 'ab', 'ba', 'bb', 'ba', 'bb']], + ['a{b,c{d,e}f}g', {}, ['abg', 'acdfg', 'acefg']], + ['a{{x,y},z}b', {}, ['axb', 'ayb', 'azb']], + ['f{x,y{g,z}}h', {}, ['fxh', 'fygh', 'fyzh']], + ['a{b,c{d,e},h}x/z', {}, ['abx/z', 'acdx/z', 'acex/z', 'ahx/z']], + ['a{b,c{d,e},h}x{y,z}', {}, ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'ahxy', 'ahxz']], + ['a{b,c{d,e},{f,g}h}x{y,z}', {}, ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz']], + + // 'should not expand escaped braces', + + ['\\{a,b,c,d,e}', {}, ['{a,b,c,d,e}']], + ['a/\\{b,c}/{d,e}/f', {}, ['a/{b,c}/d/f', 'a/{b,c}/e/f']], + ['a/\\{x,y}/cde', {}, ['a/{x,y}/cde']], + ['a/b/c/{x,y\\}', {}, ['a/b/c/{x,y}']], + ['a/{z,\\{a,b,c,d,e}/d', {}, ['a/z/d', 'a/{a/d', 'a/b/d', 'a/c/d', 'a/d/d', 'a/e/d']], + ['abcd{efgh', {}, ['abcd{efgh']], + ['{a,b\\}c,d}', {}, ['a', 'b}c', 'd']], + ['{abc}', {}, ['{abc}']], + ['{x,y,\\{a,b,c\\}}', {}, ['x', 'y', '{a', 'b', 'c}']], + ['{x,y,{abc},trie}', {}, ['x', 'y', '{abc}', 'trie']], + ['{x,y,{a,b,c\\}}', {}, ['{x,y,a', '{x,y,b', '{x,y,c}']], + + 'should not expand escaped commas', + + ['{x\\,y,\\{abc\\},trie}', {}, ['x,y', '{abc}', 'trie']], + ['a{b\\,c\\,d}e', {}, ['a{b,c,d}e']], + ['a{b\\,c}d', {}, ['a{b,c}d']], + ['{abc\\,def}', {}, ['{abc,def}']], + ['{abc\\,def,ghi}', {}, ['abc,def', 'ghi']], + ['a/{b,c}/{x\\,y}/d/e', {}, ['a/b/{x,y}/d/e', 'a/c/{x,y}/d/e']], + + 'should handle empty braces', + + ['{ }', {}, ['{ }']], + ['{', {}, ['{']], + ['{}', {}, ['{}']], + ['}', {}, ['}']], + + 'should escape braces when only one value is defined', + + ['a{b}c', {}, ['a{b}c']], + ['a/b/c{d}e', {}, ['a/b/c{d}e']], + + 'should escape closing braces when open is not defined', + + ['{a,b}c,d}', {}, ['ac,d}', 'bc,d}']], + + 'should not expand braces in sets with es6/bash-like variables', + + ['abc/${ddd}/xyz', {}, ['abc/${ddd}/xyz']], + ['a${b}c', {}, ['a${b}c']], + ['a/{${b},c}/d', {}, ['a/${b}/d', 'a/c/d']], + ['a${b,d}/{foo,bar}c', {}, ['a${b,d}/fooc', 'a${b,d}/barc']], + + 'should support sequence brace operators', + + ['ff{a,b,c}', {}, ['ffa', 'ffb', 'ffc']], + ['f{d,e,f}g', {}, ['fdg', 'feg', 'ffg']], + ['{a,b,c}', {}, ['a', 'b', 'c']], + ['{l,m,n}xyz', {}, ['lxyz', 'mxyz', 'nxyz']], + + 'should expand multiple sets', + + ['a/{a,b}/{c,d}/e', {}, ['a/a/c/e', 'a/a/d/e', 'a/b/c/e', 'a/b/d/e']], + ['a{b,c}d{e,f}g', {}, ['abdeg', 'abdfg', 'acdeg', 'acdfg']], + ['a/{x,y}/c{d,e}f.{md,txt}', {}, ['a/x/cdf.md', 'a/x/cdf.txt', 'a/x/cef.md', 'a/x/cef.txt', 'a/y/cdf.md', 'a/y/cdf.txt', 'a/y/cef.md', 'a/y/cef.txt']], + + 'should expand nested sets', + + ['a{b,c{d,e}f}g', {}, ['abg', 'acdfg', 'acefg']], + ['a{{x,y},z}b', {}, ['axb', 'ayb', 'azb']], + ['f{x,y{g,z}}h', {}, ['fxh', 'fygh', 'fyzh']], + ['a{b,c{d,e},h}x/z', {}, ['abx/z', 'acdx/z', 'acex/z', 'ahx/z']], + ['a{b,c{d,e},h}x{y,z}', {}, ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'ahxy', 'ahxz']], + ['a{b,c{d,e},{f,g}h}x{y,z}', {}, ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz']], + ['a-{b{d,e}}-c', {}, ['a-{bd}-c', 'a-{be}-c']], + + 'should do nothing to glob characters', + + ['a/b/{d,e}/*.js', {}, ['a/b/d/*.js', 'a/b/e/*.js']], + ['a/**/c/{d,e}/f*.js', {}, ['a/**/c/d/f*.js', 'a/**/c/e/f*.js']], + ['a/**/c/{d,e}/f*.{md,txt}', {}, ['a/**/c/d/f*.md', 'a/**/c/d/f*.txt', 'a/**/c/e/f*.md', 'a/**/c/e/f*.txt']], + ['a/b/{d,e,[1-5]}/*.js', {}, ['a/b/d/*.js', 'a/b/e/*.js', 'a/b/[1-5]/*.js']], + + 'should work with leading and trailing commas', + ['a{b,}c', {}, ['abc', 'ac']], + ['a{,b}c', {}, ['ac', 'abc']], + + 'should handle spaces', + ['a{ ,c{d, },h}x', {}, ['a x', 'acdx', 'ac x', 'ahx']], + ['a{ ,c{d, },h} ', {}, ['a ', 'acd ', 'ac ', 'ah ']], + + 'see https://github.com/jonschlinkert/microequal/issues/66', + ['/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.{html,ejs}', {}, ['/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.html', '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.ejs']], + + 'should handle weirdly-formed brace expansions (fixed in post-bash-3.1)', + + ['a-{b{d,e}}-c', {}, ['a-{bd}-c', 'a-{be}-c']], + ['a-{bdef-{g,i}-c', {}, ['a-{bdef-g-c', 'a-{bdef-i-c']], + + // 'should not expand quoted strings', + + ['{"foo"}{1,2,3}', {}, ['{foo}1', '{foo}2', '{foo}3']], + ['{"foo"}{1,2,3}', { keepQuotes: true }, ['{"foo"}1', '{"foo"}2', '{"foo"}3']], + ['{"x,x"}', { keepQuotes: true }, ['{"x,x"}']], + ['{\'x,x\'}', { keepQuotes: true }, ['{\'x,x\'}']], + + 'should escape outer braces in nested non-sets', + + ['{a-{b,c,d}}', {}, ['{a-b}', '{a-c}', '{a-d}']], + ['{a,{a-{b,c,d}}}', {}, ['a', '{a-b}', '{a-c}', '{a-d}']], + + 'should escape imbalanced braces', + + ['abc{', {}, ['abc{']], + ['{abc{', {}, ['{abc{']], + ['{abc', {}, ['{abc']], + ['}abc', {}, ['}abc']], + ['ab{c', {}, ['ab{c']], + ['ab{c', {}, ['ab{c']], + ['{{a,b}', {}, ['{a', '{b']], + ['{a,b}}', {}, ['a}', 'b}']], + ['a{b{c{d,e}f}gh', {}, ['a{b{cdf}gh', 'a{b{cef}gh']], + ['a{b{c{d,e}f}g}h', {}, ['a{b{cdf}g}h', 'a{b{cef}g}h']], + ['f{x,y{{g,z}}h}', {}, ['fx', 'fy{g}h', 'fy{z}h']], + ['z{a,b},c}d', {}, ['za,c}d', 'zb,c}d']], + ['a{b{c{d,e}f{x,y{{g}h', {}, ['a{b{cdf{x,y{{g}h', 'a{b{cef{x,y{{g}h']], + ['f{x,y{{g}h', {}, ['f{x,y{{g}h']], + ['f{x,y{{g}}h', {}, ['f{x,y{{g}}h']], + ['a{b{c{d,e}f{x,y{}g}h', {}, ['a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh']], + ['f{x,y{}g}h', {}, ['fxh', 'fy{}gh']], + ['z{a,b{,c}d', {}, ['z{a,bd', 'z{a,bcd']] + ]; + + fixtures.forEach(arr => { + if (typeof arr === 'string') { + return; + } + + const options = { ...arr[1] }; + const pattern = arr[0]; + const expected = arr[2]; + + if (options.skip !== true) { + it('should compile: ' + pattern, () => equal(pattern, expected, options)); + } + }); +}); diff --git a/test/bash-spec.js b/test/bash-spec.js new file mode 100644 index 0000000..5675f5e --- /dev/null +++ b/test/bash-spec.js @@ -0,0 +1,197 @@ +'use strict'; + +require('mocha'); +const assert = require('assert').strict; +const bashPath = require('bash-path'); +const cp = require('child_process'); +const braces = require('..'); + +const bash = input => { + return cp.spawnSync(bashPath(), ['-c', `echo ${input}`]) + .stdout.toString() + .split(/\s+/) + .filter(Boolean); +}; + +const equal = (input, expected = bash(input), options) => { + assert.deepEqual(braces.expand(input, options), expected); +}; + +/** + * Bash 4.3 unit tests + */ + +describe('bash', () => { + const fixtures = [ + ['{1\\.2}', {}, ['{1.2}']], + ['{1\\.2}', { keepEscaping: true }, ['{1\\.2}']], + ['{"x,x"}', {}, ['{x,x}']], + ['{x","x}', {}, ['{x,x}']], + ['\'{x,x}\'', {}, ['{x,x}']], + ['{x`,`x}', {}, ['{x,x}']], + ['{x`,`x}', { keepQuotes: true }, ['{x`,`x}']], + ['\'{a,b}{{a,b},a,b}\'', {}, ['{a,b}{{a,b},a,b}']], + ['A{b,{d,e},{f,g}}Z', {}, ['AbZ', 'AdZ', 'AeZ', 'AfZ', 'AgZ']], + ['PRE-{a,b}{{a,b},a,b}-POST', {}, ['PRE-aa-POST', 'PRE-ab-POST', 'PRE-aa-POST', 'PRE-ab-POST', 'PRE-ba-POST', 'PRE-bb-POST', 'PRE-ba-POST', 'PRE-bb-POST']], + ['\\{a,b}{{a,b},a,b}', {}, ['{a,b}a', '{a,b}b', '{a,b}a', '{a,b}b']], + ['{{a,b}', {}, ['{a', '{b']], + ['{a,b}}', {}, ['a}', 'b}']], + ['{,}', {}, ['', '']], + ['a{,}', {}, ['a', 'a']], + ['{,}b', {}, ['b', 'b']], + ['a{,}b', {}, ['ab', 'ab']], + ['a{b}c', {}, ['a{b}c']], + ['a{1..5}b', {}, ['a1b', 'a2b', 'a3b', 'a4b', 'a5b']], + ['a{01..5}b', {}, ['a01b', 'a02b', 'a03b', 'a04b', 'a05b']], + ['a{-01..5}b', {}, ['a-01b', 'a000b', 'a001b', 'a002b', 'a003b', 'a004b', 'a005b']], + ['a{-01..5..3}b', {}, ['a-01b', 'a002b', 'a005b']], + ['a{001..9}b', {}, ['a001b', 'a002b', 'a003b', 'a004b', 'a005b', 'a006b', 'a007b', 'a008b', 'a009b']], + ['a{b,c{d,e},{f,g}h}x{y,z', {}, ['abx{y,z', 'acdx{y,z', 'acex{y,z', 'afhx{y,z', 'aghx{y,z']], + ['a{b,c{d,e},{f,g}h}x{y,z\\}', {}, ['abx{y,z}', 'acdx{y,z}', 'acex{y,z}', 'afhx{y,z}', 'aghx{y,z}']], + ['a{b,c{d,e},{f,g}h}x{y,z}', {}, ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz']], + ['a{b{c{d,e}f{x,y{{g}h', {}, ['a{b{cdf{x,y{{g}h', 'a{b{cef{x,y{{g}h']], + ['a{b{c{d,e}f{x,y{}g}h', {}, ['a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh']], + ['a{b{c{d,e}f{x,y}}g}h', {}, ['a{b{cdfx}g}h', 'a{b{cdfy}g}h', 'a{b{cefx}g}h', 'a{b{cefy}g}h']], + ['a{b{c{d,e}f}g}h', {}, ['a{b{cdf}g}h', 'a{b{cef}g}h']], + ['a{{x,y},z}b', {}, ['axb', 'ayb', 'azb']], + ['f{x,y{g,z}}h', {}, ['fxh', 'fygh', 'fyzh']], + ['f{x,y{{g,z}}h', {}, ['f{x,y{g}h', 'f{x,y{z}h']], + ['f{x,y{{g,z}}h}', {}, ['fx', 'fy{g}h', 'fy{z}h']], + ['f{x,y{{g}h', {}, ['f{x,y{{g}h']], + ['f{x,y{{g}}h', {}, ['f{x,y{{g}}h']], + ['f{x,y{}g}h', {}, ['fxh', 'fy{}gh']], + ['z{a,b{,c}d', {}, ['z{a,bd', 'z{a,bcd']], + ['z{a,b},c}d', {}, ['za,c}d', 'zb,c}d']], + ['{-01..5}', {}, ['-01', '000', '001', '002', '003', '004', '005']], + ['{-05..100..5}', {}, ['-05', '000', '005', '010', '015', '020', '025', '030', '035', '040', '045', '050', '055', '060', '065', '070', '075', '080', '085', '090', '095', '100']], + ['{-05..100}', {}, ['-05', '-04', '-03', '-02', '-01', '000', '001', '002', '003', '004', '005', '006', '007', '008', '009', '010', '011', '012', '013', '014', '015', '016', '017', '018', '019', '020', '021', '022', '023', '024', '025', '026', '027', '028', '029', '030', '031', '032', '033', '034', '035', '036', '037', '038', '039', '040', '041', '042', '043', '044', '045', '046', '047', '048', '049', '050', '051', '052', '053', '054', '055', '056', '057', '058', '059', '060', '061', '062', '063', '064', '065', '066', '067', '068', '069', '070', '071', '072', '073', '074', '075', '076', '077', '078', '079', '080', '081', '082', '083', '084', '085', '086', '087', '088', '089', '090', '091', '092', '093', '094', '095', '096', '097', '098', '099', '100']], + ['{0..5..2}', {}, ['0', '2', '4']], + ['{0001..05..2}', {}, ['0001', '0003', '0005']], + ['{0001..-5..2}', {}, ['0001', '-001', '-003', '-005']], + ['{0001..-5..-2}', {}, ['0001', '-001', '-003', '-005']], + ['{0001..5..-2}', {}, ['0001', '0003', '0005']], + ['{01..5}', {}, ['01', '02', '03', '04', '05']], + ['{1..05}', {}, ['01', '02', '03', '04', '05']], + ['{1..05..3}', {}, ['01', '04']], + ['{05..100}', {}, ['005', '006', '007', '008', '009', '010', '011', '012', '013', '014', '015', '016', '017', '018', '019', '020', '021', '022', '023', '024', '025', '026', '027', '028', '029', '030', '031', '032', '033', '034', '035', '036', '037', '038', '039', '040', '041', '042', '043', '044', '045', '046', '047', '048', '049', '050', '051', '052', '053', '054', '055', '056', '057', '058', '059', '060', '061', '062', '063', '064', '065', '066', '067', '068', '069', '070', '071', '072', '073', '074', '075', '076', '077', '078', '079', '080', '081', '082', '083', '084', '085', '086', '087', '088', '089', '090', '091', '092', '093', '094', '095', '096', '097', '098', '099', '100']], + ['{0a..0z}', {}, ['{0a..0z}']], + ['{a,b\\}c,d}', {}, ['a', 'b}c', 'd']], + ['{a,b{c,d}', {}, ['{a,bc', '{a,bd']], + ['{a,b}c,d}', {}, ['ac,d}', 'bc,d}']], + ['{a..F}', {}, ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F']], + ['{A..f}', {}, ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f']], + ['{a..Z}', {}, ['a', '`', '_', '^', ']', '\\', '[', 'Z']], + ['{A..z}', {}, ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']], + ['{z..A}', {}, ['z', 'y', 'x', 'w', 'v', 'u', 't', 's', 'r', 'q', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A']], + ['{Z..a}', {}, ['Z', '[', '\\', ']', '^', '_', '`', 'a']], + ['{a..F..2}', {}, ['a', '_', ']', '[', 'Y', 'W', 'U', 'S', 'Q', 'O', 'M', 'K', 'I', 'G']], + ['{A..f..02}', {}, ['A', 'C', 'E', 'G', 'I', 'K', 'M', 'O', 'Q', 'S', 'U', 'W', 'Y', '[', ']', '_', 'a', 'c', 'e']], + ['{a..Z..5}', {}, ['a', '\\']], + ['d{a..Z..5}b', {}, ['dab', 'd\\b']], + ['{A..z..10}', {}, ['A', 'K', 'U', '_', 'i', 's']], + ['{z..A..-2}', {}, ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b', '`', '^', '\\', 'Z', 'X', 'V', 'T', 'R', 'P', 'N', 'L', 'J', 'H', 'F', 'D', 'B']], + ['{Z..a..20}', {}, ['Z']], + ['{a{,b}', {}, ['{a', '{ab']], + ['{a\\},b}', {}, ['a}', 'b']], + ['{x,y{,}g}', {}, ['x', 'yg', 'yg']], + ['{x,y{}g}', {}, ['x', 'y{}g']], + ['{{a,b}', {}, ['{a', '{b']], + ['{{a,b},c}', {}, ['a', 'b', 'c']], + ['{{a,b}c}', {}, ['{ac}', '{bc}']], + ['{{a,b},}', {}, ['a', 'b', '']], + ['X{{a,b},}X', {}, ['XaX', 'XbX', 'XX']], + ['{{a,b},}c', {}, ['ac', 'bc', 'c']], + ['{{a,b}.}', {}, ['{a.}', '{b.}']], + ['{{a,b}}', {}, ['{a}', '{b}']], + ['X{a..#}X', {}, ['XaX', 'X`X', 'X_X', 'X^X', 'X]X', 'X\\X', 'X[X', 'XZX', 'XYX', 'XXX', 'XWX', 'XVX', 'XUX', 'XTX', 'XSX', 'XRX', 'XQX', 'XPX', 'XOX', 'XNX', 'XMX', 'XLX', 'XKX', 'XJX', 'XIX', 'XHX', 'XGX', 'XFX', 'XEX', 'XDX', 'XCX', 'XBX', 'XAX', 'X@X', 'X?X', 'X>X', 'X=X', 'X { + if (typeof arr === 'string') { + return; + } + + const options = { ...arr[1] }; + const pattern = arr[0]; + const expected = arr[2]; + + if (options.skip === true) { + return; + } + + it('should compile: ' + pattern, () => { + equal(pattern, expected, options); + }); + }); +}); diff --git a/test/bash.expanded.js b/test/bash.expanded.js deleted file mode 100644 index f2a4951..0000000 --- a/test/bash.expanded.js +++ /dev/null @@ -1,417 +0,0 @@ -'use strict'; - -var extend = require('extend-shallow'); -var assert = require('assert'); -var braces = require('..'); - -function equal(pattern, expected, options) { - var actual = braces.expand(pattern, options); - assert.deepEqual(actual.sort(), expected.sort(), pattern); -} - -/** - * Bash 4.3 unit tests with `braces.expand()` - */ - -describe('bash.expanded', function() { - it('should throw an error when range exceeds rangeLimit', function() { - assert.throws(function() { - braces.expand('{214748364..2147483649}'); - }); - }); - - var fixtures = [ - [ 'a{b,c{1..50}/{foo,bar,baz}/,g}h/i', {}, [ 'abh/i', 'ac1/bar/h/i', 'ac1/baz/h/i', 'ac1/foo/h/i', 'ac10/bar/h/i', 'ac10/baz/h/i', 'ac10/foo/h/i', 'ac11/bar/h/i', 'ac11/baz/h/i', 'ac11/foo/h/i', 'ac12/bar/h/i', 'ac12/baz/h/i', 'ac12/foo/h/i', 'ac13/bar/h/i', 'ac13/baz/h/i', 'ac13/foo/h/i', 'ac14/bar/h/i', 'ac14/baz/h/i', 'ac14/foo/h/i', 'ac15/bar/h/i', 'ac15/baz/h/i', 'ac15/foo/h/i', 'ac16/bar/h/i', 'ac16/baz/h/i', 'ac16/foo/h/i', 'ac17/bar/h/i', 'ac17/baz/h/i', 'ac17/foo/h/i', 'ac18/bar/h/i', 'ac18/baz/h/i', 'ac18/foo/h/i', 'ac19/bar/h/i', 'ac19/baz/h/i', 'ac19/foo/h/i', 'ac2/bar/h/i', 'ac2/baz/h/i', 'ac2/foo/h/i', 'ac20/bar/h/i', 'ac20/baz/h/i', 'ac20/foo/h/i', 'ac21/bar/h/i', 'ac21/baz/h/i', 'ac21/foo/h/i', 'ac22/bar/h/i', 'ac22/baz/h/i', 'ac22/foo/h/i', 'ac23/bar/h/i', 'ac23/baz/h/i', 'ac23/foo/h/i', 'ac24/bar/h/i', 'ac24/baz/h/i', 'ac24/foo/h/i', 'ac25/bar/h/i', 'ac25/baz/h/i', 'ac25/foo/h/i', 'ac26/bar/h/i', 'ac26/baz/h/i', 'ac26/foo/h/i', 'ac27/bar/h/i', 'ac27/baz/h/i', 'ac27/foo/h/i', 'ac28/bar/h/i', 'ac28/baz/h/i', 'ac28/foo/h/i', 'ac29/bar/h/i', 'ac29/baz/h/i', 'ac29/foo/h/i', 'ac3/bar/h/i', 'ac3/baz/h/i', 'ac3/foo/h/i', 'ac30/bar/h/i', 'ac30/baz/h/i', 'ac30/foo/h/i', 'ac31/bar/h/i', 'ac31/baz/h/i', 'ac31/foo/h/i', 'ac32/bar/h/i', 'ac32/baz/h/i', 'ac32/foo/h/i', 'ac33/bar/h/i', 'ac33/baz/h/i', 'ac33/foo/h/i', 'ac34/bar/h/i', 'ac34/baz/h/i', 'ac34/foo/h/i', 'ac35/bar/h/i', 'ac35/baz/h/i', 'ac35/foo/h/i', 'ac36/bar/h/i', 'ac36/baz/h/i', 'ac36/foo/h/i', 'ac37/bar/h/i', 'ac37/baz/h/i', 'ac37/foo/h/i', 'ac38/bar/h/i', 'ac38/baz/h/i', 'ac38/foo/h/i', 'ac39/bar/h/i', 'ac39/baz/h/i', 'ac39/foo/h/i', 'ac4/bar/h/i', 'ac4/baz/h/i', 'ac4/foo/h/i', 'ac40/bar/h/i', 'ac40/baz/h/i', 'ac40/foo/h/i', 'ac41/bar/h/i', 'ac41/baz/h/i', 'ac41/foo/h/i', 'ac42/bar/h/i', 'ac42/baz/h/i', 'ac42/foo/h/i', 'ac43/bar/h/i', 'ac43/baz/h/i', 'ac43/foo/h/i', 'ac44/bar/h/i', 'ac44/baz/h/i', 'ac44/foo/h/i', 'ac45/bar/h/i', 'ac45/baz/h/i', 'ac45/foo/h/i', 'ac46/bar/h/i', 'ac46/baz/h/i', 'ac46/foo/h/i', 'ac47/bar/h/i', 'ac47/baz/h/i', 'ac47/foo/h/i', 'ac48/bar/h/i', 'ac48/baz/h/i', 'ac48/foo/h/i', 'ac49/bar/h/i', 'ac49/baz/h/i', 'ac49/foo/h/i', 'ac5/bar/h/i', 'ac5/baz/h/i', 'ac5/foo/h/i', 'ac50/bar/h/i', 'ac50/baz/h/i', 'ac50/foo/h/i', 'ac6/bar/h/i', 'ac6/baz/h/i', 'ac6/foo/h/i', 'ac7/bar/h/i', 'ac7/baz/h/i', 'ac7/foo/h/i', 'ac8/bar/h/i', 'ac8/baz/h/i', 'ac8/foo/h/i', 'ac9/bar/h/i', 'ac9/baz/h/i', 'ac9/foo/h/i', 'agh/i' ] ], - [ '0{1..9} {10..20}', {}, [ '01 10', '01 11', '01 12', '01 13', '01 14', '01 15', '01 16', '01 17', '01 18', '01 19', '01 20', '02 10', '02 11', '02 12', '02 13', '02 14', '02 15', '02 16', '02 17', '02 18', '02 19', '02 20', '03 10', '03 11', '03 12', '03 13', '03 14', '03 15', '03 16', '03 17', '03 18', '03 19', '03 20', '04 10', '04 11', '04 12', '04 13', '04 14', '04 15', '04 16', '04 17', '04 18', '04 19', '04 20', '05 10', '05 11', '05 12', '05 13', '05 14', '05 15', '05 16', '05 17', '05 18', '05 19', '05 20', '06 10', '06 11', '06 12', '06 13', '06 14', '06 15', '06 16', '06 17', '06 18', '06 19', '06 20', '07 10', '07 11', '07 12', '07 13', '07 14', '07 15', '07 16', '07 17', '07 18', '07 19', '07 20', '08 10', '08 11', '08 12', '08 13', '08 14', '08 15', '08 16', '08 17', '08 18', '08 19', '08 20', '09 10', '09 11', '09 12', '09 13', '09 14', '09 15', '09 16', '09 17', '09 18', '09 19', '09 20' ] ], - [ 'a/\\{b,c,d,{x,y}}{e,f\\}/g', {}, [ 'a/{b,c,d,x}{e,f}/g', 'a/{b,c,d,y}{e,f}/g' ] ], - [ 'a/\\{b,c,d\\}\\{e,f\\}/g', {}, [ 'a/{b,c,d}{e,f}/g' ] ], - [ 'a/\\{b,c,d\\}\\{e,f}/g', {}, [ 'a/{b,c,d}{e,f}/g' ] ], - [ 'a/\\{b,c,d\\}{e,f}/g', {}, [ 'a/{b,c,d}e/g', 'a/{b,c,d}f/g' ] ], - [ 'a/\\{b,c,d{x,y}}{e,f\\}/g', {}, [ 'a/{b,c,dx}{e,f}/g', 'a/{b,c,dy}{e,f}/g' ] ], - [ 'a/\\{b,c,d}{e,f\\}/g', {}, [ 'a/{b,c,d}{e,f}/g' ] ], - [ 'a/\\{b,c,d}{e,f}/g', {}, [ 'a/{b,c,d}e/g', 'a/{b,c,d}f/g' ] ], - [ 'a/\\{x,y}/cde', {}, [ 'a/{x,y}/cde' ] ], - [ 'a/\\{{b,c}{e,f}/g', {}, [ 'a/{be/g', 'a/{bf/g', 'a/{ce/g', 'a/{cf/g' ] ], - [ 'a/\\{{b,c}{e,f}\\}/g', {}, [ 'a/{be}/g', 'a/{bf}/g', 'a/{ce}/g', 'a/{cf}/g' ] ], - [ 'a/\\{{b,c}{e,f}}/g', {}, [ 'a/{be}/g', 'a/{bf}/g', 'a/{ce}/g', 'a/{cf}/g' ] ], - [ 'a/b/{b,c,{d,e{f,g},{w,x}/{y,z}}}/h/i', {}, [ 'a/b/b/h/i', 'a/b/c/h/i', 'a/b/d/h/i', 'a/b/ef/h/i', 'a/b/eg/h/i', 'a/b/w/y/h/i', 'a/b/w/z/h/i', 'a/b/x/y/h/i', 'a/b/x/z/h/i' ] ], - [ 'a/{b,c,d}{e,f}/g', {}, [ 'a/be/g', 'a/bf/g', 'a/ce/g', 'a/cf/g', 'a/de/g', 'a/df/g' ] ], - [ 'a/{b,c\\,d}{e,f}/g', {}, [ 'a/be/g', 'a/bf/g', 'a/c,de/g', 'a/c,df/g' ] ], - [ 'a/{b,c\\}}{e,f}/g', {}, [ 'a/be/g', 'a/bf/g', 'a/c}e/g', 'a/c}f/g' ] ], - [ 'a/{b,c}', {}, [ 'a/b', 'a/c' ] ], - [ 'a/{b,c}d{e,f}/g', {}, [ 'a/bde/g', 'a/bdf/g', 'a/cde/g', 'a/cdf/g' ] ], - [ 'a/{b,c}{e,f}/g', {}, [ 'a/be/g', 'a/bf/g', 'a/ce/g', 'a/cf/g' ] ], - [ 'a/{b,c}{e,f}{g,h,i}/k', {}, [ 'a/beg/k', 'a/beh/k', 'a/bei/k', 'a/bfg/k', 'a/bfh/k', 'a/bfi/k', 'a/ceg/k', 'a/ceh/k', 'a/cei/k', 'a/cfg/k', 'a/cfh/k', 'a/cfi/k' ] ], - [ 'a/{b,{c,d},e}/f', {}, [ 'a/b/f', 'a/c/f', 'a/d/f', 'a/e/f' ] ], - [ 'a/{b,{c,d}/{e,f},g}/h', {}, [ 'a/b/h', 'a/c/e/h', 'a/c/f/h', 'a/d/e/h', 'a/d/f/h', 'a/g/h' ] ], - [ 'a/{b{c,d},e{f,g}h{i,j}}/k', {}, [ 'a/bc/k', 'a/bd/k', 'a/efhi/k', 'a/efhj/k', 'a/eghi/k', 'a/eghj/k' ] ], - [ 'a/{b{c,d},e}/f', {}, [ 'a/bc/f', 'a/bd/f', 'a/e/f' ] ], - [ 'a/{b{c,d}e{f,g}h{i,j}}/k', {}, [ 'a/{bcefhi}/k', 'a/{bcefhj}/k', 'a/{bceghi}/k', 'a/{bceghj}/k', 'a/{bdefhi}/k', 'a/{bdefhj}/k', 'a/{bdeghi}/k', 'a/{bdeghj}/k' ] ], - [ 'a/{b{c,d}e{f,g},h{i,j}}/k', {}, [ 'a/bcef/k', 'a/bceg/k', 'a/bdef/k', 'a/bdeg/k', 'a/hi/k', 'a/hj/k' ] ], - [ 'a/{x,y}/{1..5}c{d,e}f.{md,txt}', {}, [ 'a/x/1cdf.md', 'a/x/1cdf.txt', 'a/x/1cef.md', 'a/x/1cef.txt', 'a/x/2cdf.md', 'a/x/2cdf.txt', 'a/x/2cef.md', 'a/x/2cef.txt', 'a/x/3cdf.md', 'a/x/3cdf.txt', 'a/x/3cef.md', 'a/x/3cef.txt', 'a/x/4cdf.md', 'a/x/4cdf.txt', 'a/x/4cef.md', 'a/x/4cef.txt', 'a/x/5cdf.md', 'a/x/5cdf.txt', 'a/x/5cef.md', 'a/x/5cef.txt', 'a/y/1cdf.md', 'a/y/1cdf.txt', 'a/y/1cef.md', 'a/y/1cef.txt', 'a/y/2cdf.md', 'a/y/2cdf.txt', 'a/y/2cef.md', 'a/y/2cef.txt', 'a/y/3cdf.md', 'a/y/3cdf.txt', 'a/y/3cef.md', 'a/y/3cef.txt', 'a/y/4cdf.md', 'a/y/4cdf.txt', 'a/y/4cef.md', 'a/y/4cef.txt', 'a/y/5cdf.md', 'a/y/5cdf.txt', 'a/y/5cef.md', 'a/y/5cef.txt' ] ], - [ 'a/{x,z}{b,{c,d}/{e,f},g}/h', {}, [ 'a/xb/h', 'a/xc/e/h', 'a/xc/f/h', 'a/xd/e/h', 'a/xd/f/h', 'a/xg/h', 'a/zb/h', 'a/zc/e/h', 'a/zc/f/h', 'a/zd/e/h', 'a/zd/f/h', 'a/zg/h' ] ], - [ 'a/{x,{1..5},y}/c{d}e', {}, [ 'a/1/c{d}e', 'a/2/c{d}e', 'a/3/c{d}e', 'a/4/c{d}e', 'a/5/c{d}e', 'a/x/c{d}e', 'a/y/c{d}e' ] ], - [ 'a/{{a,b}/{c,d}}/z', {}, [ 'a/{a/c}/z', 'a/{a/d}/z', 'a/{b/c}/z', 'a/{b/d}/z' ] ], - [ 'a/{{b,c}/{d,e}}', {}, [ 'a/{b/d}', 'a/{b/e}', 'a/{c/d}', 'a/{c/e}' ] ], - [ 'a/{{b,c}/{d,e}}/f', {}, [ 'a/{b/d}/f', 'a/{b/e}/f', 'a/{c/d}/f', 'a/{c/e}/f' ] ], - [ 'a{0..3}d', {}, [ 'a0d', 'a1d', 'a2d', 'a3d' ] ], - [ 'a{b}c', {}, [ 'a{b}c' ] ], - [ 'foo {1,2} bar', {}, [ 'foo 1 bar', 'foo 2 bar' ] ], - [ 'x{10..1}y', {}, [ 'x10y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y' ] ], - [ 'x{3..3}y', {}, [ 'x3y' ] ], - [ '{ }', {}, [ '{ }' ] ], - [ '{', {}, [ '{' ] ], - [ '{0..10,braces}', {}, [ '0..10', 'braces' ] ], - [ '{10..1}', {}, [ '1', '10', '2', '3', '4', '5', '6', '7', '8', '9' ] ], - [ '{3..3}', {}, [ '3' ] ], - [ '{5..8}', {}, [ '5', '6', '7', '8' ] ], - [ '{9..-4}', {}, [ '-1', '-2', '-3', '-4', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ] ], - [ '{a,b,{c,d},e}', {}, [ 'a', 'b', 'c', 'd', 'e' ] ], - [ '{a,b,{c,d}e}', {}, [ 'a', 'b', 'ce', 'de' ] ], - [ '{a,b,{c,d}}', {}, [ 'a', 'b', 'c', 'd' ] ], - [ '{a,b{c,d}}', {}, [ 'a', 'bc', 'bd' ] ], - [ '{a,b}/{c,d}', {}, [ 'a/c', 'a/d', 'b/c', 'b/d' ] ], - [ '{a,b}c,d\\}', {}, [ 'ac,d}', 'bc,d}' ] ], - [ '{a,b\\}c,d}', {}, [ 'a', 'b}c', 'd' ] ], - [ '{a,b}{c,d}', {}, [ 'ac', 'ad', 'bc', 'bd' ] ], - [ '{abc}', {}, [ '{abc}' ] ], - [ '{b{c,d},e}', {}, [ 'bc', 'bd', 'e' ] ], - [ '{b{c,d},e}/f', {}, [ 'bc/f', 'bd/f', 'e/f' ] ], - [ 'x,y,{abc},trie', {}, [ 'x,y,{abc},trie' ] ], - [ '{{0..10},braces}', {}, [ '0', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', 'braces' ] ], - [ '{{a,b},{c,d}}', {}, [ 'a', 'b', 'c', 'd' ] ], - [ '{{a,b}/{c,d}}', {}, [ '{a/c}', '{a/d}', '{b/c}', '{b/d}' ] ], - [ '{{a,b}/{c,d}}/z', {}, [ '{a/c}/z', '{a/d}/z', '{b/c}/z', '{b/d}/z' ] ], - [ '{}', {}, [ '{}' ] ], - - // should ignore globs - [ '}', {}, [ '}' ] ], - - 'should ignore globs', - - [ '{generate,{assemble,update,verb}{file,-generate-*},generator}.js', {}, [ 'assemble-generate-*.js', 'assemblefile.js', 'generate.js', 'generator.js', 'update-generate-*.js', 'updatefile.js', 'verb-generate-*.js', 'verbfile.js' ] ], - [ '**/{foo,bar}.js', {}, [ '**/bar.js', '**/foo.js' ] ], - [ '**/{1..5}/a.js', {}, [ '**/1/a.js', '**/2/a.js', '**/3/a.js', '**/4/a.js', '**/5/a.js' ] ], - [ '**/{a,b,c}/*.js', {}, [ '**/a/*.js', '**/b/*.js', '**/c/*.js' ] ], - [ '**/{a,b,*}/*.js', {}, [ '**/*/*.js', '**/a/*.js', '**/b/*.js' ] ], - [ '**/{**,b,*}/*.js', {}, [ '**/**/*.js', '**/*/*.js', '**/b/*.js' ] ], - [ '/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', {}, [ '/usr/lib/ex', '/usr/lib/how_ex', '/usr/ucb/edit', '/usr/ucb/ex' ] ], - [ 'ff{c,b,a}', {}, [ 'ffa', 'ffb', 'ffc' ] ], - [ 'f{d,e,f}g', {}, [ 'fdg', 'feg', 'ffg' ] ], - [ 'x{{0..10},braces}y', {}, [ 'x0y', 'x10y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'xbracesy' ] ], - [ '{a,b,c}', {}, [ 'a', 'b', 'c' ] ], - [ '{braces,{0..10}}', {}, [ '0', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', 'braces' ] ], - [ '{l,n,m}xyz', {}, [ 'lxyz', 'mxyz', 'nxyz' ] ], - [ '{{0..10},braces}', {}, [ '0', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', 'braces' ] ], - [ '{{1..10..2},braces}', {}, [ '1', '3', '5', '7', '9', 'braces' ] ], - [ '{{1..10},braces}', {}, [ '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', 'braces' ] ], - [ 'a/{a,b}/{c,d}/e', {}, [ 'a/a/c/e', 'a/a/d/e', 'a/b/c/e', 'a/b/d/e' ] ], - [ 'a{b,c}d{e,f}g', {}, [ 'abdeg', 'abdfg', 'acdeg', 'acdfg' ] ], - [ 'a/{x,y}/c{d,e}f.{md,txt}', {}, [ 'a/x/cdf.md', 'a/x/cdf.txt', 'a/x/cef.md', 'a/x/cef.txt', 'a/y/cdf.md', 'a/y/cdf.txt', 'a/y/cef.md', 'a/y/cef.txt' ] ], - [ '{a,b}{{a,b},a,b}', {}, [ 'aa', 'aa', 'ab', 'ab', 'ba', 'ba', 'bb', 'bb' ] ], - [ '/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', {}, [ '/usr/lib/ex', '/usr/lib/how_ex', '/usr/ucb/edit', '/usr/ucb/ex' ] ], - [ 'a{b,c{d,e}f}g', {}, [ 'abg', 'acdfg', 'acefg' ] ], - [ 'a{{x,y},z}b', {}, [ 'axb', 'ayb', 'azb' ] ], - [ 'f{x,y{g,z}}h', {}, [ 'fxh', 'fygh', 'fyzh' ] ], - [ 'a{b,c{d,e},h}x/z', {}, [ 'abx/z', 'acdx/z', 'acex/z', 'ahx/z' ] ], - [ 'a{b,c{d,e},h}x{y,z}', {}, [ 'abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'ahxy', 'ahxz' ] ], - [ 'a{b,c{d,e},{f,g}h}x{y,z}', {}, [ 'abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz' ] ], - - 'should gracefully handle large ranges (`braces` handles these fine,', 'they are tested elsewhere, but they break all the other reference libs)', - - [ '{214748364..2147483649}', { skip: true } ], - [ '{2147483645..2147483649}', { skip: true } ], - - 'should handle invalid sets', - - [ '{0..10,braces}', {}, [ '0..10', 'braces' ] ], - [ '{1..10,braces}', {}, [ '1..10', 'braces' ] ], - - 'should not expand escaped braces', - - [ '\\{a,b,c,d,e}', {}, [ '{a,b,c,d,e}' ] ], - [ 'a/\\{b,c}/{d,e}/f', {}, [ 'a/{b,c}/d/f', 'a/{b,c}/e/f' ] ], - [ 'a/\\{x,y}/cde', {}, [ 'a/{x,y}/cde' ] ], - [ 'a/b/c/{x,y\\}', {}, [ 'a/b/c/{x,y}' ] ], - [ 'a/{z,\\{a,b,c,d,e}/d', {}, [ 'a/b/d', 'a/c/d', 'a/d/d', 'a/e/d', 'a/z/d', 'a/{a/d' ] ], - [ 'abcd{efgh', {}, [ 'abcd{efgh' ] ], - [ '{a,b\\}c,d}', {}, [ 'a', 'b}c', 'd' ] ], - [ '{abc}', {}, [ '{abc}' ] ], - [ '{x,y,\\{a,b,c\\}}', {}, [ 'b', 'c}', 'x', 'y', '{a' ] ], - [ '{x,y,{a,b,c\\}}', {}, [ '{x,y,a', '{x,y,b', '{x,y,c}' ] ], - [ '{x,y,{abc},trie}', {}, [ 'trie', 'x', 'y', '{abc}' ] ], - [ './\\{x,y}/{a..z..3}/', {}, [ './{x,y}/a/', './{x,y}/d/', './{x,y}/g/', './{x,y}/j/', './{x,y}/m/', './{x,y}/p/', './{x,y}/s/', './{x,y}/v/', './{x,y}/y/' ] ], - - 'should not expand escaped commas', - - [ '{x\\,y,\\{abc\\},trie}', {}, [ 'trie', 'x,y', '{abc}' ] ], - [ 'a{b\\,c\\,d}e', {}, [ 'a{b,c,d}e' ] ], - [ 'a{b\\,c}d', {}, [ 'a{b,c}d' ] ], - [ '{abc\\,def}', {}, [ '{abc,def}' ] ], - [ '{abc\\,def,ghi}', {}, [ 'abc,def', 'ghi' ] ], - [ 'a/{b,c}/{x\\,y}/d/e', {}, [ 'a/b/{x,y}/d/e', 'a/c/{x,y}/d/e' ] ], - - 'should handle empty braces', - - [ '{ }', {}, [ '{ }' ] ], - [ '{', {}, [ '{' ] ], - [ '{}', {}, [ '{}' ] ], - [ '}', {}, [ '}' ] ], - - 'should escape braces when only one value is defined', - - [ 'a{b}c', {}, [ 'a{b}c' ] ], - [ 'a/b/c{d}e', {}, [ 'a/b/c{d}e' ] ], - - 'should escape closing braces when open is not defined', - - [ '{a,b}c,d}', {}, [ 'ac,d}', 'bc,d}' ] ], - - 'should not expand braces in sets with es6/bash-like variables', - - [ 'abc/${ddd}/xyz', {}, [ 'abc/${ddd}/xyz' ] ], - [ 'a${b}c', {}, [ 'a${b}c' ] ], - [ 'a/{${b},c}/d', {}, [ 'a/${b}/d', 'a/c/d' ] ], - [ 'a${b,d}/{foo,bar}c', {}, [ 'a${b,d}/barc', 'a${b,d}/fooc' ] ], - - 'should support sequence brace operators', - - [ '/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', {}, [ '/usr/lib/ex', '/usr/lib/how_ex', '/usr/ucb/edit', '/usr/ucb/ex' ] ], - [ 'ff{c,b,a}', {}, [ 'ffa', 'ffb', 'ffc' ] ], - [ 'f{d,e,f}g', {}, [ 'fdg', 'feg', 'ffg' ] ], - [ 'x{{0..10},braces}y', {}, [ 'x0y', 'x10y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'xbracesy' ] ], - [ '{a,b,c}', {}, [ 'a', 'b', 'c' ] ], - [ '{braces,{0..10}}', {}, [ '0', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', 'braces' ] ], - [ '{l,n,m}xyz', {}, [ 'lxyz', 'mxyz', 'nxyz' ] ], - [ '{{0..10},braces}', {}, [ '0', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', 'braces' ] ], - [ '{{1..10..2},braces}', {}, [ '1', '3', '5', '7', '9', 'braces' ] ], - [ '{{1..10},braces}', {}, [ '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', 'braces' ] ], - - 'should expand multiple sets', - - [ 'a/{a,b}/{c,d}/e', {}, [ 'a/a/c/e', 'a/a/d/e', 'a/b/c/e', 'a/b/d/e' ] ], - [ 'a{b,c}d{e,f}g', {}, [ 'abdeg', 'abdfg', 'acdeg', 'acdfg' ] ], - [ 'a/{x,y}/c{d,e}f.{md,txt}', {}, [ 'a/x/cdf.md', 'a/x/cdf.txt', 'a/x/cef.md', 'a/x/cef.txt', 'a/y/cdf.md', 'a/y/cdf.txt', 'a/y/cef.md', 'a/y/cef.txt' ] ], - - 'should expand nested sets', - - [ '{a,b}{{a,b},a,b}', {}, [ 'aa', 'aa', 'ab', 'ab', 'ba', 'ba', 'bb', 'bb' ] ], - [ '/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', {}, [ '/usr/lib/ex', '/usr/lib/how_ex', '/usr/ucb/edit', '/usr/ucb/ex' ] ], - [ 'a{b,c{d,e}f}g', {}, [ 'abg', 'acdfg', 'acefg' ] ], - [ 'a{{x,y},z}b', {}, [ 'axb', 'ayb', 'azb' ] ], - [ 'f{x,y{g,z}}h', {}, [ 'fxh', 'fygh', 'fyzh' ] ], - [ 'a{b,c{d,e},h}x/z', {}, [ 'abx/z', 'acdx/z', 'acex/z', 'ahx/z' ] ], - [ 'a{b,c{d,e},h}x{y,z}', {}, [ 'abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'ahxy', 'ahxz' ] ], - [ 'a{b,c{d,e},{f,g}h}x{y,z}', {}, [ 'abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz' ] ], - [ 'a-{b{d,e}}-c', {}, [ 'a-{bd}-c', 'a-{be}-c' ] ], - - 'should ignore glob characters', - - [ 'a/b/{d,e}/*.js', {}, [ 'a/b/d/*.js', 'a/b/e/*.js' ] ], - [ 'a/**/c/{d,e}/f*.js', {}, [ 'a/**/c/d/f*.js', 'a/**/c/e/f*.js' ] ], - [ 'a/**/c/{d,e}/f*.{md,txt}', {}, [ 'a/**/c/d/f*.md', 'a/**/c/d/f*.txt', 'a/**/c/e/f*.md', 'a/**/c/e/f*.txt' ] ], - [ 'a/b/{d,e,[1-5]}/*.js', {}, [ 'a/b/[1-5]/*.js', 'a/b/d/*.js', 'a/b/e/*.js' ] ], - - 'should work with leading and trailing commas', - - [ 'a{b,}c', {}, [ 'abc', 'ac' ] ], - [ 'a{,b}c', {}, [ 'abc', 'ac' ] ], - - 'should handle spaces', - - [ '0{1..9} {10..20}', {}, [ '01 10', '01 11', '01 12', '01 13', '01 14', '01 15', '01 16', '01 17', '01 18', '01 19', '01 20', '02 10', '02 11', '02 12', '02 13', '02 14', '02 15', '02 16', '02 17', '02 18', '02 19', '02 20', '03 10', '03 11', '03 12', '03 13', '03 14', '03 15', '03 16', '03 17', '03 18', '03 19', '03 20', '04 10', '04 11', '04 12', '04 13', '04 14', '04 15', '04 16', '04 17', '04 18', '04 19', '04 20', '05 10', '05 11', '05 12', '05 13', '05 14', '05 15', '05 16', '05 17', '05 18', '05 19', '05 20', '06 10', '06 11', '06 12', '06 13', '06 14', '06 15', '06 16', '06 17', '06 18', '06 19', '06 20', '07 10', '07 11', '07 12', '07 13', '07 14', '07 15', '07 16', '07 17', '07 18', '07 19', '07 20', '08 10', '08 11', '08 12', '08 13', '08 14', '08 15', '08 16', '08 17', '08 18', '08 19', '08 20', '09 10', '09 11', '09 12', '09 13', '09 14', '09 15', '09 16', '09 17', '09 18', '09 19', '09 20' ] ], - [ 'a{ ,c{d, },h}x', {}, [ 'a x', 'ac x', 'acdx', 'ahx' ] ], - [ 'a{ ,c{d, },h} ', {}, [ 'a ', 'ac ', 'acd ', 'ah ' ] ], - - 'see https://github.com/jonschlinkert/microequal/issues/66', - - [ '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.{html,ejs}', {}, [ '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.ejs', '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.html' ] ], - - 'should not try to expand ranges with decimals', - - [ '{1.1..2.1}', {}, [ '{1.1..2.1}' ] ], - [ '{1.1..~2.1}', {}, [ '{1.1..~2.1}' ] ], - - 'should escape invalid ranges', - - [ '{1..0f}', {}, [ '{1..0f}' ] ], - [ '{1..10..ff}', {}, [ '{1..10..ff}' ] ], - [ '{1..10.f}', {}, [ '{1..10.f}' ] ], - [ '{1..10f}', {}, [ '{1..10f}' ] ], - [ '{1..20..2f}', {}, [ '{1..20..2f}' ] ], - [ '{1..20..f2}', {}, [ '{1..20..f2}' ] ], - [ '{1..2f..2}', {}, [ '{1..2f..2}' ] ], - [ '{1..ff..2}', {}, [ '{1..ff..2}' ] ], - [ '{1..ff}', {}, [ '{1..ff}' ] ], - [ '{1.20..2}', {}, [ '{1.20..2}' ] ], - - 'should handle weirdly-formed brace expansions (fixed in post-bash-3.1)', - - [ 'a-{b{d,e}}-c', {}, [ 'a-{bd}-c', 'a-{be}-c' ] ], - [ 'a-{bdef-{g,i}-c', {}, [ 'a-{bdef-g-c', 'a-{bdef-i-c' ] ], - - 'should not expand quoted strings', - - [ '{"klklkl"}{1,2,3}', {}, [ '{klklkl}1', '{klklkl}2', '{klklkl}3' ] ], - [ '{"x,x"}', {}, [ '{x,x}' ] ], - [ '{\'x,x\'}', {}, [ '{x,x}' ] ], - - 'should escaped outer braces in nested non-sets', - - [ '{a-{b,c,d}}', {}, [ '{a-b}', '{a-c}', '{a-d}' ] ], - [ '{a,{a-{b,c,d}}}', {}, [ 'a', '{a-b}', '{a-c}', '{a-d}' ] ], - - 'should escape imbalanced braces', - - [ 'abc{', {}, [ 'abc{' ] ], - [ '{abc{', {}, [ '{abc{' ] ], - [ '{abc', {}, [ '{abc' ] ], - [ '}abc', {}, [ '}abc' ] ], - [ 'ab{c', {}, [ 'ab{c' ] ], - [ 'ab{c', {}, [ 'ab{c' ] ], - [ '{{a,b}', {}, [ '{a', '{b' ] ], - [ '{a,b}}', {}, [ 'a}', 'b}' ] ], - [ 'a{b{c{d,e}f}gh', {}, [ 'a{b{cdf}gh', 'a{b{cef}gh' ] ], - [ 'a{b{c{d,e}f}g}h', {}, [ 'a{b{cdf}g}h', 'a{b{cef}g}h' ] ], - [ 'f{x,y{{g,z}}h}', {}, [ 'fx', 'fy{g}h', 'fy{z}h' ] ], - [ 'z{a,b},c}d', {}, [ 'za,c}d', 'zb,c}d' ] ], - [ 'a{b{c{d,e}f{x,y{{g}h', {}, [ 'a{b{cdf{x,y{{g}h', 'a{b{cef{x,y{{g}h' ] ], - [ 'f{x,y{{g}h', {}, [ 'f{x,y{{g}h' ] ], - [ 'f{x,y{{g}}h', {}, [ 'f{x,y{{g}}h' ] ], - [ 'a{b{c{d,e}f{x,y{}g}h', {}, [ 'a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh' ] ], - [ 'f{x,y{}g}h', {}, [ 'fxh', 'fy{}gh' ] ], - [ 'z{a,b{,c}d', {}, [ 'z{a,bcd', 'z{a,bd' ] ], - - 'should expand numeric ranges', - - [ 'a{0..3}d', {}, [ 'a0d', 'a1d', 'a2d', 'a3d' ] ], - [ 'x{10..1}y', {}, [ 'x10y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y' ] ], - [ 'x{3..3}y', {}, [ 'x3y' ] ], - [ '{1..10}', {}, [ '1', '10', '2', '3', '4', '5', '6', '7', '8', '9' ] ], - [ '{1..3}', {}, [ '1', '2', '3' ] ], - [ '{1..9}', {}, [ '1', '2', '3', '4', '5', '6', '7', '8', '9' ] ], - [ '{10..1}y', {}, [ '10y', '1y', '2y', '3y', '4y', '5y', '6y', '7y', '8y', '9y' ] ], - [ '{3..3}', {}, [ '3' ] ], - [ '{5..8}', {}, [ '5', '6', '7', '8' ] ], - - 'should expand ranges with negative numbers', - - [ '{-10..-1}', {}, [ '-1', '-10', '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9' ] ], - [ '{-20..0}', {}, [ '-1', '-10', '-11', '-12', '-13', '-14', '-15', '-16', '-17', '-18', '-19', '-2', '-20', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '0' ] ], - [ '{0..-5}', {}, [ '-1', '-2', '-3', '-4', '-5', '0' ] ], - [ '{9..-4}', {}, [ '-1', '-2', '-3', '-4', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ] ], - - 'should expand alphabetical ranges', - - [ '0{1..9}/{10..20}', {}, [ '01/10', '01/11', '01/12', '01/13', '01/14', '01/15', '01/16', '01/17', '01/18', '01/19', '01/20', '02/10', '02/11', '02/12', '02/13', '02/14', '02/15', '02/16', '02/17', '02/18', '02/19', '02/20', '03/10', '03/11', '03/12', '03/13', '03/14', '03/15', '03/16', '03/17', '03/18', '03/19', '03/20', '04/10', '04/11', '04/12', '04/13', '04/14', '04/15', '04/16', '04/17', '04/18', '04/19', '04/20', '05/10', '05/11', '05/12', '05/13', '05/14', '05/15', '05/16', '05/17', '05/18', '05/19', '05/20', '06/10', '06/11', '06/12', '06/13', '06/14', '06/15', '06/16', '06/17', '06/18', '06/19', '06/20', '07/10', '07/11', '07/12', '07/13', '07/14', '07/15', '07/16', '07/17', '07/18', '07/19', '07/20', '08/10', '08/11', '08/12', '08/13', '08/14', '08/15', '08/16', '08/17', '08/18', '08/19', '08/20', '09/10', '09/11', '09/12', '09/13', '09/14', '09/15', '09/16', '09/17', '09/18', '09/19', '09/20' ] ], - [ '0{a..d}0', {}, [ '0a0', '0b0', '0c0', '0d0' ] ], - [ 'a/{b..d}/e', {}, [ 'a/b/e', 'a/c/e', 'a/d/e' ] ], - [ '{1..f}', { minimatch: false }, [ '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f' ] ], - [ '{a..A}', {}, [ 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A' ] ], - [ '{A..a}', {}, [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a' ] ], - [ '{a..e}', {}, [ 'a', 'b', 'c', 'd', 'e' ] ], - [ '{A..E}', {}, [ 'A', 'B', 'C', 'D', 'E' ] ], - [ '{a..f}', {}, [ 'a', 'b', 'c', 'd', 'e', 'f' ] ], - [ '{a..z}', {}, [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' ] ], - [ '{E..A}', {}, [ 'A', 'B', 'C', 'D', 'E' ] ], - [ '{f..1}', { minimatch: false }, [ 'f', 'e', 'd', 'c', 'b', 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A', '@', '?', '>', '=', '<', ';', ':', '9', '8', '7', '6', '5', '4', '3', '2', '1' ] ], - [ '{f..a}', {}, [ 'a', 'b', 'c', 'd', 'e', 'f' ] ], - [ '{f..f}', {}, [ 'f' ] ], - - 'should expand multiple ranges', - - [ 'a/{b..d}/e/{f..h}', {}, [ 'a/b/e/f', 'a/b/e/g', 'a/b/e/h', 'a/c/e/f', 'a/c/e/g', 'a/c/e/h', 'a/d/e/f', 'a/d/e/g', 'a/d/e/h' ] ], - - 'should expand numerical ranges - positive and negative', - - [ '{-10..10}', {}, [ '-1', '-10', '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '0', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9' ] ], - - 'HEADS UP! If you\'re using the `--mm` flag minimatch freezes on these', 'should expand large numbers', - - [ '{2147483645..2147483649}', { minimatch: false }, [ '2147483645', '2147483646', '2147483647', '2147483648', '2147483649' ] ], - - 'should expand ranges using steps', - - [ '{1..10..1}', { optimize: false }, [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ] ], - [ '{1..10..2}', { optimize: false }, [ '1', '3', '5', '7', '9' ] ], - [ '{1..20..20}', { optimize: false }, [ '1' ] ], - [ '{1..20..20}', { optimize: false }, [ '1' ] ], - [ '{1..20..20}', { optimize: false }, [ '1' ] ], - [ '{1..20..2}', { optimize: false }, [ '1', '3', '5', '7', '9', '11', '13', '15', '17', '19' ] ], - [ '{10..0..2}', { optimize: false }, [ '10', '8', '6', '4', '2', '0' ] ], - [ '{10..1..2}', { optimize: false }, [ '10', '8', '6', '4', '2' ] ], - [ '{100..0..5}', { optimize: false }, [ '100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0' ] ], - [ '{2..10..1}', { optimize: false }, [ '2', '3', '4', '5', '6', '7', '8', '9', '10' ] ], - [ '{2..10..2}', { optimize: false }, [ '2', '4', '6', '8', '10' ] ], - [ '{2..10..3}', { optimize: false }, [ '2', '5', '8' ] ], - [ '{a..z..2}', { optimize: false }, [ 'a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y' ] ], - - 'should expand positive ranges with negative steps', - - [ '{10..0..-2}', { optimize: false }, [ '10', '8', '6', '4', '2', '0' ] ], - - 'should expand negative ranges using steps', - - [ '{-1..-10..-2}', { optimize: false }, [ '-1', '-3', '-5', '-7', '-9' ] ], - [ '{-1..-10..2}', { optimize: false }, [ '-1', '-3', '-5', '-7', '-9' ] ], - [ '{-10..-2..2}', { optimize: false }, [ '-10', '-8', '-6', '-4', '-2' ] ], - [ '{-2..-10..1}', { optimize: false }, [ '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10' ] ], - [ '{-2..-10..2}', { optimize: false }, [ '-2', '-4', '-6', '-8', '-10' ] ], - [ '{-2..-10..3}', { optimize: false }, [ '-2', '-5', '-8' ] ], - [ '{-50..-0..5}', { optimize: false }, [ '-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0' ] ], - [ '{-9..9..3}', { optimize: false }, [ '-9', '-6', '-3', '0', '3', '6', '9' ] ], - [ '{10..1..-2}', { optimize: false }, [ '10', '8', '6', '4', '2' ] ], - [ '{100..0..-5}', { optimize: false }, [ '100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0' ] ], - - 'should expand alpha ranges with steps', - - [ '{a..e..2}', { optimize: false }, [ 'a', 'c', 'e' ] ], - [ '{E..A..2}', { optimize: false }, [ 'E', 'C', 'A' ] ], - [ '{a..z..2}', { optimize: false }, [ 'a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y' ] ], - [ '{z..a..-2}', { optimize: false }, [ 'z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b' ] ], - - 'should expand alpha ranges with negative steps', - - [ '{z..a..-2}', { optimize: false }, [ 'z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b' ] ], - - 'should handle unwanted zero-padding (fixed post-bash-4.0)', - - [ '{10..0..2}', { optimize: false }, [ '10', '8', '6', '4', '2', '0' ] ], - [ '{10..0..-2}', { optimize: false }, [ '10', '8', '6', '4', '2', '0' ] ], - [ '{-50..-0..5}', { optimize: false }, [ '-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0' ] ], - - 'should work with dots in file paths', - - [ '../{1..3}/../foo', {}, [ '../1/../foo', '../2/../foo', '../3/../foo' ] ], - [ '../{2..10..2}/../foo', { optimize: false }, [ '../2/../foo', '../4/../foo', '../6/../foo', '../8/../foo', '../10/../foo' ] ], - [ '../{1..3}/../{a,b,c}/foo', {}, [ '../1/../a/foo', '../1/../b/foo', '../1/../c/foo', '../2/../a/foo', '../2/../b/foo', '../2/../c/foo', '../3/../a/foo', '../3/../b/foo', '../3/../c/foo' ] ], - [ './{a..z..3}/', { optimize: false }, [ './a/', './d/', './g/', './j/', './m/', './p/', './s/', './v/', './y/' ] ], - [ './{"x,y"}/{a..z..3}/', { minimatch: false, optimize: false }, [ './{x,y}/a/', './{x,y}/d/', './{x,y}/g/', './{x,y}/j/', './{x,y}/m/', './{x,y}/p/', './{x,y}/s/', './{x,y}/v/', './{x,y}/y/' ] ], - - 'should expand a complex combination of ranges and sets', - - [ 'a/{x,y}/{1..5}c{d,e}f.{md,txt}', {}, [ 'a/x/1cdf.md', 'a/x/1cdf.txt', 'a/x/1cef.md', 'a/x/1cef.txt', 'a/x/2cdf.md', 'a/x/2cdf.txt', 'a/x/2cef.md', 'a/x/2cef.txt', 'a/x/3cdf.md', 'a/x/3cdf.txt', 'a/x/3cef.md', 'a/x/3cef.txt', 'a/x/4cdf.md', 'a/x/4cdf.txt', 'a/x/4cef.md', 'a/x/4cef.txt', 'a/x/5cdf.md', 'a/x/5cdf.txt', 'a/x/5cef.md', 'a/x/5cef.txt', 'a/y/1cdf.md', 'a/y/1cdf.txt', 'a/y/1cef.md', 'a/y/1cef.txt', 'a/y/2cdf.md', 'a/y/2cdf.txt', 'a/y/2cef.md', 'a/y/2cef.txt', 'a/y/3cdf.md', 'a/y/3cdf.txt', 'a/y/3cef.md', 'a/y/3cef.txt', 'a/y/4cdf.md', 'a/y/4cdf.txt', 'a/y/4cef.md', 'a/y/4cef.txt', 'a/y/5cdf.md', 'a/y/5cdf.txt', 'a/y/5cef.md', 'a/y/5cef.txt' ] ], - - 'should expand complex sets and ranges in `bash` mode', - - [ 'a/{x,{1..5},y}/c{d}e', {}, [ 'a/1/c{d}e', 'a/2/c{d}e', 'a/3/c{d}e', 'a/4/c{d}e', 'a/5/c{d}e', 'a/x/c{d}e', 'a/y/c{d}e' ] ] - ]; - - fixtures.forEach(function(arr) { - if (typeof arr === 'string') { - return; - } - - var options = extend({}, arr[1]); - var pattern = arr[0]; - var expected = arr[2]; - - if (options.skip === true) { - return; - } - - it('should compile: ' + pattern, function() { - equal(pattern, expected, options); - }); - }); -}); diff --git a/test/bash.optimized.js b/test/bash.optimized.js deleted file mode 100644 index f0ce837..0000000 --- a/test/bash.optimized.js +++ /dev/null @@ -1,410 +0,0 @@ -'use strict'; - -var extend = require('extend-shallow'); -var assert = require('assert'); -var braces = require('..'); - -function equal(pattern, expected, options) { - var actual = braces.optimize(pattern, options).sort(); - assert.deepEqual(actual, expected.sort(), pattern); -} - -/** - * Bash 4.3 unit tests with `braces.optimize()` - */ - -describe('bash.optimized', function() { - var fixtures = [ - ['a{b,c{1..100}/{foo,bar}/,h}x/z', {}, ['a(b|c([1-9]|[1-9][0-9]|100)/(foo|bar)/|h)x/z']], - ['0{1..9} {10..20}', {}, ['0([1-9]) (1[0-9]|20)']], - ['{a,b,c,d,e}', {}, ['(a|b|c|d|e)']], - ['\\{a,b,c,d,e}', {}, ['{a,b,c,d,e}']], - ['a${b}c', {}, ['a${b}c']], - ['a/\\{b,c,d,{x,y}}{e,f\\}/g', {}, ['a/{b,c,d,(x|y)}{e,f}/g']], - ['a/\\{b,c,d\\}\\{e,f\\}/g', {}, ['a/{b,c,d}{e,f}/g']], - ['a/\\{b,c,d\\}\\{e,f}/g', {}, ['a/{b,c,d}{e,f}/g']], - ['a/\\{b,c,d\\}{e,f}/g', {}, ['a/{b,c,d}(e|f)/g']], - ['a/\\{b,c,d{x,y}}{e,f\\}/g', {}, ['a/{b,c,d(x|y)}{e,f}/g']], - ['a/\\{b,c,d}{e,f\\}/g', {}, ['a/{b,c,d}{e,f}/g']], - ['a/\\{b,c,d}{e,f}/g', {}, ['a/{b,c,d}(e|f)/g']], - ['a/\\{x,y}/cde', {}, ['a/{x,y}/cde']], - ['a/\\{{b,c}{e,f}/g', {}, ['a/{(b|c)(e|f)/g']], - ['a/\\{{b,c}{e,f}\\}/g', {}, ['a/{(b|c)(e|f)}/g']], - ['a/\\{{b,c}{e,f}}/g', {}, ['a/{(b|c)(e|f)}/g']], - ['a/b/c/{x,y\\}', {}, ['a/b/c/{x,y}']], - ['a/b/c{d}e', {}, ['a/b/c{d}e']], - ['a/b/{b,c,{d,e{f,g},{w,x}/{y,z}}}/h/i', {}, ['a/b/(b|c|(d|e(f|g)|(w|x)/(y|z)))/h/i']], - ['a/{${b},c}/d', {}, ['a/(${b}|c)/d']], - ['a/{b,c}}{e,f}/g', {}, ['a/(b|c)}(e|f)/g']], - ['a/{b,c\\,d}{e,f}/g', {}, ['a/(b|c,d)(e|f)/g']], - ['a/{b,c\\}}{e,f}/g', {}, ['a/(b|c})(e|f)/g']], - ['a/{b,c}', {}, ['a/(b|c)']], - ['a/{b,c}d{e,f}/g', {}, ['a/(b|c)d(e|f)/g']], - ['a/{b,c}{e,f}/g', {}, ['a/(b|c)(e|f)/g']], - ['a/{b,c}{e,f}{g,h,i}/k', {}, ['a/(b|c)(e|f)(g|h|i)/k']], - ['a/{b,{c,d},e}/f', {}, ['a/(b|(c|d)|e)/f']], - ['a/{b,{c,d}/{e,f},g}/h', {}, ['a/(b|(c|d)/(e|f)|g)/h']], - ['a/{b{c,d},e{f,g}h{i,j}}/k', {}, ['a/(b(c|d)|e(f|g)h(i|j))/k']], - ['a/{b{c,d},e}/f', {}, ['a/(b(c|d)|e)/f']], - ['a/{b{c,d}e{f,g}h{i,j}}/k', {}, ['a/(b(c|d)e(f|g)h(i|j))/k']], - ['a/{b{c,d}e{f,g},h{i,j}}/k', {}, ['a/(b(c|d)e(f|g)|h(i|j))/k']], - ['a/{x,y}/{1..5}c{d,e}f.{md,txt}', {}, ['a/(x|y)/([1-5])c(d|e)f.(md|txt)']], - ['a/{x,z}{b,{c,d}/{e,f},g}/h', {}, ['a/(x|z)(b|(c|d)/(e|f)|g)/h']], - ['a/{x,{1..5},y}/c{d}e', {}, ['a/(x|([1-5])|y)/c{d}e']], - ['a/{{a,b}/{c,d}}/z', {}, ['a/((a|b)/(c|d))/z']], - ['a/{{b,c}/{d,e}}', {}, ['a/((b|c)/(d|e))']], - ['a/{{b,c}/{d,e}}/f', {}, ['a/((b|c)/(d|e))/f']], - ['abc/${ddd}/xyz', {}, ['abc/${ddd}/xyz']], - ['abcd{efgh', {}, ['abcd{efgh']], - ['a{ ,c{d, },h} ', {}, ['a( |c(d| )|h) ']], - ['a{ ,c{d, },h}x', {}, ['a( |c(d| )|h)x']], - ['a{0..3}d', {}, ['a([0-3])d']], - ['a{b{c{d,e}f{x,y{{g}h', {}, ['a{b{c(d|e)f{x,y{{g}h']], - ['a{b}c', {}, ['a{b}c']], - ['foo {1,2} bar', {}, ['foo (1|2) bar']], - ['x{10..1}y', {}, ['x([1-9]|10)y']], - ['x{3..3}y', {}, ['x3y']], - ['{ }', {}, ['{ }']], - ['{', {}, ['{']], - ['{0..10,braces}', {}, ['(0..10|braces)']], - ['{1..0f}', {}, ['{1..0f}']], - ['{1..10,braces}', {}, ['(1..10|braces)']], - ['{1..10..ff}', {}, ['{1..10..ff}']], - ['{1..10.f}', {}, ['{1..10.f}']], - ['{1..10f}', {}, ['{1..10f}']], - ['{1..10}', {}, ['([1-9]|10)']], - ['{1..3}', {}, ['([1-3])']], - ['{1..9}', {}, ['([1-9])']], - ['{1..ff}', {}, ['{1..ff}']], - ['{1.20..2}', {}, ['{1.20..2}']], - ['{10..1}', {}, ['([1-9]|10)']], - ['{214748364..2147483649}', {}, ['(21474836[4-9]|2147483[7-9][0-9]|214748[4-9][0-9]{2}|214749[0-9]{3}|2147[5-9][0-9]{4}|214[89][0-9]{5}|21[5-9][0-9]{6}|2[2-9][0-9]{7}|[3-9][0-9]{8}|1[0-9]{9}|20[0-9]{8}|21[0-3][0-9]{7}|214[0-6][0-9]{6}|2147[0-3][0-9]{5}|21474[0-7][0-9]{4}|214748[0-2][0-9]{3}|2147483[0-5][0-9]{2}|21474836[0-4][0-9])']], - ['{2147483645..2147483649}', {}, ['(214748364[5-9])']], - ['{3..3}', {}, ['3']], - ['{5..8}', {}, ['([5-8])']], - ['{9..-4}', {}, ['(-[1-4]|[0-9])']], - ['{a,b,{c,d},e}', {}, ['(a|b|(c|d)|e)']], - ['{a,b,{c,d}e}', {}, ['(a|b|(c|d)e)']], - ['{a,b,{c,d}}', {}, ['(a|b|(c|d))']], - ['{a,b{c,d}}', {}, ['(a|b(c|d))']], - ['{a,b}/{c,d}', {}, ['(a|b)/(c|d)']], - ['{a,b}{c,d}', {}, ['(a|b)(c|d)']], - - ['{{0..10},braces}', {}, ['(([0-9]|10)|braces)']], - ['{{a,b},{c,d}}', {}, ['((a|b)|(c|d))']], - ['{{a,b}/{c,d}}', {}, ['((a|b)/(c|d))']], - ['{{a,b}/{c,d}}/z', {}, ['((a|b)/(c|d))/z']], - ['{}', {}, ['{}']], - ['}', {}, ['}']], - - // should not process glob characters - ['{generate,{assemble,update,verb}{file,-generate-*},generator}.js', {}, ['(generate|(assemble|update|verb)(file|-generate-*)|generator).js']], - ['**/{foo,bar}.js', {}, ['**/(foo|bar).js']], - ['**/{1..5}/a.js', {}, ['**/([1-5])/a.js']], - ['**/{a,b,c}/*.js', {}, ['**/(a|b|c)/*.js']], - ['**/{a,b,*}/*.js', {}, ['**/(a|b|*)/*.js']], - ['**/{**,b,*}/*.js', {}, ['**/(**|b|*)/*.js']], - - ['/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', {}, ['/usr/(ucb/(ex|edit)|lib/(ex|how_ex))']], - ['ff{c,b,a}', {}, ['ff(c|b|a)']], - ['f{d,e,f}g', {}, ['f(d|e|f)g']], - ['x{{0..10},braces}y', {}, ['x(([0-9]|10)|braces)y']], - ['{1..10}', {}, ['([1-9]|10)']], - ['{a,b,c}', {}, ['(a|b|c)']], - ['{braces,{0..10}}', {}, ['(braces|([0-9]|10))']], - ['{l,n,m}xyz', {}, ['(l|n|m)xyz']], - ['{{0..10},braces}', {}, ['(([0-9]|10)|braces)']], - ['{{1..10..2},braces}', {bash: false }, ['((1|3|5|7|9)|braces)']], - ['{{1..10},braces}', {}, ['(([1-9]|10)|braces)']], - - ['a/{a,b}/{c,d}/e', {}, ['a/(a|b)/(c|d)/e']], - ['a{b,c}d{e,f}g', {}, ['a(b|c)d(e|f)g']], - ['a/{x,y}/c{d,e}f.{md,txt}', {}, ['a/(x|y)/c(d|e)f.(md|txt)']], - ['{a,b}{{a,b},a,b}', {}, ['(a|b)((a|b)|a|b)']], - ['/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', {}, ['/usr/(ucb/(ex|edit)|lib/(ex|how_ex))']], - ['a{b,c{d,e}f}g', {}, ['a(b|c(d|e)f)g']], - ['a{{x,y},z}b', {}, ['a((x|y)|z)b']], - ['f{x,y{g,z}}h', {}, ['f(x|y(g|z))h']], - ['a{b,c{d,e},h}x/z', {}, ['a(b|c(d|e)|h)x/z']], - ['a{b,c{d,e},h}x{y,z}', {}, ['a(b|c(d|e)|h)x(y|z)']], - ['a{b,c{d,e},{f,g}h}x{y,z}', {}, ['a(b|c(d|e)|(f|g)h)x(y|z)']], - - // should handle invalid sets - ['{0..10,braces}', {}, ['(0..10|braces)']], - ['{1..10,braces}', {}, ['(1..10|braces)']], - - // should not expand escaped braces - ['\\{a,b,c,d,e}', {}, ['{a,b,c,d,e}']], - ['a/b/c/{x,y\\}', {}, ['a/b/c/{x,y}']], - ['a/\\{x,y}/cde', {}, ['a/{x,y}/cde']], - ['abcd{efgh', {}, ['abcd{efgh']], - ['\\{abc\\}', {}, ['{abc}']], - ['{x,y,\\{a,b,c\\}}', {}, ['(x|y|{a|b|c})']], - ['{x,y,{a,b,c\\}}', {}, ['{x,y,(a|b|c})']], - ['{x\\,y,\\{abc\\},trie}', {}, ['(x,y|{abc}|trie)']], - ['{x,y,{abc},trie}', {}, ['(x|y|{abc}|trie)']], - ['x,y,{abc},trie', {}, ['x,y,{abc},trie']], - ['{b{c,d},e}', {}, ['(b(c|d)|e)']], - ['{b{c,d},e}/f', {}, ['(b(c|d)|e)/f']], - ['{abc}', {}, ['{abc}']], - - // should handle empty braces - ['{ }', {}, ['{ }']], - ['{', {}, ['{']], - ['{}', {}, ['{}']], - ['}', {}, ['}']], - - // should escape braces when only one value is defined - ['a{b}c', {}, ['a{b}c']], - ['a/b/c{d}e', {}, ['a/b/c{d}e']], - - // should escape closing braces when open is not defined - ['{a,b}c,d}', {}, ['(a|b)c,d}']], - ['a,b,c,d}', {}, ['a,b,c,d}']], - - // should not expand braces in sets with es6/bash-like variables - ['abc/${ddd}/xyz', {}, ['abc/${ddd}/xyz']], - ['a${b}c', {}, ['a${b}c']], - ['a${b{a,b}}c', {}, ['a${b{a,b}}c']], - ['a/{${b},c}/d', {}, ['a/(${b}|c)/d']], - ['a${b,d}/{foo,bar}c', {}, ['a${b,d}/(foo|bar)c']], - - // should not expand escaped commas - ['a{b\\,c\\,d}e', {}, ['a{b,c,d}e']], - ['a{b\\,c}d', {}, ['a{b,c}d']], - ['{abc\\,def}', {}, ['{abc,def}']], - ['{abc\\,def,ghi}', {}, ['(abc,def|ghi)']], - ['a/{b,c}/{x\\,y}/d/e', {}, ['a/(b|c)/{x,y}/d/e']], - - // should not expand escaped braces - ['{a,b\\}c,d}', {}, ['(a|b}c|d)']], - ['\\{a,b,c,d,e}', {}, ['{a,b,c,d,e}']], - ['a/{z,\\{a,b,c,d,e}/d', {}, ['a/(z|{a|b|c|d|e)/d']], - ['a/\\{b,c}/{d,e}/f', {}, ['a/{b,c}/(d|e)/f']], - ['./\\{x,y}/{a..z..3}/', {}, ['./{x,y}/(a|d|g|j|m|p|s|v|y)/']], - - // should not expand escaped braces or commas - ['{x\\,y,\\{abc\\},trie}', {}, ['(x,y|{abc}|trie)']], - - // should support sequence brace operators - ['/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', {}, ['/usr/(ucb/(ex|edit)|lib/(ex|how_ex))']], - ['ff{c,b,a}', {}, ['ff(c|b|a)']], - ['f{d,e,f}g', {}, ['f(d|e|f)g']], - ['x{{0..10},braces}y', {}, ['x(([0-9]|10)|braces)y']], - ['{1..10}', {}, ['([1-9]|10)']], - ['{a,b,c}', {}, ['(a|b|c)']], - ['{braces,{0..10}}', {}, ['(braces|([0-9]|10))']], - ['{l,n,m}xyz', {}, ['(l|n|m)xyz']], - ['{{0..10},braces}', {}, ['(([0-9]|10)|braces)']], - ['{{1..10..2},braces}', {}, ['((1|3|5|7|9)|braces)']], - ['{{1..10},braces}', {}, ['(([1-9]|10)|braces)']], - - // should expand multiple sets - ['a/{a,b}/{c,d}/e', {}, ['a/(a|b)/(c|d)/e']], - ['a{b,c}d{e,f}g', {}, ['a(b|c)d(e|f)g']], - ['a/{x,y}/c{d,e}f.{md,txt}', {}, ['a/(x|y)/c(d|e)f.(md|txt)']], - - // should expand nested sets - ['{a,b}{{a,b},a,b}', {}, ['(a|b)((a|b)|a|b)']], - ['/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', {}, ['/usr/(ucb/(ex|edit)|lib/(ex|how_ex))']], - ['a{b,c{d,e}f}g', {}, ['a(b|c(d|e)f)g']], - ['a{{x,y},z}b', {}, ['a((x|y)|z)b']], - ['f{x,y{g,z}}h', {}, ['f(x|y(g|z))h']], - ['a{b,c{d,e},h}x/z', {}, ['a(b|c(d|e)|h)x/z']], - ['a{b,c{d,e},h}x{y,z}', {}, ['a(b|c(d|e)|h)x(y|z)']], - ['a{b,c{d,e},{f,g}h}x{y,z}', {}, ['a(b|c(d|e)|(f|g)h)x(y|z)']], - ['a-{b{d,e}}-c', {}, ['a-{b(d|e)}-c']], - - // should expand not modify non-brace characters - ['a/b/{d,e}/*.js', {}, ['a/b/(d|e)/*.js']], - ['a/**/c/{d,e}/f*.js', {}, ['a/**/c/(d|e)/f*.js']], - ['a/**/c/{d,e}/f*.{md,txt}', {}, ['a/**/c/(d|e)/f*.(md|txt)']], - - // should work with leading and trailing commas - ['a{b,}c', {}, ['a(b|)c']], - ['a{,b}c', {}, ['a(|b)c']], - - // should handle spaces - // Bash 4.3 says the this first one should be equivalent to `foo|(1|2)|bar - // That makes sense in Bash, since ' ' is a separator, but not here. - ['foo {1,2} bar', {}, ['foo (1|2) bar']], - ['0{1..9} {10..20}', {}, ['0([1-9]) (1[0-9]|20)']], - ['a{ ,c{d, },h}x', {}, ['a( |c(d| )|h)x']], - ['a{ ,c{d, },h} ', {}, ['a( |c(d| )|h) ']], - - // see https://github.com/jonschlinkert/microequal/issues/66 - ['/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.{html,ejs}', {}, ['/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.(html|ejs)']], - - /** - * Ranges - */ - - // should not try to expand ranges with decimals - ['{1.1..2.1}', {}, ['{1.1..2.1}']], - ['{1.1..~2.1}', {}, ['{1.1..~2.1}']], - - // should escape invalid ranges - ['{1..0f}', {}, ['{1..0f}']], - ['{1..10..ff}', {}, ['{1..10..ff}']], - ['{1..10.f}', {}, ['{1..10.f}']], - ['{1..10f}', {}, ['{1..10f}']], - ['{1..20..2f}', {}, ['{1..20..2f}']], - ['{1..20..f2}', {}, ['{1..20..f2}']], - ['{1..2f..2}', {}, ['{1..2f..2}']], - ['{1..ff..2}', {}, ['{1..ff..2}']], - ['{1..ff}', {}, ['{1..ff}']], - ['{1.20..2}', {}, ['{1.20..2}']], - - // should handle weirdly-formed brace expansions (fixed in post-bash-3.1) - ['a-{b{d,e}}-c', {}, ['a-{b(d|e)}-c']], - ['a-{bdef-{g,i}-c', {}, ['a-{bdef-(g|i)-c']], - - // should not expand quoted strings - ['{"klklkl"}{1,2,3}', {}, ['{klklkl}(1|2|3)']], - ['{"x,x"}', {}, ['{x,x}']], - - // should escaped outer braces in nested non-sets - ['{a-{b,c,d}}', {}, ['{a-(b|c|d)}']], - ['{a,{a-{b,c,d}}}', {}, ['(a|{a-(b|c|d)})']], - - // should escape imbalanced braces - ['a-{bdef-{g,i}-c', {}, ['a-{bdef-(g|i)-c']], - ['abc{', {}, ['abc{']], - ['{abc{', {}, ['{abc{']], - ['{abc', {}, ['{abc']], - ['}abc', {}, ['}abc']], - ['ab{c', {}, ['ab{c']], - ['ab{c', {}, ['ab{c']], - ['{{a,b}', {}, ['{(a|b)']], - ['{a,b}}', {}, ['(a|b)}']], - ['abcd{efgh', {}, ['abcd{efgh']], - ['a{b{c{d,e}f}gh', {}, ['a{b(c(d|e)f)gh']], - ['a{b{c{d,e}f}g}h', {}, ['a(b(c(d|e)f)g)h']], - ['f{x,y{{g,z}}h}', {}, ['f(x|y((g|z))h)']], - ['z{a,b},c}d', {}, ['z(a|b),c}d']], - ['a{b{c{d,e}f{x,y{{g}h', {}, ['a{b{c(d|e)f{x,y{{g}h']], - ['f{x,y{{g}h', {}, ['f{x,y{{g}h']], - ['f{x,y{{g}}h', {}, ['f{x,y{{g}}h']], - ['a{b{c{d,e}f{x,y{}g}h', {}, ['a{b{c(d|e)f(x|y{}g)h']], - ['f{x,y{}g}h', {}, ['f(x|y{}g)h']], - ['z{a,b{,c}d', {}, ['z{a,b(|c)d']], - - // should expand numeric ranges - ['a{0..3}d', {}, ['a([0-3])d']], - ['x{10..1}y', {}, ['x([1-9]|10)y']], - ['x{3..3}y', {}, ['x3y']], - ['{1..10}', {}, ['([1-9]|10)']], - ['{1..3}', {}, ['([1-3])']], - ['{1..9}', {}, ['([1-9])']], - ['{10..1}', {}, ['([1-9]|10)']], - ['{10..1}y', {}, ['([1-9]|10)y']], - ['{3..3}', {}, ['3']], - ['{5..8}', {}, ['([5-8])']], - - // should expand ranges with negative numbers - ['{-10..-1}', {}, ['(-[1-9]|-10)']], - ['{-20..0}', {}, ['(-[1-9]|-1[0-9]|-20|0)']], - ['{0..-5}', {}, ['(-[1-5]|0)']], - ['{9..-4}', {}, ['(-[1-4]|[0-9])']], - - // should expand alphabetical ranges - ['0{1..9}/{10..20}', {}, ['0([1-9])/(1[0-9]|20)']], - ['0{a..d}0', {}, ['0([a-d])0']], - ['a/{b..d}/e', {}, ['a/([b-d])/e']], - ['{1..f}', {}, ['([1-f])']], - ['{a..A}', {}, ['([A-a])']], - ['{A..a}', {}, ['([A-a])']], - ['{a..e}', {}, ['([a-e])']], - ['{A..E}', {}, ['([A-E])']], - ['{a..f}', {}, ['([a-f])']], - ['{a..z}', {}, ['([a-z])']], - ['{E..A}', {}, ['([A-E])']], - ['{f..1}', {}, ['([1-f])']], - ['{f..a}', {}, ['([a-f])']], - ['{f..f}', {}, ['f']], - - // should expand multiple ranges - ['a/{b..d}/e/{f..h}', {}, ['a/([b-d])/e/([f-h])']], - - // should expand numerical ranges - positive and negative - ['{-10..10}', {}, ['(-[1-9]|-?10|[0-9])']], - - // HEADS UP! If you're using the `--mm` flag minimatch freezes on these - // should expand large numbers - ['{2147483645..2147483649}', {}, ['(214748364[5-9])']], - ['{214748364..2147483649}', {}, ['(21474836[4-9]|2147483[7-9][0-9]|214748[4-9][0-9]{2}|214749[0-9]{3}|2147[5-9][0-9]{4}|214[89][0-9]{5}|21[5-9][0-9]{6}|2[2-9][0-9]{7}|[3-9][0-9]{8}|1[0-9]{9}|20[0-9]{8}|21[0-3][0-9]{7}|214[0-6][0-9]{6}|2147[0-3][0-9]{5}|21474[0-7][0-9]{4}|214748[0-2][0-9]{3}|2147483[0-5][0-9]{2}|21474836[0-4][0-9])']], - - // should expand ranges using steps - ['{1..10..1}', {bash: false}, ['([1-9]|10)']], - ['{1..10..2}', {bash: false}, ['(1|3|5|7|9)']], - ['{1..20..20}', {bash: false}, ['1']], - ['{1..20..2}', {bash: false}, ['(1|3|5|7|9|11|13|15|17|19)']], - ['{10..0..2}', {bash: false}, ['(10|8|6|4|2|0)']], - ['{10..1..2}', {bash: false}, ['(10|8|6|4|2)']], - ['{100..0..5}', {bash: false}, ['(100|95|90|85|80|75|70|65|60|55|50|45|40|35|30|25|20|15|10|5|0)']], - ['{2..10..1}', {bash: false}, ['([2-9]|10)']], - ['{2..10..2}', {bash: false}, ['(2|4|6|8|10)']], - ['{2..10..3}', {bash: false}, ['(2|5|8)']], - ['{a..z..2}', {bash: false}, ['(a|c|e|g|i|k|m|o|q|s|u|w|y)']], - - // should expand positive ranges with negative steps - ['{10..0..-2}', {bash: false}, ['(10|8|6|4|2|0)']], - - // should expand negative ranges using steps - ['{-1..-10..-2}', {bash: false}, ['(-(1|3|5|7|9))']], - ['{-1..-10..2}', {bash: false}, ['(-(1|3|5|7|9))']], - ['{-10..-2..2}', {bash: false}, ['(-(10|8|6|4|2))']], - ['{-2..-10..1}', {bash: false}, ['(-[2-9]|-10)']], - ['{-2..-10..2}', {bash: false}, ['(-(2|4|6|8|10))']], - ['{-2..-10..3}', {bash: false}, ['(-(2|5|8))']], - ['{-50..-0..5}', {bash: false}, ['(0|-(50|45|40|35|30|25|20|15|10|5))']], - ['{-9..9..3}', {bash: false}, ['(0|3|6|9|-(9|6|3))']], - ['{10..1..-2}', {bash: false}, ['(10|8|6|4|2)']], - ['{100..0..-5}', {bash: false}, ['(100|95|90|85|80|75|70|65|60|55|50|45|40|35|30|25|20|15|10|5|0)']], - - // should expand alpha ranges with steps - ['{a..e..2}', {bash: false}, ['(a|c|e)']], - ['{E..A..2}', {bash: false}, ['(E|C|A)']], - ['{a..z..2}', {bash: false}, ['(a|c|e|g|i|k|m|o|q|s|u|w|y)']], - ['{z..a..-2}', {bash: false}, ['(z|x|v|t|r|p|n|l|j|h|f|d|b)']], - - // should expand alpha ranges with negative steps - ['{z..a..-2}', {bash: false}, ['(z|x|v|t|r|p|n|l|j|h|f|d|b)']], - - // unwanted zero-padding (fixed post-bash-4.0) - ['{10..0..2}', {bash: false}, ['(10|8|6|4|2|0)']], - ['{10..0..-2}', {bash: false}, ['(10|8|6|4|2|0)']], - ['{-50..-0..5}', {bash: false}, ['(0|-(50|45|40|35|30|25|20|15|10|5))']], - - // should work with dots in file paths - ['../{1..3}/../foo', {}, ['../([1-3])/../foo']], - ['../{2..10..2}/../foo', {}, ['../(2|4|6|8|10)/../foo']], - ['../{1..3}/../{a,b,c}/foo', {}, ['../([1-3])/../(a|b|c)/foo']], - ['./{a..z..3}/', {}, ['./(a|d|g|j|m|p|s|v|y)/']], - ['./{"x,y"}/{a..z..3}/', {}, ['./{x,y}/(a|d|g|j|m|p|s|v|y)/']], - - // should expand a complex combination of ranges and sets - ['a/{x,y}/{1..5}c{d,e}f.{md,txt}', {}, ['a/(x|y)/([1-5])c(d|e)f.(md|txt)']], - - // should expand complex sets and ranges in `bash` mode - ['a/{x,{1..5},y}/c{d}e', {}, ['a/(x|([1-5])|y)/c{d}e']] - ]; - - fixtures.forEach(function(arr) { - if (typeof arr === 'string') { - return; - } - - var options = extend({}, arr[1]); - var pattern = arr[0]; - var expected = arr[2]; - - if (options.skip === true) { - return; - } - - it('should compile: ' + pattern, function() { - equal(pattern, expected, options); - }); - }); -}); diff --git a/test/bash.spec.js b/test/bash.spec.js deleted file mode 100644 index bf22ab4..0000000 --- a/test/bash.spec.js +++ /dev/null @@ -1,189 +0,0 @@ -'use strict'; - -var extend = require('extend-shallow'); -var assert = require('assert'); -var braces = require('..'); - -function equal(pattern, expected, options) { - var actual = braces.expand(pattern, options).sort(); - assert.deepEqual(actual, expected.sort(), pattern); -} - -/** - * Bash 4.3 unit tests - */ - -describe('bash', function() { - var fixtures = [ - [ '{1\\.2}', {}, [ '{1.2}' ] ], - [ '{1\\.2}', {unescape: false}, [ '{1\\.2}' ] ], - [ '{"x,x"}', {}, [ '{x,x}' ] ], - [ '{x","x}', {}, [ '{x,x}' ] ], - [ '\'{x,x}\'', {}, [ '{x,x}' ] ], - [ '{x`,`x}', {}, [ '{x,x}' ] ], - [ '{x`,`x}', {unescape: false}, [ '{x`,`x}' ] ], - [ '\'{a,b}{{a,b},a,b}\'', {}, [ '{a,b}{{a,b},a,b}' ] ], - [ 'A{b,{d,e},{f,g}}Z', {}, [ 'AbZ', 'AdZ', 'AeZ', 'AfZ', 'AgZ' ] ], - [ 'PRE-{a,b}{{a,b},a,b}-POST', {}, [ 'PRE-aa-POST', 'PRE-ab-POST', 'PRE-aa-POST', 'PRE-ab-POST', 'PRE-ba-POST', 'PRE-bb-POST', 'PRE-ba-POST', 'PRE-bb-POST' ] ], - [ '\\{a,b}{{a,b},a,b}', {}, [ '{a,b}a', '{a,b}b', '{a,b}a', '{a,b}b' ] ], - [ '{{a,b}', {}, [ '{a', '{b' ] ], - [ '{a,b}}', {}, [ 'a}', 'b}' ] ], - [ '{,}', {}, [] ], - [ 'a{,}', {}, [ 'a', 'a' ] ], - [ '{,}b', {}, [ 'b', 'b' ] ], - [ 'a{,}b', {}, [ 'ab', 'ab' ] ], - [ 'a{b}c', {}, [ 'a{b}c' ] ], - [ 'a{1..5}b', {}, [ 'a1b', 'a2b', 'a3b', 'a4b', 'a5b' ] ], - [ 'a{01..5}b', {}, [ 'a01b', 'a02b', 'a03b', 'a04b', 'a05b' ] ], - [ 'a{-01..5}b', {}, [ 'a-01b', 'a000b', 'a001b', 'a002b', 'a003b', 'a004b', 'a005b' ] ], - [ 'a{-01..5..3}b', {}, [ 'a-01b', 'a002b', 'a005b' ] ], - [ 'a{001..9}b', {}, [ 'a001b', 'a002b', 'a003b', 'a004b', 'a005b', 'a006b', 'a007b', 'a008b', 'a009b' ] ], - [ 'a{b,c{d,e},{f,g}h}x{y,z', {}, [ 'abx{y,z', 'acdx{y,z', 'acex{y,z', 'afhx{y,z', 'aghx{y,z' ] ], - [ 'a{b,c{d,e},{f,g}h}x{y,z\\}', {}, [ 'abx{y,z}', 'acdx{y,z}', 'acex{y,z}', 'afhx{y,z}', 'aghx{y,z}' ] ], - [ 'a{b,c{d,e},{f,g}h}x{y,z}', {}, [ 'abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz' ] ], - [ 'a{b{c{d,e}f{x,y{{g}h', {}, [ 'a{b{cdf{x,y{{g}h', 'a{b{cef{x,y{{g}h' ] ], - [ 'a{b{c{d,e}f{x,y{}g}h', {}, [ 'a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh' ] ], - [ 'a{b{c{d,e}f{x,y}}g}h', {}, [ 'a{b{cdfx}g}h', 'a{b{cdfy}g}h', 'a{b{cefx}g}h', 'a{b{cefy}g}h' ] ], - [ 'a{b{c{d,e}f}g}h', {}, [ 'a{b{cdf}g}h', 'a{b{cef}g}h' ] ], - [ 'a{{x,y},z}b', {}, [ 'axb', 'ayb', 'azb' ] ], - [ 'f{x,y{g,z}}h', {}, [ 'fxh', 'fygh', 'fyzh' ] ], - [ 'f{x,y{{g,z}}h', {}, [ 'f{x,y{g}h', 'f{x,y{z}h' ] ], - [ 'f{x,y{{g,z}}h}', {}, [ 'fx', 'fy{g}h', 'fy{z}h' ] ], - [ 'f{x,y{{g}h', {}, [ 'f{x,y{{g}h' ] ], - [ 'f{x,y{{g}}h', {}, [ 'f{x,y{{g}}h' ] ], - [ 'f{x,y{}g}h', {}, [ 'fxh', 'fy{}gh' ] ], - [ 'z{a,b{,c}d', {}, [ 'z{a,bd', 'z{a,bcd' ] ], - [ 'z{a,b},c}d', {}, [ 'za,c}d', 'zb,c}d' ] ], - [ '{-01..5}', {}, [ '-01', '000', '001', '002', '003', '004', '005' ] ], - [ '{-05..100..5}', {}, [ '-05', '000', '005', '010', '015', '020', '025', '030', '035', '040', '045', '050', '055', '060', '065', '070', '075', '080', '085', '090', '095', '100' ] ], - [ '{-05..100}', {}, [ '-05', '-04', '-03', '-02', '-01', '000', '001', '002', '003', '004', '005', '006', '007', '008', '009', '010', '011', '012', '013', '014', '015', '016', '017', '018', '019', '020', '021', '022', '023', '024', '025', '026', '027', '028', '029', '030', '031', '032', '033', '034', '035', '036', '037', '038', '039', '040', '041', '042', '043', '044', '045', '046', '047', '048', '049', '050', '051', '052', '053', '054', '055', '056', '057', '058', '059', '060', '061', '062', '063', '064', '065', '066', '067', '068', '069', '070', '071', '072', '073', '074', '075', '076', '077', '078', '079', '080', '081', '082', '083', '084', '085', '086', '087', '088', '089', '090', '091', '092', '093', '094', '095', '096', '097', '098', '099', '100' ] ], - [ '{0..5..2}', {}, [ '0', '2', '4' ] ], - [ '{0001..05..2}', {}, [ '0001', '0003', '0005' ] ], - [ '{0001..-5..2}', {}, [ '0001', '-001', '-003', '-005' ] ], - [ '{0001..-5..-2}', {}, [ '0001', '-001', '-003', '-005' ] ], - [ '{0001..5..-2}', {}, [ '0001', '0003', '0005' ] ], - [ '{01..5}', {}, [ '01', '02', '03', '04', '05' ] ], - [ '{1..05}', {}, [ '01', '02', '03', '04', '05' ] ], - [ '{1..05..3}', {}, [ '01', '04' ] ], - [ '{05..100}', {}, [ '005', '006', '007', '008', '009', '010', '011', '012', '013', '014', '015', '016', '017', '018', '019', '020', '021', '022', '023', '024', '025', '026', '027', '028', '029', '030', '031', '032', '033', '034', '035', '036', '037', '038', '039', '040', '041', '042', '043', '044', '045', '046', '047', '048', '049', '050', '051', '052', '053', '054', '055', '056', '057', '058', '059', '060', '061', '062', '063', '064', '065', '066', '067', '068', '069', '070', '071', '072', '073', '074', '075', '076', '077', '078', '079', '080', '081', '082', '083', '084', '085', '086', '087', '088', '089', '090', '091', '092', '093', '094', '095', '096', '097', '098', '099', '100' ] ], - [ '{0a..0z}', {}, [ '{0a..0z}' ] ], - [ '{a,b\\}c,d}', {}, [ 'a', 'b}c', 'd' ] ], - [ '{a,b{c,d}', {}, [ '{a,bc', '{a,bd' ] ], - [ '{a,b}c,d}', {}, [ 'ac,d}', 'bc,d}' ] ], - [ '{a..F}', {}, [ 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F' ] ], - [ '{A..f}', {}, [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f' ] ], - [ '{a..Z}', {}, [ 'a', '`', '_', '^', ']', '\\', '[', 'Z' ] ], - [ '{A..z}', {}, [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' ] ], - [ '{z..A}', {}, [ 'z', 'y', 'x', 'w', 'v', 'u', 't', 's', 'r', 'q', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A' ] ], - [ '{Z..a}', {}, [ 'Z', '[', '\\', ']', '^', '_', '`', 'a' ] ], - [ '{a..F..2}', {}, [ 'a', '_', ']', '[', 'Y', 'W', 'U', 'S', 'Q', 'O', 'M', 'K', 'I', 'G' ] ], - [ '{A..f..02}', {}, [ 'A', 'C', 'E', 'G', 'I', 'K', 'M', 'O', 'Q', 'S', 'U', 'W', 'Y', '[', ']', '_', 'a', 'c', 'e' ] ], - [ '{a..Z..5}', {}, [ 'a', '\\' ] ], - [ 'd{a..Z..5}b', {}, [ 'dab', 'd\\b' ] ], - [ '{A..z..10}', {}, [ 'A', 'K', 'U', '_', 'i', 's' ] ], - [ '{z..A..-2}', {}, [ 'z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b', '`', '^', '\\', 'Z', 'X', 'V', 'T', 'R', 'P', 'N', 'L', 'J', 'H', 'F', 'D', 'B' ] ], - [ '{Z..a..20}', {}, [ 'Z' ] ], - [ '{a{,b}', {}, [ '{a', '{ab' ] ], - [ '{a\\},b}', {}, [ 'a}', 'b' ] ], - [ '{x,y{,}g}', {}, [ 'x', 'yg', 'yg' ] ], - [ '{x,y{}g}', {}, [ 'x', 'y{}g' ] ], - [ '{{a,b}', {}, [ '{a', '{b' ] ], - [ '{{a,b},c}', {}, [ 'a', 'b', 'c' ] ], - [ '{{a,b}c}', {}, [ '{ac}', '{bc}' ] ], - [ '{{a,b},}', {}, [ '', 'a', 'b' ] ], - [ 'X{{a,b},}X', {}, [ 'XaX', 'XbX', 'XX' ] ], - [ '{{a,b},}c', {}, [ 'ac', 'bc', 'c' ] ], - [ '{{a,b}.}', {}, [ '{a.}', '{b.}' ] ], - [ '{{a,b}}', {}, [ '{a}', '{b}' ] ], - [ 'X{a..#}X', {}, [ 'X{a..#}X' ] ], - [ '', {}, [ '' ] ], - [ '{-10..00}', {}, [ '-10', '-09', '-08', '-07', '-06', '-05', '-04', '-03', '-02', '-01', '000' ] ], - [ '{a,\\\\{a,b}c}', {}, [ 'a', '\\\\ac', '\\\\bc' ] ], - [ '{a,\\{a,b}c}', {}, [ 'ac}', '{ac}', 'bc}' ] ], - [ 'a,\\{b,c}', {}, [ 'a,{b,c}' ] ], - [ '{-10.\\.00}', {}, [ '{-10..00}' ] ], - [ 'ff{c,b,a}', {}, [ 'ffc', 'ffb', 'ffa' ] ], - [ 'f{d,e,f}g', {}, [ 'fdg', 'feg', 'ffg' ] ], - [ '{l,n,m}xyz', {}, [ 'lxyz', 'nxyz', 'mxyz' ] ], - [ '{abc\\,def}', {}, [ '{abc,def}' ] ], - [ '{abc}', {}, [ '{abc}' ] ], - [ '{x\\,y,\\{abc\\},trie}', {}, [ 'x,y', '{abc}', 'trie' ] ], - [ '{}', {}, [ '{}' ] ], - [ '{ }', {}, [ '{ }' ] ], - [ '}', {}, [ '}' ] ], - [ '{', {}, [ '{' ] ], - [ 'abcd{efgh', {}, [ 'abcd{efgh' ] ], - [ 'foo {1,2} bar', {}, [ 'foo 1 bar', 'foo 2 bar' ] ], - [ '"${var}"{x,y}', {}, [ '${var}x', '${var}y' ] ], - [ '{1..10}', {}, [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ] ], - [ '{0..10,braces}', {}, [ '0..10', 'braces' ] ], - [ '{{0..10},braces}', {}, [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces' ] ], - [ 'x{{0..10},braces}y', {}, [ 'x0y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'x10y', 'xbracesy' ] ], - [ '{3..3}', {}, [ '3' ] ], - [ 'x{3..3}y', {}, [ 'x3y' ] ], - [ '{10..1}', {}, [ '10', '9', '8', '7', '6', '5', '4', '3', '2', '1' ] ], - [ '{10..1}y', {}, [ '10y', '9y', '8y', '7y', '6y', '5y', '4y', '3y', '2y', '1y' ] ], - [ 'x{10..1}y', {}, [ 'x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y' ] ], - [ '{a..f}', {}, [ 'a', 'b', 'c', 'd', 'e', 'f' ] ], - [ '{f..a}', {}, [ 'f', 'e', 'd', 'c', 'b', 'a' ] ], - [ '{a..A}', {}, [ 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A' ] ], - [ '{A..a}', {}, [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a' ] ], - [ '{f..f}', {}, [ 'f' ] ], - [ '0{1..9} {10..20}', {}, [ '01 10', '01 11', '01 12', '01 13', '01 14', '01 15', '01 16', '01 17', '01 18', '01 19', '01 20', '02 10', '02 11', '02 12', '02 13', '02 14', '02 15', '02 16', '02 17', '02 18', '02 19', '02 20', '03 10', '03 11', '03 12', '03 13', '03 14', '03 15', '03 16', '03 17', '03 18', '03 19', '03 20', '04 10', '04 11', '04 12', '04 13', '04 14', '04 15', '04 16', '04 17', '04 18', '04 19', '04 20', '05 10', '05 11', '05 12', '05 13', '05 14', '05 15', '05 16', '05 17', '05 18', '05 19', '05 20', '06 10', '06 11', '06 12', '06 13', '06 14', '06 15', '06 16', '06 17', '06 18', '06 19', '06 20', '07 10', '07 11', '07 12', '07 13', '07 14', '07 15', '07 16', '07 17', '07 18', '07 19', '07 20', '08 10', '08 11', '08 12', '08 13', '08 14', '08 15', '08 16', '08 17', '08 18', '08 19', '08 20', '09 10', '09 11', '09 12', '09 13', '09 14', '09 15', '09 16', '09 17', '09 18', '09 19', '09 20' ] ], - [ '{-1..-10}', {}, [ '-1', '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10' ] ], - [ '{-20..0}', {}, [ '-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0' ] ], - [ 'a-{b{d,e}}-c', {}, [ 'a-{bd}-c', 'a-{be}-c' ] ], - [ 'a-{bdef-{g,i}-c', {}, [ 'a-{bdef-g-c', 'a-{bdef-i-c' ] ], - [ '{"klklkl"}{1,2,3}', {}, [ '{klklkl}1', '{klklkl}2', '{klklkl}3' ] ], - [ '{"x,x"}', {}, [ '{x,x}' ] ], - [ '{klklkl}{1,2,3}', {}, [ '{klklkl}1', '{klklkl}2', '{klklkl}3' ] ], - [ '{1..10..2}', {}, [ '1', '3', '5', '7', '9' ] ], - [ '{-1..-10..2}', {}, [ '-1', '-3', '-5', '-7', '-9' ] ], - [ '{-1..-10..-2}', {}, [ '-1', '-3', '-5', '-7', '-9' ] ], - [ '{10..1..-2}', {}, [ '10', '8', '6', '4', '2' ] ], - [ '{10..1..2}', {}, [ '10', '8', '6', '4', '2' ] ], - [ '{1..20..2}', {}, [ '1', '3', '5', '7', '9', '11', '13', '15', '17', '19' ] ], - [ '{1..20..20}', {}, [ '1' ] ], - [ '{100..0..5}', {}, [ '100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0' ] ], - [ '{100..0..-5}', {}, [ '100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0' ] ], - [ '{a..z}', {}, [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' ] ], - [ '{a..z..2}', {}, [ 'a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y' ] ], - [ '{z..a..-2}', {}, [ 'z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b' ] ], - [ '{2147483645..2147483649}', {}, [ '2147483645', '2147483646', '2147483647', '2147483648', '2147483649' ] ], - [ '{10..0..2}', {}, [ '10', '8', '6', '4', '2', '0' ] ], - [ '{10..0..-2}', {}, [ '10', '8', '6', '4', '2', '0' ] ], - [ '{-50..-0..5}', {}, [ '-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0' ] ], - [ '{1..10.f}', {}, [ '{1..10.f}' ] ], - [ '{1..ff}', {}, [ '{1..ff}' ] ], - [ '{1..10..ff}', {}, [ '{1..10..ff}' ] ], - [ '{1.20..2}', {}, [ '{1.20..2}' ] ], - [ '{1..20..f2}', {}, [ '{1..20..f2}' ] ], - [ '{1..20..2f}', {}, [ '{1..20..2f}' ] ], - [ '{1..2f..2}', {}, [ '{1..2f..2}' ] ], - [ '{1..ff..2}', {}, [ '{1..ff..2}' ] ], - [ '{1..ff}', {}, [ '{1..ff}' ] ], - [ '{1..0f}', {}, [ '{1..0f}' ] ], - [ '{1..10f}', {}, [ '{1..10f}' ] ], - [ '{1..10.f}', {}, [ '{1..10.f}' ] ], - [ '{},b}.h', {}, [ '{},b}.h' ] ], - [ 'y{\\},a}x', {}, [ 'y}x', 'yax' ] ], - [ '{}a,b}c', {}, [ '{}a,b}c' ] ] - ]; - - fixtures.forEach(function(arr) { - if (typeof arr === 'string') { - return; - } - - var options = extend({}, arr[1]); - var pattern = arr[0]; - var expected = arr[2]; - if (options.skip === true) { - return; - } - - it('should compile: ' + pattern, function() { - equal(pattern, expected, options); - }); - }); -}); diff --git a/test/brace-expansion.js b/test/brace-expansion.js deleted file mode 100644 index af729d8..0000000 --- a/test/brace-expansion.js +++ /dev/null @@ -1,91 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var braces = require('..'); - -function equal(pattern, expected, options) { - var actual = braces.expand(pattern, options).sort(); - assert.deepEqual(actual.filter(Boolean), expected.sort(), pattern); -} - -/** - * All of the unit tests from brace-expansion v1.1.6 - * https://github.com/juliangruber/brace-expansion - */ - -describe('unit tests from brace-expand', function() { - describe('sequences', function() { - it('numeric sequences', function() { - equal('a{1..2}b{2..3}c', ['a1b2c', 'a1b3c', 'a2b2c', 'a2b3c']); - equal('{1..2}{2..3}', ['12', '13', '22', '23']); - }); - - it('numeric sequences with step count', function() { - equal('{0..8..2}', ['0', '2', '4', '6', '8']); - equal('{1..8..2}', ['1', '3', '5', '7']); - }); - - it('numeric sequence with negative x / y', function() { - equal('{3..-2}', ['3', '2', '1', '0', '-1', '-2']); - }); - - it('alphabetic sequences', function() { - equal('1{a..b}2{b..c}3', ['1a2b3', '1a2c3', '1b2b3', '1b2c3']); - equal('{a..b}{b..c}', ['ab', 'ac', 'bb', 'bc']); - }); - - it('alphabetic sequences with step count', function() { - equal('{a..k..2}', ['a', 'c', 'e', 'g', 'i', 'k']); - equal('{b..k..2}', ['b', 'd', 'f', 'h', 'j']); - }); - }); - - describe('dollar', function() { - it('ignores ${', function() { - equal('${1..3}', ['${1..3}']); - equal('${a,b}${c,d}', ['${a,b}${c,d}']); - equal('x${a,b}x${c,d}x', ['x${a,b}x${c,d}x']); - }); - }); - - describe('empty option', function() { - it('should support empty sets', function() { - equal('-v{,,,,}', ['-v', '-v', '-v', '-v', '-v']); - }); - }); - - describe('negative increments', function() { - it('should support negative steps', function() { - equal('{3..1}', ['3', '2', '1']); - equal('{10..8}', ['10', '9', '8']); - equal('{10..08}', ['10', '09', '08']); - equal('{c..a}', ['c', 'b', 'a']); - - equal('{4..0..2}', ['4', '2', '0']); - equal('{4..0..-2}', ['4', '2', '0']); - equal('{e..a..2}', ['e', 'c', 'a']); - }); - }); - - describe('nested', function() { - it('should support nested sets', function() { - equal('{a,b{1..3},c}', ['a', 'b1', 'b2', 'b3', 'c']); - equal('{{A..Z},{a..z}}', ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'].sort()); - equal('ppp{,config,oe{,conf}}', ['ppp', 'pppconfig', 'pppoe', 'pppoeconf']); - }); - }); - - describe('order', function() { - it('should expand in given order', function() { - equal('a{d,c,b}e', ['ade', 'ace', 'abe']); - }); - }); - - describe('pad', function() { - it('should support padding', function() { - equal('{9..11}', ['9', '10', '11']); - equal('{09..11}', ['09', '10', '11']); - }); - }); -}); - diff --git a/test/braces.compile.js b/test/braces.compile.js index 03a5be6..04b42b1 100644 --- a/test/braces.compile.js +++ b/test/braces.compile.js @@ -1,19 +1,76 @@ 'use strict'; require('mocha'); -var assert = require('assert'); -var braces = require('..'); - -describe('.compile', function() { - it('should return an object', function() { - var res = braces.compile('a/{b,c}/d'); - assert(res); - assert.equal(typeof res, 'object'); +const assert = require('assert').strict; +const compile = require('../lib/compile'); +const parse = require('../lib/parse'); + +describe('braces.compile()', () => { + describe('errors', () => { + it('should throw an error when invalid args are passed', () => { + assert.throws(() => compile()); + }); + }); + + describe('invalid characters', () => { + it('should escape invalid bracket characters', () => { + assert.equal(compile(parse(']{a,b,c}')), '\\](a|b|c)'); + }); + }); + + describe('sets', () => { + it('should support empty sets', () => { + assert.equal(compile(parse('{a,}')), '(a|)'); + assert.equal(compile(parse('{a,,}')), '(a|)'); + assert.equal(compile(parse('{a,,,}')), '(a|)'); + assert.equal(compile(parse('{a,,,,}')), '(a|)'); + assert.equal(compile(parse('{a,,,,,}')), '(a|)'); + }); + }); + + describe('ranges', () => { + it('should escape braces with invalid ranges', () => { + assert.equal(compile(parse('{a...b}')), '{a...b}'); + assert.equal(compile(parse('{a...b}'), { escapeInvalid: true }), '\\{a...b\\}'); + }); + + it('should expand brace patterns with both sets and ranges', () => { + assert.equal(compile(parse('{a..e,z}')), '(a..e|z)'); + assert.equal(compile(parse('{a..e,a..z}')), '(a..e|a..z)'); + }); + + it('should escape braces with too many range expressions', () => { + assert.equal(compile(parse('{a..e..x..z}')), '{a..e..x..z}'); + assert.equal(compile(parse('{a..e..x..z}'), { escapeInvalid: true }), '\\{a..e..x..z\\}'); + }); + + it('should compile very simple numeric ranges', () => { + assert.equal(compile(parse('{1..5}')), '([1-5])'); + }); + + it('should compile numeric ranges with increments', () => { + assert.equal(compile(parse('{1..5..2}')), '(1|3|5)'); + }); + + it('should compile zero-padded numeric ranges', () => { + assert.equal(compile(parse('{01..05}')), '(0[1-5])'); + }); + + it('should compile zero-padded numeric ranges with increments', () => { + assert.equal(compile(parse('{01..05..2}')), '(01|03|05)'); + assert.equal(compile(parse('{01..05..3}')), '(01|04)'); + }); }); - it('should return output as an array', function() { - var res = braces.compile('a/{b,c}/d'); - assert(Array.isArray(res.output)); - assert.deepEqual(res.output, ['a/(b|c)/d']); + describe('invalid', () => { + it('should escape incomplete brace patterns', () => { + assert.equal(compile(parse(']{a/b')), '\\]{a/b'); + assert.equal(compile(parse(']{a/b'), { escapeInvalid: true }), '\\]\\{a/b'); + }); + + it('should escape non-brace patterns (no sets or ranges)', () => { + assert.equal(compile(parse(']{a/b}')), '\\]{a/b}'); + assert.equal(compile(parse(']{a/b}'), { escapeInvalid: true }), '\\]\\{a/b\\}'); + }); }); }); diff --git a/test/braces.create.js b/test/braces.create.js deleted file mode 100644 index ee733e0..0000000 --- a/test/braces.create.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -require('mocha'); -var assert = require('assert'); -var braces = require('..'); - -describe('.makeRe', function() { - it('should throw an error when invalid args are passed', function() { - assert.throws(function() { - braces.makeRe(); - }); - }); - - it('should throw an error when string exceeds max safe length', function() { - var MAX_LENGTH = 1024 * 64; - - assert.throws(function() { - braces.makeRe(Array(MAX_LENGTH + 1).join('.')); - }); - }); -}); diff --git a/test/braces.expand.js b/test/braces.expand.js new file mode 100644 index 0000000..c95fbb8 --- /dev/null +++ b/test/braces.expand.js @@ -0,0 +1,202 @@ +'use strict'; + +require('mocha'); +const assert = require('assert').strict; +const expand = require('../lib/expand'); +const parse = require('../lib/parse'); +const bashPath = require('bash-path'); +const cp = require('child_process'); +const braces = require('..'); + +const bash = input => { + return cp + .spawnSync(bashPath(), ['-c', `echo ${input}`]) + .stdout.toString() + .split(/\s+/) + .filter(Boolean); +}; + +const equal = (input, expected = bash(input), options) => { + assert.deepEqual(braces.expand(input, options), expected); +}; + +describe('unit tests from brace-expand', () => { + describe('extglobs', () => { + it('should split on commas when braces are inside extglobs', () => { + equal('*(a|{b|c,d})', ['*(a|b|c)', '*(a|d)']); + }); + + it('should not split on commas in extglobs when inside braces', () => { + equal('{a,@(b,c)}', ['a', '@(b,c)']); + equal('{a,*(b|c,d)}', ['a', '*(b|c,d)']); + }); + }); + + describe('expand', () => { + it('should expand an AST', () => { + assert.deepEqual(expand(parse('a/{b,c}/d')), ['a/b/d', 'a/c/d']); + }); + + it('should support expanded nested empty sets', () => { + equal('{`foo,bar`}', ['{`foo,bar`}'], { keepQuotes: true }); + equal('{\\`foo,bar\\`}', ['`foo', 'bar`'], { keepQuotes: true }); + equal('{`foo,bar`}', ['{`foo,bar`}'], { keepQuotes: true }); + equal('{`foo\\,bar`}', ['{`foo\\,bar`}'], { keepQuotes: true }); + + equal('{`foo,bar`}', ['{foo,bar}']); + equal('{\\`foo,bar\\`}', ['`foo', 'bar`']); + equal('{`foo,bar`}', ['{foo,bar}']); + equal('{`foo\\,bar`}', ['{foo\\,bar}']); + + equal('{a,\\\\{a,b}c}', ['a', '\\ac', '\\bc']); + equal('{a,\\{a,b}c}', ['ac}', '{ac}', 'bc}']); + equal('{,eno,thro,ro}ugh', ['ugh', 'enough', 'through', 'rough']); + equal('{,{,eno,thro,ro}ugh}{,out}', [ + '', + 'out', + 'ugh', + 'ughout', + 'enough', + 'enoughout', + 'through', + 'throughout', + 'rough', + 'roughout' + ]); + equal('{{,eno,thro,ro}ugh,}{,out}', [ + 'ugh', + 'ughout', + 'enough', + 'enoughout', + 'through', + 'throughout', + 'rough', + 'roughout', + '', + 'out' + ]); + equal('{,{,a,b}z}{,c}', ['', 'c', 'z', 'zc', 'az', 'azc', 'bz', 'bzc']); + equal('{,{,a,b}z}{c,}', ['c', '', 'zc', 'z', 'azc', 'az', 'bzc', 'bz']); + equal('{,{,a,b}z}{,c,}', ['', 'c', '', 'z', 'zc', 'z', 'az', 'azc', 'az', 'bz', 'bzc', 'bz']); + equal('{,{,a,b}z}{c,d}', ['c', 'd', 'zc', 'zd', 'azc', 'azd', 'bzc', 'bzd']); + equal('{{,a,b}z,}{,c}', ['z', 'zc', 'az', 'azc', 'bz', 'bzc', '', 'c']); + equal('{,a{,b}z,}{,c}', ['', 'c', 'az', 'azc', 'abz', 'abzc', '', 'c']); + equal('{,a{,b},}{,c}', ['', 'c', 'a', 'ac', 'ab', 'abc', '', 'c']); + equal('{,a{,b}}{,c}', ['', 'c', 'a', 'ac', 'ab', 'abc']); + equal('{,b}{,d}', ['', 'd', 'b', 'bd']); + equal('{a,b}{,d}', ['a', 'ad', 'b', 'bd']); + equal('{,a}{z,c}', ['z', 'c', 'az', 'ac']); + equal('{,{a,}}{z,c}', ['z', 'c', 'az', 'ac', 'z', 'c']); + equal('{,{,a}}{z,c}', ['z', 'c', 'z', 'c', 'az', 'ac']); + equal('{,{,a},}{z,c}', ['z', 'c', 'z', 'c', 'az', 'ac', 'z', 'c']); + equal('{{,,a}}{z,c}', ['{}z', '{}c', '{}z', '{}c', '{a}z', '{a}c']); + equal('{{,a},}{z,c}', ['z', 'c', 'az', 'ac', 'z', 'c']); + equal('{,,a}{z,c}', ['z', 'c', 'z', 'c', 'az', 'ac']); + equal('{,{,}}{z,c}', ['z', 'c', 'z', 'c', 'z', 'c']); + equal('{,{a,b}}{,c}', ['', 'c', 'a', 'ac', 'b', 'bc']); + equal('{,{a,}}{,c}', ['', 'c', 'a', 'ac', '', 'c']); + equal('{,{,b}}{,c}', ['', 'c', '', 'c', 'b', 'bc']); + equal('{,{,}}{,c}', ['', 'c', '', 'c', '', 'c']); + equal('{,a}{,c}', ['', 'c', 'a', 'ac']); + equal('{,{,a}b}', ['', 'b', 'ab']); + equal('{,b}', ['', 'b']); + equal('{,b{,a}}', ['', 'b', 'ba']); + equal('{b,{,a}}', ['b', '', 'a']); + equal('{,b}{,d}', ['', 'd', 'b', 'bd']); + equal('{a,b}{,d}', ['a', 'ad', 'b', 'bd']); + }); + }); + + /** + * The following unit tests are from brace-expansion v1.1.6 + * https://github.com/juliangruber/brace-expansion + */ + + describe('brace expansion unit tests from brace-expand', () => { + describe('sequences', () => { + it('numeric sequences', () => { + equal('a{1..2}b{2..3}c', ['a1b2c', 'a1b3c', 'a2b2c', 'a2b3c']); + equal('{1..2}{2..3}', ['12', '13', '22', '23']); + }); + + it('numeric sequences with step count', () => { + equal('{0..8..2}', ['0', '2', '4', '6', '8']); + equal('{1..8..2}', ['1', '3', '5', '7']); + }); + + it('numeric sequence with negative x / y', () => { + equal('{3..-2}', ['3', '2', '1', '0', '-1', '-2']); + }); + + it('alphabetic sequences', () => { + equal('1{a..b}2{b..c}3', ['1a2b3', '1a2c3', '1b2b3', '1b2c3']); + equal('{a..b}{b..c}', ['ab', 'ac', 'bb', 'bc']); + }); + + it('alphabetic sequences with step count', () => { + equal('{a..k..2}', ['a', 'c', 'e', 'g', 'i', 'k']); + equal('{b..k..2}', ['b', 'd', 'f', 'h', 'j']); + }); + }); + + describe('dollar', () => { + it('ignores ${', () => { + equal('${1..3}', ['${1..3}']); + equal('${a,b}${c,d}', ['${a,b}${c,d}']); + equal('x${a,b}x${c,d}x', ['x${a,b}x${c,d}x']); + }); + }); + + describe('empty option', () => { + it('should support empty sets', () => { + equal('-v{,,,,}', ['-v', '-v', '-v', '-v', '-v']); + }); + }); + + describe('negative increments', () => { + it('should support negative steps', () => { + equal('{3..1}', ['3', '2', '1']); + equal('{10..8}', ['10', '9', '8']); + equal('{10..08}', ['10', '09', '08']); + equal('{c..a}', ['c', 'b', 'a']); + + equal('{4..0..2}', ['4', '2', '0']); + equal('{4..0..-2}', ['4', '2', '0']); + equal('{e..a..2}', ['e', 'c', 'a']); + }); + }); + + describe('nested', () => { + it('should support nested sets', () => { + equal('{a,b{1..3},c}', ['a', 'b1', 'b2', 'b3', 'c']); + equal('{{A..E},{a..e}}', ['A', 'B', 'C', 'D', 'E', 'a', 'b', 'c', 'd', 'e']); + equal('ppp{,config,oe{,conf}}', ['ppp', 'pppconfig', 'pppoe', 'pppoeconf']); + }); + }); + + describe('order', () => { + it('should expand in given order', () => { + equal('a{d,c,b}e', ['ade', 'ace', 'abe']); + }); + }); + + describe('pad', () => { + it('should support padding', () => { + equal('{9..11}', ['9', '10', '11']); + equal('{09..11}', ['09', '10', '11']); + }); + }); + }); + + describe('additional brace expansion test', () => { + describe('sequences', () => { + it('zero-padded numeric sequences', () => { + equal('{008..012}', ['008', '009', '010', '011', '012']); + }); + + it('zero-padded numeric sequences with increments', () => { + equal('{008..012..2}', ['008', '010', '012']); + }); + }); + }); +}); diff --git a/test/braces.js b/test/braces.js deleted file mode 100644 index 19d9868..0000000 --- a/test/braces.js +++ /dev/null @@ -1,146 +0,0 @@ -'use strict'; - -require('mocha'); -var assert = require('assert'); -var braces = require('..'); - -function equal(pattern, expected, options) { - assert.deepEqual(braces(pattern, options), expected); -} - -equal.expand = function(pattern, expected, options) { - var actual = braces.expand(pattern, options).sort(); - assert.deepEqual(actual.filter(Boolean), expected.sort(), pattern); -}; - -describe('braces', function() { - it('should return an array', function() { - assert(Array.isArray(braces('{a,b}'))); - }); - - it('should return an optimized string by default', function() { - equal('a/{b,c}/d', ['a/(b|c)/d']); - }); - - it('should return an expanded array if defined on options', function() { - equal('a/{b,c}/d', ['a/b/d', 'a/c/d'], {expand: true}); - }); - - it('should optimize an array of patterns', function() { - equal(['a/{b,c}/d', 'x/{foo,bar}/z'], ['a/(b|c)/d', 'x/(foo|bar)/z']); - }); - - it('should expand an array of patterns', function() { - var actual = braces(['a/{b,c}/d', 'a/{b,c}/d']); - assert.deepEqual(actual, ['a/(b|c)/d', 'a/(b|c)/d']); - }); - - it('should not uniquify by default', function() { - var actual = braces(['a/{b,c}/d', 'a/{b,c}/d']); - assert.deepEqual(actual, ['a/(b|c)/d', 'a/(b|c)/d']); - }); - - it('should uniquify when `options.nodupes` is true', function() { - var actual = braces(['a/{b,c}/d', 'a/{b,c}/d'], {nodupes: true}); - assert.deepEqual(actual, ['a/(b|c)/d']); - }); - - it('should expand ranges', function() { - equal('a{1..5}b', ['a1b', 'a2b', 'a3b', 'a4b', 'a5b'], {expand: true}); - }); - - it('should expand ranges that are nested in a set', function() { - equal('a{b,c,{1..5}}e', ['abe', 'ace', 'a1e', 'a2e', 'a3e', 'a4e', 'a5e'], {expand: true}); - }); - - it('should not expand ranges when they are just characters in a set', function() { - equal('a{b,c,1..5}e', ['abe', 'ace', 'a1..5e'], {expand: true}); - equal('a{/../}e', ['a/e'], {expand: true}); - equal('a{/../,z}e', ['a/../e', 'aze'], {expand: true}); - equal('a{b,c/*/../d}e', ['abe', 'ac/*/../de'], {expand: true}); - equal('a{b,b,c/../b}d', ['abd', 'abd', 'ac/../bd'], {expand: true}); - }); - - it('should support expanded nested empty sets', function() { - equal.expand('{\`foo,bar\`}', ['{foo,bar}']); - equal.expand('{\\`foo,bar\\`}', ['`foo', 'bar`']); - equal.expand('{`foo\,bar`}', ['{foo,bar}']); - equal.expand('{`foo\\,bar`}', ['{`foo\\,bar`}']); - equal.expand('{a,\\\\{a,b}c}', ['a', '\\\\ac', '\\\\bc']); - equal.expand('{a,\\{a,b}c}', ['ac}', '{ac}', 'bc}']); - equal.expand('{,eno,thro,ro}ugh', ['ugh', 'enough', 'through', 'rough']); - equal.expand('{,{,eno,thro,ro}ugh}{,out}', ['out', 'ugh', 'ughout', 'enough', 'enoughout', 'through', 'throughout', 'rough', 'roughout']); - equal.expand('{{,eno,thro,ro}ugh,}{,out}', ['ugh', 'ughout', 'enough', 'enoughout', 'through', 'throughout', 'rough', 'roughout', 'out']); - equal.expand('{,{,a,b}z}{,c}', ['c', 'z', 'zc', 'az', 'azc', 'bz', 'bzc']); - equal.expand('{,{,a,b}z}{c,}', ['c', 'zc', 'z', 'azc', 'az', 'bzc', 'bz']); - equal.expand('{,{,a,b}z}{,c,}', ['c', 'z', 'zc', 'z', 'az', 'azc', 'az', 'bz', 'bzc', 'bz']); - equal.expand('{,{,a,b}z}{c,d}', ['c', 'd', 'zc', 'zd', 'azc', 'azd', 'bzc', 'bzd']); - equal.expand('{{,a,b}z,}{,c}', ['z', 'zc', 'az', 'azc', 'bz', 'bzc', 'c']); - equal.expand('{,a{,b}z,}{,c}', ['c', 'az', 'azc', 'abz', 'abzc', 'c']); - equal.expand('{,a{,b},}{,c}', ['c', 'a', 'ac', 'ab', 'abc', 'c']); - equal.expand('{,a{,b}}{,c}', ['c', 'a', 'ac', 'ab', 'abc']); - equal.expand('{,b}{,d}', ['d', 'b', 'bd']); - equal.expand('{a,b}{,d}', ['a', 'ad', 'b', 'bd']); - equal.expand('{,a}{z,c}', ['z', 'c', 'az', 'ac']); - equal.expand('{,{a,}}{z,c}', ['z', 'c', 'z', 'c', 'az', 'ac']); - equal.expand('{,{,a}}{z,c}', ['z', 'c', 'z', 'c', 'az', 'ac']); - equal.expand('{,{,a},}{z,c}', ['z', 'c', 'z', 'c', 'az', 'ac', 'z', 'c']); - equal.expand('{{,,a}}{z,c}', [ '{}z', '{}c', '{}z', '{}c', '{a}z', '{a}c' ]); - equal.expand('{{,a},}{z,c}', ['z', 'c', 'az', 'ac', 'z', 'c']); - equal.expand('{,,a}{z,c}', ['z', 'c', 'z', 'c', 'az', 'ac']); - equal.expand('{,{,}}{z,c}', ['z', 'c', 'z', 'c', 'z', 'c']); - equal.expand('{,{a,b}}{,c}', ['c', 'a', 'ac', 'b', 'bc']); - equal.expand('{,{a,}}{,c}', ['c', 'a', 'ac', 'c']); - equal.expand('{,{,b}}{,c}', ['c', 'c', 'b', 'bc']); - equal.expand('{,{,}}{,c}', ['c', 'c', 'c']); - equal.expand('{,a}{,c}', ['c', 'a', 'ac']); - equal.expand('{,{,a}b}', ['b', 'ab']); - equal.expand('{,b}', ['b']); - equal.expand('{,b{,a}}', ['b', 'ba']); - equal.expand('{b,{,a}}', ['b', 'a']); - equal.expand('{,b}{,d}', ['d', 'b', 'bd']); - equal.expand('{a,b}{,d}', ['a', 'ad', 'b', 'bd']); - }); - - it('should support optimized nested empty sets', function() { - equal('{\`foo,bar\`}', [ '{foo,bar}' ]); - equal('{\\`foo,bar\\`}', [ '(`foo|bar`)' ]); - equal('{`foo\,bar`}', [ '{foo,bar}' ]); - equal('{`foo\\,bar`}', [ '(`foo\\,bar`)' ]); - equal('{a,\\\\{a,b}c}', [ '(a|\\\\(a|b)c)' ]); - equal('{a,\\{a,b}c}', [ '(a|{a|b)c}' ]); - equal('a/{\\{b,c,d},z}/e', [ 'a/({b|c|d),z}/e' ]); - equal('{,eno,thro,ro}ugh', [ '(|eno|thro|ro)ugh' ]); - equal('{,{,eno,thro,ro}ugh}{,out}', [ '((|eno|thro|ro)ugh|)(|out)' ]); - equal('{{,eno,thro,ro}ugh,}{,out}', [ '((|eno|thro|ro)ugh|)(|out)' ]); - equal('{,{,a,b}z}{,c}', [ '((|a|b)z|)(|c)' ]); - equal('{,{,a,b}z}{c,}', [ '((|a|b)z|)(c|)' ]); - equal('{,{,a,b}z}{,c,}', [ '((|a|b)z|)(|c|)' ]); - equal('{,{,a,b}z}{c,d}', [ '((|a|b)z|)(c|d)' ]); - equal('{{,a,b}z,}{,c}', [ '((|a|b)z|)(|c)' ]); - equal('{,a{,b}z,}{,c}', [ '(|a(|b)z|)(|c)' ]); - equal('{,a{,b},}{,c}', [ '(|a(|b)|)(|c)' ]); - equal('{,a{,b}}{,c}', [ '(|a(|b))(|c)' ]); - equal('{,b}{,d}', [ '(|b)(|d)' ]); - equal('{a,b}{,d}', [ '(a|b)(|d)' ]); - equal('{,a}{z,c}', [ '(|a)(z|c)' ]); - equal('{,{a,}}{z,c}', [ '((a|)|)(z|c)' ]); - equal('{,{,a}}{z,c}', [ '((|a)|)(z|c)' ]); - equal('{,{,a},}{z,c}', [ '((|a)||)(z|c)' ]); - equal('{{,,a}}{z,c}', [ '((||a))(z|c)' ]); - equal('{{,a},}{z,c}', [ '((|a)|)(z|c)' ]); - equal('{,,a}{z,c}', [ '(||a)(z|c)' ]); - equal('{,{,}}{z,c}', [ '(z|c)' ]); - equal('{,{a,b}}{,c}', [ '((a|b)|)(|c)' ]); - equal('{,{a,}}{,c}', [ '((a|)|)(|c)' ]); - equal('{,{,b}}{,c}', [ '((|b)|)(|c)' ]); - equal('{,{,}}{,c}', [ '(|c)' ]); - equal('{,a}{,c}', [ '(|a)(|c)' ]); - equal('{,{,a}b}', [ '((|a)b|)' ]); - equal('{,b}', [ '(|b)' ]); - equal('{,b{,a}}', [ '(|b(|a))' ]); - equal('{b,{,a}}', [ '(b|(|a))' ]); - equal('{,b}{,d}', [ '(|b)(|d)' ]); - equal('{a,b}{,d}', [ '(a|b)(|d)' ]); - }); -}); diff --git a/test/braces.makeRe.js b/test/braces.makeRe.js deleted file mode 100644 index ae02896..0000000 --- a/test/braces.makeRe.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -require('mocha'); -var assert = require('assert'); -var braces = require('..'); - -describe('.create', function() { - it('should throw an error when invalid args are passed', function() { - assert.throws(function() { - braces.create(); - }); - }); - - it('should throw an error when string exceeds max safe length', function() { - var MAX_LENGTH = 1024 * 64; - - assert.throws(function() { - braces.create(Array(MAX_LENGTH + 1).join('.')); - }); - }); -}); diff --git a/test/braces.parse.js b/test/braces.parse.js index 7e94f44..9158316 100644 --- a/test/braces.parse.js +++ b/test/braces.parse.js @@ -1,18 +1,49 @@ 'use strict'; require('mocha'); -var assert = require('assert'); -var braces = require('..'); +const assert = require('assert').strict; +const parse = require('../lib/parse'); -describe('.parse', function() { - it('should return an AST object', function() { - var ast = braces.parse('a/{b,c}/d'); - assert(ast); - assert.equal(typeof ast, 'object'); +describe('braces.parse()', () => { + describe('errors', () => { + it('should throw an error when string exceeds max safe length', () => { + const MAX_LENGTH = 1024 * 64; + assert.throws(() => parse('.'.repeat(MAX_LENGTH + 2))); + }); }); - it('should have an array of nodes', function() { - var ast = braces.parse('a/{b,c}/d'); - assert(Array.isArray(ast.nodes)); + describe('valid', () => { + it('should return an AST', () => { + const ast = parse('a/{b,c}/d'); + const brace = ast.nodes.find(node => node.type === 'brace'); + assert(brace); + assert.equal(brace.nodes.length, 5); + }); + + it('should ignore braces inside brackets', () => { + const ast = parse('a/[{b,c}]/d'); + assert.equal(ast.nodes[1].type, 'text'); + assert.equal(ast.nodes[1].value, 'a/[{b,c}]/d'); + }); + + it('should parse braces with brackets inside', () => { + const ast = parse('a/{a,b,[{c,d}]}/e'); + const brace = ast.nodes[2]; + const bracket = brace.nodes.find(node => node.value[0] === '['); + assert(bracket); + assert.equal(bracket.value, '[{c,d}]'); + }); + }); + + describe('invalid', () => { + it('should escape standalone closing braces', () => { + const one = parse('}'); + assert.equal(one.nodes[1].type, 'text'); + assert.equal(one.nodes[1].value, '}'); + + const two = parse('a}b'); + assert.equal(two.nodes[1].type, 'text'); + assert.equal(two.nodes[1].value, 'a}b'); + }); }); }); diff --git a/test/expanded.integration.js b/test/expanded.integration.js deleted file mode 100644 index dc664ae..0000000 --- a/test/expanded.integration.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var braces = require('..'); - -function equal(pattern, expected, options) { - var actual = braces.expand(pattern, options).sort(); - assert.deepEqual(actual, expected.sort(), pattern); -} - -describe('integration', function() { - it('should work with dots in file paths', function() { - equal('../{1..3}/../foo', ['../1/../foo', '../2/../foo', '../3/../foo']); - equal('../{2..10..2}/../foo', ['../2/../foo', '../4/../foo', '../6/../foo', '../8/../foo', '../10/../foo']); - equal('../{1..3}/../{a,b,c}/foo', ['../1/../a/foo', '../2/../a/foo', '../3/../a/foo', '../1/../b/foo', '../2/../b/foo', '../3/../b/foo', '../1/../c/foo', '../2/../c/foo', '../3/../c/foo']); - equal('./{a..z..3}/', ['./a/', './d/', './g/', './j/', './m/', './p/', './s/', './v/', './y/']); - equal('./{"x,y"}/{a..z..3}/', ['./{x,y}/a/', './{x,y}/d/', './{x,y}/g/', './{x,y}/j/', './{x,y}/m/', './{x,y}/p/', './{x,y}/s/', './{x,y}/v/', './{x,y}/y/']); - }); - - it('should expand a complex combination of ranges and sets:', function() { - equal('a/{x,y}/{1..5}c{d,e}f.{md,txt}', ['a/x/1cdf.md', 'a/y/1cdf.md', 'a/x/2cdf.md', 'a/y/2cdf.md', 'a/x/3cdf.md', 'a/y/3cdf.md', 'a/x/4cdf.md', 'a/y/4cdf.md', 'a/x/5cdf.md', 'a/y/5cdf.md', 'a/x/1cef.md', 'a/y/1cef.md', 'a/x/2cef.md', 'a/y/2cef.md', 'a/x/3cef.md', 'a/y/3cef.md', 'a/x/4cef.md', 'a/y/4cef.md', 'a/x/5cef.md', 'a/y/5cef.md', 'a/x/1cdf.txt', 'a/y/1cdf.txt', 'a/x/2cdf.txt', 'a/y/2cdf.txt', 'a/x/3cdf.txt', 'a/y/3cdf.txt', 'a/x/4cdf.txt', 'a/y/4cdf.txt', 'a/x/5cdf.txt', 'a/y/5cdf.txt', 'a/x/1cef.txt', 'a/y/1cef.txt', 'a/x/2cef.txt', 'a/y/2cef.txt', 'a/x/3cef.txt', 'a/y/3cef.txt', 'a/x/4cef.txt', 'a/y/4cef.txt', 'a/x/5cef.txt', 'a/y/5cef.txt']); - }); - - it('should expand complex sets and ranges in `bash` mode:', function() { - equal('a/{x,{1..5},y}/c{d}e', ['a/x/c{d}e', 'a/1/c{d}e', 'a/2/c{d}e', 'a/3/c{d}e', 'a/4/c{d}e', 'a/5/c{d}e', 'a/y/c{d}e']); - }); -}); diff --git a/test/expanded.ranges.js b/test/expanded.ranges.js deleted file mode 100644 index d118fa8..0000000 --- a/test/expanded.ranges.js +++ /dev/null @@ -1,193 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var braces = require('..'); - -function equal(pattern, expected) { - var actual = braces.expand(pattern).sort(); - assert.deepEqual(actual, expected.sort()); -} - -describe('expanded ranges', function() { - - // HEADS UP! If you're using the `--mm` flag minimatch freezes on these - describe('large numbers', function() { - it('should expand large numbers', function() { - equal('{2147483645..2147483649}', ['2147483645', '2147483646', '2147483647', '2147483648', '2147483649']); - }); - - it('should throw an error when range exceeds rangeLimit', function() { - assert.throws(function() { - braces.expand('{214748364..2147483649}'); - }); - }); - }); - - describe('escaping / invalid ranges', function() { - it('should not try to expand ranges with decimals', function() { - equal('{1.1..2.1}', ['{1.1..2.1}']); - equal('{1.1..~2.1}', ['{1.1..~2.1}']); - }); - - it('should escape invalid ranges:', function() { - equal('{1..0f}', ['{1..0f}']); - equal('{1..10..ff}', ['{1..10..ff}']); - equal('{1..10.f}', ['{1..10.f}']); - equal('{1..10f}', ['{1..10f}']); - equal('{1..20..2f}', ['{1..20..2f}']); - equal('{1..20..f2}', ['{1..20..f2}']); - equal('{1..2f..2}', ['{1..2f..2}']); - equal('{1..ff..2}', ['{1..ff..2}']); - equal('{1..ff}', ['{1..ff}']); - equal('{1.20..2}', ['{1.20..2}']); - }); - - it('weirdly-formed brace expansions -- fixed in post-bash-3.1', function() { - equal('a-{b{d,e}}-c', ['a-{bd}-c', 'a-{be}-c']); - equal('a-{bdef-{g,i}-c', ['a-{bdef-g-c', 'a-{bdef-i-c']); - }); - - it('should not expand quoted strings.', function() { - equal('{"klklkl"}{1,2,3}', ['{klklkl}1', '{klklkl}2', '{klklkl}3']); - equal('{"x,x"}', ['{x,x}']); - }); - - it('should escaped outer braces in nested non-sets', function() { - equal('{a-{b,c,d}}', ['{a-b}', '{a-c}', '{a-d}']); - equal('{a,{a-{b,c,d}}}', ['a', '{a-b}', '{a-c}', '{a-d}']); - }); - - it('should escape imbalanced braces', function() { - equal('a-{bdef-{g,i}-c', ['a-{bdef-g-c', 'a-{bdef-i-c']); - equal('abc{', ['abc{']); - equal('{abc{', ['{abc{']); - equal('{abc', ['{abc']); - equal('}abc', ['}abc']); - equal('ab{c', ['ab{c']); - equal('ab{c', ['ab{c']); - equal('{{a,b}', ['{a', '{b']); - equal('{a,b}}', ['a}', 'b}']); - equal('abcd{efgh', ['abcd{efgh']); - equal('a{b{c{d,e}f}g}h', ['a{b{cdf}g}h', 'a{b{cef}g}h']); - equal('f{x,y{{g,z}}h}', ['fx', 'fy{g}h', 'fy{z}h']); - equal('z{a,b},c}d', ['za,c}d', 'zb,c}d']); - equal('a{b{c{d,e}f{x,y{{g}h', ['a{b{cdf{x,y{{g}h', 'a{b{cef{x,y{{g}h']); - equal('f{x,y{{g}h', ['f{x,y{{g}h']); - equal('f{x,y{{g}}h', ['f{x,y{{g}}h']); - equal('a{b{c{d,e}f{x,y{}g}h', ['a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh']); - equal('f{x,y{}g}h', ['fxh', 'fy{}gh']); - equal('z{a,b{,c}d', ['z{a,bd', 'z{a,bcd']); - }); - }); - - describe('positive numeric ranges', function() { - it('should expand numeric ranges', function() { - equal('a{0..3}d', ['a0d', 'a1d', 'a2d', 'a3d']); - equal('x{10..1}y', ['x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y']); - equal('x{3..3}y', ['x3y']); - equal('{1..10}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); - equal('{1..3}', ['1', '2', '3']); - equal('{1..9}', ['1', '2', '3', '4', '5', '6', '7', '8', '9']); - equal('{10..1}', ['10', '9', '8', '7', '6', '5', '4', '3', '2', '1']); - equal('{10..1}y', ['10y', '9y', '8y', '7y', '6y', '5y', '4y', '3y', '2y', '1y']); - equal('{3..3}', ['3']); - equal('{5..8}', ['5', '6', '7', '8']); - }); - }); - - describe('negative ranges', function() { - it('should expand ranges with negative numbers', function() { - equal('{-10..-1}', ['-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1']); - equal('{-20..0}', ['-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0']); - equal('{0..-5}', ['0', '-1', '-2', '-3', '-4', '-5']); - equal('{9..-4}', ['9', '8', '7', '6', '5', '4', '3', '2', '1', '0', '-1', '-2', '-3', '-4']); - }); - }); - - describe('alphabetical ranges', function() { - it('should expand alphabetical ranges', function() { - equal('{a..F}', ['F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a']); - equal('0{a..d}0', ['0a0', '0b0', '0c0', '0d0']); - equal('a/{b..d}/e', ['a/b/e', 'a/c/e', 'a/d/e']); - equal('{1..f}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f']); - equal('{a..A}', ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A']); - equal('{A..a}', ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a']); - equal('{a..e}', ['a', 'b', 'c', 'd', 'e']); - equal('{A..E}', ['A', 'B', 'C', 'D', 'E']); - equal('{a..f}', ['a', 'b', 'c', 'd', 'e', 'f']); - equal('{a..z}', ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']); - equal('{E..A}', ['E', 'D', 'C', 'B', 'A']); - equal('{f..1}', ['f', 'e', 'd', 'c', 'b', 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A', '@', '?', '>', '=', '<', ';', ':', '9', '8', '7', '6', '5', '4', '3', '2', '1']); - equal('{f..a}', ['f', 'e', 'd', 'c', 'b', 'a']); - equal('{f..f}', ['f']); - }); - - it('should expand multiple ranges:', function() { - equal('a/{b..d}/e/{f..h}', ['a/b/e/f', 'a/b/e/g', 'a/b/e/h', 'a/c/e/f', 'a/c/e/g', 'a/c/e/h', 'a/d/e/f', 'a/d/e/g', 'a/d/e/h']); - }); - }); - - describe('combo', function() { - it('should expand numerical ranges - positive and negative', function() { - equal('a{01..05}b', ['a01b', 'a02b', 'a03b', 'a04b', 'a05b' ]); - equal('0{1..9}/{10..20}', ['01/10', '01/11', '01/12', '01/13', '01/14', '01/15', '01/16', '01/17', '01/18', '01/19', '01/20', '02/10', '02/11', '02/12', '02/13', '02/14', '02/15', '02/16', '02/17', '02/18', '02/19', '02/20', '03/10', '03/11', '03/12', '03/13', '03/14', '03/15', '03/16', '03/17', '03/18', '03/19', '03/20', '04/10', '04/11', '04/12', '04/13', '04/14', '04/15', '04/16', '04/17', '04/18', '04/19', '04/20', '05/10', '05/11', '05/12', '05/13', '05/14', '05/15', '05/16', '05/17', '05/18', '05/19', '05/20', '06/10', '06/11', '06/12', '06/13', '06/14', '06/15', '06/16', '06/17', '06/18', '06/19', '06/20', '07/10', '07/11', '07/12', '07/13', '07/14', '07/15', '07/16', '07/17', '07/18', '07/19', '07/20', '08/10', '08/11', '08/12', '08/13', '08/14', '08/15', '08/16', '08/17', '08/18', '08/19', '08/20', '09/10', '09/11', '09/12', '09/13', '09/14', '09/15', '09/16', '09/17', '09/18', '09/19', '09/20' ]); - equal('{-10..10}', ['-1', '-10', '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '0', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9' ]); - }); - }); - - describe('steps > positive ranges', function() { - it('should expand ranges using steps:', function() { - equal('{1..10..1}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); - equal('{1..10..2}', ['1', '3', '5', '7', '9']); - equal('{1..20..20}', ['1']); - equal('{1..20..2}', ['1', '3', '5', '7', '9', '11', '13', '15', '17', '19']); - equal('{10..0..2}', ['10', '8', '6', '4', '2', '0']); - equal('{10..1..2}', ['10', '8', '6', '4', '2']); - equal('{100..0..5}', ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']); - equal('{2..10..1}', ['2', '3', '4', '5', '6', '7', '8', '9', '10']); - equal('{2..10..2}', ['2', '4', '6', '8', '10']); - equal('{2..10..3}', ['2', '5', '8']); - equal('{a..z..2}', ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']); - }); - - it('should expand positive ranges with negative steps:', function() { - equal('{10..0..-2}', ['10', '8', '6', '4', '2', '0']); - }); - }); - - describe('steps > negative ranges', function() { - it('should expand negative ranges using steps:', function() { - equal('{-1..-10..-2}', ['-1', '-3', '-5', '-7', '-9']); - equal('{-1..-10..2}', ['-1', '-3', '-5', '-7', '-9']); - equal('{-10..-2..2}', ['-10', '-8', '-6', '-4', '-2']); - equal('{-2..-10..1}', ['-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10']); - equal('{-2..-10..2}', ['-2', '-4', '-6', '-8', '-10']); - equal('{-2..-10..3}', ['-2', '-5', '-8']); - equal('{-50..-0..5}', ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); - equal('{10..1..-2}', ['2', '4', '6', '8', '10']); - equal('{100..0..-5}', ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']); - }); - }); - - describe('steps > alphabetical ranges', function() { - it('should expand alpha ranges with steps', function() { - equal('{a..e..2}', ['a', 'c', 'e']); - equal('{E..A..2}', ['E', 'C', 'A']); - equal('{a..z}', ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']); - equal('{a..z..2}', ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']); - equal('{z..a..-2}', ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']); - }); - - it('should expand alpha ranges with negative steps', function() { - equal('{z..a..-2}', ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']); - }); - }); - - describe('padding', function() { - it('unwanted zero-padding -- fixed post-bash-4.0', function() { - equal('{10..0..2}', ['10', '8', '6', '4', '2', '0']); - equal('{10..0..-2}', ['10', '8', '6', '4', '2', '0']); - equal('{-50..-0..5}', ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); - }); - }); -}); diff --git a/test/expanded.sets.js b/test/expanded.sets.js deleted file mode 100644 index a3a0d0c..0000000 --- a/test/expanded.sets.js +++ /dev/null @@ -1,216 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var braces = require('..'); - -function equal(pattern, expected) { - var actual = braces.expand(pattern); - assert.deepEqual(actual.sort(), expected.sort()); -} - -describe('expanded sets', function() { - describe('invalid sets', function() { - it('should handle invalid sets:', function() { - equal('{0..10,braces}', ['0..10', 'braces']); - equal('{1..10,braces}', ['1..10', 'braces']); - }); - }); - - describe('extglobs', function() { - it('should split on commas when braces are inside extglobs', function() { - equal('*(a|{b|c,d})', ['*(a|b|c)', '*(a|d)']); - }); - - it('should not split on commas in extglobs when inside braces', function() { - equal('{a,@(b,c)}', ['a', '@(b,c)']); - equal('{a,*(b|c,d)}', ['a', '*(b|c,d)']); - }); - }); - - describe('escaping', function() { - it('should not expand escaped braces', function() { - equal('y{\\},a}x', ['y}x', 'yax']); - equal('{a,\\\\{a,b}c}', ['a', '\\\\ac', '\\\\bc']); - equal('\\{a,b,c,d,e}', ['{a,b,c,d,e}']); - equal('a/b/c/{x,y\\}', ['a/b/c/{x,y}']); - equal('a/\\{x,y}/cde', ['a/{x,y}/cde']); - equal('abcd{efgh', ['abcd{efgh']); - equal('{abc}', ['{abc}']); - equal('{x,y,\\{a,b,c\\}}', ['x', 'y', '{a', 'b', 'c}']); - equal('{x,y,{a,b,c\\}}', ['{x,y,a', '{x,y,b', '{x,y,c}']); - equal('{x,y,{abc},trie}', ['x', 'y', '{abc}', 'trie']); - equal('{x\\,y,\\{abc\\},trie}', ['x,y', '{abc}', 'trie']); - }); - - it('should handle empty braces', function() { - equal('{ }', ['{ }']); - equal('{', ['{']); - equal('{}', ['{}']); - equal('}', ['}']); - }); - - it('should handle empty sets', function() { - equal('{ }', ['{ }']); - equal('{', ['{']); - equal('{}', ['{}']); - equal('}', ['}']); - }); - - it('should escape braces when only one value is defined', function() { - equal('a{b}c', ['a{b}c']); - equal('a/b/c{d}e', ['a/b/c{d}e']); - }); - - it('should escape closing braces when open is not defined', function() { - equal('{a,b}c,d}', ['ac,d}', 'bc,d}']); - }); - - it('should not expand braces in sets with es6/bash-like variables', function() { - equal('abc/${ddd}/xyz', ['abc/${ddd}/xyz']); - equal('a${b}c', ['a${b}c']); - equal('a/{${b},c}/d', ['a/${b}/d', 'a/c/d']); - equal('a${b,d}/{foo,bar}c', ['a${b,d}/fooc', 'a${b,d}/barc']); - }); - - it('should not expand escaped commas.', function() { - equal('a{b\\,c\\,d}e', ['a{b,c,d}e']); - equal('a{b\\,c}d', ['a{b,c}d']); - equal('{abc\\,def}', ['{abc,def}']); - equal('{abc\\,def,ghi}', ['abc,def', 'ghi']); - equal('a/{b,c}/{x\\,y}/d/e', ['a/b/{x,y}/d/e', 'a/c/{x,y}/d/e']); - }); - - it('should return sets with escaped commas', function() { - }); - - it('should not expand escaped braces.', function() { - equal('{a,b\\}c,d}', ['a', 'b}c', 'd']); - equal('\\{a,b,c,d,e}', ['{a,b,c,d,e}']); - equal('a/{z,\\{a,b,c,d,e}/d', ['a/{a/d', 'a/b/d', 'a/c/d', 'a/d/d', 'a/e/d', 'a/z/d']); - equal('a/\\{b,c}/{d,e}/f', ['a/{b,c}/d/f', 'a/{b,c}/e/f']); - equal('./\\{x,y}/{a..z..3}/', ['./{x,y}/a/', './{x,y}/d/', './{x,y}/g/', './{x,y}/j/', './{x,y}/m/', './{x,y}/p/', './{x,y}/s/', './{x,y}/v/', './{x,y}/y/']); - }); - - it('should not expand escaped braces or commas.', function() { - equal('{x\\,y,\\{abc\\},trie}', ['{abc}', 'trie', 'x,y']); - }); - }); - - describe('multipliers', function() { - it('should support multipliers', function() { - equal('{{d,d},e}{,}', ['d', 'd', 'd', 'd', 'e', 'e']); - equal('{d{,},e}{,}', ['d', 'd', 'd', 'd', 'e', 'e']); - equal('a/{,}{c,d}/e', ['a/c/e', 'a/c/e', 'a/d/e', 'a/d/e']); - equal('a/{c,d}{,}/e', ['a/c/e', 'a/c/e', 'a/d/e', 'a/d/e']); - equal('a/{b,c{,}}', ['a/b', 'a/c', 'a/c']); - equal('a{,,}', ['a', 'a', 'a']); - equal('a{,}', ['a', 'a']); - equal('a{,}/{c,d}/e', ['a/c/e', 'a/c/e', 'a/d/e', 'a/d/e']); - equal('a{,}{,}', ['a', 'a', 'a', 'a']); - equal('a{,}{,}{,}', ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']); - equal('a{,}{,}{,}{,}', ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']); - equal('{,}', []); - equal('{a,b{,}{,}{,},c}d', ['ad', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'cd']); - equal('{a,b{,}{,}{,}}', ['a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b']); - equal('{a{,,}b{,}}', ['{ab}', '{ab}', '{ab}', '{ab}', '{ab}', '{ab}']); - equal('{d{,},e}{,}', ['d', 'd', 'd', 'd', 'e', 'e']); - equal('a/{b,c}{,}/{d{,},e}{,}', ['a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/e', 'a/b/e', 'a/b/e', 'a/b/e', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/e', 'a/c/e', 'a/c/e', 'a/c/e']); - equal('a/{b,c{,}}', ['a/b', 'a/c', 'a/c']); - equal('a{,,}', ['a', 'a', 'a']); - equal('a{,}', ['a', 'a']); - equal('a{,}/{c,d}/e', ['a/c/e', 'a/c/e', 'a/d/e', 'a/d/e']); - equal('a/{,}{c,d}/e', ['a/c/e', 'a/c/e', 'a/d/e', 'a/d/e']); - equal('a/{c,d}{,}/e', ['a/c/e', 'a/c/e', 'a/d/e', 'a/d/e']); - equal('a{,}{,}', ['a', 'a', 'a', 'a']); - equal('a{,}{,}{,}', ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']); - equal('a{,}{,}{,}{,}', ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']); - equal('{,}', []); - equal('{a,b{,}{,}{,},c}d', ['ad', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'cd']); - equal('{a,b{,}{,}{,}}', ['a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b']); - equal('{a{,,}b{,}}', ['{ab}', '{ab}', '{ab}', '{ab}', '{ab}', '{ab}']); - }); - }); - - describe('set expansion', function() { - it('should support sequence brace operators', function() { - equal('{a,b,c}', ['a', 'b', 'c']); - equal('{1..10}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); - }); - - it('should support sequence braces with leading characters', function() { - equal('/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', ['/usr/lib/ex', '/usr/lib/how_ex', '/usr/ucb/edit', '/usr/ucb/ex']); - equal('ff{c,b,a}', ['ffa', 'ffb', 'ffc']); - }); - - it('should support sequence braces with trailing characters', function() { - equal('f{d,e,f}g', ['fdg', 'feg', 'ffg']); - equal('{l,n,m}xyz', ['lxyz', 'mxyz', 'nxyz']); - }); - - it('should support sequence braces with trailing characters', function() { - equal('{braces,{0..10}}', ['braces', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); - equal('{{0..10},braces}', ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces']); - equal('{{1..10..2},braces}', ['1', '3', '5', '7', '9', 'braces']); - equal('{{1..10},braces}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces']); - }); - - it('should support nested sequence braces with trailing characters', function() { - equal('x{{0..10},braces}y', ['xbracesy', 'x0y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'x10y']); - }); - - it('should expand multiple sets', function() { - equal('a/{a,b}/{c,d}/e', ['a/a/c/e', 'a/a/d/e', 'a/b/c/e', 'a/b/d/e']); - equal('a{b,c}d{e,f}g', ['abdeg', 'abdfg', 'acdeg', 'acdfg']); - equal('a/{x,y}/c{d,e}f.{md,txt}', ['a/x/cdf.md', 'a/x/cdf.txt', 'a/x/cef.md', 'a/x/cef.txt', 'a/y/cdf.md', 'a/y/cdf.txt', 'a/y/cef.md', 'a/y/cef.txt']); - }); - - it('should expand nested sets', function() { - equal('{{d,d},e}{}', ['d{}', 'd{}', 'e{}']); - equal('{{d,d},e}a', ['da', 'da', 'ea']); - equal('{{d,d},e}{a,b}', ['da', 'da', 'db', 'db', 'ea', 'eb']); - equal('{d,d,{d,d},{e,e}}', ['d', 'd', 'd', 'd', 'e', 'e']); - equal('{{d,d},e}{a,b}', ['da', 'da', 'db', 'db', 'ea', 'eb']); - equal('{{d,d},e}', ['d', 'd', 'e']); - equal('{a,b}{{a,b},a,b}', ['aa', 'aa', 'ab', 'ab', 'ba', 'ba', 'bb', 'bb']); - equal('a{b,c{d,e}f}g', ['abg', 'acdfg', 'acefg']); - equal('a{{x,y},z}b', ['axb', 'ayb', 'azb']); - equal('f{x,y{g,z}}h', ['fxh', 'fygh', 'fyzh']); - equal('a{b,c{d,e},h}x/z', ['abx/z', 'acdx/z', 'acex/z', 'ahx/z']); - equal('a{b,c{d,e},h}x{y,z}', ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'ahxy', 'ahxz']); - equal('a{b,c{d,e},{f,g}h}x{y,z}', ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz']); - equal('a-{b{d,e}}-c', ['a-{bd}-c', 'a-{be}-c']); - }); - - it('should expand with globs.', function() { - equal('a/b/{d,e}/*.js', ['a/b/d/*.js', 'a/b/e/*.js']); - equal('a/**/c/{d,e}/f*.js', ['a/**/c/d/f*.js', 'a/**/c/e/f*.js']); - equal('a/**/c/{d,e}/f*.{md,txt}', ['a/**/c/d/f*.md', 'a/**/c/d/f*.txt', 'a/**/c/e/f*.md', 'a/**/c/e/f*.txt']); - equal('a/b/{d,e,[1-5]}/*.js', ['a/b/d/*.js', 'a/b/e/*.js', 'a/b/[1-5]/*.js']); - }); - }); - - describe('commas', function() { - it('should work with leading and trailing commas.', function() { - equal('a{b,}c', ['abc', 'ac']); - equal('a{,b}c', ['abc', 'ac']); - equal('{{a,b},a}c', ['ac', 'ac', 'bc']); - equal('{{a,b},}c', ['ac', 'bc', 'c']); - equal('a{{a,b},}c', ['aac', 'abc', 'ac']); - }); - }); - - describe('spaces', function() { - it('should handle spaces', function() { - equal('0{1..9} {10..20}', ['01 10', '01 11', '01 12', '01 13', '01 14', '01 15', '01 16', '01 17', '01 18', '01 19', '01 20', '02 10', '02 11', '02 12', '02 13', '02 14', '02 15', '02 16', '02 17', '02 18', '02 19', '02 20', '03 10', '03 11', '03 12', '03 13', '03 14', '03 15', '03 16', '03 17', '03 18', '03 19', '03 20', '04 10', '04 11', '04 12', '04 13', '04 14', '04 15', '04 16', '04 17', '04 18', '04 19', '04 20', '05 10', '05 11', '05 12', '05 13', '05 14', '05 15', '05 16', '05 17', '05 18', '05 19', '05 20', '06 10', '06 11', '06 12', '06 13', '06 14', '06 15', '06 16', '06 17', '06 18', '06 19', '06 20', '07 10', '07 11', '07 12', '07 13', '07 14', '07 15', '07 16', '07 17', '07 18', '07 19', '07 20', '08 10', '08 11', '08 12', '08 13', '08 14', '08 15', '08 16', '08 17', '08 18', '08 19', '08 20', '09 10', '09 11', '09 12', '09 13', '09 14', '09 15', '09 16', '09 17', '09 18', '09 19', '09 20']); - equal('a{ ,c{d, },h}x', ['acdx', 'ac x', 'ahx', 'a x']); - equal('a{ ,c{d, },h} ', ['a ', 'ac ', 'acd ', 'ah ']); - - // see https://github.com/jonschlinkert/micromatch/issues/66 - equal('/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.{html,ejs}', ['/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.ejs', '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.html' ]); - - // Bash 4.3 says the following should be equivalent to `foo|(1|2)|bar`, - // That makes sense in Bash, since ' ' is a separator, but not here. - equal('foo {1,2} bar', ['foo 1 bar', 'foo 2 bar']); - }); - }); -}); diff --git a/test/minimatch.js b/test/minimatch.js index ba935c8..c2d42f2 100644 --- a/test/minimatch.js +++ b/test/minimatch.js @@ -1,27 +1,27 @@ 'use strict'; -var assert = require('assert'); -var braces = require('..'); +const assert = require('assert').strict; +const braces = require('..'); /** * minimatch v3.0.3 unit tests */ -describe('brace expansion', function() { - var units = [ +describe('brace expansion', () => { + const units = [ ['a{b,c{d,e},{f,g}h}x{y,z}', ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz']], - ['a{1..5}b', ['a1b', 'a2b', 'a3b', 'a4b', 'a5b'] ], + ['a{1..5}b', ['a1b', 'a2b', 'a3b', 'a4b', 'a5b']], ['a{b}c', ['a{b}c']], - ['a{00..05}b', ['a00b', 'a01b', 'a02b', 'a03b', 'a04b', 'a05b'] ], + ['a{00..05}b', ['a00b', 'a01b', 'a02b', 'a03b', 'a04b', 'a05b']], ['z{a,b},c}d', ['za,c}d', 'zb,c}d']], - ['z{a,b{,c}d', ['z{a,bcd', 'z{a,bd']], + ['z{a,b{,c}d', ['z{a,bd', 'z{a,bcd']], ['a{b{c{d,e}f}g}h', ['a{b{cdf}g}h', 'a{b{cef}g}h']], - ['a{b{c{d,e}f{x,y}}g}h', ['a{b{cdfx}g}h', 'a{b{cdfy}g}h', 'a{b{cefx}g}h', 'a{b{cefy}g}h'] ], - ['a{b{c{d,e}f{x,y{}g}h', ['a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh'] ] + ['a{b{c{d,e}f{x,y}}g}h', ['a{b{cdfx}g}h', 'a{b{cdfy}g}h', 'a{b{cefx}g}h', 'a{b{cefy}g}h']], + ['a{b{c{d,e}f{x,y{}g}h', ['a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh']] ]; - units.forEach(function(unit) { - it('should expand: ' + unit[0], function() { + units.forEach(unit => { + it('should expand: ' + unit[0], () => { assert.deepEqual(braces.expand(unit[0]), unit[1], unit[0]); }); }); diff --git a/test/multiples.js b/test/multiples.js index ff8b244..e433aa4 100644 --- a/test/multiples.js +++ b/test/multiples.js @@ -1,32 +1,31 @@ 'use strict'; -var assert = require('assert'); -var braces = require('..'); +const assert = require('assert').strict; +const braces = require('..'); -function equal(pattern, expected, options) { - var actual = braces.expand(pattern, options).sort(); - assert.deepEqual(actual, expected.sort(), pattern); -} +const equal = (input, expected, options) => { + assert.deepEqual(braces.expand(input, options), expected); +}; -describe('multiples', function() { - var patterns = [ +describe('multiples', () => { + const patterns = [ ['-v{,,,,}', ['-v', '-v', '-v', '-v', '-v']], ['-v{,,,,}{,}', ['-v', '-v', '-v', '-v', '-v', '-v', '-v', '-v', '-v', '-v']], ['a/b{,}', ['a/b', 'a/b']], ['a/{,}/b', ['a//b', 'a//b']], - ['a/{,}{c,d}/e', ['a/c/e', 'a/c/e', 'a/d/e', 'a/d/e']], - ['a/{a,b,{,}{,}{,},c}/b', ['a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a/a/b', 'a/b/b', 'a/c/b']], - ['a/{a,b,{,},c}/b', ['a//b', 'a//b', 'a/a/b', 'a/b/b', 'a/c/b']], - ['a/{a,b,{,}{,}{,}}/b', ['a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a/a/b', 'a/b/b']], - ['a/{b,cz{,}}/{d{,},ef}{,}', ['a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/ef', 'a/b/ef', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/ef', 'a/cz/ef', 'a/cz/ef', 'a/cz/ef']], - ['a/{b,cz}{,}/{d{,},ef}{,}', ['a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/ef', 'a/b/ef', 'a/b/ef', 'a/b/ef', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/ef', 'a/cz/ef', 'a/cz/ef', 'a/cz/ef']], + ['a/{,}{c,d}/e', ['a/c/e', 'a/d/e', 'a/c/e', 'a/d/e']], + ['a/{a,b,{,}{,}{,},c}/b', ['a/a/b', 'a/b/b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a/c/b']], + ['a/{a,b,{,},c}/b', ['a/a/b', 'a/b/b', 'a//b', 'a//b', 'a/c/b']], + ['a/{a,b,{,}{,}{,}}/b', ['a/a/b', 'a/b/b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b', 'a//b']], + ['a/{b,cz{,}}/{d{,},ef}{,}', ['a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/ef', 'a/b/ef', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/ef', 'a/cz/ef', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/ef', 'a/cz/ef']], + ['a/{b,cz}{,}/{d{,},ef}{,}', ['a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/ef', 'a/b/ef', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/ef', 'a/b/ef', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/ef', 'a/cz/ef', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/d', 'a/cz/ef', 'a/cz/ef']], ['a/{b,c{,}}', ['a/b', 'a/c', 'a/c']], ['a/{b,c{,}}/{,}', ['a/b/', 'a/b/', 'a/c/', 'a/c/', 'a/c/', 'a/c/']], ['a/{b,c}/{,}', ['a/b/', 'a/b/', 'a/c/', 'a/c/']], ['a/{b,c}{,}/d{,}', ['a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d']], - ['a/{b,c}{,}/{d,e{,}}', ['a/b/d', 'a/b/d', 'a/b/e', 'a/b/e', 'a/b/e', 'a/b/e', 'a/c/d', 'a/c/d', 'a/c/e', 'a/c/e', 'a/c/e', 'a/c/e']], - ['a/{b,c}{,}/{d,e}{,}', ['a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/e', 'a/b/e', 'a/b/e', 'a/b/e', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/e', 'a/c/e', 'a/c/e', 'a/c/e']], - ['a/{b,c}{,}/{d{,},e}{,}', ['a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/e', 'a/b/e', 'a/b/e', 'a/b/e', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/e', 'a/c/e', 'a/c/e', 'a/c/e']], + ['a/{b,c}{,}/{d,e{,}}', ['a/b/d', 'a/b/e', 'a/b/e', 'a/b/d', 'a/b/e', 'a/b/e', 'a/c/d', 'a/c/e', 'a/c/e', 'a/c/d', 'a/c/e', 'a/c/e']], + ['a/{b,c}{,}/{d,e}{,}', ['a/b/d', 'a/b/d', 'a/b/e', 'a/b/e', 'a/b/d', 'a/b/d', 'a/b/e', 'a/b/e', 'a/c/d', 'a/c/d', 'a/c/e', 'a/c/e', 'a/c/d', 'a/c/d', 'a/c/e', 'a/c/e']], + ['a/{b,c}{,}/{d{,},e}{,}', ['a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/e', 'a/b/e', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/d', 'a/b/e', 'a/b/e', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/e', 'a/c/e', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/d', 'a/c/e', 'a/c/e']], ['a/{c,d}/{x,y{,}}/e', ['a/c/x/e', 'a/c/y/e', 'a/c/y/e', 'a/d/x/e', 'a/d/y/e', 'a/d/y/e']], ['a/{c,d}{,}/e', ['a/c/e', 'a/c/e', 'a/d/e', 'a/d/e']], ['a{,,,,,}', ['a', 'a', 'a', 'a', 'a', 'a']], @@ -39,23 +38,21 @@ describe('multiples', function() { ['a{,,}{,,}{,,}{,}/b', ['a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b', 'a/b']], ['a{,,}{,}', ['a', 'a', 'a', 'a', 'a', 'a']], ['a{,}', ['a', 'a']], - ['a{,}/{c,d}/e', ['a/c/e', 'a/c/e', 'a/d/e', 'a/d/e']], + ['a{,}/{c,d}/e', ['a/c/e', 'a/d/e', 'a/c/e', 'a/d/e']], ['a{,}b', ['ab', 'ab']], ['a{,}{,}', ['a', 'a', 'a', 'a']], ['a{,}{,}{,}', ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']], ['a{,}{,}{,}{,}', ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']], ['one/{a{,}{,}}/{b/c{,,}{,}{,,}{,}}/two', ['one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two', 'one/{a}/{b/c}/two']], - ['{,}', []], + ['{,}', ['', '']], ['{,}a/{,}', ['a/', 'a/', 'a/', 'a/']], - ['{,}{,}', []], + ['{,}{,}', ['', '', '', '']], ['{a,b{,}{,}{,},c}d', ['ad', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'cd']], ['{a,b{,}{,}{,}}', ['a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b']], ['{a{,,}b{,}}', ['{ab}', '{ab}', '{ab}', '{ab}', '{ab}', '{ab}']] ]; - patterns.forEach(function(pattern) { - it('should expand: ' + pattern[0], function() { - equal(pattern[0], pattern[1]); - }); + patterns.forEach(pattern => { + it('should expand: ' + pattern[0], () => equal(pattern[0], pattern[1])); }); }); diff --git a/test/optimized.js b/test/optimized.js deleted file mode 100644 index e11eb84..0000000 --- a/test/optimized.js +++ /dev/null @@ -1,418 +0,0 @@ -/*! - * braces - * - * Copyright (c) 2014-2016, Jon Schlinkert. - * Licensed under the MIT License - */ - -'use strict'; - -require('mocha'); -var assert = require('assert'); -var braces = require('..'); - -function equal(pattern, expected, options) { - assert.deepEqual(braces(pattern, options), expected); -} - -describe('optimized', function() { - describe('sets', function() { - describe('invalid sets', function() { - it('should handle invalid sets:', function() { - equal('{0..10,braces}', ['(0..10|braces)']); - equal('{1..10,braces}', ['(1..10|braces)']); - }); - }); - - describe('extglobs', function() { - it('should not optimize when preceded by an extglob character', function() { - equal('a/@{b,c}/d', ['a/@b/d', 'a/@c/d']); - equal('a/!{b,c}/d', ['a/!b/d', 'a/!c/d']); - equal('a/*{b,c}/d', ['a/*b/d', 'a/*c/d']); - equal('a/+{b,c}/d', ['a/+b/d', 'a/+c/d']); - equal('a/?{b,c}/d', ['a/?b/d', 'a/?c/d']); - }); - - it('should not optimize when brace set contains extglobs', function() { - equal('{b,[F-Z],!([B-F])}.js', ['b.js', '[F-Z].js', '!([B-F]).js']); - }); - }); - - describe('escaping', function() { - it('should not expand escaped braces', function() { - equal('\\{a,b,c,d,e}', ['{a,b,c,d,e}']); - equal('a/b/c/{x,y\\}', ['a/b/c/{x,y}']); - equal('a/\\{x,y}/cde', ['a/{x,y}/cde']); - equal('abcd{efgh', ['abcd{efgh']); - equal('{abc}', ['{abc}']); - equal('{x,y,\\{a,b,c\\}}', ['(x|y|{a|b|c})']); - equal('{x,y,{a,b,c\\}}', ['{x,y,(a|b|c})']); - equal('{x,y,{abc},trie}', ['(x|y|{abc}|trie)']); - equal('{x\\,y,\\{abc\\},trie}', ['(x,y|{abc}|trie)']); - }); - - it('should handle spaces', function() { - // Bash 4.3 says the following should be equivalent to `foo|(1|2)|bar`, - // That makes sense in Bash, since ' ' is a separator, but not here. - equal('foo {1,2} bar', ['foo (1|2) bar']); - }); - - it('should handle empty braces', function() { - equal('{ }', ['{ }']); - equal('{', ['{']); - equal('{}', ['{}']); - equal('}', ['}']); - }); - - it('should escape braces when only one value is defined', function() { - equal('a{b}c', ['a{b}c']); - equal('a/b/c{d}e', ['a/b/c{d}e']); - }); - - it('should not expand braces in sets with es6/bash-like variables', function() { - equal('abc/${ddd}/xyz', ['abc/${ddd}/xyz']); - equal('a${b}c', ['a${b}c']); - equal('a/{${b},c}/d', ['a/(${b}|c)/d']); - equal('a${b,d}/{foo,bar}c', ['a${b,d}/(foo|bar)c']); - }); - - it('should not expand escaped commas.', function() { - equal('a{b\\,c\\,d}e', ['a{b,c,d}e']); - equal('a{b\\,c}d', ['a{b,c}d']); - equal('{abc\\,def}', ['{abc,def}']); - equal('{abc\\,def,ghi}', ['(abc,def|ghi)']); - equal('a/{b,c}/{x\\,y}/d/e', ['a/(b|c)/{x,y}/d/e']); - }); - - it('should return sets with escaped commas', function() { - equal('a/{b,c}/{x\\,y}/d/e', ['a/(b|c)/{x,y}/d/e']); - }); - - it('should not expand escaped braces.', function() { - equal('{a,b\\}c,d}', ['(a|b}c|d)']); - equal('\\{a,b,c,d,e}', ['{a,b,c,d,e}']); - equal('a/{z,\\{a,b,c,d,e}/d', ['a/(z|{a|b|c|d|e)/d']); - equal('a/\\{b,c}/{d,e}/f', ['a/{b,c}/(d|e)/f']); - equal('./\\{x,y}/{a..z..3}/', ['./{x,y}/(a|d|g|j|m|p|s|v|y)/']); - }); - - it('should not expand escaped braces or commas.', function() { - equal('{x\\,y,\\{abc\\},trie}', ['(x,y|{abc}|trie)']); - }); - }); - - describe('set expansion', function() { - it('should support sequence brace operators', function() { - equal('/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', ['/usr/(ucb/(ex|edit)|lib/(ex|how_ex))']); - equal('ff{c,b,a}', ['ff(c|b|a)']); - equal('f{d,e,f}g', ['f(d|e|f)g']); - equal('x{{0..10},braces}y', ['x(([0-9]|10)|braces)y']); - equal('{1..10}', ['([1-9]|10)']); - equal('{a,b,c}', ['(a|b|c)']); - equal('{braces,{0..10}}', ['(braces|([0-9]|10))']); - equal('{l,n,m}xyz', ['(l|n|m)xyz']); - equal('{{0..10},braces}', ['(([0-9]|10)|braces)']); - equal('{{1..10..2},braces}', ['((1|3|5|7|9)|braces)']); - equal('{{1..10},braces}', ['(([1-9]|10)|braces)']); - }); - - it('should expand multiple sets', function() { - equal('a/{a,b}/{c,d}/e', ['a/(a|b)/(c|d)/e']); - equal('a{b,c}d{e,f}g', ['a(b|c)d(e|f)g']); - equal('a/{x,y}/c{d,e}f.{md,txt}', ['a/(x|y)/c(d|e)f.(md|txt)']); - }); - - it('should expand nested sets', function() { - equal('{a,b}{{a,b},a,b}', ['(a|b)((a|b)|a|b)']); - equal('a{b,c{d,e}f}g', ['a(b|c(d|e)f)g']); - equal('a{{x,y},z}b', ['a((x|y)|z)b']); - equal('f{x,y{g,z}}h', ['f(x|y(g|z))h']); - equal('a{b,c}{d,e}/hx/z', ['a(b|c)(d|e)/hx/z']); - equal('a{b,c{d,e},h}x/z', ['a(b|c(d|e)|h)x/z']); - equal('a{b,c{d,e},h}x{y,z}', ['a(b|c(d|e)|h)x(y|z)']); - equal('a{b,c{d,e},{f,g}h}x{y,z}', ['a(b|c(d|e)|(f|g)h)x(y|z)']); - equal('a-{b{d,e}}-c', ['a-{b(d|e)}-c']); - }); - - it('should expand not modify non-brace characters', function() { - equal('a/b/{d,e}/*.js', ['a/b/(d|e)/*.js']); - equal('a/**/c/{d,e}/f*.js', ['a/**/c/(d|e)/f*.js']); - equal('a/**/c/{d,e}/f*.{md,txt}', ['a/**/c/(d|e)/f*.(md|txt)']); - }); - }); - - describe('commas', function() { - it('should work with leading and trailing commas.', function() { - equal('a{b,}c', ['a(b|)c']); - equal('a{,b}c', ['a(|b)c']); - }); - }); - - describe('spaces', function() { - it('should handle spaces', function() { - equal('0{1..9} {10..20}', ['0([1-9]) (1[0-9]|20)']); - equal('a{ ,c{d, },h}x', ['a( |c(d| )|h)x']); - equal('a{ ,c{d, },h} ', ['a( |c(d| )|h) ']); - - // see https://github.com/jonschlinkert/microequal/issues/66 - equal('/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.{html,ejs}', ['/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.(html|ejs)']); - }); - }); - }); - - /** - * Ranges - */ - - describe('ranges', function() { - describe('escaping / invalid ranges', function() { - it('should not try to expand ranges with decimals', function() { - equal('{1.1..2.1}', ['{1.1..2.1}']); - equal('{1.1..~2.1}', ['{1.1..~2.1}']); - }); - - it('should escape invalid ranges:', function() { - equal('{1..0f}', ['{1..0f}']); - equal('{1..10..ff}', ['{1..10..ff}']); - equal('{1..10.f}', ['{1..10.f}']); - equal('{1..10f}', ['{1..10f}']); - equal('{1..20..2f}', ['{1..20..2f}']); - equal('{1..20..f2}', ['{1..20..f2}']); - equal('{1..2f..2}', ['{1..2f..2}']); - equal('{1..ff..2}', ['{1..ff..2}']); - equal('{1..ff}', ['{1..ff}']); - equal('{1..f}', ['([1-f])']); - equal('{1.20..2}', ['{1.20..2}']); - }); - - it('weirdly-formed brace expansions -- fixed in post-bash-3.1', function() { - equal('a-{b{d,e}}-c', ['a-{b(d|e)}-c']); - equal('a-{bdef-{g,i}-c', ['a-{bdef-(g|i)-c']); - }); - - it('should not expand quoted strings.', function() { - equal('{"klklkl"}{1,2,3}', ['{klklkl}(1|2|3)']); - equal('{"x,x"}', ['{x,x}']); - }); - - it('should escaped outer braces in nested non-sets', function() { - equal('{a-{b,c,d}}', ['{a-(b|c|d)}']); - equal('{a,{a-{b,c,d}}}', ['(a|{a-(b|c|d)})']); - }); - - it('should escape imbalanced braces', function() { - equal('a-{bdef-{g,i}-c', ['a-{bdef-(g|i)-c']); - equal('abc{', ['abc{']); - equal('{abc{', ['{abc{']); - equal('{abc', ['{abc']); - equal('}abc', ['}abc']); - equal('ab{c', ['ab{c']); - equal('{{a,b}', ['{(a|b)']); - equal('{a,b}}', ['(a|b)}']); - equal('abcd{efgh', ['abcd{efgh']); - equal('a{b{c{d,e}f}g}h', ['a(b(c(d|e)f)g)h']); - equal('f{x,y{{g,z}}h}', ['f(x|y((g|z))h)']); - equal('z{a,b},c}d', ['z(a|b),c}d']); - equal('a{b{c{d,e}f{x,y{{g}h', ['a{b{c(d|e)f{x,y{{g}h']); - equal('f{x,y{{g}h', ['f{x,y{{g}h']); - equal('f{x,y{{g}}h', ['f{x,y{{g}}h']); - equal('a{b{c{d,e}f{x,y{}g}h', ['a{b{c(d|e)f(x|y{}g)h']); - equal('f{x,y{}g}h', ['f(x|y{}g)h']); - equal('z{a,b{,c}d', ['z{a,b(|c)d']); - }); - }); - - describe('positive numeric ranges', function() { - it('should expand numeric ranges', function() { - equal('a{0..3}d', ['a([0-3])d']); - equal('x{10..1}y', ['x([1-9]|10)y']); - equal('x{3..3}y', ['x3y']); - equal('{1..10}', ['([1-9]|10)']); - equal('{1..3}', ['([1-3])']); - equal('{1..9}', ['([1-9])']); - equal('{10..1}', ['([1-9]|10)']); - equal('{10..1}y', ['([1-9]|10)y']); - equal('{3..3}', ['3']); - equal('{5..8}', ['([5-8])']); - }); - }); - - describe('negative ranges', function() { - it('should expand ranges with negative numbers', function() { - equal('{-1..-10}', ['(-[1-9]|-10)']); - equal('{-10..-1}', ['(-[1-9]|-10)']); - equal('{-20..0}', ['(-[1-9]|-1[0-9]|-20|0)']); - equal('{0..-5}', ['(-[1-5]|0)']); - equal('{9..-4}', ['(-[1-4]|[0-9])']); - }); - }); - - describe('alphabetical ranges', function() { - it('should expand alphabetical ranges', function() { - equal('0{1..9}/{10..20}', ['0([1-9])/(1[0-9]|20)']); - equal('0{a..d}0', ['0([a-d])0']); - equal('a/{b..d}/e', ['a/([b-d])/e']); - equal('{1..f}', ['([1-f])']); - equal('{a..A}', ['([A-a])']); - equal('{A..a}', ['([A-a])']); - equal('{a..e}', ['([a-e])']); - equal('{A..E}', ['([A-E])']); - equal('{a..f}', ['([a-f])']); - equal('{a..z}', ['([a-z])']); - equal('{E..A}', ['([A-E])']); - equal('{f..1}', ['([1-f])']); - equal('{f..a}', ['([a-f])']); - equal('{f..f}', ['f']); - }); - - it('should expand multiple ranges:', function() { - equal('a/{b..d}/e/{f..h}', ['a/([b-d])/e/([f-h])']); - }); - }); - - describe('combo', function() { - it('should expand numerical ranges - positive and negative', function() { - equal('{-10..10}', ['(-[1-9]|-?10|[0-9])']); - }); - }); - - // HEADS UP! If you're using the `--mm` flag minimatch freezes on these - describe('large numbers', function() { - it('should expand large numbers', function() { - equal('{2147483645..2147483649}', ['(214748364[5-9])']); - equal('{214748364..2147483649}', ['(21474836[4-9]|2147483[7-9][0-9]|214748[4-9][0-9]{2}|214749[0-9]{3}|2147[5-9][0-9]{4}|214[89][0-9]{5}|21[5-9][0-9]{6}|2[2-9][0-9]{7}|[3-9][0-9]{8}|1[0-9]{9}|20[0-9]{8}|21[0-3][0-9]{7}|214[0-6][0-9]{6}|2147[0-3][0-9]{5}|21474[0-7][0-9]{4}|214748[0-2][0-9]{3}|2147483[0-5][0-9]{2}|21474836[0-4][0-9])']); - }); - }); - - describe('steps > positive ranges', function() { - it('should expand ranges using steps:', function() { - equal('{1..10..1}', ['([1-9]|10)']); - equal('{1..10..2}', ['(1|3|5|7|9)']); - equal('{1..20..20}', ['1']); - equal('{1..20..20}', ['1']); - equal('{1..20..20}', ['1']); - equal('{1..20..2}', ['(1|3|5|7|9|11|13|15|17|19)']); - equal('{10..0..2}', ['(10|8|6|4|2|0)']); - equal('{10..1..2}', ['(10|8|6|4|2)']); - equal('{100..0..5}', ['(100|95|90|85|80|75|70|65|60|55|50|45|40|35|30|25|20|15|10|5|0)']); - equal('{2..10..1}', ['([2-9]|10)']); - equal('{2..10..2}', ['(2|4|6|8|10)']); - equal('{2..10..3}', ['(2|5|8)']); - equal('{a..z..2}', ['(a|c|e|g|i|k|m|o|q|s|u|w|y)']); - }); - - it('should expand positive ranges with negative steps:', function() { - equal('{10..0..-2}', ['(10|8|6|4|2|0)']); - }); - }); - - describe('steps > negative ranges', function() { - it('should expand negative ranges using steps:', function() { - equal('{-1..-10..-2}', ['(-(1|3|5|7|9))']); - equal('{-1..-10..2}', ['(-(1|3|5|7|9))']); - equal('{-10..-2..2}', ['(-(10|8|6|4|2))']); - equal('{-2..-10..1}', ['(-[2-9]|-10)']); - equal('{-2..-10..2}', ['(-(2|4|6|8|10))']); - equal('{-2..-10..3}', ['(-(2|5|8))']); - equal('{-50..-0..5}', ['(0|-(50|45|40|35|30|25|20|15|10|5))']); - equal('{-9..9..3}', ['(0|3|6|9|-(9|6|3))']); - equal('{10..1..-2}', ['(10|8|6|4|2)']); - equal('{100..0..-5}', ['(100|95|90|85|80|75|70|65|60|55|50|45|40|35|30|25|20|15|10|5|0)']); - }); - }); - - describe('steps > alphabetical ranges', function() { - it('should expand alpha ranges with steps', function() { - equal('{a..e..2}', ['(a|c|e)']); - equal('{E..A..2}', ['(E|C|A)']); - equal('{a..z}', ['([a-z])']); - equal('{a..z..2}', ['(a|c|e|g|i|k|m|o|q|s|u|w|y)']); - equal('{z..a..-2}', ['(z|x|v|t|r|p|n|l|j|h|f|d|b)']); - }); - - it('should expand alpha ranges with negative steps', function() { - equal('{z..a..-2}', ['(z|x|v|t|r|p|n|l|j|h|f|d|b)']); - }); - }); - - describe('padding', function() { - function isMatch(str, pattern) { - return braces.makeRe(pattern).test(str); - } - - it('should handled padded ranges', function() { - // 1..5 - assert(!isMatch('1', '{001..005}')); - assert(!isMatch('2', '{001..005}')); - assert(!isMatch('3', '{001..005}')); - assert(!isMatch('4', '{001..005}')); - assert(!isMatch('5', '{001..005}')); - - assert(isMatch('001', '{001..005}')); - assert(isMatch('002', '{001..005}')); - assert(isMatch('003', '{001..005}')); - assert(isMatch('004', '{001..005}')); - assert(isMatch('005', '{001..005}')); - - // 1..100 - assert(!isMatch('01', '{001..100}')); - assert(!isMatch('10', '{001..100}')); - assert(!isMatch('99', '{001..100}')); - assert(isMatch('001', '{001..100}')); - assert(isMatch('010', '{001..100}')); - assert(isMatch('099', '{001..100}')); - assert(isMatch('100', '{001..100}')); - - // -001..100 - assert(!isMatch('01', '{-0100..100}')); - assert(!isMatch('10', '{-0100..100}')); - assert(!isMatch('99', '{-0100..100}')); - assert(isMatch('-01', '{-0100..100}')); - assert(isMatch('-010', '{-0100..100}')); - assert(isMatch('-100', '{-0100..100}')); - assert(isMatch('-099', '{-0100..100}')); - assert(isMatch('100', '{-0100..100}')); - assert(isMatch('001', '{-0100..100}')); - assert(isMatch('010', '{-0100..100}')); - assert(isMatch('099', '{-0100..100}')); - assert(isMatch('100', '{-0100..100}')); - - assert(!isMatch('100', '{-001..-100}')); - assert(!isMatch('001', '{-001..-100}')); - assert(!isMatch('010', '{-001..-100}')); - assert(!isMatch('099', '{-001..-100}')); - assert(!isMatch('100', '{-001..-100}')); - assert(isMatch('-1', '{-001..-100}')); - assert(isMatch('-001', '{-001..-100}')); - assert(isMatch('-01', '{-001..-100}')); - assert(isMatch('-010', '{-001..-100}')); - assert(isMatch('-100', '{-001..-100}')); - assert(isMatch('-099', '{-001..-100}')); - }); - - it('unwanted zero-padding -- fixed post-bash-4.0', function() { - equal('{10..0..2}', ['(10|8|6|4|2|0)']); - equal('{10..0..-2}', ['(10|8|6|4|2|0)']); - equal('{-50..-0..5}', ['(0|-(50|45|40|35|30|25|20|15|10|5))']); - }); - }); - }); - - describe('integration', function() { - it('should work with dots in file paths', function() { - equal('../{1..3}/../foo', ['../([1-3])/../foo']); - equal('../{2..10..2}/../foo', ['../(2|4|6|8|10)/../foo']); - equal('../{1..3}/../{a,b,c}/foo', ['../([1-3])/../(a|b|c)/foo']); - equal('./{a..z..3}/', ['./(a|d|g|j|m|p|s|v|y)/']); - equal('./{"x,y"}/{a..z..3}/', ['./{x,y}/(a|d|g|j|m|p|s|v|y)/']); - }); - - it('should expand a complex combination of ranges and sets:', function() { - equal('a/{x,y}/{1..5}c{d,e}f.{md,txt}', ['a/(x|y)/([1-5])c(d|e)f.(md|txt)']); - }); - - it('should expand complex sets and ranges in `bash` mode:', function() { - equal('a/{x,{1..5},y}/c{d}e', ['a/(x|([1-5])|y)/c{d}e']); - }); - }); -}); diff --git a/test/options.js b/test/options.js deleted file mode 100644 index b472b9f..0000000 --- a/test/options.js +++ /dev/null @@ -1,98 +0,0 @@ -/*! - * braces - * - * Copyright (c) 2014-2016, Jon Schlinkert. - * Licensed under the MIT License - */ - -'use strict'; - -require('mocha'); -var assert = require('assert'); -var braces = require('..'); - -function equal(pattern, expected, options) { - assert.deepEqual(braces(pattern, options).sort(), expected.sort()); -} - -describe('options', function() { - describe('options.expand', function() { - it('should expand braces when `options.expand` is true', function() { - equal('a/{b,c}/d', ['a/b/d', 'a/c/d'], {expand: true}); - }); - }); - - describe('options.cache', function() { - it('should disable caching', function() { - braces('a/{b,c}/d'); - assert(braces.cache.hasOwnProperty('a/{b,c}/d')); - braces('a/{b,c}/d'); - assert(braces.cache.hasOwnProperty('a/{b,c}/d')); - braces('a/{b,c}/d'); - assert(braces.cache.hasOwnProperty('a/{b,c}/d')); - braces('a/{b,c}/d', {cache: false}); - braces('a/{b,c}/d', {cache: false}); - braces('a/{b,c}/d', {cache: false}); - assert.deepEqual(braces.cache, {}); - }); - }); - - describe('options.noempty', function() { - it('should not remove empty values by default', function() { - equal('{,b{,a}}', ['', 'b', 'ba'], {expand: true}); - }); - - it('should remove empty values when `options.noempty` is false', function() { - equal('{,b{,a}}', ['b', 'ba'], {expand: true, noempty: true}); - }); - }); - - describe('options.nodupes', function() { - it('should not remove duplicates by default', function() { - equal('a/{b,b,b}/c', ['a/b/c', 'a/b/c', 'a/b/c'], {expand: true}); - }); - - it('should remove duplicates when `options.nodupes` is true', function() { - equal('a/{b,b,b}/c', ['a/b/c'], {expand: true, nodupes: true}); - }); - }); - - describe('options.optimize', function() { - it('should optimize braces when `options.optimize` is true', function() { - equal('a/{b,c}/d', ['a/(b|c)/d'], {optimize: true}); - }); - }); - - describe('options.quantifiers:', function() { - it('should not expand regex quantifiers when `options.quantifiers` is true', function() { - equal('a{2}c', ['a{2}c']); - equal('a{2}c', ['a{2}c'], {quantifiers: true}); - equal('a{2,}c', ['a{2,}c'], {quantifiers: true}); - equal('a{,2}c', ['a{,2}c'], {quantifiers: true}); - equal('a{2,3}c', ['a{2,3}c'], {quantifiers: true}); - }); - - it('should expand non-quantifiers when `options.quantifiers` is true', function() { - equal('a{2}c/{x,y}/z', ['a{2}c/(x|y)/z'], {quantifiers: true}); - equal('a{2}c/{x,y}/z', ['a{2}c/x/z', 'a{2}c/y/z'], {quantifiers: true, expand: true}); - }); - }); - - describe('options.unescape', function() { - it('should remove backslashes from escaped brace characters', function() { - equal('{a,b\\}c,d}', ['(a|b}c|d)']); - equal('\\{a,b,c,d,e}', ['{a,b,c,d,e}']); - equal('a/{z,\\{a,b,c,d,e}/d', ['a/(z|{a|b|c|d|e)/d']); - equal('a/\\{b,c}/{d,e}/f', ['a/{b,c}/(d|e)/f']); - equal('./\\{x,y}/{a..z..3}/', ['./{x,y}/(a|d|g|j|m|p|s|v|y)/']); - }); - - it('should not remove backslashes when `options.unescape` is false', function() { - equal('{a,b\\}c,d}', ['(a|b\\}c|d)'], {unescape: false}); - equal('\\{a,b,c,d,e}', ['\\{a,b,c,d,e}'], {unescape: false}); - equal('a/{z,\\{a,b,c,d,e}/d', ['a/(z|\\{a|b|c|d|e)/d'], {unescape: false}); - equal('a/\\{b,c}/{d,e}/f', ['a/\\{b,c}/(d|e)/f'], {unescape: false}); - equal('./\\{x,y}/{a..z..3}/', ['./\\{x,y}/(a|d|g|j|m|p|s|v|y)/'], {unescape: false}); - }); - }); -}); diff --git a/test/readme.js b/test/readme.js new file mode 100644 index 0000000..a3bc702 --- /dev/null +++ b/test/readme.js @@ -0,0 +1,48 @@ +'use strict'; + +require('mocha'); +const assert = require('assert').strict; +const braces = require('..'); + +describe('Examples from README.md', () => { + describe('Brace Expansion vs. Compilation', () => { + it('Compiled', () => { + assert.deepEqual(braces('a/{x,y,z}/b'), ['a/(x|y|z)/b']); + assert.deepEqual(braces(['a/{01..20}/b', 'a/{1..5}/b']), [ + 'a/(0[1-9]|1[0-9]|20)/b', + 'a/([1-5])/b' + ]); + }); + + it('Expanded', () => { + assert.deepEqual(braces('a/{x,y,z}/b', { expand: true }), ['a/x/b', 'a/y/b', 'a/z/b']); + assert.deepEqual(braces.expand('{01..10}'), [ + '01', + '02', + '03', + '04', + '05', + '06', + '07', + '08', + '09', + '10' + ]); + }); + }); + + describe('Sequences', () => { + it('first set of examples', () => { + assert.deepEqual(braces.expand('{1..3}'), ['1', '2', '3']); + assert.deepEqual(braces.expand('a/{1..3}/b'), ['a/1/b', 'a/2/b', 'a/3/b']); + assert.deepEqual(braces('{a..c}', { expand: true }), ['a', 'b', 'c']); + assert.deepEqual(braces('foo/{a..c}', { expand: true }), ['foo/a', 'foo/b', 'foo/c']); + }); + + it('zero-padding examples', () => { + // supports zero-padded ranges + assert.deepEqual(braces('a/{01..03}/b'), ['a/(0[1-3])/b']); + assert.deepEqual(braces('a/{001..300}/b'), ['a/(00[1-9]|0[1-9][0-9]|[12][0-9]{2}|300)/b']); + }); + }); +}); diff --git a/test/regression-1.8.5.js b/test/regression.js similarity index 68% rename from test/regression-1.8.5.js rename to test/regression.js index 00f652a..544044a 100644 --- a/test/regression-1.8.5.js +++ b/test/regression.js @@ -1,20 +1,15 @@ 'use strict'; -var assert = require('assert'); -var braces = require('..'); - -function equal(pattern, expected, options) { - options = options || {}; - var fn = braces; - if (options.optimize !== true) { - fn = braces.expand; - } - var actual = fn(pattern, options).sort(); - assert.deepEqual(actual, expected.sort(), pattern); -} - -describe('braces tests from 1.8.5', function() { - it('braces', function() { +require('mocha'); +const assert = require('assert').strict; +const braces = require('..'); + +const equal = (input, expected, options) => { + assert.deepEqual(braces.expand(input, options), expected); +}; + +describe('braces tests from 1.8.5', () => { + it('braces', () => { equal('ff{c,b,a}', ['ffc', 'ffb', 'ffa']); equal('f{d,e,f}g', ['fdg', 'feg', 'ffg']); equal('{l,n,m}xyz', ['lxyz', 'nxyz', 'mxyz']); @@ -25,7 +20,7 @@ describe('braces tests from 1.8.5', function() { equal('{x,y,\\{a,b,c}}', ['x}', 'y}', '{a}', 'b}', 'c}']); equal('{x\\,y,\\{abc\\},trie}', ['x,y', '{abc}', 'trie']); - equal('/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', ['/usr/ucb/ex', '/usr/lib/ex', '/usr/ucb/edit', '/usr/lib/how_ex']); + equal('/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', ['/usr/ucb/ex', '/usr/ucb/edit', '/usr/lib/ex', '/usr/lib/how_ex']); equal('{}', ['{}']); equal('{ }', ['{ }']); @@ -36,15 +31,15 @@ describe('braces tests from 1.8.5', function() { equal('foo {1,2} bar', ['foo 1 bar', 'foo 2 bar']); }); - it('new sequence brace operators', function() { + it('new sequence brace operators', () => { equal('{1..10}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); equal('{0..10,braces}', ['0..10', 'braces']); equal('{braces,{0..10}}', ['braces', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); - equal('{{0..10},braces}', ['0', 'braces', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); - equal('x{{0..10},braces}y', ['x0y', 'xbracesy', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'x10y']); + equal('{{0..10},braces}', ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces']); + equal('x{{0..10},braces}y', ['x0y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'x10y', 'xbracesy']); }); - it('ranges', function() { + it('ranges', () => { equal('{3..3}', ['3']); equal('x{3..3}y', ['x3y']); equal('{10..1}', ['10', '9', '8', '7', '6', '5', '4', '3', '2', '1']); @@ -60,18 +55,18 @@ describe('braces tests from 1.8.5', function() { equal('0{1..9} {10..20}', ['01 10', '01 11', '01 12', '01 13', '01 14', '01 15', '01 16', '01 17', '01 18', '01 19', '01 20', '02 10', '02 11', '02 12', '02 13', '02 14', '02 15', '02 16', '02 17', '02 18', '02 19', '02 20', '03 10', '03 11', '03 12', '03 13', '03 14', '03 15', '03 16', '03 17', '03 18', '03 19', '03 20', '04 10', '04 11', '04 12', '04 13', '04 14', '04 15', '04 16', '04 17', '04 18', '04 19', '04 20', '05 10', '05 11', '05 12', '05 13', '05 14', '05 15', '05 16', '05 17', '05 18', '05 19', '05 20', '06 10', '06 11', '06 12', '06 13', '06 14', '06 15', '06 16', '06 17', '06 18', '06 19', '06 20', '07 10', '07 11', '07 12', '07 13', '07 14', '07 15', '07 16', '07 17', '07 18', '07 19', '07 20', '08 10', '08 11', '08 12', '08 13', '08 14', '08 15', '08 16', '08 17', '08 18', '08 19', '08 20', '09 10', '09 11', '09 12', '09 13', '09 14', '09 15', '09 16', '09 17', '09 18', '09 19', '09 20']); }); - it('mixes are incorrectly-formed brace expansions', function() { + it('mixes are incorrectly-formed brace expansions', () => { // the first one is valid, but Bash fails on it equal('{1..f}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f']); - equal('{f..1}', ['1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f']); + equal('{f..1}', ['f', 'e', 'd', 'c', 'b', 'a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A', '@', '?', '>', '=', '<', ';', ':', '9', '8', '7', '6', '5', '4', '3', '2', '1']); }); - it('do negative numbers work?', function() { + it('do negative numbers work?', () => { equal('{-1..-10}', ['-1', '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10']); equal('{-20..0}', ['-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0']); }); - it('weirdly-formed brace expansions -- fixed in post-bash-3.1', function() { + it('weirdly-formed brace expansions -- fixed in post-bash-3.1', () => { equal('{-1..-10}', ['-1', '-2', '-3', '-4', '-5', '-6', '-7', '-8', '-9', '-10']); equal('{-20..0}', ['-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', '0']); equal('a-{b{d,e}}-c', ['a-{bd}-c', 'a-{be}-c']); @@ -82,7 +77,7 @@ describe('braces tests from 1.8.5', function() { equal('{"x,x"}', ['{x,x}']); }); - it('numerical ranges with steps', function() { + it('numerical ranges with steps', () => { equal('{1..10..2}', ['1', '3', '5', '7', '9']); equal('{-1..-10..2}', ['-1', '-3', '-5', '-7', '-9']); equal('{-1..-10..-2}', ['-1', '-3', '-5', '-7', '-9']); @@ -97,23 +92,23 @@ describe('braces tests from 1.8.5', function() { equal('{100..0..-5}', ['100', '95', '90', '85', '80', '75', '70', '65', '60', '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '5', '0']); }); - it('alpha ranges with steps', function() { + it('alpha ranges with steps', () => { equal('{a..z}', ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']); equal('{a..z..2}', ['a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y']); equal('{z..a..-2}', ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']); }); - it('make sure brace expansion handles ints > 2**31 - 1 using intmax_t', function() { + it('make sure brace expansion handles ints > 2**31 - 1 using intmax_t', () => { equal('{2147483645..2147483649}', ['2147483645', '2147483646', '2147483647', '2147483648', '2147483649']); }); - it('unwanted zero-padding -- fixed post-bash-4.0', function() { + it('unwanted zero-padding -- fixed post-bash-4.0', () => { equal('{10..0..2}', ['10', '8', '6', '4', '2', '0']); equal('{10..0..-2}', ['10', '8', '6', '4', '2', '0']); equal('{-50..-0..5}', ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); }); - it('bad', function() { + it('bad', () => { equal('{1..10.f}', ['{1..10.f}']); equal('{1..ff}', ['{1..ff}']); equal('{1..10..ff}', ['{1..10..ff}']); @@ -130,13 +125,13 @@ describe('braces tests from 1.8.5', function() { }); }); -describe('bash tests', function() { - describe('brace expansion', function() { - it('should return an empty array when no braces are found', function() { - equal('', ['']); +describe('bash tests', () => { + describe('brace expansion', () => { + it('should return an empty array when no braces are found', () => { + equal('', []); }); - it('should expand emty sets', function() { + it('should expand emty sets', () => { equal('a{,}', ['a', 'a']); equal('{,}b', ['b', 'b']); equal('a{,}b', ['ab', 'ab']); @@ -144,51 +139,51 @@ describe('bash tests', function() { equal('a{,}{,}', ['a', 'a', 'a', 'a']); equal('a{,}{,}{,}', ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']); equal('{a,b{,}{,}{,}}', ['a', 'b', 'b', 'b', 'b', 'b', 'b', 'b', 'b']); - equal('a{,}/{c,d}/e', ['a/c/e', 'a/c/e', 'a/d/e', 'a/d/e']); + equal('a{,}/{c,d}/e', ['a/c/e', 'a/d/e', 'a/c/e', 'a/d/e']); equal('{a,b{,}{,}{,},c}d', ['ad', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'bd', 'cd']); }); - it('should eliminate dupes in repeated strings', function() { - equal('a{,}', ['a'], {nodupes: true}); - equal('a{,}{,}', ['a'], {nodupes: true}); - equal('a{,}{,}{,}', ['a'], {nodupes: true}); - equal('{a,b{,}{,}{,}}', ['a', 'b'], {nodupes: true}); - equal('{a,b{,}{,}{,},c}d', ['ad', 'bd', 'cd'], {nodupes: true}); - equal('{a,b{,}{,}{,},c}d', ['ad', 'bd', 'cd'], {nodupes: true}); + it('should eliminate dupes in repeated strings', () => { + equal('a{,}', ['a'], { nodupes: true }); + equal('a{,}{,}', ['a'], { nodupes: true }); + equal('a{,}{,}{,}', ['a'], { nodupes: true }); + equal('{a,b{,}{,}{,}}', ['a', 'b'], { nodupes: true }); + equal('{a,b{,}{,}{,},c}d', ['ad', 'bd', 'cd'], { nodupes: true }); + equal('{a,b{,}{,}{,},c}d', ['ad', 'bd', 'cd'], { nodupes: true }); }); - it('should work with no braces', function() { + it('should work with no braces', () => { equal('abc', ['abc']); }); - it('should work with no commas', function() { + it('should work with no commas', () => { equal('a{b}c', ['a{b}c']); }); - it('should work with no commas in `bash` mode', function() { + it('should work with no commas in `bash` mode', () => { equal('a{b}c', ['a{b}c']); }); - it('should handle spaces', function() { + it('should handle spaces', () => { equal('a{ ,c{d, },h}x', ['a x', 'acdx', 'ac x', 'ahx']); - equal('a{ ,c{d, },h} ', [ 'a ', 'acd ', 'ac ', 'ah ' ]); + equal('a{ ,c{d, },h} ', ['a ', 'acd ', 'ac ', 'ah ']); - // see https://github.com/jonschlinkert/microequal/issues/66 + // see https://github.com/jonschlinkert/micromatch/issues/66 equal('/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.{html,ejs}', [ '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.html', '/Users/tobiasreich/Sites/aaa/bbb/ccc 2016/src/**/[^_]*.ejs' ]); }); - it('should handle empty braces', function() { + it('should handle empty braces', () => { equal('{ }', ['{ }']); equal('{}', ['{}']); equal('}', ['}']); equal('{', ['{']); - equal('{,}', []); + equal('{,}', ['', '']); }); - it('should handle imbalanced braces', function() { + it('should handle imbalanced braces', () => { equal('a-{bdef-{g,i}-c', ['a-{bdef-g-c', 'a-{bdef-i-c']); equal('abc{', ['abc{']); equal('{abc{', ['{abc{']); @@ -205,12 +200,12 @@ describe('bash tests', function() { equal('a{b{c{d,e}f{x,y{{g}h', ['a{b{cdf{x,y{{g}h', 'a{b{cef{x,y{{g}h']); equal('f{x,y{{g}h', ['f{x,y{{g}h']); equal('f{x,y{{g}}h', ['f{x,y{{g}}h']); - equal('a{b{c{d,e}f{x,y{}g}h', ['a{b{cdfxh', 'a{b{cefxh', 'a{b{cdfy{}gh', 'a{b{cefy{}gh']); + equal('a{b{c{d,e}f{x,y{}g}h', ['a{b{cdfxh', 'a{b{cdfy{}gh', 'a{b{cefxh', 'a{b{cefy{}gh']); equal('f{x,y{}g}h', ['fxh', 'fy{}gh']); equal('z{a,b{,c}d', ['z{a,bd', 'z{a,bcd']); }); - it('should handle invalid braces in `bash mode`:', function() { + it('should handle invalid braces in `bash mode`:', () => { equal('a{b{c{d,e}f}g}h', ['a{b{cdf}g}h', 'a{b{cef}g}h']); equal('f{x,y{{g,z}}h}', ['fx', 'fy{g}h', 'fy{z}h']); equal('z{a,b},c}d', ['za,c}d', 'zb,c}d']); @@ -219,47 +214,47 @@ describe('bash tests', function() { equal('f{x,y{{g}}h', ['f{x,y{{g}}h']); }); - it('should return invalid braces:', function() { + it('should return invalid braces:', () => { equal('{0..10,braces}', ['0..10', 'braces']); }); - it('should not expand quoted strings.', function() { + it('should not expand quoted strings.', () => { equal('{"x,x"}', ['{x,x}']); equal('{"klklkl"}{1,2,3}', ['{klklkl}1', '{klklkl}2', '{klklkl}3']); }); - it('should work with one value', function() { + it('should work with one value', () => { equal('a{b}c', ['a{b}c']); equal('a/b/c{d}e', ['a/b/c{d}e']); }); - it('should work with one value in `bash` mode', function() { + it('should work with one value in `bash` mode', () => { equal('a{b}c', ['a{b}c']); equal('a/b/c{d}e', ['a/b/c{d}e']); }); - it('should work with nested non-sets', function() { + it('should work with nested non-sets', () => { equal('foo {1,2} bar', ['foo 1 bar', 'foo 2 bar']); equal('{a-{b,c,d}}', ['{a-b}', '{a-c}', '{a-d}']); equal('{a,{a-{b,c,d}}}', ['a', '{a-b}', '{a-c}', '{a-d}']); }); - it('should work with nested non-sets in `bash` mode', function() { + it('should work with nested non-sets in `bash` mode', () => { equal('{a-{b,c,d}}', ['{a-b}', '{a-c}', '{a-d}']); equal('{a,{a-{b,c,d}}}', ['a', '{a-b}', '{a-c}', '{a-d}']); }); - it('should not expand dots with leading slashes (escaped or paths).', function() { + it('should not expand dots with leading slashes (escaped or paths).', () => { equal('a{b,c/*/../d}e', ['abe', 'ac/*/../de']); equal('a{b,b,c/../b}d', ['abd', 'abd', 'ac/../bd']); }); - it('should work with commas.', function() { + it('should work with commas.', () => { equal('a{b,}c', ['abc', 'ac']); equal('a{,b}c', ['ac', 'abc']); }); - it('should expand sets', function() { + it('should expand sets', () => { equal('a/{x,y}/cde', ['a/x/cde', 'a/y/cde']); equal('a/b/c/{x,y}', ['a/b/c/x', 'a/b/c/y']); equal('ff{c,b,a}', ['ffc', 'ffb', 'ffa']); @@ -268,46 +263,46 @@ describe('bash tests', function() { equal('{x,y,{abc},trie}', ['x', 'y', '{abc}', 'trie']); }); - it('should expand multiple sets', function() { - equal('a/{a,b}/{c,d}/e', ['a/a/c/e', 'a/b/c/e', 'a/a/d/e', 'a/b/d/e']); - equal('a{b,c}d{e,f}g', ['abdeg', 'acdeg', 'abdfg', 'acdfg']); - equal('a/{x,y}/c{d,e}f.{md,txt}', ['a/x/cdf.md', 'a/y/cdf.md', 'a/x/cef.md', 'a/y/cef.md', 'a/x/cdf.txt', 'a/y/cdf.txt', 'a/x/cef.txt', 'a/y/cef.txt']); + it('should expand multiple sets', () => { + equal('a/{a,b}/{c,d}/e', ['a/a/c/e', 'a/a/d/e', 'a/b/c/e', 'a/b/d/e']); + equal('a{b,c}d{e,f}g', ['abdeg', 'abdfg', 'acdeg', 'acdfg']); + equal('a/{x,y}/c{d,e}f.{md,txt}', ['a/x/cdf.md', 'a/x/cdf.txt', 'a/x/cef.md', 'a/x/cef.txt', 'a/y/cdf.md', 'a/y/cdf.txt', 'a/y/cef.md', 'a/y/cef.txt']); }); - it('should expand nested sets', function() { + it('should expand nested sets', () => { equal('a/{b,c,{d,e}}/g', ['a/b/g', 'a/c/g', 'a/d/g', 'a/e/g']); equal('a/{a,b}/{c,d}/e', ['a/a/c/e', 'a/a/d/e', 'a/b/c/e', 'a/b/d/e']); - equal('{a,b}{{a,b},a,b}', ['aa', 'aa', 'ab', 'ab', 'ba', 'ba', 'bb', 'bb']); - equal('/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', ['/usr/ucb/ex', '/usr/lib/ex', '/usr/ucb/edit', '/usr/lib/how_ex']); + equal('{a,b}{{a,b},a,b}', ['aa', 'ab', 'aa', 'ab', 'ba', 'bb', 'ba', 'bb']); + equal('/usr/{ucb/{ex,edit},lib/{ex,how_ex}}', ['/usr/ucb/ex', '/usr/ucb/edit', '/usr/lib/ex', '/usr/lib/how_ex']); equal('a{b,c{d,e}f}g', ['abg', 'acdfg', 'acefg']); - equal('a{{x,y},z}b', ['axb', 'azb', 'ayb']); + equal('a{{x,y},z}b', ['axb', 'ayb', 'azb']); equal('f{x,y{g,z}}h', ['fxh', 'fygh', 'fyzh']); - equal('a{b,c{d,e},h}x/z', ['abx/z', 'acdx/z', 'ahx/z', 'acex/z']); - equal('a{b,c{d,e},h}x{y,z}', ['abxy', 'acdxy', 'ahxy', 'acexy', 'abxz', 'acdxz', 'ahxz', 'acexz']); - equal('a{b,c{d,e},{f,g}h}x{y,z}', ['abxy', 'acdxy', 'afhxy', 'acexy', 'aghxy', 'abxz', 'acdxz', 'afhxz', 'acexz', 'aghxz']); + equal('a{b,c{d,e},h}x/z', ['abx/z', 'acdx/z', 'acex/z', 'ahx/z']); + equal('a{b,c{d,e},h}x{y,z}', ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'ahxy', 'ahxz']); + equal('a{b,c{d,e},{f,g}h}x{y,z}', ['abxy', 'abxz', 'acdxy', 'acdxz', 'acexy', 'acexz', 'afhxy', 'afhxz', 'aghxy', 'aghxz']); equal('a-{b{d,e}}-c', ['a-{bd}-c', 'a-{be}-c']); }); - it('should expand with globs.', function() { + it('should expand with globs.', () => { equal('a/b/{d,e}/*.js', ['a/b/d/*.js', 'a/b/e/*.js']); equal('a/**/c/{d,e}/f*.js', ['a/**/c/d/f*.js', 'a/**/c/e/f*.js']); - equal('a/**/c/{d,e}/f*.{md,txt}', ['a/**/c/d/f*.md', 'a/**/c/e/f*.md', 'a/**/c/d/f*.txt', 'a/**/c/e/f*.txt']); + equal('a/**/c/{d,e}/f*.{md,txt}', ['a/**/c/d/f*.md', 'a/**/c/d/f*.txt', 'a/**/c/e/f*.md', 'a/**/c/e/f*.txt']); }); - it('should expand with extglobs.', function() { + it('should expand with brackets.', () => { equal('a/b/{d,e,[1-5]}/*.js', ['a/b/d/*.js', 'a/b/e/*.js', 'a/b/[1-5]/*.js']); }); }); - describe('escaping:', function() { - it('should not expand strings with es6/bash-like variables.', function() { + describe('escaping:', () => { + it('should not expand strings with es6/bash-like variables.', () => { equal('abc/${ddd}/xyz', ['abc/${ddd}/xyz']); equal('a${b}c', ['a${b}c']); equal('a/{${b},c}/d', ['a/${b}/d', 'a/c/d']); equal('a${b,d}/{foo,bar}c', ['a${b,d}/fooc', 'a${b,d}/barc']); }); - it('should not expand escaped commas.', function() { + it('should not expand escaped commas.', () => { equal('a{b\\,c}d', ['a{b,c}d']); equal('a{b\\,c\\,d}e', ['a{b,c,d}e']); equal('{abc\\,def}', ['{abc,def}']); @@ -315,36 +310,25 @@ describe('bash tests', function() { equal('a/{b,c}/{x\\,y}/d/e', ['a/b/{x,y}/d/e', 'a/c/{x,y}/d/e']); }); - it('should return sets with escaped commas in `bash` mode.', function() { + it('should return sets with escaped commas in `bash` mode.', () => { equal('a/{b,c}/{x\\,y}/d/e', ['a/b/{x,y}/d/e', 'a/c/{x,y}/d/e']); }); - it('should not expand escaped braces.', function() { + it('should not expand escaped braces.', () => { equal('{a,b\\}c,d}', ['a', 'b}c', 'd']); equal('\\{a,b,c,d,e}', ['{a,b,c,d,e}']); - equal('a/{b,\\{a,b,c,d,e}/d', ['a/b/d', 'a/b/d', 'a/{a/d', 'a/c/d', 'a/d/d', 'a/e/d']); + equal('a/{b,\\{a,b,c,d,e}/d', ['a/b/d', 'a/{a/d', 'a/b/d', 'a/c/d', 'a/d/d', 'a/e/d']); equal('a/\\{b,c}/{d,e}/f', ['a/{b,c}/d/f', 'a/{b,c}/e/f']); - equal('./\\{x,y}/{a..z..3}/', ['./{x,y}/(a|d|g|j|m|p|s|v|y)/'], {optimize: true}); }); - it('should not expand escaped braces or commas.', function() { + it('should not expand escaped braces or commas.', () => { equal('{x\\,y,\\{abc\\},trie}', ['x,y', '{abc}', 'trie']); }); }); - - describe('complex', function() { - it('should expand a complex combination of ranges and sets:', function() { - equal('a/{x,y}/{1..5}c{d,e}f.{md,txt}', ['a/x/1cdf.md', 'a/y/1cdf.md', 'a/x/2cdf.md', 'a/y/2cdf.md', 'a/x/3cdf.md', 'a/y/3cdf.md', 'a/x/4cdf.md', 'a/y/4cdf.md', 'a/x/5cdf.md', 'a/y/5cdf.md', 'a/x/1cef.md', 'a/y/1cef.md', 'a/x/2cef.md', 'a/y/2cef.md', 'a/x/3cef.md', 'a/y/3cef.md', 'a/x/4cef.md', 'a/y/4cef.md', 'a/x/5cef.md', 'a/y/5cef.md', 'a/x/1cdf.txt', 'a/y/1cdf.txt', 'a/x/2cdf.txt', 'a/y/2cdf.txt', 'a/x/3cdf.txt', 'a/y/3cdf.txt', 'a/x/4cdf.txt', 'a/y/4cdf.txt', 'a/x/5cdf.txt', 'a/y/5cdf.txt', 'a/x/1cef.txt', 'a/y/1cef.txt', 'a/x/2cef.txt', 'a/y/2cef.txt', 'a/x/3cef.txt', 'a/y/3cef.txt', 'a/x/4cef.txt', 'a/y/4cef.txt', 'a/x/5cef.txt', 'a/y/5cef.txt']); - }); - - it('should expand complex sets and ranges in `bash` mode:', function() { - equal('a/{x,{1..5},y}/c{d}e', ['a/x/c{d}e', 'a/1/c{d}e', 'a/y/c{d}e', 'a/2/c{d}e', 'a/3/c{d}e', 'a/4/c{d}e', 'a/5/c{d}e']); - }); - }); }); -describe('range expansion', function() { - it('should expand numerical ranges', function() { +describe('range expansion', () => { + it('should expand numerical ranges', () => { equal('a{0..3}d', ['a0d', 'a1d', 'a2d', 'a3d']); equal('x{10..1}y', ['x10y', 'x9y', 'x8y', 'x7y', 'x6y', 'x5y', 'x4y', 'x3y', 'x2y', 'x1y']); equal('x{3..3}y', ['x3y']); @@ -357,7 +341,7 @@ describe('range expansion', function() { equal('{5..8}', ['5', '6', '7', '8']); }); - it('should expand alphabetical ranges', function() { + it('should expand alphabetical ranges', () => { equal('0{a..d}0', ['0a0', '0b0', '0c0', '0d0']); equal('a/{b..d}/e', ['a/b/e', 'a/c/e', 'a/d/e']); equal('{a..A}', ['a', '`', '_', '^', ']', '\\', '[', 'Z', 'Y', 'X', 'W', 'V', 'U', 'T', 'S', 'R', 'Q', 'P', 'O', 'N', 'M', 'L', 'K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A']); @@ -371,41 +355,31 @@ describe('range expansion', function() { equal('{f..f}', ['f']); }); - it('should use steps with alphabetical ranges', function() { + it('should use steps with alphabetical ranges', () => { equal('{a..e..2}', ['a', 'c', 'e']); equal('{E..A..2}', ['E', 'C', 'A']); }); - it('should not try to expand ranges with decimals', function() { + it('should not try to expand ranges with decimals', () => { equal('{1.1..2.1}', ['{1.1..2.1}']); - equal('{1.1..2.1}', ['{1.1..2.1}'], {optimize: true}); - equal('{1.1..~2.1}', ['{1.1..~2.1}'], {optimize: true}); }); - it('should expand negative ranges', function() { + it('should expand negative ranges', () => { equal('{z..a..-2}', ['z', 'x', 'v', 't', 'r', 'p', 'n', 'l', 'j', 'h', 'f', 'd', 'b']); equal('{-10..-1}', ['-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1']); equal('{0..-5}', ['0', '-1', '-2', '-3', '-4', '-5']); equal('{9..-4}', ['9', '8', '7', '6', '5', '4', '3', '2', '1', '0', '-1', '-2', '-3', '-4']); }); - it('should expand multiple ranges:', function() { - equal('a/{b..d}/e/{f..h}', ['a/b/e/f', 'a/c/e/f', 'a/d/e/f', 'a/b/e/g', 'a/c/e/g', 'a/d/e/g', 'a/b/e/h', 'a/c/e/h', 'a/d/e/h']); + it('should expand multiple ranges:', () => { + equal('a/{b..d}/e/{f..h}', ['a/b/e/f', 'a/b/e/g', 'a/b/e/h', 'a/c/e/f', 'a/c/e/g', 'a/c/e/h', 'a/d/e/f', 'a/d/e/g', 'a/d/e/h']); }); - it('should work with dots in file paths', function() { + it('should work with dots in file paths', () => { equal('../{1..3}/../foo', ['../1/../foo', '../2/../foo', '../3/../foo']); }); - it('should make a regex-string when `options.optimize` is defined:', function() { - equal('../{1..3}/../foo', ['../([1-3])/../foo'], {optimize: true}); - equal('../{2..10..2}/../foo', ['../(2|4|6|8|10)/../foo'], {optimize: true}); - equal('../{1..3}/../{a,b,c}/foo', ['../([1-3])/../(a|b|c)/foo'], {optimize: true}); - equal('./{a..z..3}/', ['./(a|d|g|j|m|p|s|v|y)/'], {optimize: true}); - equal('./{"x,y"}/{a..z..3}/', ['./{x,y}/(a|d|g|j|m|p|s|v|y)/'], {optimize: true}); - }); - - it('should expand ranges using steps:', function() { + it('should expand ranges using steps:', () => { equal('{-1..-10..-2}', ['-1', '-3', '-5', '-7', '-9']); equal('{-1..-10..2}', ['-1', '-3', '-5', '-7', '-9']); equal('{-50..-0..5}', ['-50', '-45', '-40', '-35', '-30', '-25', '-20', '-15', '-10', '-5', '0']); @@ -432,7 +406,7 @@ describe('range expansion', function() { equal('{2..10..3}', ['2', '5', '8']); }); - it('should expand negative ranges using steps:', function() { + it('should expand negative ranges using steps:', () => { equal('{-1..-10..-2}', ['-1', '-3', '-5', '-7', '-9']); equal('{-1..-10..2}', ['-1', '-3', '-5', '-7', '-9']); equal('{-10..-2..2}', ['-10', '-8', '-6', '-4', '-2']); @@ -442,13 +416,13 @@ describe('range expansion', function() { equal('{-9..9..3}', ['-9', '-6', '-3', '0', '3', '6', '9']); }); - it('should expand mixed ranges and sets:', function() { - equal('x{{0..10},braces}y', ['x0y', 'xbracesy', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'x10y']); - equal('{{0..10},braces}', ['0', 'braces', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']); + it('should expand mixed ranges and sets:', () => { + equal('x{{0..10},braces}y', ['x0y', 'x1y', 'x2y', 'x3y', 'x4y', 'x5y', 'x6y', 'x7y', 'x8y', 'x9y', 'x10y', 'xbracesy']); + equal('{{0..10},braces}', ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'braces']); equal('{2147483645..2147483649}', ['2147483645', '2147483646', '2147483647', '2147483648', '2147483649']); }); - it('should return invalid ranges:', function() { + it('should return invalid ranges:', () => { equal('{1.20..2}', ['{1.20..2}']); equal('{1..0f}', ['{1..0f}']); equal('{1..10..ff}', ['{1..10..ff}']); diff --git a/test/support/bash.js b/test/support/bash.js deleted file mode 100644 index b06c73e..0000000 --- a/test/support/bash.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; - -var isWindows = require('is-windows'); -var spawn = require('cross-spawn'); -var utils = require('./utils'); - -/** - * Expose `bash` util - */ - -module.exports = function(pattern) { - if (isWindows()) { - throw new Error('windows not supported'); - } - - var cmd = pattern; - if (!/echo/.test(cmd)) { - cmd = 'echo ' + escape(pattern); - } - - var res = spawn.sync(utils.getBashPath(), ['-c', cmd]); - var err = res.stderr && res.stderr.toString().trim(); - if (err) { - console.error(cmd); - throw new Error(err); - } - - if (!res.stdout) { - return []; - } - return unescape(res.stdout).sort(); -}; - -/** - * Escape characters that behave differently in bash than node (like spaces, which are - * valid path characters in node.js but indicate a delimiter in Bash) - */ - -function escape(buf) { - return buf.split(/\\? /).join('_SPACE_') - .replace(/([*`\[\]])/g, '\\$1') - .replace(/(\$\{)([^{}]+)(\})/g, function(m, $1, $2, $3) { - return utils.nc[0] + $2 + utils.nc[2]; - }); -} - -/** - * Unescape previously-escaped characters - */ - -function unescape(buf) { - return buf.toString().split(/[ \n]/) - .filter(Boolean) - .map(function(str) { - return utils.unescape(str, {escape: true}) - .split('_SPACE_').join(' ') - .split(/\\(?!`)/).join(''); - }); -} diff --git a/test/support/generate.js b/test/support/generate.js deleted file mode 100644 index f892749..0000000 --- a/test/support/generate.js +++ /dev/null @@ -1,86 +0,0 @@ -'use strict'; - -var braces = require('../..'); -var mm = require('minimatch'); -var text = require('text-table'); -var Time = require('time-diff'); -var time = new Time(); - -var table = [ - ['**Pattern**', '**braces**', '**minimatch**'], - ['---', '---', '---'] -]; - -// warm up both libs -mm.braceExpand('{a,b}'); -braces('{a,b}'); - -function generate(pattern) { - time.start('braces'); - var bval = braces(pattern, {rangeLimit: false}).join('|'); - var b = [wrap(format(bval.length)), '(' + time.end('braces', 'μs') + ')'].join(' '); - - time.start('minimatch'); - var mval = mm.braceExpand(pattern).join('|'); - var m = [wrap(format(mval.length)), '(' + time.end('minimatch', 'μs') + ')'].join(' '); - - table.push([wrap(pattern), b, m]); - return table; -} - -function wrap(str) { - return '`' + str + '`'; -} - -var patterns = [ - // '{1..9007199254740991}', - // '{1..1000000000000000}', - // '{1..100000000000000}', - // '{1..10000000000000}', - // '{1..1000000000000}', - // '{1..100000000000}', - // '{1..10000000000}', - // '{1..1000000000}', - // '{1..100000000}', - '{1..10000000}', - '{1..1000000}', - '{1..100000}', - '{1..10000}', - '{1..1000}', - '{1..100}', - '{1..10}', - '{1..3}', - // '/some/file/path/id-{0001..2017}', - // '/some/file/path/id-{0100..2017}', - // '/some/file/path/id-{1000..2017}', - // '/some/file/path/id-{1900..2017}', - // '/some/file/path/id-{2000..2017}', -]; - -for (var i = 0; i < patterns.length; i++) { - generate(patterns[i]); -} - -console.log(text(table, {hsep: ' | '})); - -function format(number, precision) { - if (typeof precision !== 'number') { - precision = 2; - } - - var abbr = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - precision = Math.pow(10, precision); - number = Number(number); - - var len = abbr.length - 1; - while (len-- >= 0) { - var size = Math.pow(10, len * 3); - if (size <= (number + 1)) { - number = Math.round(number * precision / size) / precision; - number += ' ' + abbr[len]; - break; - } - } - return number; -} - diff --git a/test/support/utils.js b/test/support/utils.js deleted file mode 100644 index 21be11a..0000000 --- a/test/support/utils.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var util = require('util'); -var bashPath = ''; - -/** - * Utils - */ - -exports.extend = require('extend-shallow'); -exports.nc = require('noncharacters'); - -exports.getBashPath = function() { - if (bashPath) return bashPath; - if (fs.existsSync('/usr/local/bin/bash')) { - bashPath = '/usr/local/bin/bash'; - } else if (fs.existsSync('/bin/bash')) { - bashPath = '/bin/bash'; - } else { - bashPath = 'bash'; - } - return bashPath; -}; - -exports.escape = function(str, options) { - if (typeof str !== 'string') { - throw new TypeError('expected a string: ' + util.inspect(str)); - } - var opts = exports.extend({}, options); - if (!opts.expand && !opts.escape) return str; - str = str.replace(/(\$\{([^{}]+?)\})/g, function(m, $1, $2) { - return exports.nc[0] + $2 + exports.nc[2]; - }); - str = str.replace(/(\{)([^{,.}]+?)(\})/g, function(m, $1, $2, $3) { - return exports.nc[1] + $2 + exports.nc[2]; - }); - str = str.replace(/\\\{|\{(?!.*\})/g, exports.nc[1]); - str = str.replace(/\\\}/g, exports.nc[2]); - str = str.replace(/\\\,/g, exports.nc[3]); - if (!/\{/.test(str)) { - return str.replace(/\}/g, exports.nc[2]); - } - return str; -}; - -exports.unescape = function(str, options) { - if (typeof str !== 'string') { - throw new TypeError('expected a string: ' + util.inspect(str)); - } - var opts = exports.extend({}, options); - if (!opts.expand && !opts.escape) return str; - var pre = opts.noescape ? '' : '\\'; - str = str.split(exports.nc[0]).join(pre ? '\\$\\{' : '${'); - str = str.split(exports.nc[1]).join(pre + '{'); - str = str.split(exports.nc[2]).join(pre + '}'); - str = str.split(exports.nc[3]).join(','); - return str.replace(/\\+/g, '\\'); -}; diff --git a/test/utils.js b/test/utils.js deleted file mode 100644 index c2afd72..0000000 --- a/test/utils.js +++ /dev/null @@ -1,78 +0,0 @@ -/*! - * braces - * - * Copyright (c) 2014-2016, Jon Schlinkert. - * Licensed under the MIT License - */ - -'use strict'; - -require('mocha'); -var assert = require('assert'); -var utils = require('../lib/utils'); - -describe('utils', function() { - describe('.isEmptySets', function() { - it('should return true if string contains only empty stems', function() { - assert(utils.isEmptySets('{,}')); - assert(utils.isEmptySets('{,}{,}')); - assert(utils.isEmptySets('{,}{,}{,}{,}{,}')); - }); - - it('should return false if string contains more than empty stems', function() { - assert(!utils.isEmptySets('{,}foo')); - }); - - it('should return false if string contains other than empty stems', function() { - assert(!utils.isEmptySets('foo')); - }); - }); - - describe('.split', function() { - it('should split on commas by default', function() { - assert.deepEqual(utils.split('a,b,c'), ['a', 'b', 'c']); - assert.deepEqual(utils.split('{a,b,c}'), ['{a', 'b', 'c}']); - }); - - it('should not split inside parens', function() { - assert.deepEqual(utils.split('*(a|{b|c,d})'), ['*(a|{b|c,d})']); - assert.deepEqual(utils.split('a,@(b,c)'), ['a', '@(b,c)']); - assert.deepEqual(utils.split('a,*(b|c,d),z'), ['a', '*(b|c,d)', 'z']); - }); - - it('should work with unclosed parens', function() { - assert.deepEqual(utils.split('*(a|{b|c,d}'), ['*(a|{b|c,d}']); - }); - - it('should not split inside nested parens', function() { - assert.deepEqual(utils.split('a,*(b|(c,d)),z'), ['a', '*(b|(c,d))', 'z']); - assert.deepEqual(utils.split('a,*(b,(c,d)),z'), ['a', '*(b,(c,d))', 'z']); - }); - - it('should not split inside brackets', function() { - assert.deepEqual(utils.split('[a-z,"]*'), ['[a-z,"]*']); - }); - - it('should work with unclosed brackets', function() { - assert.deepEqual(utils.split('[a-z,"*'), ['[a-z,"*']); - }); - - it('should not split parens nested inside brackets', function() { - assert.deepEqual(utils.split('[-a(z,")]*'), ['[-a(z,")]*']); - }); - - it('should not split brackets nested inside parens', function() { - assert.deepEqual(utils.split('x,(a,[-a,])*'), ['x', '(a,[-a,])*']); - assert.deepEqual(utils.split('a,(1,[^(x,y)],3),z'), ['a', '(1,[^(x,y)],3)', 'z']); - }); - - it('should support escaped parens', function() { - assert.deepEqual(utils.split('a,@(b,c\\),z)'), ['a', '@(b,c\\),z)']); - }); - - it('should support escaped brackets', function() { - assert.deepEqual(utils.split('a,@([b,c\\],x]|b),z'), ['a', '@([b,c\\],x]|b)', 'z']); - assert.deepEqual(utils.split('a,@([b,c\\],x],b),z'), ['a', '@([b,c\\],x],b)', 'z']); - }); - }); -});