glob-to-regex.js 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -15
- package/lib/index.d.ts +20 -2
- package/lib/index.js +403 -115
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Transform GLOB patterns to JavaScript regular expressions for fast file path matching.
|
|
4
4
|
|
|
5
|
-
This tiny library converts familiar shell-style glob patterns like `**/*.ts` or `src/{a,b}/**/*.js` into JavaScript `RegExp` objects and provides a convenient matcher utility.
|
|
5
|
+
This tiny library converts familiar shell-style glob patterns like `**/*.ts` or `src/{a,b}/**/*.js` into JavaScript `RegExp` objects and provides a convenient matcher utility. Matching semantics follow `minimatch`,
|
|
6
|
+
so the regexes can stand in for it segment by segment.
|
|
6
7
|
|
|
7
8
|
## Install
|
|
8
9
|
|
|
@@ -15,7 +16,7 @@ npm i glob-to-regex.js
|
|
|
15
16
|
## Quick start
|
|
16
17
|
|
|
17
18
|
```ts
|
|
18
|
-
import {toRegex, toMatcher} from 'glob-to-regex.js';
|
|
19
|
+
import {toRegex, toMatcher, expandBraces} from 'glob-to-regex.js';
|
|
19
20
|
|
|
20
21
|
// Build a RegExp from a glob
|
|
21
22
|
const re = toRegex('src/**/test.ts');
|
|
@@ -23,34 +24,61 @@ re.test('src/a/b/test.ts'); // true
|
|
|
23
24
|
re.test('src/test.ts'); // true
|
|
24
25
|
re.test('src/test.tsx'); // false
|
|
25
26
|
|
|
27
|
+
// Shell rules for dotfiles and extended globs
|
|
28
|
+
const shell = toRegex('**/!(*.test).js', {dot: false, extglob: true});
|
|
29
|
+
shell.test('src/index.js'); // true
|
|
30
|
+
shell.test('src/app.test.js'); // false
|
|
31
|
+
shell.test('.git/hooks.js'); // false
|
|
32
|
+
|
|
26
33
|
// Build a predicate function from a pattern or an array of patterns
|
|
27
|
-
const match = toMatcher(['**/*.ts', '
|
|
28
|
-
match('index.ts');
|
|
29
|
-
match('types.d.ts');
|
|
34
|
+
const match = toMatcher(['**/*.ts', '/\\.d\\.ts$/']);
|
|
35
|
+
match('index.ts'); // true
|
|
36
|
+
match('types.d.ts'); // true
|
|
37
|
+
|
|
38
|
+
expandBraces('src/{lib,test}/{1..3}.ts');
|
|
39
|
+
// ['src/lib/1.ts', 'src/lib/2.ts', ..., 'src/test/3.ts']
|
|
30
40
|
```
|
|
31
41
|
|
|
32
42
|
## API
|
|
33
43
|
|
|
34
|
-
- toRegex(pattern: string): RegExp
|
|
44
|
+
- `toRegex(pattern: string, options?: GlobOptions): RegExp`
|
|
35
45
|
- Converts a glob pattern to an anchored regular expression (`^...$`).
|
|
36
46
|
|
|
37
|
-
- toMatcher(pattern: string | RegExp | Array<string | RegExp
|
|
47
|
+
- `toMatcher(pattern: string | RegExp | Array<string | RegExp>, options?: GlobOptions): (path: string) => boolean`
|
|
38
48
|
- Accepts a glob string, a RegExp, or an array of them. If given an array, it returns true if any item matches (logical OR, short-circuited).
|
|
39
49
|
- Strings starting with `/` and ending with `/flags?` are treated as regular expressions (e.g. `"/\\.test\\.ts$/"`).
|
|
40
50
|
|
|
51
|
+
- `expandBraces(pattern: string, max?: number): string[]`
|
|
52
|
+
- Expands `{a,b}` alternations and `{1..3}` ranges into the list of patterns they stand for, nesting included. Stops after `max` patterns (default 100,000).
|
|
53
|
+
|
|
54
|
+
### Options
|
|
55
|
+
|
|
56
|
+
- `nocase` — case-insensitive matching.
|
|
57
|
+
- `extglob` — enables the `?(...)`, `*(...)`, `+(...)`, `@(...)` and `!(...)` groups.
|
|
58
|
+
- `dot` — whether `*`, `?`, `[...]` and extglobs match a leading `.` of a path segment. Defaults to `true`; `false` is the shell rule, where a dotfile is matched only by a literal dot in the pattern (`.*` matches `.gitignore`, `*` does not) and `**` does not descend into dot directories.
|
|
59
|
+
|
|
41
60
|
## Supported glob features
|
|
42
61
|
|
|
43
62
|
- `/` separates path segments
|
|
44
|
-
- `*` matches zero or more characters within a single segment (does not cross `/`)
|
|
63
|
+
- `*` matches zero or more characters within a single segment (does not cross `/`). A segment that is only `*` matches at least one character, as a file name is never empty: `a/*` does not match `a/`
|
|
45
64
|
- `?` matches exactly one character within a single segment
|
|
46
|
-
- `**` matches across path segments, including none
|
|
47
|
-
- `{a,b,c}` alternation groups
|
|
48
|
-
-
|
|
65
|
+
- `**` matches across path segments, including none. `**/**/` is the same as `**/`
|
|
66
|
+
- `{a,b,c}` alternation groups, nested as in `{a,{b,c}}`. Each item can itself contain glob syntax
|
|
67
|
+
- `{1..3}`, `{a..c}`, `{01..10..2}` ranges, with the step and zero padding of bash
|
|
68
|
+
- Character classes: `[abc]`, `[a-z]`, `[!a-z]`, `[^a-z]`, and POSIX classes such as `[[:alpha:]]` or `[a[:digit:]]`. A `]` right after the opening bracket is a member of the class: `[]a]` matches `]` or `a`
|
|
69
|
+
- **Extended globbing** (when `extglob: true` option is set):
|
|
70
|
+
- `?(pattern-list)` matches zero or one occurrence of the given patterns
|
|
71
|
+
- `*(pattern-list)` matches zero or more occurrences of the given patterns
|
|
72
|
+
- `+(pattern-list)` matches one or more occurrences of the given patterns
|
|
73
|
+
- `@(pattern-list)` matches exactly one of the given patterns
|
|
74
|
+
- `!(pattern-list)` matches anything except one of the given patterns. The exclusion covers the rest of the pattern, so `!(a).js` rejects `a.js` and accepts `ab.js`; `!()` matches any non-empty segment
|
|
75
|
+
- Pattern lists use `|` as separator (e.g., `@(jpg|png|gif)`)
|
|
49
76
|
|
|
50
77
|
Notes:
|
|
51
78
|
- The produced RegExp is anchored at start and end (`^...$`).
|
|
52
|
-
-
|
|
53
|
-
-
|
|
79
|
+
- A brace group without a comma or range (`{a}`, `{}`) and an unmatched `{`, `[` or `(` are literal.
|
|
80
|
+
- A range with its ends reversed, like `[z-a]`, matches nothing instead of throwing.
|
|
81
|
+
- There is no escape character: a backslash is a literal backslash.
|
|
54
82
|
|
|
55
83
|
## Examples
|
|
56
84
|
|
|
@@ -63,9 +91,28 @@ toRegex('src/**/test.ts').test('src/a/b/test.ts'); // true
|
|
|
63
91
|
toRegex('assets/**').test('assets/a/b.png'); // true
|
|
64
92
|
toRegex('*.{html,txt}').test('page.html'); // true
|
|
65
93
|
toRegex('src/{a,b}/**/*.ts').test('src/b/x/y.ts'); // true
|
|
94
|
+
toRegex('v{1..3}.tgz').test('v2.tgz'); // true
|
|
66
95
|
toRegex('file[0-9].txt').test('file5.txt'); // true
|
|
67
96
|
toRegex('file[!0-9].txt').test('filea.txt'); // true
|
|
97
|
+
toRegex('[[:upper:]]*.ts').test('App.ts'); // true
|
|
68
98
|
toRegex('**/*.[jt]s{,x}').test('dir/a/b.jsx'); // true
|
|
99
|
+
|
|
100
|
+
// Dotfiles
|
|
101
|
+
toRegex('*.js').test('.eslintrc.js'); // true
|
|
102
|
+
toRegex('*.js', {dot: false}).test('.eslintrc.js'); // false
|
|
103
|
+
toRegex('.*', {dot: false}).test('.eslintrc.js'); // true
|
|
104
|
+
toRegex('**/*.js', {dot: false}).test('node_modules/.cache/a.js'); // false
|
|
105
|
+
|
|
106
|
+
// Extended globbing examples
|
|
107
|
+
toRegex('file?(s).txt', {extglob: true}).test('file.txt'); // true
|
|
108
|
+
toRegex('file?(s).txt', {extglob: true}).test('files.txt'); // true
|
|
109
|
+
toRegex('file.@(jpg|png|gif)', {extglob: true}).test('file.jpg'); // true
|
|
110
|
+
toRegex('/var/log/!(*.gz)', {extglob: true}).test('/var/log/syslog'); // true
|
|
111
|
+
toRegex('/var/log/!(*.gz)', {extglob: true}).test('/var/log/error.log.gz'); // false
|
|
112
|
+
toRegex('src/**/!(*.test).js', {extglob: true}).test('src/app.test.js'); // false
|
|
113
|
+
toRegex('src/**/!(*.test).js', {extglob: true}).test('src/index.js'); // true
|
|
114
|
+
toRegex('!(a).js', {extglob: true}).test('ab.js'); // true
|
|
115
|
+
toRegex('!(a).js', {extglob: true}).test('a.js'); // false
|
|
69
116
|
```
|
|
70
117
|
|
|
71
118
|
## TypeScript
|
|
@@ -78,10 +125,10 @@ Types are bundled. The library targets modern Node.js and browsers.
|
|
|
78
125
|
|
|
79
126
|
## Limitations
|
|
80
127
|
|
|
81
|
-
- Brace groups are not nested.
|
|
82
128
|
- Negated globs like `!**/*.d.ts` are not parsed specially. If you need exclusion, combine multiple matchers or filter results separately.
|
|
129
|
+
- `**` is a globstar wherever it appears, not only as a whole segment: `a**` matches across `/`.
|
|
130
|
+
- Like minimatch, `!(a)*` does not match `a`: the negation rejects the whole remainder, while bash tries every split.
|
|
83
131
|
|
|
84
132
|
## License
|
|
85
133
|
|
|
86
134
|
Apache-2.0 © streamich
|
|
87
|
-
|
package/lib/index.d.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
export interface GlobOptions {
|
|
2
2
|
/** Treat pattern as case insensitive */
|
|
3
3
|
nocase?: boolean;
|
|
4
|
+
/** Enable extended globbing: ?(pattern), *(pattern), +(pattern), @(pattern), !(pattern) */
|
|
5
|
+
extglob?: boolean;
|
|
6
|
+
/** Whether `*`, `?`, `[...]` and extglobs match a leading `.` of a path segment. */
|
|
7
|
+
dot?: boolean;
|
|
4
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Expands `{a,b}` alternations and `{1..3}`, `{a..c}`, `{01..10..2}` ranges the
|
|
11
|
+
* way bash does, nesting included. A group with neither a comma nor a range,
|
|
12
|
+
* or without its closing brace, is kept as it is.
|
|
13
|
+
*
|
|
14
|
+
* @param max Number of expansions to stop at, as `brace-expansion` does.
|
|
15
|
+
*/
|
|
16
|
+
export declare const expandBraces: (pattern: string, max?: number) => string[];
|
|
5
17
|
/**
|
|
6
18
|
* Convert a glob pattern to a regular expression
|
|
7
19
|
*
|
|
@@ -10,8 +22,14 @@ export interface GlobOptions {
|
|
|
10
22
|
* - `*` to match zero or more characters in a path segment
|
|
11
23
|
* - `?` to match one character in a path segment
|
|
12
24
|
* - `**` to match any number of path segments, including none
|
|
13
|
-
* - `{}` to group conditions (e.g. `{html,txt}`)
|
|
14
|
-
* - `[abc]`, `[a-z]`, `[!a-z]`, `[!abc]` character classes
|
|
25
|
+
* - `{}` to group conditions (e.g. `{html,txt}`), nested, and `{1..3}` ranges
|
|
26
|
+
* - `[abc]`, `[a-z]`, `[!a-z]`, `[!abc]`, `[[:alpha:]]` character classes
|
|
27
|
+
* - Extended globbing (when `extglob: true` option is set):
|
|
28
|
+
* - `?(pattern-list)` zero or one occurrence
|
|
29
|
+
* - `*(pattern-list)` zero or more occurrences
|
|
30
|
+
* - `+(pattern-list)` one or more occurrences
|
|
31
|
+
* - `@(pattern-list)` exactly one of the patterns
|
|
32
|
+
* - `!(pattern-list)` anything except the patterns
|
|
15
33
|
*/
|
|
16
34
|
export declare const toRegex: (pattern: string, options?: GlobOptions) => RegExp;
|
|
17
35
|
/**
|
package/lib/index.js
CHANGED
|
@@ -1,141 +1,429 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toMatcher = exports.toRegex = void 0;
|
|
4
|
-
const
|
|
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
|
-
|
|
3
|
+
exports.toMatcher = exports.toRegex = exports.expandBraces = void 0;
|
|
4
|
+
const NODOT = '(?!\\.)';
|
|
5
|
+
const STAR = '[^/]*';
|
|
6
|
+
const GLOBSTAR_NODOT = '[^/]*(?:/(?!\\.)[^/]*)*';
|
|
7
|
+
const literal = (pattern, i, code) => {
|
|
8
|
+
switch (code) {
|
|
9
|
+
case 36 /* Code.Dollar */:
|
|
10
|
+
case 40 /* Code.LParen */:
|
|
11
|
+
case 41 /* Code.RParen */:
|
|
12
|
+
case 43 /* Code.Plus */:
|
|
13
|
+
case 46 /* Code.Dot */:
|
|
14
|
+
case 92 /* Code.Backslash */:
|
|
15
|
+
case 93 /* Code.RBracket */:
|
|
16
|
+
case 94 /* Code.Caret */:
|
|
17
|
+
case 123 /* Code.LBrace */:
|
|
18
|
+
case 124 /* Code.Pipe */:
|
|
19
|
+
case 125 /* Code.RBrace */:
|
|
20
|
+
return '\\' + pattern[i];
|
|
21
|
+
default:
|
|
22
|
+
return pattern[i];
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const posixClass = (name) => {
|
|
26
|
+
switch (name) {
|
|
27
|
+
case 'alnum':
|
|
28
|
+
return '0-9A-Za-z';
|
|
29
|
+
case 'alpha':
|
|
30
|
+
return 'A-Za-z';
|
|
31
|
+
case 'ascii':
|
|
32
|
+
return '\\x00-\\x7f';
|
|
33
|
+
case 'blank':
|
|
34
|
+
return ' \\t';
|
|
35
|
+
case 'cntrl':
|
|
36
|
+
return '\\x00-\\x1f\\x7f';
|
|
37
|
+
case 'digit':
|
|
38
|
+
return '0-9';
|
|
39
|
+
case 'graph':
|
|
40
|
+
return '\\x21-\\x7e';
|
|
41
|
+
case 'lower':
|
|
42
|
+
return 'a-z';
|
|
43
|
+
case 'print':
|
|
44
|
+
return '\\x20-\\x7e';
|
|
45
|
+
case 'punct':
|
|
46
|
+
return '\\x21-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\x7e';
|
|
47
|
+
case 'space':
|
|
48
|
+
return ' \\t\\r\\n\\v\\f';
|
|
49
|
+
case 'upper':
|
|
50
|
+
return 'A-Z';
|
|
51
|
+
case 'word':
|
|
52
|
+
return '0-9A-Za-z_';
|
|
53
|
+
case 'xdigit':
|
|
54
|
+
return '0-9A-Fa-f';
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
};
|
|
58
|
+
const classEnd = (pattern, i) => {
|
|
59
|
+
const length = pattern.length;
|
|
60
|
+
let j = i + 1;
|
|
61
|
+
const first = pattern.charCodeAt(j);
|
|
62
|
+
if (first === 33 /* Code.Bang */ || first === 94 /* Code.Caret */)
|
|
63
|
+
j++;
|
|
64
|
+
if (pattern.charCodeAt(j) === 93 /* Code.RBracket */)
|
|
65
|
+
j++;
|
|
66
|
+
for (; j < length; j++) {
|
|
67
|
+
const code = pattern.charCodeAt(j);
|
|
68
|
+
if (code === 93 /* Code.RBracket */)
|
|
69
|
+
return j + 1;
|
|
70
|
+
if (code === 91 /* Code.LBracket */ && pattern.charCodeAt(j + 1) === 58 /* Code.Colon */) {
|
|
71
|
+
const close = pattern.indexOf(':]', j + 2);
|
|
72
|
+
if (close > 0)
|
|
73
|
+
j = close + 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return -1;
|
|
77
|
+
};
|
|
78
|
+
const member = (pattern, i) => {
|
|
79
|
+
const code = pattern.charCodeAt(i);
|
|
80
|
+
const special = code === 93 /* Code.RBracket */ ||
|
|
81
|
+
code === 92 /* Code.Backslash */ ||
|
|
82
|
+
code === 91 /* Code.LBracket */ ||
|
|
83
|
+
code === 94 /* Code.Caret */ ||
|
|
84
|
+
code === 45 /* Code.Minus */;
|
|
85
|
+
return (special ? '\\' : '') + pattern[i];
|
|
86
|
+
};
|
|
87
|
+
const classSource = (pattern, i, end) => {
|
|
88
|
+
const last = end - 1;
|
|
89
|
+
let j = i + 1;
|
|
90
|
+
let out = '';
|
|
91
|
+
let members = 0;
|
|
92
|
+
const first = pattern.charCodeAt(j);
|
|
93
|
+
const negate = first === 33 /* Code.Bang */ || first === 94 /* Code.Caret */;
|
|
94
|
+
if (negate)
|
|
95
|
+
j++;
|
|
96
|
+
while (j < last) {
|
|
97
|
+
if (pattern.charCodeAt(j) === 91 /* Code.LBracket */ && pattern.charCodeAt(j + 1) === 58 /* Code.Colon */) {
|
|
98
|
+
const close = pattern.indexOf(':]', j + 2);
|
|
99
|
+
const cls = close > 0 ? posixClass(pattern.slice(j + 2, close)) : undefined;
|
|
100
|
+
if (cls !== undefined) {
|
|
101
|
+
out += cls;
|
|
102
|
+
members += 2;
|
|
103
|
+
j = close + 2;
|
|
38
104
|
continue;
|
|
39
105
|
}
|
|
40
|
-
cur += ch;
|
|
41
|
-
i++;
|
|
42
106
|
}
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
107
|
+
if (pattern.charCodeAt(j + 1) === 45 /* Code.Minus */ && j + 2 < last) {
|
|
108
|
+
if (pattern.charCodeAt(j + 2) >= pattern.charCodeAt(j)) {
|
|
109
|
+
out += member(pattern, j) + '-' + member(pattern, j + 2);
|
|
110
|
+
members += 2;
|
|
111
|
+
}
|
|
112
|
+
j += 3;
|
|
113
|
+
continue;
|
|
46
114
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
115
|
+
out += member(pattern, j);
|
|
116
|
+
members++;
|
|
117
|
+
j++;
|
|
118
|
+
}
|
|
119
|
+
if (!negate && members === 1)
|
|
120
|
+
return literal(pattern, last - 1, pattern.charCodeAt(last - 1));
|
|
121
|
+
if (!members)
|
|
122
|
+
return negate ? '[^/]' : '[]';
|
|
123
|
+
return (negate ? '[^' : '[') + out + ']';
|
|
124
|
+
};
|
|
125
|
+
const groupEnd = (pattern, i) => {
|
|
126
|
+
const length = pattern.length;
|
|
127
|
+
let depth = 1;
|
|
128
|
+
for (; i < length; i++) {
|
|
129
|
+
const code = pattern.charCodeAt(i);
|
|
130
|
+
if (code === 91 /* Code.LBracket */) {
|
|
131
|
+
const end = classEnd(pattern, i);
|
|
132
|
+
if (end > 0)
|
|
133
|
+
i = end - 1;
|
|
134
|
+
}
|
|
135
|
+
else if (code === 40 /* Code.LParen */)
|
|
136
|
+
depth++;
|
|
137
|
+
else if (code === 41 /* Code.RParen */ && !--depth)
|
|
138
|
+
return i + 1;
|
|
139
|
+
}
|
|
140
|
+
return -1;
|
|
141
|
+
};
|
|
142
|
+
const splitAlts = (body) => {
|
|
143
|
+
const length = body.length;
|
|
144
|
+
const alts = [];
|
|
145
|
+
let depth = 0;
|
|
146
|
+
let from = 0;
|
|
147
|
+
for (let i = 0; i < length; i++) {
|
|
148
|
+
const code = body.charCodeAt(i);
|
|
149
|
+
if (code === 91 /* Code.LBracket */) {
|
|
150
|
+
const end = classEnd(body, i);
|
|
151
|
+
if (end > 0)
|
|
152
|
+
i = end - 1;
|
|
153
|
+
}
|
|
154
|
+
else if (code === 40 /* Code.LParen */)
|
|
155
|
+
depth++;
|
|
156
|
+
else if (code === 41 /* Code.RParen */)
|
|
157
|
+
depth--;
|
|
158
|
+
else if (code === 124 /* Code.Pipe */ && !depth) {
|
|
159
|
+
alts.push(body.slice(from, i));
|
|
160
|
+
from = i + 1;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
alts.push(body.slice(from));
|
|
164
|
+
return alts;
|
|
165
|
+
};
|
|
166
|
+
const isExtglobPrefix = (code) => code === 63 /* Code.Qmark */ || code === 42 /* Code.Star */ || code === 43 /* Code.Plus */ || code === 64 /* Code.At */ || code === 33 /* Code.Bang */;
|
|
167
|
+
const altsSource = (alts, extglob, nodot, tail, segStart) => {
|
|
168
|
+
let out = '';
|
|
169
|
+
for (let i = 0; i < alts.length; i++)
|
|
170
|
+
out += (i ? '|' : '') + compile(alts[i], extglob, nodot, tail, segStart);
|
|
171
|
+
return out;
|
|
172
|
+
};
|
|
173
|
+
const extglobSource = (type, alts, extglob, nodot, tail, segStart) => {
|
|
174
|
+
const guard = nodot && segStart ? NODOT : '';
|
|
175
|
+
const body = altsSource(alts, extglob, nodot, tail, segStart);
|
|
176
|
+
switch (type) {
|
|
177
|
+
case 63 /* Code.Qmark */:
|
|
178
|
+
return '(?:' + body + ')?';
|
|
179
|
+
case 64 /* Code.At */:
|
|
180
|
+
return '(?:' + body + ')';
|
|
181
|
+
case 33 /* Code.Bang */:
|
|
182
|
+
if (alts.length === 1 && !alts[0])
|
|
183
|
+
return guard + '[^/]+';
|
|
184
|
+
return '(?!(?:' + body + ')' + tail + '$)' + guard + STAR;
|
|
185
|
+
}
|
|
186
|
+
const more = guard ? altsSource(alts, extglob, nodot, tail, false) : body;
|
|
187
|
+
if (more === body)
|
|
188
|
+
return '(?:' + body + ')' + (type === 42 /* Code.Star */ ? '*' : '+');
|
|
189
|
+
const once = '(?:' + body + ')(?:' + more + ')*';
|
|
190
|
+
return type === 42 /* Code.Star */ ? '(?:' + once + ')?' : once;
|
|
191
|
+
};
|
|
192
|
+
const compile = (pattern, extglob, nodot, tail, segStart) => {
|
|
193
|
+
const length = pattern.length;
|
|
194
|
+
let out = '';
|
|
195
|
+
let i = 0;
|
|
196
|
+
let start = segStart;
|
|
197
|
+
while (i < length) {
|
|
198
|
+
const code = pattern.charCodeAt(i);
|
|
199
|
+
if (extglob && pattern.charCodeAt(i + 1) === 40 /* Code.LParen */ && isExtglobPrefix(code)) {
|
|
200
|
+
const end = groupEnd(pattern, i + 2);
|
|
201
|
+
if (end > 0) {
|
|
202
|
+
const after = compile(pattern.slice(end), extglob, nodot, tail, false);
|
|
203
|
+
const alts = splitAlts(pattern.slice(i + 2, end - 1));
|
|
204
|
+
return out + extglobSource(code, alts, extglob, nodot, after + tail, start) + after;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const atStart = start;
|
|
208
|
+
const guard = nodot && atStart ? NODOT : '';
|
|
209
|
+
start = false;
|
|
210
|
+
switch (code) {
|
|
211
|
+
case 42 /* Code.Star */: {
|
|
212
|
+
let j = i + 1;
|
|
213
|
+
while (pattern.charCodeAt(j) === 42 /* Code.Star */)
|
|
214
|
+
j++;
|
|
215
|
+
if (j === i + 1) {
|
|
216
|
+
// a whole segment of `*` needs a character: a file name is never empty
|
|
217
|
+
const whole = atStart && (j === length || pattern.charCodeAt(j) === 47 /* Code.Slash */);
|
|
218
|
+
out += guard + (whole ? '[^/]+' : STAR);
|
|
219
|
+
i = j;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
// `**/**/` is `**/`, and `**/**` is `**`: a second globstar only multiplies the backtracking
|
|
223
|
+
let slash = pattern.charCodeAt(j) === 47 /* Code.Slash */;
|
|
224
|
+
while (slash) {
|
|
225
|
+
let k = j + 1;
|
|
226
|
+
if (pattern.charCodeAt(k) !== 42 /* Code.Star */ || pattern.charCodeAt(k + 1) !== 42 /* Code.Star */)
|
|
227
|
+
break;
|
|
228
|
+
k += 2;
|
|
229
|
+
while (pattern.charCodeAt(k) === 42 /* Code.Star */)
|
|
230
|
+
k++;
|
|
231
|
+
if (k === length) {
|
|
232
|
+
j = k;
|
|
233
|
+
slash = false;
|
|
69
234
|
}
|
|
235
|
+
else if (pattern.charCodeAt(k) === 47 /* Code.Slash */)
|
|
236
|
+
j = k;
|
|
237
|
+
else
|
|
238
|
+
break;
|
|
70
239
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
240
|
+
if (slash) {
|
|
241
|
+
out += nodot ? '(?:' + guard + GLOBSTAR_NODOT + '/)?' : '(?:.*/)?';
|
|
242
|
+
j++;
|
|
243
|
+
start = true;
|
|
74
244
|
}
|
|
245
|
+
else
|
|
246
|
+
out += nodot ? guard + GLOBSTAR_NODOT : '.*';
|
|
247
|
+
i = j;
|
|
75
248
|
break;
|
|
76
249
|
}
|
|
77
|
-
case
|
|
78
|
-
|
|
250
|
+
case 63 /* Code.Qmark */:
|
|
251
|
+
out += guard + '[^/]';
|
|
79
252
|
i++;
|
|
80
253
|
break;
|
|
81
|
-
case
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (i < pattern.length && pattern[i] === '!') {
|
|
86
|
-
cls += '^';
|
|
254
|
+
case 91 /* Code.LBracket */: {
|
|
255
|
+
const end = classEnd(pattern, i);
|
|
256
|
+
if (end < 0) {
|
|
257
|
+
out += '\\[';
|
|
87
258
|
i++;
|
|
259
|
+
break;
|
|
88
260
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
i++;
|
|
93
|
-
}
|
|
94
|
-
while (i < pattern.length && pattern[i] !== ']') {
|
|
95
|
-
const ch = pattern[i];
|
|
96
|
-
// Escape backslash inside class
|
|
97
|
-
cls += ch === '\\' ? '\\\\' : ch;
|
|
98
|
-
i++;
|
|
99
|
-
}
|
|
100
|
-
if (i < pattern.length && pattern[i] === ']') {
|
|
101
|
-
cls += ']';
|
|
102
|
-
i++;
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
// Unclosed class -> treat '[' literally
|
|
106
|
-
regexStr += '\\[';
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
regexStr += cls;
|
|
261
|
+
const cls = classSource(pattern, i, end);
|
|
262
|
+
out += (cls.charCodeAt(0) === 91 /* Code.LBracket */ ? guard : '') + cls;
|
|
263
|
+
i = end;
|
|
110
264
|
break;
|
|
111
265
|
}
|
|
112
|
-
case
|
|
113
|
-
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
case '/':
|
|
117
|
-
regexStr += '/';
|
|
118
|
-
i++;
|
|
119
|
-
break;
|
|
120
|
-
case '.':
|
|
121
|
-
case '^':
|
|
122
|
-
case '$':
|
|
123
|
-
case '+':
|
|
124
|
-
case '(':
|
|
125
|
-
case ')':
|
|
126
|
-
case '|':
|
|
127
|
-
case '\\':
|
|
128
|
-
regexStr += `\\${char}`;
|
|
266
|
+
case 47 /* Code.Slash */:
|
|
267
|
+
out += '/';
|
|
129
268
|
i++;
|
|
269
|
+
start = true;
|
|
130
270
|
break;
|
|
131
271
|
default:
|
|
132
|
-
|
|
272
|
+
out += literal(pattern, i, code);
|
|
133
273
|
i++;
|
|
134
|
-
break;
|
|
135
274
|
}
|
|
136
275
|
}
|
|
137
|
-
|
|
138
|
-
|
|
276
|
+
return out;
|
|
277
|
+
};
|
|
278
|
+
const closingBrace = (pattern, open) => {
|
|
279
|
+
const length = pattern.length;
|
|
280
|
+
let depth = 0;
|
|
281
|
+
for (let i = open; i < length; i++) {
|
|
282
|
+
const code = pattern.charCodeAt(i);
|
|
283
|
+
if (code === 123 /* Code.LBrace */)
|
|
284
|
+
depth++;
|
|
285
|
+
else if (code === 125 /* Code.RBrace */ && !--depth)
|
|
286
|
+
return i;
|
|
287
|
+
}
|
|
288
|
+
return -1;
|
|
289
|
+
};
|
|
290
|
+
const commaSplit = (body) => {
|
|
291
|
+
const length = body.length;
|
|
292
|
+
const parts = [];
|
|
293
|
+
let depth = 0;
|
|
294
|
+
let from = 0;
|
|
295
|
+
for (let i = 0; i < length; i++) {
|
|
296
|
+
const code = body.charCodeAt(i);
|
|
297
|
+
if (code === 123 /* Code.LBrace */)
|
|
298
|
+
depth++;
|
|
299
|
+
else if (code === 125 /* Code.RBrace */)
|
|
300
|
+
depth--;
|
|
301
|
+
else if (code === 44 /* Code.Comma */ && !depth) {
|
|
302
|
+
parts.push(body.slice(from, i));
|
|
303
|
+
from = i + 1;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (!parts.length)
|
|
307
|
+
return;
|
|
308
|
+
parts.push(body.slice(from));
|
|
309
|
+
return parts;
|
|
310
|
+
};
|
|
311
|
+
const NUMERIC_RANGE = /^(-?\d+)\.\.(-?\d+)(?:\.\.(-?\d+))?$/;
|
|
312
|
+
const ALPHA_RANGE = /^([a-zA-Z])\.\.([a-zA-Z])(?:\.\.(-?\d+))?$/;
|
|
313
|
+
const PADDED = /^-?0\d/;
|
|
314
|
+
const rangeOf = (body, max) => {
|
|
315
|
+
let match = NUMERIC_RANGE.exec(body);
|
|
316
|
+
const alpha = !match;
|
|
317
|
+
if (alpha)
|
|
318
|
+
match = ALPHA_RANGE.exec(body);
|
|
319
|
+
if (!match)
|
|
320
|
+
return;
|
|
321
|
+
const from = alpha ? match[1].charCodeAt(0) : +match[1];
|
|
322
|
+
const to = alpha ? match[2].charCodeAt(0) : +match[2];
|
|
323
|
+
const step = match[3] === undefined ? 1 : Math.abs(+match[3]) || 1;
|
|
324
|
+
const width = !alpha && (PADDED.test(match[1]) || PADDED.test(match[2])) ? Math.max(match[1].length, match[2].length) : 0;
|
|
325
|
+
const out = [];
|
|
326
|
+
const dir = from <= to ? step : -step;
|
|
327
|
+
for (let n = from; (dir > 0 ? n <= to : n >= to) && out.length < max; n += dir) {
|
|
328
|
+
if (alpha) {
|
|
329
|
+
out.push(String.fromCharCode(n));
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
let s = String(n);
|
|
333
|
+
const need = width - s.length;
|
|
334
|
+
if (need > 0) {
|
|
335
|
+
let zeros = '';
|
|
336
|
+
for (let k = 0; k < need; k++)
|
|
337
|
+
zeros += '0';
|
|
338
|
+
s = n < 0 ? '-' + zeros + s.slice(1) : zeros + s;
|
|
339
|
+
}
|
|
340
|
+
out.push(s);
|
|
341
|
+
}
|
|
342
|
+
return out;
|
|
343
|
+
};
|
|
344
|
+
const expandInto = (pattern, out, max) => {
|
|
345
|
+
for (let open = pattern.indexOf('{'); open >= 0; open = pattern.indexOf('{', open + 1)) {
|
|
346
|
+
const close = closingBrace(pattern, open);
|
|
347
|
+
if (close < 0)
|
|
348
|
+
continue;
|
|
349
|
+
const body = pattern.slice(open + 1, close);
|
|
350
|
+
const alts = commaSplit(body) || rangeOf(body, max);
|
|
351
|
+
if (!alts)
|
|
352
|
+
continue;
|
|
353
|
+
const prefix = pattern.slice(0, open);
|
|
354
|
+
const suffixes = (0, exports.expandBraces)(pattern.slice(close + 1), max);
|
|
355
|
+
for (let i = 0; i < alts.length; i++) {
|
|
356
|
+
const heads = (0, exports.expandBraces)(alts[i], max);
|
|
357
|
+
for (let j = 0; j < heads.length; j++) {
|
|
358
|
+
const head = prefix + heads[j];
|
|
359
|
+
for (let k = 0; k < suffixes.length; k++) {
|
|
360
|
+
if (out.length >= max)
|
|
361
|
+
return;
|
|
362
|
+
out.push(head + suffixes[k]);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
out.push(pattern);
|
|
369
|
+
};
|
|
370
|
+
/**
|
|
371
|
+
* Expands `{a,b}` alternations and `{1..3}`, `{a..c}`, `{01..10..2}` ranges the
|
|
372
|
+
* way bash does, nesting included. A group with neither a comma nor a range,
|
|
373
|
+
* or without its closing brace, is kept as it is.
|
|
374
|
+
*
|
|
375
|
+
* @param max Number of expansions to stop at, as `brace-expansion` does.
|
|
376
|
+
*/
|
|
377
|
+
const expandBraces = (pattern, max = 100000) => {
|
|
378
|
+
const out = [];
|
|
379
|
+
expandInto(pattern, out, max);
|
|
380
|
+
return out;
|
|
381
|
+
};
|
|
382
|
+
exports.expandBraces = expandBraces;
|
|
383
|
+
/**
|
|
384
|
+
* Convert a glob pattern to a regular expression
|
|
385
|
+
*
|
|
386
|
+
* Supports:
|
|
387
|
+
* - `/` to separate path segments
|
|
388
|
+
* - `*` to match zero or more characters in a path segment
|
|
389
|
+
* - `?` to match one character in a path segment
|
|
390
|
+
* - `**` to match any number of path segments, including none
|
|
391
|
+
* - `{}` to group conditions (e.g. `{html,txt}`), nested, and `{1..3}` ranges
|
|
392
|
+
* - `[abc]`, `[a-z]`, `[!a-z]`, `[!abc]`, `[[:alpha:]]` character classes
|
|
393
|
+
* - Extended globbing (when `extglob: true` option is set):
|
|
394
|
+
* - `?(pattern-list)` zero or one occurrence
|
|
395
|
+
* - `*(pattern-list)` zero or more occurrences
|
|
396
|
+
* - `+(pattern-list)` one or more occurrences
|
|
397
|
+
* - `@(pattern-list)` exactly one of the patterns
|
|
398
|
+
* - `!(pattern-list)` anything except the patterns
|
|
399
|
+
*/
|
|
400
|
+
const toRegex = (pattern, options) => {
|
|
401
|
+
const extglob = !!options?.extglob;
|
|
402
|
+
const nodot = options?.dot === false;
|
|
403
|
+
let source;
|
|
404
|
+
if (pattern.indexOf('{') < 0)
|
|
405
|
+
source = compile(pattern, extglob, nodot, '', true);
|
|
406
|
+
else {
|
|
407
|
+
const set = (0, exports.expandBraces)(pattern);
|
|
408
|
+
const length = set.length;
|
|
409
|
+
if (length === 1)
|
|
410
|
+
source = compile(set[0], extglob, nodot, '', true);
|
|
411
|
+
else {
|
|
412
|
+
const seen = new Set();
|
|
413
|
+
source = '(?:';
|
|
414
|
+
for (let i = 0; i < length; i++) {
|
|
415
|
+
const one = set[i];
|
|
416
|
+
if (seen.has(one))
|
|
417
|
+
continue;
|
|
418
|
+
if (seen.size)
|
|
419
|
+
source += '|';
|
|
420
|
+
seen.add(one);
|
|
421
|
+
source += compile(one, extglob, nodot, '', true);
|
|
422
|
+
}
|
|
423
|
+
source += ')';
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return new RegExp('^' + source + '$', options?.nocase ? 'i' : '');
|
|
139
427
|
};
|
|
140
428
|
exports.toRegex = toRegex;
|
|
141
429
|
const isRegExp = /^\/(.{1,4096})\/([gimsuy]{0,6})$/;
|