diff --git a/.travis.yml b/.travis.yml index 693c6db..47f9bcc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ +os: + - linux + - osx + - windows language: node_js node_js: - node - '10' - '8' -os: - - linux - - osx - - windows diff --git a/.verb.md b/.verb.md index e21679b..cdedd55 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: @@ -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/README.md b/README.md index b45d311..cba2f60 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) +# 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. @@ -9,70 +9,70 @@ Please consider following this project's author, [Jon Schlinkert](https://github Install with [npm](https://www.npmjs.com/): ```sh -$ npm install braces +$ 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')); +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: @@ -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,7 +278,7 @@ 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})); @@ -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,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) ``` ## About @@ -593,48 +564,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. -
-
+_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on April 08, 2019._ \ No newline at end of file diff --git a/bench/index.js b/bench/index.js index e4b47bf..d1ff170 100644 --- a/bench/index.js +++ b/bench/index.js @@ -50,22 +50,42 @@ bench.skip = name => { return skip; }; -bench('expand - set') - .add(' braces', () => braces.compile('foo/{a,b,c}/bar')) +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 - range') - .add(' braces', () => braces.compile('foo/{a..z}/bar')) - .add('minimatch', () => minimatch.braceExpand('foo/{a..z}/bar')) +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') - .add(' braces', () => braces.compile('foo/{a,b,{x,y,z}}/bar')) +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 ranges') - .add(' braces', () => braces.compile('foo/{a,b,{1..25}}/bar')) - .add('minimatch', () => minimatch.braceExpand('foo/{a,b,{1..25}}/bar')) +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/examples/option-transform.js b/examples/option-transform.js new file mode 100644 index 0000000..c012e9a --- /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, index) { + return 'foo/' + value * 2; + } +}); +console.log(numeric); //=> [ 'foo/2', 'foo/4', 'foo/6', 'foo/8', 'foo/10' ] diff --git a/index.js b/index.js index 9a05d47..0eee0f5 100644 --- a/index.js +++ b/index.js @@ -20,21 +20,25 @@ const parse = require('./lib/parse'); */ const braces = (input, options = {}) => { - let result = []; + let output = []; if (Array.isArray(input)) { - for (let i = 0; i < input.length; i++) { - result.push(...braces.create(input[i], options)); + for (let pattern of input) { + let result = braces.create(pattern, options); + if (Array.isArray(result)) { + output.push(...result); + } else { + output.push(result); + } } } else { - result = braces.create(input, options); + output = [].concat(braces.create(input, options)); } - if (options && options.nodupes === true) { - result = [...new Set(result)]; + if (options && options.expand === true && options.nodupes === true) { + output = [...new Set(output)]; } - - return result; + return output; }; /** @@ -91,7 +95,7 @@ braces.stringify = (input, options = {}) => { braces.compile = (input, options = {}) => { if (typeof input === 'string') { - return compile(braces.parse(input, options), options); + input = braces.parse(input, options); } return compile(input, options); }; @@ -120,10 +124,16 @@ braces.expand = (input, options = {}) => { let result = expand(input, options); + // filter out empty strings if specified + if (options.noempty === true) { + result = result.filter(Boolean); + } + // filter out duplicates if specified if (options.nodupes === true) { result = [...new Set(result)]; } + return result; }; @@ -148,21 +158,9 @@ braces.create = (input, options = {}) => { return [input]; } - let result = options.expand !== true + return options.expand !== true ? braces.compile(input, options) : braces.expand(input, options); - - // filter out empty strings if specified - if (options.noempty === true) { - result = result.filter(Boolean); - } - - // filter out duplicates if specified - if (options.nodupes === true) { - result = [...new Set(result)]; - } - - return result; }; /** diff --git a/package.json b/package.json index aeac709..f25d02a 100644 --- a/package.json +++ b/package.json @@ -28,11 +28,14 @@ "test": "mocha", "benchmark": "node benchmark" }, + "dependencies": { + "fill-range": "^7.0.1" + }, "devDependencies": { "ansi-colors": "^3.2.4", "bash-path": "^2.0.1", "gulp-format-md": "^2.0.0", - "mocha": "^6.0.2" + "mocha": "^6.1.1" }, "keywords": [ "alpha", @@ -69,18 +72,6 @@ }, "plugins": [ "gulp-format-md" - ], - "related": { - "list": [ - "expand-brackets", - "extglob", - "fill-range", - "micromatch", - "nanomatch" - ] - } - }, - "dependencies": { - "fill-range": "^6.0.0" + ] } -} +} \ No newline at end of file diff --git a/test/bash-compiled-ranges.js b/test/bash-compiled-ranges.js index 02ac3bc..857e5ce 100644 --- a/test/bash-compiled-ranges.js +++ b/test/bash-compiled-ranges.js @@ -124,22 +124,22 @@ describe('bash ranges - braces.compile()', () => { ['{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 }, '(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)'], + ['{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 }, '(-(10|8|6|4|2))'], + ['{-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|-(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)'], + ['{-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)'], @@ -150,9 +150,9 @@ describe('bash ranges - braces.compile()', () => { ['{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))'], + ['{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'], diff --git a/test/bash-spec.js b/test/bash-spec.js index 3cb6f6f..a32f42d 100644 --- a/test/bash-spec.js +++ b/test/bash-spec.js @@ -103,7 +103,7 @@ describe('bash', () => { [ '{{a,b},}c', {}, [ 'ac', 'bc', 'c' ] ], [ '{{a,b}.}', {}, [ '{a.}', '{b.}' ] ], [ '{{a,b}}', {}, [ '{a}', '{b}' ] ], - [ 'X{a..#}X', {}, [ 'X{a..#}X' ] ], + [ '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