glob-to-regex.js 1.2.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 +47 -16
- package/lib/index.d.ts +12 -2
- package/lib/index.js +390 -183
- 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,41 +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`
|
|
49
69
|
- **Extended globbing** (when `extglob: true` option is set):
|
|
50
70
|
- `?(pattern-list)` matches zero or one occurrence of the given patterns
|
|
51
71
|
- `*(pattern-list)` matches zero or more occurrences of the given patterns
|
|
52
72
|
- `+(pattern-list)` matches one or more occurrences of the given patterns
|
|
53
73
|
- `@(pattern-list)` matches exactly one of the given patterns
|
|
54
|
-
- `!(pattern-list)` matches anything except 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
|
|
55
75
|
- Pattern lists use `|` as separator (e.g., `@(jpg|png|gif)`)
|
|
56
76
|
|
|
57
77
|
Notes:
|
|
58
78
|
- The produced RegExp is anchored at start and end (`^...$`).
|
|
59
|
-
-
|
|
60
|
-
-
|
|
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.
|
|
61
82
|
|
|
62
83
|
## Examples
|
|
63
84
|
|
|
@@ -70,10 +91,18 @@ toRegex('src/**/test.ts').test('src/a/b/test.ts'); // true
|
|
|
70
91
|
toRegex('assets/**').test('assets/a/b.png'); // true
|
|
71
92
|
toRegex('*.{html,txt}').test('page.html'); // true
|
|
72
93
|
toRegex('src/{a,b}/**/*.ts').test('src/b/x/y.ts'); // true
|
|
94
|
+
toRegex('v{1..3}.tgz').test('v2.tgz'); // true
|
|
73
95
|
toRegex('file[0-9].txt').test('file5.txt'); // true
|
|
74
96
|
toRegex('file[!0-9].txt').test('filea.txt'); // true
|
|
97
|
+
toRegex('[[:upper:]]*.ts').test('App.ts'); // true
|
|
75
98
|
toRegex('**/*.[jt]s{,x}').test('dir/a/b.jsx'); // true
|
|
76
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
|
+
|
|
77
106
|
// Extended globbing examples
|
|
78
107
|
toRegex('file?(s).txt', {extglob: true}).test('file.txt'); // true
|
|
79
108
|
toRegex('file?(s).txt', {extglob: true}).test('files.txt'); // true
|
|
@@ -82,6 +111,8 @@ toRegex('/var/log/!(*.gz)', {extglob: true}).test('/var/log/syslog'); // true
|
|
|
82
111
|
toRegex('/var/log/!(*.gz)', {extglob: true}).test('/var/log/error.log.gz'); // false
|
|
83
112
|
toRegex('src/**/!(*.test).js', {extglob: true}).test('src/app.test.js'); // false
|
|
84
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
|
|
85
116
|
```
|
|
86
117
|
|
|
87
118
|
## TypeScript
|
|
@@ -94,10 +125,10 @@ Types are bundled. The library targets modern Node.js and browsers.
|
|
|
94
125
|
|
|
95
126
|
## Limitations
|
|
96
127
|
|
|
97
|
-
- Brace groups are not nested.
|
|
98
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.
|
|
99
131
|
|
|
100
132
|
## License
|
|
101
133
|
|
|
102
134
|
Apache-2.0 © streamich
|
|
103
|
-
|
package/lib/index.d.ts
CHANGED
|
@@ -3,7 +3,17 @@ export interface GlobOptions {
|
|
|
3
3
|
nocase?: boolean;
|
|
4
4
|
/** Enable extended globbing: ?(pattern), *(pattern), +(pattern), @(pattern), !(pattern) */
|
|
5
5
|
extglob?: boolean;
|
|
6
|
+
/** Whether `*`, `?`, `[...]` and extglobs match a leading `.` of a path segment. */
|
|
7
|
+
dot?: boolean;
|
|
6
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[];
|
|
7
17
|
/**
|
|
8
18
|
* Convert a glob pattern to a regular expression
|
|
9
19
|
*
|
|
@@ -12,8 +22,8 @@ export interface GlobOptions {
|
|
|
12
22
|
* - `*` to match zero or more characters in a path segment
|
|
13
23
|
* - `?` to match one character in a path segment
|
|
14
24
|
* - `**` to match any number of path segments, including none
|
|
15
|
-
* - `{}` to group conditions (e.g. `{html,txt}`)
|
|
16
|
-
* - `[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
|
|
17
27
|
* - Extended globbing (when `extglob: true` option is set):
|
|
18
28
|
* - `?(pattern-list)` zero or one occurrence
|
|
19
29
|
* - `*(pattern-list)` zero or more occurrences
|
package/lib/index.js
CHANGED
|
@@ -1,69 +1,385 @@
|
|
|
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
|
-
|
|
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;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
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;
|
|
114
|
+
}
|
|
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 */)
|
|
17
136
|
depth++;
|
|
18
|
-
|
|
19
|
-
i
|
|
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;
|
|
20
153
|
}
|
|
21
|
-
else if (
|
|
154
|
+
else if (code === 40 /* Code.LParen */)
|
|
155
|
+
depth++;
|
|
156
|
+
else if (code === 41 /* Code.RParen */)
|
|
22
157
|
depth--;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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;
|
|
234
|
+
}
|
|
235
|
+
else if (pattern.charCodeAt(k) === 47 /* Code.Slash */)
|
|
236
|
+
j = k;
|
|
237
|
+
else
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
if (slash) {
|
|
241
|
+
out += nodot ? '(?:' + guard + GLOBSTAR_NODOT + '/)?' : '(?:.*/)?';
|
|
242
|
+
j++;
|
|
243
|
+
start = true;
|
|
244
|
+
}
|
|
245
|
+
else
|
|
246
|
+
out += nodot ? guard + GLOBSTAR_NODOT : '.*';
|
|
247
|
+
i = j;
|
|
27
248
|
break;
|
|
28
249
|
}
|
|
29
|
-
|
|
30
|
-
|
|
250
|
+
case 63 /* Code.Qmark */:
|
|
251
|
+
out += guard + '[^/]';
|
|
31
252
|
i++;
|
|
253
|
+
break;
|
|
254
|
+
case 91 /* Code.LBracket */: {
|
|
255
|
+
const end = classEnd(pattern, i);
|
|
256
|
+
if (end < 0) {
|
|
257
|
+
out += '\\[';
|
|
258
|
+
i++;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
const cls = classSource(pattern, i, end);
|
|
262
|
+
out += (cls.charCodeAt(0) === 91 /* Code.LBracket */ ? guard : '') + cls;
|
|
263
|
+
i = end;
|
|
264
|
+
break;
|
|
32
265
|
}
|
|
266
|
+
case 47 /* Code.Slash */:
|
|
267
|
+
out += '/';
|
|
268
|
+
i++;
|
|
269
|
+
start = true;
|
|
270
|
+
break;
|
|
271
|
+
default:
|
|
272
|
+
out += literal(pattern, i, code);
|
|
273
|
+
i++;
|
|
33
274
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
275
|
+
}
|
|
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;
|
|
39
304
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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;
|
|
43
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);
|
|
44
341
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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;
|
|
64
367
|
}
|
|
65
|
-
|
|
368
|
+
out.push(pattern);
|
|
66
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;
|
|
67
383
|
/**
|
|
68
384
|
* Convert a glob pattern to a regular expression
|
|
69
385
|
*
|
|
@@ -72,8 +388,8 @@ const parseExtGlob = (pattern, startIdx, prefix, options) => {
|
|
|
72
388
|
* - `*` to match zero or more characters in a path segment
|
|
73
389
|
* - `?` to match one character in a path segment
|
|
74
390
|
* - `**` to match any number of path segments, including none
|
|
75
|
-
* - `{}` to group conditions (e.g. `{html,txt}`)
|
|
76
|
-
* - `[abc]`, `[a-z]`, `[!a-z]`, `[!abc]` character classes
|
|
391
|
+
* - `{}` to group conditions (e.g. `{html,txt}`), nested, and `{1..3}` ranges
|
|
392
|
+
* - `[abc]`, `[a-z]`, `[!a-z]`, `[!abc]`, `[[:alpha:]]` character classes
|
|
77
393
|
* - Extended globbing (when `extglob: true` option is set):
|
|
78
394
|
* - `?(pattern-list)` zero or one occurrence
|
|
79
395
|
* - `*(pattern-list)` zero or more occurrences
|
|
@@ -82,141 +398,32 @@ const parseExtGlob = (pattern, startIdx, prefix, options) => {
|
|
|
82
398
|
* - `!(pattern-list)` anything except the patterns
|
|
83
399
|
*/
|
|
84
400
|
const toRegex = (pattern, options) => {
|
|
85
|
-
let regexStr = '';
|
|
86
|
-
let i = 0;
|
|
87
|
-
// Helper to parse a brace group like {a,b,c}. No nesting support.
|
|
88
|
-
const parseBraceGroup = () => {
|
|
89
|
-
// Assume current char is '{'
|
|
90
|
-
i++; // skip '{'
|
|
91
|
-
const parts = [];
|
|
92
|
-
let cur = '';
|
|
93
|
-
let closed = false;
|
|
94
|
-
while (i < pattern.length) {
|
|
95
|
-
const ch = pattern[i];
|
|
96
|
-
if (ch === '}') {
|
|
97
|
-
parts.push(cur);
|
|
98
|
-
i++; // consume '}'
|
|
99
|
-
closed = true;
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
if (ch === ',') {
|
|
103
|
-
parts.push(cur);
|
|
104
|
-
cur = '';
|
|
105
|
-
i++;
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
cur += ch;
|
|
109
|
-
i++;
|
|
110
|
-
}
|
|
111
|
-
if (!closed) {
|
|
112
|
-
// treat as literal '{...'
|
|
113
|
-
return '\\{' + escapeRe(cur);
|
|
114
|
-
}
|
|
115
|
-
// Convert each part recursively to support globs inside braces
|
|
116
|
-
const alt = parts.map((p) => (0, exports.toRegex)(p, options).source.replace(/^\^/, '').replace(/\$$/, '')).join('|');
|
|
117
|
-
return `(?:${alt})`;
|
|
118
|
-
};
|
|
119
401
|
const extglob = !!options?.extglob;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
case '*': {
|
|
136
|
-
// Check for double star **
|
|
137
|
-
if (pattern[i + 1] === '*') {
|
|
138
|
-
// Collapse consecutive * beyond two (e.g., *** -> **)
|
|
139
|
-
let j = i + 2;
|
|
140
|
-
while (pattern[j] === '*')
|
|
141
|
-
j++;
|
|
142
|
-
// If followed by a slash, make it optional to allow zero segments
|
|
143
|
-
if (pattern[j] === '/') {
|
|
144
|
-
regexStr += '(?:.*/)?';
|
|
145
|
-
i = j + 1; // consume **/
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
regexStr += '.*';
|
|
149
|
-
i = j; // consume **
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
regexStr += '[^/]*';
|
|
154
|
-
i++;
|
|
155
|
-
}
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
case '?':
|
|
159
|
-
regexStr += '[^/]';
|
|
160
|
-
i++;
|
|
161
|
-
break;
|
|
162
|
-
case '[': {
|
|
163
|
-
// Copy character class as-is with support for leading '!'
|
|
164
|
-
let cls = '[';
|
|
165
|
-
i++;
|
|
166
|
-
if (i < pattern.length && pattern[i] === '!') {
|
|
167
|
-
cls += '^';
|
|
168
|
-
i++;
|
|
169
|
-
}
|
|
170
|
-
// if first after [ or [^ is ']' include it literally
|
|
171
|
-
if (i < pattern.length && pattern[i] === ']') {
|
|
172
|
-
cls += ']';
|
|
173
|
-
i++;
|
|
174
|
-
}
|
|
175
|
-
while (i < pattern.length && pattern[i] !== ']') {
|
|
176
|
-
const ch = pattern[i];
|
|
177
|
-
// Escape backslash inside class
|
|
178
|
-
cls += ch === '\\' ? '\\\\' : ch;
|
|
179
|
-
i++;
|
|
180
|
-
}
|
|
181
|
-
if (i < pattern.length && pattern[i] === ']') {
|
|
182
|
-
cls += ']';
|
|
183
|
-
i++;
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
// Unclosed class -> treat '[' literally
|
|
187
|
-
regexStr += '\\[';
|
|
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))
|
|
188
417
|
continue;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
418
|
+
if (seen.size)
|
|
419
|
+
source += '|';
|
|
420
|
+
seen.add(one);
|
|
421
|
+
source += compile(one, extglob, nodot, '', true);
|
|
192
422
|
}
|
|
193
|
-
|
|
194
|
-
regexStr += parseBraceGroup();
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
case '/':
|
|
198
|
-
regexStr += '/';
|
|
199
|
-
i++;
|
|
200
|
-
break;
|
|
201
|
-
case '.':
|
|
202
|
-
case '^':
|
|
203
|
-
case '$':
|
|
204
|
-
case '+':
|
|
205
|
-
case '(':
|
|
206
|
-
case ')':
|
|
207
|
-
case '|':
|
|
208
|
-
case '\\':
|
|
209
|
-
regexStr += `\\${char}`;
|
|
210
|
-
i++;
|
|
211
|
-
break;
|
|
212
|
-
default:
|
|
213
|
-
regexStr += char;
|
|
214
|
-
i++;
|
|
215
|
-
break;
|
|
423
|
+
source += ')';
|
|
216
424
|
}
|
|
217
425
|
}
|
|
218
|
-
|
|
219
|
-
return new RegExp('^' + regexStr + '$', flags);
|
|
426
|
+
return new RegExp('^' + source + '$', options?.nocase ? 'i' : '');
|
|
220
427
|
};
|
|
221
428
|
exports.toRegex = toRegex;
|
|
222
429
|
const isRegExp = /^\/(.{1,4096})\/([gimsuy]{0,6})$/;
|