This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2018, 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+640
View File
@@ -0,0 +1,640 @@
# 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)
> 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.
Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support.
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save braces
```
## 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)_.
* **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).
## Usage
The main export is a function that takes one or more brace `patterns` and `options`.
```js
var braces = require('braces');
braces(pattern[, options]);
```
By default, braces returns an optimized regex-source string. To get an array of brace patterns, use `brace.expand()`.
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)_.
### Optimized vs. expanded braces
**Optimized**
By default, patterns are optimized for regex and matching:
```js
console.log(braces('a/{x,y,z}/b'));
//=> ['a/(x|y|z)/b']
```
**Expanded**
To expand patterns the same way as Bash or [minimatch](https://github.com/isaacs/minimatch), use the [.expand](#expand) method:
```js
console.log(braces.expand('a/{x,y,z}/b'));
//=> ['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']
```
## 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:
```js
console.log(braces('a/{foo,bar,baz}/*.js'));
//=> ['a/(foo|bar|baz)/*.js']
console.log(braces.expand('a/{foo,bar,baz}/*.js'));
//=> ['a/foo/*.js', 'a/bar/*.js', 'a/baz/*.js']
```
### Sequences
Uses [fill-range](https://github.com/jonschlinkert/fill-range) for expanding alphabetical or numeric ranges (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']
// 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' ]
```
### Steps
Steps, or increments, may be used with ranges:
```js
console.log(braces.expand('{2..10..2}'));
//=> ['2', '4', '6', '8', '10']
console.log(braces('{2..10..2}'));
//=> ['(2|4|6|8|10)']
```
When the [.optimize](#optimize) method is used, or [options.optimize](#optionsoptimize) is set to true, sequences are passed to [to-regex-range](https://github.com/jonschlinkert/to-regex-range) for expansion.
### Nesting
Brace patterns may be nested. The results of each expanded string are not sorted, and left to right order is preserved.
**"Expanded" braces**
```js
console.log(braces.expand('a{b,c,/{x,y}}/e'));
//=> ['ab/e', 'ac/e', 'a/x/e', 'a/y/e']
console.log(braces.expand('a/{x,{1..5},y}/c'));
//=> ['a/x/c', 'a/1/c', 'a/2/c', 'a/3/c', 'a/4/c', 'a/5/c', 'a/y/c']
```
**"Optimized" braces**
```js
console.log(braces('a{b,c,/{x,y}}/e'));
//=> ['a(b|c|/(x|y))/e']
console.log(braces('a/{x,{1..5},y}/c'));
//=> ['a/(x|([1-5])|y)/c']
```
### Escaping
**Escaping braces**
A brace pattern will not be expanded or evaluted if _either the opening or closing brace is escaped_:
```js
console.log(braces.expand('a\\{d,c,b}e'));
//=> ['a{d,c,b}e']
console.log(braces.expand('a{d,c,b\\}e'));
//=> ['a{d,c,b}e']
```
**Escaping commas**
Commas inside braces may also be escaped:
```js
console.log(braces.expand('a{b\\,c}d'));
//=> ['a{b,c}d']
console.log(braces.expand('a{d\\,c,b}e'));
//=> ['ad,ce', 'abe']
```
**Single items**
Following bash conventions, a brace pattern is also not expanded when it contains a single character:
```js
console.log(braces.expand('a{b}c'));
//=> ['a{b}c']
```
## Options
### options.maxLength
**Type**: `Number`
**Default**: `65,536`
**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
```
### options.expand
**Type**: `Boolean`
**Default**: `undefined`
**Description**: Generate an "expanded" brace pattern (this option is unncessary with the `.expand` method, which does the same thing).
```js
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`
**Description**: Duplicates are removed by default. To keep duplicates, pass `{nodupes: false}` on the options
### options.rangeLimit
**Type**: `Number`
**Default**: `250`
**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.
You can set this to any number, or change `options.rangeLimit` to `Inifinity` to disable this altogether.
**Examples**
```js
// pattern exceeds the "rangeLimit", so it's optimized automatically
console.log(braces.expand('{1..1000}'));
//=> ['([1-9]|[1-9][0-9]{1,2}|1000)']
// pattern does not exceed "rangeLimit", so it's NOT optimized
console.log(braces.expand('{1..100}'));
//=> ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '100']
```
### options.transform
**Type**: `Function`
**Default**: `undefined`
**Description**: Customize range expansion.
```js
var range = braces.expand('x{a..e}y', {
transform: function(str) {
return 'foo' + str;
}
});
console.log(range);
//=> [ 'xfooay', 'xfooby', 'xfoocy', 'xfoody', 'xfooey' ]
```
### options.quantifiers
**Type**: `Boolean`
**Default**: `undefined`
**Description**: In regular expressions, quanitifiers can be used to specify how many times a token can be repeated. For example, `a{1,3}` will match the letter `a` one to three times.
Unfortunately, regex quantifiers happen to share the same syntax as [Bash lists](#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');
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}));
//=> [ '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' ]
```
### options.unescape
**Type**: `Boolean`
**Default**: `undefined`
**Description**: Strip backslashes that were used for escaping from the result.
## What is "brace expansion"?
Brace expansion is a type of parameter expansion that was made popular by unix shells for generating lists of strings, as well as regex-like matching when used alongside wildcards (globs).
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
<details>
<summary><strong>More about brace expansion</strong> (click to expand)</summary>
There are two main types of brace expansion:
1. **lists**: which are defined using comma-separated values inside curly braces: `{a,b,c}`
2. **sequences**: which are defined using a starting value and an ending value, separated by two dots: `a{1..3}b`. Optionally, a third argument may be passed to define a "step" or increment to use: `a{1..100..10}b`. These are also sometimes referred to as "ranges".
Here are some example brace patterns to illustrate how they work:
**Sets**
```
{a,b,c} => a b c
{a,b,c}{1,2} => a1 a2 b1 b2 c1 c2
```
**Sequences**
```
{1..9} => 1 2 3 4 5 6 7 8 9
{4..-4} => 4 3 2 1 0 -1 -2 -3 -4
{1..20..3} => 1 4 7 10 13 16 19
{a..j} => a b c d e f g h i j
{j..a} => j i h g f e d c b a
{a..z..3} => a d g j m p s v y
```
**Combination**
Sets and sequences can be mixed together or used along with any other strings.
```
{a,b,c}{1..3} => a1 a2 a3 b1 b2 b3 c1 c2 c3
foo/{a,b,c}/bar => foo/a/bar foo/b/bar foo/c/bar
```
The fact that braces can be "expanded" from relatively simple patterns makes them ideal for quickly generating test fixtures, file paths, and similar use cases.
## Brace matching
In addition to _expansion_, brace patterns are also useful for performing regular-expression-like matching.
For example, the pattern `foo/{1..3}/bar` would match any of following strings:
```
foo/1/bar
foo/2/bar
foo/3/bar
```
But not:
```
baz/1/qux
baz/2/qux
baz/3/qux
```
Braces can also be combined with [glob patterns](https://github.com/jonschlinkert/micromatch) to perform more advanced wildcard matching. For example, the pattern `*/{1..3}/*` would match any of following strings:
```
foo/1/bar
foo/2/bar
foo/3/bar
baz/1/qux
baz/2/qux
baz/3/qux
```
## Brace matching pitfalls
Although brace patterns offer a user-friendly way of matching ranges or sets of strings, there are also some major disadvantages and potential risks you should be aware of.
### tldr
**"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!)
For a more detailed explanation with examples, see the [geometric complexity](#geometric-complexity) section.
### The solution
Jump to the [performance section](#performance) to see how Braces solves this problem in comparison to other libraries.
### Geometric complexity
At minimum, brace patterns with sets limited to two elements have quadradic or `O(n^2)` complexity. But the complexity of the algorithm increases exponentially as the number of sets, _and elements per set_, increases, which is `O(n^c)`.
For example, the following sets demonstrate quadratic (`O(n^2)`) complexity:
```
{1,2}{3,4} => (2X2) => 13 14 23 24
{1,2}{3,4}{5,6} => (2X2X2) => 135 136 145 146 235 236 245 246
```
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
358 359 367 368 369
```
Now, imagine how this complexity grows given that each element is a n-tuple:
```
{1..100}{1..100} => (100X100) => 10,000 elements (38.4 kB)
{1..100}{1..100}{1..100} => (100X100X100) => 1,000,000 elements (5.76 MB)
```
Although these examples are clearly contrived, they demonstrate how brace patterns can quickly grow out of control.
**More information**
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)
</details>
## Performance
Braces is not only screaming fast, it's also more accurate the other brace expansion libraries.
### Better algorithms
Fortunately there is a solution to the ["brace bomb" problem](#brace-matching-pitfalls): _don't expand brace patterns into an array when they're used for matching_.
Instead, convert the pattern into an optimized regular expression. This is easier said than done, and braces is the only library that does this currently.
**The proof is in the numbers**
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}`<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> | `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
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)** |
| --- | --- | --- |
| `{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) |
If you'd like to run these comparisons yourself, see [test/support/generate.js](test/support/generate.js).
## Benchmarks
### Running benchmarks
Install dev dependencies:
```bash
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
# 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
```
## About
<details>
<summary><strong>Contributing</strong></summary>
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
</details>
<details>
<summary><strong>Running Tests</strong></summary>
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
```sh
$ npm install && npm test
```
</details>
<details>
<summary><strong>Building docs</strong></summary>
_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
To generate the readme, run the following command:
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
</details>
### 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) |
### Author
**Jon Schlinkert**
* [linkedin/in/jonschlinkert](https://linkedin.com/in/jonschlinkert)
* [github/jonschlinkert](https://github.com/jonschlinkert)
* [twitter/jonschlinkert](https://twitter.com/jonschlinkert)
### License
Copyright © 2018, [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._
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item">this is the largest safe integer allowed in JavaScript. <a href="#fnref1" class="footnote-backref">↩</a>
</li>
</ol>
</section>
+318
View File
@@ -0,0 +1,318 @@
'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 = {};
/**
* 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)).
*
* ```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']
* ```
* @param {String} `str`
* @param {Object} `options`
* @return {String}
* @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));
}
} else {
arr = braces.create(pattern, options);
}
if (options && options.nodupes === true) {
arr = unique(arr);
}
if (!disabled) {
cache[key] = arr;
}
return arr;
}
/**
* 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.
*
* ```js
* var braces = require('braces');
* console.log(braces.expand('a/{b,c}/d'));
* //=> ['a/b/d', 'a/c/d'];
* ```
* @param {String} `pattern` Brace pattern
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.expand = function(pattern, options) {
return braces.create(pattern, extend({}, options, {expand: true}));
};
/**
* Expands 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.expand('a/{b,c}/d'));
* //=> ['a/(b|c)/d']
* ```
* @param {String} `pattern` Brace pattern
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.optimize = function(pattern, options) {
return braces.create(pattern, 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');
* 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 {String} `pattern` Brace pattern
* @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');
}
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);
};
/**
* Create a regular expression from the given string `pattern`.
*
* ```js
* var braces = require('braces');
*
* console.log(braces.makeRe('id-{200..300}'));
* //=> /^(?:id-(20[0-9]|2[1-9][0-9]|300))$/
* ```
* @param {String} `pattern` The pattern to convert to regex.
* @param {Object} `options`
* @return {RegExp}
* @api public
*/
braces.makeRe = 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');
}
function makeRe() {
var arr = braces(pattern, options);
var opts = extend({strictErrors: false}, options);
return toRegex(arr, opts);
}
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
*/
braces.parse = function(pattern, options) {
var proto = new Braces(options);
return proto.parse(pattern, options);
};
/**
* Compile the given `ast` or string with the given `options`.
*
* ```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: [] }
* ```
* @param {Object|String} `ast` AST from [.parse](#parse). If a string is passed it will be parsed first.
* @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();
* ```
* @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];
}
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;
/**
* Expose `braces`
* @type {Function}
*/
module.exports = braces;
+104
View File
@@ -0,0 +1,104 @@
'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;
+282
View File
@@ -0,0 +1,282 @@
'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;
}
+360
View File
@@ -0,0 +1,360 @@
'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);
}
+343
View File
@@ -0,0 +1,343 @@
'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
});
};
/**
* Returns true if the given string contains only empty brace sets.
*/
utils.isEmptySets = function(str) {
return /^(?:\{,\})+$/.test(str);
};
/**
* Returns true if the given string contains only empty brace sets.
*/
utils.isQuotedString = function(str) {
var open = str.charAt(0);
if (open === '\'' || open === '"' || open === '`') {
return str.slice(-1) === open;
}
return false;
};
/**
* 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.
*/
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]);
}
return id;
};
/**
* Normalize options
*/
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;
}
if (opts.optimize === true) {
opts.makeRe = true;
}
return opts;
};
/**
* Join patterns in `a` to patterns in `b`
*/
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);
}
}
}
return arr;
};
/**
* Split the given string on `,` if not escaped.
*/
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;
}
return splitString(str, opts, utils.escapeBrackets(opts));
};
/**
* Expand ranges or sets in the given `pattern`.
*
* @param {String} `str`
* @param {Object} `options`
* @return {Object}
*/
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;
};
/**
* Ensure commas inside brackets and parens are not split.
* @param {Object} `tok` Token from the `split-string` module
* @return {undefined}
*/
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;
while (++i < str.length) {
var ch = str[i];
if (ch === '\\') {
val += (opts.keepEscaping === false ? '' : ch) + str[++i];
continue;
}
if (ch === '(') {
parens.push(ch);
stack.push(ch);
}
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;
};
};
/**
* 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');
};
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2015, 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,61 @@
# extend-shallow [![NPM version](https://badge.fury.io/js/extend-shallow.svg)](http://badge.fury.io/js/extend-shallow) [![Build Status](https://travis-ci.org/jonschlinkert/extend-shallow.svg)](https://travis-ci.org/jonschlinkert/extend-shallow)
> Extend an object with the properties of additional objects. node.js/javascript util.
## Install
Install with [npm](https://www.npmjs.com/)
```sh
$ npm i extend-shallow --save
```
## Usage
```js
var extend = require('extend-shallow');
extend({a: 'b'}, {c: 'd'})
//=> {a: 'b', c: 'd'}
```
Pass an empty object to shallow clone:
```js
var obj = {};
extend(obj, {a: 'b'}, {c: 'd'})
//=> {a: 'b', c: 'd'}
```
## Related
* [extend-shallow](https://github.com/jonschlinkert/extend-shallow): Extend an object with the properties of additional objects. node.js/javascript util.
* [for-own](https://github.com/jonschlinkert/for-own): Iterate over the own enumerable properties of an object, and return an object with properties… [more](https://github.com/jonschlinkert/for-own)
* [for-in](https://github.com/jonschlinkert/for-in): Iterate over the own and inherited enumerable properties of an objecte, and return an object… [more](https://github.com/jonschlinkert/for-in)
* [is-plain-object](https://github.com/jonschlinkert/is-plain-object): Returns true if an object was created by the `Object` constructor.
* [isobject](https://github.com/jonschlinkert/isobject): Returns true if the value is an object and not an array or null.
* [kind-of](https://github.com/jonschlinkert/kind-of): Get the native type of a value.
## Running tests
Install dev dependencies:
```sh
$ npm i -d && npm test
```
## Author
**Jon Schlinkert**
+ [github/jonschlinkert](https://github.com/jonschlinkert)
+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert)
## License
Copyright © 2015 Jon Schlinkert
Released under the MIT license.
***
_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on June 29, 2015._
@@ -0,0 +1,33 @@
'use strict';
var isObject = require('is-extendable');
module.exports = function extend(o/*, objects*/) {
if (!isObject(o)) { o = {}; }
var len = arguments.length;
for (var i = 1; i < len; i++) {
var obj = arguments[i];
if (isObject(obj)) {
assign(o, obj);
}
}
return o;
};
function assign(a, b) {
for (var key in b) {
if (hasOwn(b, key)) {
a[key] = b[key];
}
}
}
/**
* Returns true if the given `key` is an own property of `obj`.
*/
function hasOwn(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
@@ -0,0 +1,56 @@
{
"name": "extend-shallow",
"description": "Extend an object with the properties of additional objects. node.js/javascript util.",
"version": "2.0.1",
"homepage": "https://github.com/jonschlinkert/extend-shallow",
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"repository": "jonschlinkert/extend-shallow",
"bugs": {
"url": "https://github.com/jonschlinkert/extend-shallow/issues"
},
"license": "MIT",
"files": [
"index.js"
],
"main": "index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"test": "mocha"
},
"dependencies": {
"is-extendable": "^0.1.0"
},
"devDependencies": {
"array-slice": "^0.2.3",
"benchmarked": "^0.1.4",
"chalk": "^1.0.0",
"for-own": "^0.1.3",
"glob": "^5.0.12",
"is-plain-object": "^2.0.1",
"kind-of": "^2.0.0",
"minimist": "^1.1.1",
"mocha": "^2.2.5",
"should": "^7.0.1"
},
"keywords": [
"assign",
"extend",
"javascript",
"js",
"keys",
"merge",
"obj",
"object",
"prop",
"properties",
"property",
"props",
"shallow",
"util",
"utility",
"utils",
"value"
]
}
+108
View File
@@ -0,0 +1,108 @@
{
"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.2",
"homepage": "https://github.com/micromatch/braces",
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"contributors": [
"Brian Woodward (https://twitter.com/doowb)",
"Elan Shanker (https://github.com/es128)",
"Eugene Sharygin (https://github.com/eush77)",
"hemanth.hm (http://h3manth.com)",
"Jon Schlinkert (http://twitter.com/jonschlinkert)"
],
"repository": "micromatch/braces",
"bugs": {
"url": "https://github.com/micromatch/braces/issues"
},
"license": "MIT",
"files": [
"index.js",
"lib"
],
"main": "index.js",
"engines": {
"node": ">=0.10.0"
},
"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"
},
"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"
},
"keywords": [
"alpha",
"alphabetical",
"bash",
"brace",
"braces",
"expand",
"expansion",
"filepath",
"fill",
"fs",
"glob",
"globbing",
"letter",
"match",
"matches",
"matching",
"number",
"numerical",
"path",
"range",
"ranges",
"sh"
],
"verb": {
"toc": false,
"layout": "default",
"tasks": [
"readme"
],
"lint": {
"reflinks": true
},
"plugins": [
"gulp-format-md"
],
"related": {
"list": [
"expand-brackets",
"extglob",
"fill-range",
"micromatch",
"nanomatch"
]
}
}
}
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2017, 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+250
View File
@@ -0,0 +1,250 @@
# fill-range [![NPM version](https://img.shields.io/npm/v/fill-range.svg?style=flat)](https://www.npmjs.com/package/fill-range) [![NPM monthly downloads](https://img.shields.io/npm/dm/fill-range.svg?style=flat)](https://npmjs.org/package/fill-range) [![NPM total downloads](https://img.shields.io/npm/dt/fill-range.svg?style=flat)](https://npmjs.org/package/fill-range) [![Linux Build Status](https://img.shields.io/travis/jonschlinkert/fill-range.svg?style=flat&label=Travis)](https://travis-ci.org/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`
## Table of Contents
- [Install](#install)
- [Usage](#usage)
- [Examples](#examples)
- [Options](#options)
* [options.step](#optionsstep)
* [options.strictRanges](#optionsstrictranges)
* [options.stringify](#optionsstringify)
* [options.toRegex](#optionstoregex)
* [options.transform](#optionstransform)
- [About](#about)
_(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save fill-range
```
Install with [yarn](https://yarnpkg.com):
```sh
$ yarn add fill-range
```
## Usage
Expands numbers and letters, optionally using a `step` as the last argument. _(Numbers may be defined as JavaScript numbers or strings)_.
```js
var fill = require('fill-range');
fill(from, to[, step, options]);
// examples
console.log(fill('1', '10')); //=> '[ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ]'
console.log(fill('1', '10', {toRegex: true})); //=> [1-9]|10
```
**Params**
* `from`: **{String|Number}** the number or letter to start with
* `to`: **{String|Number}** the number or letter to end with
* `step`: **{String|Number|Object|Function}** Optionally pass a [step](#optionsstep) to use.
* `options`: **{Object|Function}**: See all available [options](#options)
## Examples
By default, an array of values is returned.
**Alphabetical ranges**
```js
console.log(fill('a', 'e')); //=> ['a', 'b', 'c', 'd', 'e']
console.log(fill('A', 'E')); //=> [ 'A', 'B', 'C', 'D', 'E' ]
```
**Numerical ranges**
Numbers can be defined as actual numbers or strings.
```js
console.log(fill(1, 5)); //=> [ 1, 2, 3, 4, 5 ]
console.log(fill('1', '5')); //=> [ 1, 2, 3, 4, 5 ]
```
**Negative ranges**
Numbers can be defined as actual numbers or strings.
```js
console.log(fill('-5', '-1')); //=> [ '-5', '-4', '-3', '-2', '-1' ]
console.log(fill('-5', '5')); //=> [ '-5', '-4', '-3', '-2', '-1', '0', '1', '2', '3', '4', '5' ]
```
**Steps (increments)**
```js
// numerical ranges with increments
console.log(fill('0', '25', 4)); //=> [ '0', '4', '8', '12', '16', '20', '24' ]
console.log(fill('0', '25', 5)); //=> [ '0', '5', '10', '15', '20', '25' ]
console.log(fill('0', '25', 6)); //=> [ '0', '6', '12', '18', '24' ]
// alphabetical ranges with increments
console.log(fill('a', 'z', 4)); //=> [ 'a', 'e', 'i', 'm', 'q', 'u', 'y' ]
console.log(fill('a', 'z', 5)); //=> [ 'a', 'f', 'k', 'p', 'u', 'z' ]
console.log(fill('a', 'z', 6)); //=> [ 'a', 'g', 'm', 's', 'y' ]
```
## Options
### options.step
**Type**: `number` (formatted as a string or number)
**Default**: `undefined`
**Description**: The increment to use for the range. Can be used with letters or numbers.
**Example(s)**
```js
// numbers
console.log(fill('1', '10', 2)); //=> [ '1', '3', '5', '7', '9' ]
console.log(fill('1', '10', 3)); //=> [ '1', '4', '7', '10' ]
console.log(fill('1', '10', 4)); //=> [ '1', '5', '9' ]
// letters
console.log(fill('a', 'z', 5)); //=> [ 'a', 'f', 'k', 'p', 'u', 'z' ]
console.log(fill('a', 'z', 7)); //=> [ 'a', 'h', 'o', 'v' ]
console.log(fill('a', 'z', 9)); //=> [ 'a', 'j', 's' ]
```
### options.strictRanges
**Type**: `boolean`
**Default**: `false`
**Description**: By default, `null` is returned when an invalid range is passed. Enable this option to throw a `RangeError` on invalid ranges.
**Example(s)**
The following are all invalid:
```js
fill('1.1', '2'); // decimals not supported in ranges
fill('a', '2'); // incompatible range values
fill(1, 10, 'foo'); // invalid "step" argument
```
### options.stringify
**Type**: `boolean`
**Default**: `undefined`
**Description**: Cast all returned values to strings. By default, integers are returned as numbers.
**Example(s)**
```js
console.log(fill(1, 5)); //=> [ 1, 2, 3, 4, 5 ]
console.log(fill(1, 5, {stringify: true})); //=> [ '1', '2', '3', '4', '5' ]
```
### options.toRegex
**Type**: `boolean`
**Default**: `undefined`
**Description**: Create a regex-compatible source string, instead of expanding values to an array.
**Example(s)**
```js
// alphabetical range
console.log(fill('a', 'e', {toRegex: true})); //=> '[a-e]'
// alphabetical with step
console.log(fill('a', 'z', 3, {toRegex: true})); //=> 'a|d|g|j|m|p|s|v|y'
// numerical range
console.log(fill('1', '100', {toRegex: true})); //=> '[1-9]|[1-9][0-9]|100'
// numerical range with zero padding
console.log(fill('000001', '100000', {toRegex: true}));
//=> '0{5}[1-9]|0{4}[1-9][0-9]|0{3}[1-9][0-9]{2}|0{2}[1-9][0-9]{3}|0[1-9][0-9]{4}|100000'
```
### options.transform
**Type**: `function`
**Default**: `undefined`
**Description**: Customize each value in the returned array (or [string](#optionstoRegex)). _(you can also pass this function as the last argument to `fill()`)_.
**Example(s)**
```js
// increase padding by two
var arr = fill('01', '05', function(val, a, b, step, idx, arr, options) {
return repeat('0', (options.maxLength + 2) - val.length) + val;
});
console.log(arr);
//=> ['0001', '0002', '0003', '0004', '0005']
```
## About
### Related projects
* [braces](https://www.npmjs.com/package/braces): Fast, comprehensive, bash-like brace expansion implemented in JavaScript. Complete support for the Bash 4.3 braces… [more](https://github.com/jonschlinkert/braces) | [homepage](https://github.com/jonschlinkert/braces "Fast, comprehensive, bash-like brace expansion implemented in JavaScript. Complete support for the Bash 4.3 braces specification, without sacrificing speed.")
* [expand-range](https://www.npmjs.com/package/expand-range): Fast, bash-like range expansion. Expand a range of numbers or letters, uppercase or lowercase. See… [more](https://github.com/jonschlinkert/expand-range) | [homepage](https://github.com/jonschlinkert/expand-range "Fast, bash-like range expansion. Expand a range of numbers or letters, uppercase or lowercase. See the benchmarks. Used by micromatch.")
* [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/jonschlinkert/micromatch "Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch.")
* [to-regex-range](https://www.npmjs.com/package/to-regex-range): Pass two numbers, get a regex-compatible source string for matching ranges. Validated against more than… [more](https://github.com/jonschlinkert/to-regex-range) | [homepage](https://github.com/jonschlinkert/to-regex-range "Pass two numbers, get a regex-compatible source string for matching ranges. Validated against more than 2.87 million test assertions.")
### Contributing
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
### Contributors
| **Commits** | **Contributor** |
| --- | --- |
| 103 | [jonschlinkert](https://github.com/jonschlinkert) |
| 2 | [paulmillr](https://github.com/paulmillr) |
| 1 | [edorivai](https://github.com/edorivai) |
| 1 | [wtgtybhertgeghgtwtg](https://github.com/wtgtybhertgeghgtwtg) |
### Building docs
_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
To generate the readme, run the following command:
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
### Running tests
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
```sh
$ npm install && npm test
```
### Author
**Jon Schlinkert**
* [github/jonschlinkert](https://github.com/jonschlinkert)
* [twitter/jonschlinkert](https://twitter.com/jonschlinkert)
### License
Copyright © 2017, [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.5.0, on April 23, 2017._
+208
View File
@@ -0,0 +1,208 @@
/*!
* fill-range <https://github.com/jonschlinkert/fill-range>
*
* Copyright (c) 2014-2015, 2017, Jon Schlinkert.
* Released under the MIT License.
*/
'use strict';
var util = require('util');
var isNumber = require('is-number');
var extend = require('extend-shallow');
var repeat = require('repeat-string');
var toRegex = require('to-regex-range');
/**
* Return a range of numbers or letters.
*
* @param {String} `start` Start of the range
* @param {String} `stop` End of the range
* @param {String} `step` Increment or decrement to use.
* @param {Function} `fn` Custom function to modify each element in the range.
* @return {Array}
*/
function fillRange(start, stop, step, options) {
if (typeof start === 'undefined') {
return [];
}
if (typeof stop === 'undefined' || start === stop) {
// special case, for handling negative zero
var isString = typeof start === 'string';
if (isNumber(start) && !toNumber(start)) {
return [isString ? '0' : 0];
}
return [start];
}
if (typeof step !== 'number' && typeof step !== 'string') {
options = step;
step = undefined;
}
if (typeof options === 'function') {
options = { transform: options };
}
var opts = extend({step: step}, options);
if (opts.step && !isValidNumber(opts.step)) {
if (opts.strictRanges === true) {
throw new TypeError('expected options.step to be a number');
}
return [];
}
opts.isNumber = isValidNumber(start) && isValidNumber(stop);
if (!opts.isNumber && !isValid(start, stop)) {
if (opts.strictRanges === true) {
throw new RangeError('invalid range arguments: ' + util.inspect([start, stop]));
}
return [];
}
opts.isPadded = isPadded(start) || isPadded(stop);
opts.toString = opts.stringify
|| typeof opts.step === 'string'
|| typeof start === 'string'
|| typeof stop === 'string'
|| !opts.isNumber;
if (opts.isPadded) {
opts.maxLength = Math.max(String(start).length, String(stop).length);
}
// support legacy minimatch/fill-range options
if (typeof opts.optimize === 'boolean') opts.toRegex = opts.optimize;
if (typeof opts.makeRe === 'boolean') opts.toRegex = opts.makeRe;
return expand(start, stop, opts);
}
function expand(start, stop, options) {
var a = options.isNumber ? toNumber(start) : start.charCodeAt(0);
var b = options.isNumber ? toNumber(stop) : stop.charCodeAt(0);
var step = Math.abs(toNumber(options.step)) || 1;
if (options.toRegex && step === 1) {
return toRange(a, b, start, stop, options);
}
var zero = {greater: [], lesser: []};
var asc = a < b;
var arr = new Array(Math.round((asc ? b - a : a - b) / step));
var idx = 0;
while (asc ? a <= b : a >= b) {
var val = options.isNumber ? a : String.fromCharCode(a);
if (options.toRegex && (val >= 0 || !options.isNumber)) {
zero.greater.push(val);
} else {
zero.lesser.push(Math.abs(val));
}
if (options.isPadded) {
val = zeros(val, options);
}
if (options.toString) {
val = String(val);
}
if (typeof options.transform === 'function') {
arr[idx++] = options.transform(val, a, b, step, idx, arr, options);
} else {
arr[idx++] = val;
}
if (asc) {
a += step;
} else {
a -= step;
}
}
if (options.toRegex === true) {
return toSequence(arr, zero, options);
}
return arr;
}
function toRange(a, b, start, stop, options) {
if (options.isPadded) {
return toRegex(start, stop, options);
}
if (options.isNumber) {
return toRegex(Math.min(a, b), Math.max(a, b), options);
}
var start = String.fromCharCode(Math.min(a, b));
var stop = String.fromCharCode(Math.max(a, b));
return '[' + start + '-' + stop + ']';
}
function toSequence(arr, zeros, options) {
var greater = '', lesser = '';
if (zeros.greater.length) {
greater = zeros.greater.join('|');
}
if (zeros.lesser.length) {
lesser = '-(' + zeros.lesser.join('|') + ')';
}
var res = greater && lesser
? greater + '|' + lesser
: greater || lesser;
if (options.capture) {
return '(' + res + ')';
}
return res;
}
function zeros(val, options) {
if (options.isPadded) {
var str = String(val);
var len = str.length;
var dash = '';
if (str.charAt(0) === '-') {
dash = '-';
str = str.slice(1);
}
var diff = options.maxLength - len;
var pad = repeat('0', diff);
val = (dash + pad + str);
}
if (options.stringify) {
return String(val);
}
return val;
}
function toNumber(val) {
return Number(val) || 0;
}
function isPadded(str) {
return /^-?0\d/.test(str);
}
function isValid(min, max) {
return (isValidNumber(min) || isValidLetter(min))
&& (isValidNumber(max) || isValidLetter(max));
}
function isValidLetter(ch) {
return typeof ch === 'string' && ch.length === 1 && /^\w+$/.test(ch);
}
function isValidNumber(n) {
return isNumber(n) && !/\./.test(n);
}
/**
* Expose `fillRange`
* @type {Function}
*/
module.exports = fillRange;
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2015, 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,61 @@
# extend-shallow [![NPM version](https://badge.fury.io/js/extend-shallow.svg)](http://badge.fury.io/js/extend-shallow) [![Build Status](https://travis-ci.org/jonschlinkert/extend-shallow.svg)](https://travis-ci.org/jonschlinkert/extend-shallow)
> Extend an object with the properties of additional objects. node.js/javascript util.
## Install
Install with [npm](https://www.npmjs.com/)
```sh
$ npm i extend-shallow --save
```
## Usage
```js
var extend = require('extend-shallow');
extend({a: 'b'}, {c: 'd'})
//=> {a: 'b', c: 'd'}
```
Pass an empty object to shallow clone:
```js
var obj = {};
extend(obj, {a: 'b'}, {c: 'd'})
//=> {a: 'b', c: 'd'}
```
## Related
* [extend-shallow](https://github.com/jonschlinkert/extend-shallow): Extend an object with the properties of additional objects. node.js/javascript util.
* [for-own](https://github.com/jonschlinkert/for-own): Iterate over the own enumerable properties of an object, and return an object with properties… [more](https://github.com/jonschlinkert/for-own)
* [for-in](https://github.com/jonschlinkert/for-in): Iterate over the own and inherited enumerable properties of an objecte, and return an object… [more](https://github.com/jonschlinkert/for-in)
* [is-plain-object](https://github.com/jonschlinkert/is-plain-object): Returns true if an object was created by the `Object` constructor.
* [isobject](https://github.com/jonschlinkert/isobject): Returns true if the value is an object and not an array or null.
* [kind-of](https://github.com/jonschlinkert/kind-of): Get the native type of a value.
## Running tests
Install dev dependencies:
```sh
$ npm i -d && npm test
```
## Author
**Jon Schlinkert**
+ [github/jonschlinkert](https://github.com/jonschlinkert)
+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert)
## License
Copyright © 2015 Jon Schlinkert
Released under the MIT license.
***
_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on June 29, 2015._
@@ -0,0 +1,33 @@
'use strict';
var isObject = require('is-extendable');
module.exports = function extend(o/*, objects*/) {
if (!isObject(o)) { o = {}; }
var len = arguments.length;
for (var i = 1; i < len; i++) {
var obj = arguments[i];
if (isObject(obj)) {
assign(o, obj);
}
}
return o;
};
function assign(a, b) {
for (var key in b) {
if (hasOwn(b, key)) {
a[key] = b[key];
}
}
}
/**
* Returns true if the given `key` is an own property of `obj`.
*/
function hasOwn(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
@@ -0,0 +1,56 @@
{
"name": "extend-shallow",
"description": "Extend an object with the properties of additional objects. node.js/javascript util.",
"version": "2.0.1",
"homepage": "https://github.com/jonschlinkert/extend-shallow",
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"repository": "jonschlinkert/extend-shallow",
"bugs": {
"url": "https://github.com/jonschlinkert/extend-shallow/issues"
},
"license": "MIT",
"files": [
"index.js"
],
"main": "index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"test": "mocha"
},
"dependencies": {
"is-extendable": "^0.1.0"
},
"devDependencies": {
"array-slice": "^0.2.3",
"benchmarked": "^0.1.4",
"chalk": "^1.0.0",
"for-own": "^0.1.3",
"glob": "^5.0.12",
"is-plain-object": "^2.0.1",
"kind-of": "^2.0.0",
"minimist": "^1.1.1",
"mocha": "^2.2.5",
"should": "^7.0.1"
},
"keywords": [
"assign",
"extend",
"javascript",
"js",
"keys",
"merge",
"obj",
"object",
"prop",
"properties",
"property",
"props",
"shallow",
"util",
"utility",
"utils",
"value"
]
}
+82
View File
@@ -0,0 +1,82 @@
{
"name": "fill-range",
"description": "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`",
"version": "4.0.0",
"homepage": "https://github.com/jonschlinkert/fill-range",
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"contributors": [
"<wtgtybhertgeghgtwtg@gmail.com> (https://github.com/wtgtybhertgeghgtwtg)",
"Edo Rivai <edo.rivai@gmail.com> (edo.rivai.nl)",
"Jon Schlinkert <jon.schlinkert@sellside.com> (http://twitter.com/jonschlinkert)",
"Paul Miller <paul+gh@paulmillr.com> (paulmillr.com)"
],
"repository": "jonschlinkert/fill-range",
"bugs": {
"url": "https://github.com/jonschlinkert/fill-range/issues"
},
"license": "MIT",
"files": [
"index.js"
],
"main": "index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"test": "mocha"
},
"dependencies": {
"extend-shallow": "^2.0.1",
"is-number": "^3.0.0",
"repeat-string": "^1.6.1",
"to-regex-range": "^2.1.0"
},
"devDependencies": {
"ansi-cyan": "^0.1.1",
"benchmarked": "^1.0.0",
"gulp-format-md": "^0.1.12",
"minimist": "^1.2.0",
"mocha": "^3.2.0"
},
"keywords": [
"alpha",
"alphabetical",
"array",
"bash",
"brace",
"expand",
"expansion",
"fill",
"glob",
"match",
"matches",
"matching",
"number",
"numerical",
"range",
"ranges",
"regex",
"sh"
],
"verb": {
"related": {
"list": [
"braces",
"expand-range",
"micromatch",
"to-regex-range"
]
},
"toc": true,
"layout": "default",
"tasks": [
"readme"
],
"plugins": [
"gulp-format-md"
],
"lint": {
"reflinks": true
}
}
}
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015, 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+72
View File
@@ -0,0 +1,72 @@
# is-extendable [![NPM version](https://badge.fury.io/js/is-extendable.svg)](http://badge.fury.io/js/is-extendable)
> Returns true if a value is any of the object types: array, regexp, plain object, function or date. This is useful for determining if a value can be extended, e.g. "can the value have keys?"
## Install
Install with [npm](https://www.npmjs.com/)
```sh
$ npm i is-extendable --save
```
## Usage
```js
var isExtendable = require('is-extendable');
```
Returns true if the value is any of the following:
* `array`
* `regexp`
* `plain object`
* `function`
* `date`
* `error`
## Notes
All objects in JavaScript can have keys, but it's a pain to check for this, since we ether need to verify that the value is not `null` or `undefined` and:
* the value is not a primitive, or
* that the object is an `object`, `function`
Also note that an `extendable` object is not the same as an [extensible object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible), which is one that (in es6) is not sealed, frozen, or marked as non-extensible using `preventExtensions`.
## Related projects
* [assign-deep](https://github.com/jonschlinkert/assign-deep): Deeply assign the enumerable properties of source objects to a destination object.
* [extend-shallow](https://github.com/jonschlinkert/extend-shallow): Extend an object with the properties of additional objects. node.js/javascript util.
* [isobject](https://github.com/jonschlinkert/isobject): Returns true if the value is an object and not an array or null.
* [is-plain-object](https://github.com/jonschlinkert/is-plain-object): Returns true if an object was created by the `Object` constructor.
* [is-equal-shallow](https://github.com/jonschlinkert/is-equal-shallow): Does a shallow comparison of two objects, returning false if the keys or values differ.
* [kind-of](https://github.com/jonschlinkert/kind-of): Get the native type of a value.
## Running tests
Install dev dependencies:
```sh
$ npm i -d && npm test
```
## Contributing
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/is-extendable/issues/new)
## Author
**Jon Schlinkert**
+ [github/jonschlinkert](https://github.com/jonschlinkert)
+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert)
## License
Copyright © 2015 Jon Schlinkert
Released under the MIT license.
***
_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on July 04, 2015._
+13
View File
@@ -0,0 +1,13 @@
/*!
* is-extendable <https://github.com/jonschlinkert/is-extendable>
*
* Copyright (c) 2015, Jon Schlinkert.
* Licensed under the MIT License.
*/
'use strict';
module.exports = function isExtendable(val) {
return typeof val !== 'undefined' && val !== null
&& (typeof val === 'object' || typeof val === 'function');
};
+51
View File
@@ -0,0 +1,51 @@
{
"name": "is-extendable",
"description": "Returns true if a value is any of the object types: array, regexp, plain object, function or date. This is useful for determining if a value can be extended, e.g. \"can the value have keys?\"",
"version": "0.1.1",
"homepage": "https://github.com/jonschlinkert/is-extendable",
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"repository": "jonschlinkert/is-extendable",
"bugs": {
"url": "https://github.com/jonschlinkert/is-extendable/issues"
},
"license": "MIT",
"files": [
"index.js"
],
"main": "index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"test": "mocha"
},
"devDependencies": {
"mocha": "*"
},
"keywords": [
"array",
"assign",
"check",
"date",
"extend",
"extensible",
"function",
"is",
"object",
"regex",
"test"
],
"verbiage": {
"related": {
"list": [
"isobject",
"is-plain-object",
"kind-of",
"is-extendable",
"is-equal-shallow",
"extend-shallow",
"assign-deep"
]
}
}
}
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-2017, 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+281
View File
@@ -0,0 +1,281 @@
# to-regex-range [![NPM version](https://img.shields.io/npm/v/to-regex-range.svg?style=flat)](https://www.npmjs.com/package/to-regex-range) [![NPM monthly downloads](https://img.shields.io/npm/dm/to-regex-range.svg?style=flat)](https://npmjs.org/package/to-regex-range) [![NPM total downloads](https://img.shields.io/npm/dt/to-regex-range.svg?style=flat)](https://npmjs.org/package/to-regex-range) [![Linux Build Status](https://img.shields.io/travis/micromatch/to-regex-range.svg?style=flat&label=Travis)](https://travis-ci.org/micromatch/to-regex-range)
> Pass two numbers, get a regex-compatible source string for matching ranges. Validated against more than 2.78 million test assertions.
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save to-regex-range
```
Install with [yarn](https://yarnpkg.com):
```sh
$ yarn add to-regex-range
```
<details>
<summary><strong>What does this do?</strong></summary>
<br>
This libary generates the `source` string to be passed to `new RegExp()` for matching a range of numbers.
**Example**
```js
var toRegexRange = require('to-regex-range');
var regex = new RegExp(toRegexRange('15', '95'));
```
A string is returned so that you can do whatever you need with it before passing it to `new RegExp()` (like adding `^` or `$` boundaries, defining flags, or combining it another string).
<br>
</details>
<details>
<summary><strong>Why use this library?</strong></summary>
<br>
### Convenience
Creating regular expressions for matching numbers gets deceptively complicated pretty fast.
For example, let's say you need a validation regex for matching part of a user-id, postal code, social security number, tax id, etc:
* regex for matching `1` => `/1/` (easy enough)
* regex for matching `1` through `5` => `/[1-5]/` (not bad...)
* regex for matching `1` or `5` => `/(1|5)/` (still easy...)
* regex for matching `1` through `50` => `/([1-9]|[1-4][0-9]|50)/` (uh-oh...)
* regex for matching `1` through `55` => `/([1-9]|[1-4][0-9]|5[0-5])/` (no prob, I can do this...)
* regex for matching `1` through `555` => `/([1-9]|[1-9][0-9]|[1-4][0-9]{2}|5[0-4][0-9]|55[0-5])/` (maybe not...)
* regex for matching `0001` through `5555` => `/(0{3}[1-9]|0{2}[1-9][0-9]|0[1-9][0-9]{2}|[1-4][0-9]{3}|5[0-4][0-9]{2}|55[0-4][0-9]|555[0-5])/` (okay, I get the point!)
The numbers are contrived, but they're also really basic. In the real world you might need to generate a regex on-the-fly for validation.
**Learn more**
If you're interested in learning more about [character classes](http://www.regular-expressions.info/charclass.html) and other regex features, I personally have always found [regular-expressions.info](http://www.regular-expressions.info/charclass.html) to be pretty useful.
### Heavily tested
As of April 27, 2017, this library runs [2,783,483 test assertions](./test/test.js) against generated regex-ranges to provide brute-force verification that results are indeed correct.
Tests run in ~870ms on my MacBook Pro, 2.5 GHz Intel Core i7.
### Highly optimized
Generated regular expressions are highly optimized:
* duplicate sequences and character classes are reduced using quantifiers
* smart enough to use `?` conditionals when number(s) or range(s) can be positive or negative
* uses fragment caching to avoid processing the same exact string more than once
<br>
</details>
## Usage
Add this library to your javascript application with the following line of code
```js
var toRegexRange = require('to-regex-range');
```
The main export is a function that takes two integers: the `min` value and `max` value (formatted as strings or numbers).
```js
var source = toRegexRange('15', '95');
//=> 1[5-9]|[2-8][0-9]|9[0-5]
var re = new RegExp('^' + source + '$');
console.log(re.test('14')); //=> false
console.log(re.test('50')); //=> true
console.log(re.test('94')); //=> true
console.log(re.test('96')); //=> false
```
## Options
### options.capture
**Type**: `boolean`
**Deafault**: `undefined`
Wrap the returned value in parentheses when there is more than one regex condition. Useful when you're dynamically generating ranges.
```js
console.log(toRegexRange('-10', '10'));
//=> -[1-9]|-?10|[0-9]
console.log(toRegexRange('-10', '10', {capture: true}));
//=> (-[1-9]|-?10|[0-9])
```
### options.shorthand
**Type**: `boolean`
**Deafault**: `undefined`
Use the regex shorthand for `[0-9]`:
```js
console.log(toRegexRange('0', '999999'));
//=> [0-9]|[1-9][0-9]{1,5}
console.log(toRegexRange('0', '999999', {shorthand: true}));
//=> \d|[1-9]\d{1,5}
```
### options.relaxZeros
**Type**: `boolean`
**Default**: `true`
This option only applies to **negative zero-padded ranges**. By default, when a negative zero-padded range is defined, the number of leading zeros is relaxed using `-0*`.
```js
console.log(toRegexRange('-001', '100'));
//=> -0*1|0{2}[0-9]|0[1-9][0-9]|100
console.log(toRegexRange('-001', '100', {relaxZeros: false}));
//=> -0{2}1|0{2}[0-9]|0[1-9][0-9]|100
```
<details>
<summary><strong>Why are zeros relaxed for negative zero-padded ranges by default?</strong></summary>
Consider the following.
```js
var regex = toRegexRange('-001', '100');
```
_Note that `-001` and `100` are both three digits long_.
In most zero-padding implementations, only a single leading zero is enough to indicate that zero-padding should be applied. Thus, the leading zeros would be "corrected" on the negative range in the example to `-01`, instead of `-001`, to make total length of each string no greater than the length of the largest number in the range (in other words, `-001` is 4 digits, but `100` is only three digits).
If zeros were not relaxed by default, you might expect the resulting regex of the above pattern to match `-001` - given that it's defined that way in the arguments - _but it wouldn't_. It would, however, match `-01`. This gets even more ambiguous with large ranges, like `-01` to `1000000`.
Thus, we relax zeros by default to provide a more predictable experience for users.
</details>
## Examples
| **Range** | **Result** | **Compile time** |
| --- | --- | --- |
| `toRegexRange('5, 5')` | `5` | _33μs_ |
| `toRegexRange('5, 6')` | `5\|6` | _53μs_ |
| `toRegexRange('29, 51')` | `29\|[34][0-9]\|5[01]` | _699μs_ |
| `toRegexRange('31, 877')` | `3[1-9]\|[4-9][0-9]\|[1-7][0-9]{2}\|8[0-6][0-9]\|87[0-7]` | _711μs_ |
| `toRegexRange('111, 555')` | `11[1-9]\|1[2-9][0-9]\|[2-4][0-9]{2}\|5[0-4][0-9]\|55[0-5]` | _62μs_ |
| `toRegexRange('-10, 10')` | `-[1-9]\|-?10\|[0-9]` | _74μs_ |
| `toRegexRange('-100, -10')` | `-1[0-9]\|-[2-9][0-9]\|-100` | _49μs_ |
| `toRegexRange('-100, 100')` | `-[1-9]\|-?[1-9][0-9]\|-?100\|[0-9]` | _45μs_ |
| `toRegexRange('001, 100')` | `0{2}[1-9]\|0[1-9][0-9]\|100` | _158μs_ |
| `toRegexRange('0010, 1000')` | `0{2}1[0-9]\|0{2}[2-9][0-9]\|0[1-9][0-9]{2}\|1000` | _61μs_ |
| `toRegexRange('1, 2')` | `1\|2` | _10μs_ |
| `toRegexRange('1, 5')` | `[1-5]` | _24μs_ |
| `toRegexRange('1, 10')` | `[1-9]\|10` | _23μs_ |
| `toRegexRange('1, 100')` | `[1-9]\|[1-9][0-9]\|100` | _30μs_ |
| `toRegexRange('1, 1000')` | `[1-9]\|[1-9][0-9]{1,2}\|1000` | _52μs_ |
| `toRegexRange('1, 10000')` | `[1-9]\|[1-9][0-9]{1,3}\|10000` | _47μs_ |
| `toRegexRange('1, 100000')` | `[1-9]\|[1-9][0-9]{1,4}\|100000` | _44μs_ |
| `toRegexRange('1, 1000000')` | `[1-9]\|[1-9][0-9]{1,5}\|1000000` | _49μs_ |
| `toRegexRange('1, 10000000')` | `[1-9]\|[1-9][0-9]{1,6}\|10000000` | _63μs_ |
## Heads up!
**Order of arguments**
When the `min` is larger than the `max`, values will be flipped to create a valid range:
```js
toRegexRange('51', '29');
```
Is effectively flipped to:
```js
toRegexRange('29', '51');
//=> 29|[3-4][0-9]|5[0-1]
```
**Steps / increments**
This library does not support steps (increments). A pr to add support would be welcome.
## History
### v2.0.0 - 2017-04-21
**New features**
Adds support for zero-padding!
### v1.0.0
**Optimizations**
Repeating ranges are now grouped using quantifiers. rocessing time is roughly the same, but the generated regex is much smaller, which should result in faster matching.
## Attribution
Inspired by the python library [range-regex](https://github.com/dimka665/range-regex).
## About
### Related projects
* [expand-range](https://www.npmjs.com/package/expand-range): Fast, bash-like range expansion. Expand a range of numbers or letters, uppercase or lowercase. See… [more](https://github.com/jonschlinkert/expand-range) | [homepage](https://github.com/jonschlinkert/expand-range "Fast, bash-like range expansion. Expand a range of numbers or letters, uppercase or lowercase. See the benchmarks. Used by micromatch.")
* [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/jonschlinkert/micromatch "Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch.")
* [repeat-element](https://www.npmjs.com/package/repeat-element): Create an array by repeating the given value n times. | [homepage](https://github.com/jonschlinkert/repeat-element "Create an array by repeating the given value n times.")
* [repeat-string](https://www.npmjs.com/package/repeat-string): Repeat the given string n times. Fastest implementation for repeating a string. | [homepage](https://github.com/jonschlinkert/repeat-string "Repeat the given string n times. Fastest implementation for repeating a string.")
### Contributing
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
### Building docs
_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
To generate the readme, run the following command:
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
### Running tests
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
```sh
$ npm install && npm test
```
### Author
**Jon Schlinkert**
* [github/jonschlinkert](https://github.com/jonschlinkert)
* [twitter/jonschlinkert](https://twitter.com/jonschlinkert)
### License
Copyright © 2017, [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 April 27, 2017._
+294
View File
@@ -0,0 +1,294 @@
/*!
* to-regex-range <https://github.com/jonschlinkert/to-regex-range>
*
* Copyright (c) 2015, 2017, Jon Schlinkert.
* Released under the MIT License.
*/
'use strict';
var repeat = require('repeat-string');
var isNumber = require('is-number');
var cache = {};
function toRegexRange(min, max, options) {
if (isNumber(min) === false) {
throw new RangeError('toRegexRange: first argument is invalid.');
}
if (typeof max === 'undefined' || min === max) {
return String(min);
}
if (isNumber(max) === false) {
throw new RangeError('toRegexRange: second argument is invalid.');
}
options = options || {};
var relax = String(options.relaxZeros);
var shorthand = String(options.shorthand);
var capture = String(options.capture);
var key = min + ':' + max + '=' + relax + shorthand + capture;
if (cache.hasOwnProperty(key)) {
return cache[key].result;
}
var a = Math.min(min, max);
var b = Math.max(min, max);
if (Math.abs(a - b) === 1) {
var result = min + '|' + max;
if (options.capture) {
return '(' + result + ')';
}
return result;
}
var isPadded = padding(min) || padding(max);
var positives = [];
var negatives = [];
var tok = {min: min, max: max, a: a, b: b};
if (isPadded) {
tok.isPadded = isPadded;
tok.maxLen = String(tok.max).length;
}
if (a < 0) {
var newMin = b < 0 ? Math.abs(b) : 1;
var newMax = Math.abs(a);
negatives = splitToPatterns(newMin, newMax, tok, options);
a = tok.a = 0;
}
if (b >= 0) {
positives = splitToPatterns(a, b, tok, options);
}
tok.negatives = negatives;
tok.positives = positives;
tok.result = siftPatterns(negatives, positives, options);
if (options.capture && (positives.length + negatives.length) > 1) {
tok.result = '(' + tok.result + ')';
}
cache[key] = tok;
return tok.result;
}
function siftPatterns(neg, pos, options) {
var onlyNegative = filterPatterns(neg, pos, '-', false, options) || [];
var onlyPositive = filterPatterns(pos, neg, '', false, options) || [];
var intersected = filterPatterns(neg, pos, '-?', true, options) || [];
var subpatterns = onlyNegative.concat(intersected).concat(onlyPositive);
return subpatterns.join('|');
}
function splitToRanges(min, max) {
min = Number(min);
max = Number(max);
var nines = 1;
var stops = [max];
var stop = +countNines(min, nines);
while (min <= stop && stop <= max) {
stops = push(stops, stop);
nines += 1;
stop = +countNines(min, nines);
}
var zeros = 1;
stop = countZeros(max + 1, zeros) - 1;
while (min < stop && stop <= max) {
stops = push(stops, stop);
zeros += 1;
stop = countZeros(max + 1, zeros) - 1;
}
stops.sort(compare);
return stops;
}
/**
* Convert a range to a regex pattern
* @param {Number} `start`
* @param {Number} `stop`
* @return {String}
*/
function rangeToPattern(start, stop, options) {
if (start === stop) {
return {pattern: String(start), digits: []};
}
var zipped = zip(String(start), String(stop));
var len = zipped.length, i = -1;
var pattern = '';
var digits = 0;
while (++i < len) {
var numbers = zipped[i];
var startDigit = numbers[0];
var stopDigit = numbers[1];
if (startDigit === stopDigit) {
pattern += startDigit;
} else if (startDigit !== '0' || stopDigit !== '9') {
pattern += toCharacterClass(startDigit, stopDigit);
} else {
digits += 1;
}
}
if (digits) {
pattern += options.shorthand ? '\\d' : '[0-9]';
}
return { pattern: pattern, digits: [digits] };
}
function splitToPatterns(min, max, tok, options) {
var ranges = splitToRanges(min, max);
var len = ranges.length;
var idx = -1;
var tokens = [];
var start = min;
var prev;
while (++idx < len) {
var range = ranges[idx];
var obj = rangeToPattern(start, range, options);
var zeros = '';
if (!tok.isPadded && prev && prev.pattern === obj.pattern) {
if (prev.digits.length > 1) {
prev.digits.pop();
}
prev.digits.push(obj.digits[0]);
prev.string = prev.pattern + toQuantifier(prev.digits);
start = range + 1;
continue;
}
if (tok.isPadded) {
zeros = padZeros(range, tok);
}
obj.string = zeros + obj.pattern + toQuantifier(obj.digits);
tokens.push(obj);
start = range + 1;
prev = obj;
}
return tokens;
}
function filterPatterns(arr, comparison, prefix, intersection, options) {
var res = [];
for (var i = 0; i < arr.length; i++) {
var tok = arr[i];
var ele = tok.string;
if (options.relaxZeros !== false) {
if (prefix === '-' && ele.charAt(0) === '0') {
if (ele.charAt(1) === '{') {
ele = '0*' + ele.replace(/^0\{\d+\}/, '');
} else {
ele = '0*' + ele.slice(1);
}
}
}
if (!intersection && !contains(comparison, 'string', ele)) {
res.push(prefix + ele);
}
if (intersection && contains(comparison, 'string', ele)) {
res.push(prefix + ele);
}
}
return res;
}
/**
* Zip strings (`for in` can be used on string characters)
*/
function zip(a, b) {
var arr = [];
for (var ch in a) arr.push([a[ch], b[ch]]);
return arr;
}
function compare(a, b) {
return a > b ? 1 : b > a ? -1 : 0;
}
function push(arr, ele) {
if (arr.indexOf(ele) === -1) arr.push(ele);
return arr;
}
function contains(arr, key, val) {
for (var i = 0; i < arr.length; i++) {
if (arr[i][key] === val) {
return true;
}
}
return false;
}
function countNines(min, len) {
return String(min).slice(0, -len) + repeat('9', len);
}
function countZeros(integer, zeros) {
return integer - (integer % Math.pow(10, zeros));
}
function toQuantifier(digits) {
var start = digits[0];
var stop = digits[1] ? (',' + digits[1]) : '';
if (!stop && (!start || start === 1)) {
return '';
}
return '{' + start + stop + '}';
}
function toCharacterClass(a, b) {
return '[' + a + ((b - a === 1) ? '' : '-') + b + ']';
}
function padding(str) {
return /^-?(0+)\d/.exec(str);
}
function padZeros(val, tok) {
if (tok.isPadded) {
var diff = Math.abs(tok.maxLen - String(val).length);
switch (diff) {
case 0:
return '';
case 1:
return '0';
default: {
return '0{' + diff + '}';
}
}
}
return val;
}
/**
* Expose `toRegexRange`
*/
module.exports = toRegexRange;
+86
View File
@@ -0,0 +1,86 @@
{
"name": "to-regex-range",
"description": "Pass two numbers, get a regex-compatible source string for matching ranges. Validated against more than 2.78 million test assertions.",
"version": "2.1.1",
"homepage": "https://github.com/micromatch/to-regex-range",
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"repository": "micromatch/to-regex-range",
"bugs": {
"url": "https://github.com/micromatch/to-regex-range/issues"
},
"license": "MIT",
"files": [
"index.js"
],
"main": "index.js",
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"test": "mocha"
},
"dependencies": {
"is-number": "^3.0.0",
"repeat-string": "^1.6.1"
},
"devDependencies": {
"fill-range": "^3.1.1",
"gulp-format-md": "^0.1.12",
"mocha": "^3.2.0",
"text-table": "^0.2.0",
"time-diff": "^0.3.1"
},
"keywords": [
"alpha",
"alphabetical",
"bash",
"brace",
"date",
"expand",
"expansion",
"glob",
"match",
"matches",
"matching",
"number",
"numerical",
"range",
"ranges",
"regex",
"sequence",
"sh",
"to",
"year"
],
"verb": {
"related": {
"list": [
"expand-range",
"fill-range",
"micromatch",
"repeat-element",
"repeat-string"
]
},
"toc": false,
"layout": "default",
"tasks": [
"readme"
],
"plugins": [
"gulp-format-md"
],
"lint": {
"reflinks": true
},
"helpers": [
"./examples.js"
],
"reflinks": [
"0-5",
"0-9",
"1-5",
"1-9"
]
}
}