Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
jonschlinkert committed Apr 8, 2019
1 parent bb5b5ba commit 68a3fdf
Show file tree
Hide file tree
Showing 10 changed files with 520 additions and 380 deletions.
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
os:
- linux
- osx
- windows
language: node_js
node_js:
- node
- '10'
- '8'
os:
- linux
- osx
- windows
232 changes: 104 additions & 128 deletions .verb.md
Original file line number Diff line number Diff line change
@@ -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'));
Expand All @@ -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:

Expand Down Expand Up @@ -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**

Expand All @@ -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`
Expand All @@ -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}));
Expand Down Expand Up @@ -482,83 +491,50 @@ 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.

[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
Loading

0 comments on commit 68a3fdf

Please sign in to comment.