glob-to-regex.js 1.0.1 → 1.2.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 +16 -0
- package/lib/index.d.ts +14 -2
- package/lib/index.js +90 -12
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -46,6 +46,13 @@ match('types.d.ts'); // true (negation is not parsed specially)
|
|
|
46
46
|
- `**` matches across path segments, including none
|
|
47
47
|
- `{a,b,c}` alternation groups (no nesting). Each item inside can itself contain glob syntax
|
|
48
48
|
- Character classes: `[abc]`, `[a-z]`, `[!a-z]`, `[!abc]`
|
|
49
|
+
- **Extended globbing** (when `extglob: true` option is set):
|
|
50
|
+
- `?(pattern-list)` matches zero or one occurrence of the given patterns
|
|
51
|
+
- `*(pattern-list)` matches zero or more occurrences of the given patterns
|
|
52
|
+
- `+(pattern-list)` matches one or more occurrences of the given patterns
|
|
53
|
+
- `@(pattern-list)` matches exactly one of the given patterns
|
|
54
|
+
- `!(pattern-list)` matches anything except one of the given patterns
|
|
55
|
+
- Pattern lists use `|` as separator (e.g., `@(jpg|png|gif)`)
|
|
49
56
|
|
|
50
57
|
Notes:
|
|
51
58
|
- The produced RegExp is anchored at start and end (`^...$`).
|
|
@@ -66,6 +73,15 @@ toRegex('src/{a,b}/**/*.ts').test('src/b/x/y.ts'); // true
|
|
|
66
73
|
toRegex('file[0-9].txt').test('file5.txt'); // true
|
|
67
74
|
toRegex('file[!0-9].txt').test('filea.txt'); // true
|
|
68
75
|
toRegex('**/*.[jt]s{,x}').test('dir/a/b.jsx'); // true
|
|
76
|
+
|
|
77
|
+
// Extended globbing examples
|
|
78
|
+
toRegex('file?(s).txt', {extglob: true}).test('file.txt'); // true
|
|
79
|
+
toRegex('file?(s).txt', {extglob: true}).test('files.txt'); // true
|
|
80
|
+
toRegex('file.@(jpg|png|gif)', {extglob: true}).test('file.jpg'); // true
|
|
81
|
+
toRegex('/var/log/!(*.gz)', {extglob: true}).test('/var/log/syslog'); // true
|
|
82
|
+
toRegex('/var/log/!(*.gz)', {extglob: true}).test('/var/log/error.log.gz'); // false
|
|
83
|
+
toRegex('src/**/!(*.test).js', {extglob: true}).test('src/app.test.js'); // false
|
|
84
|
+
toRegex('src/**/!(*.test).js', {extglob: true}).test('src/index.js'); // true
|
|
69
85
|
```
|
|
70
86
|
|
|
71
87
|
## TypeScript
|
package/lib/index.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export interface GlobOptions {
|
|
2
|
+
/** Treat pattern as case insensitive */
|
|
3
|
+
nocase?: boolean;
|
|
4
|
+
/** Enable extended globbing: ?(pattern), *(pattern), +(pattern), @(pattern), !(pattern) */
|
|
5
|
+
extglob?: boolean;
|
|
6
|
+
}
|
|
1
7
|
/**
|
|
2
8
|
* Convert a glob pattern to a regular expression
|
|
3
9
|
*
|
|
@@ -8,8 +14,14 @@
|
|
|
8
14
|
* - `**` to match any number of path segments, including none
|
|
9
15
|
* - `{}` to group conditions (e.g. `{html,txt}`)
|
|
10
16
|
* - `[abc]`, `[a-z]`, `[!a-z]`, `[!abc]` character classes
|
|
17
|
+
* - Extended globbing (when `extglob: true` option is set):
|
|
18
|
+
* - `?(pattern-list)` zero or one occurrence
|
|
19
|
+
* - `*(pattern-list)` zero or more occurrences
|
|
20
|
+
* - `+(pattern-list)` one or more occurrences
|
|
21
|
+
* - `@(pattern-list)` exactly one of the patterns
|
|
22
|
+
* - `!(pattern-list)` anything except the patterns
|
|
11
23
|
*/
|
|
12
|
-
export declare const toRegex: (pattern: string) => RegExp;
|
|
24
|
+
export declare const toRegex: (pattern: string, options?: GlobOptions) => RegExp;
|
|
13
25
|
/**
|
|
14
26
|
* A glob pattern to match files paths against. An array or a single pattern
|
|
15
27
|
* can be provided, if an array is given, then individual patterns will be
|
|
@@ -21,4 +33,4 @@ export declare const toRegex: (pattern: string) => RegExp;
|
|
|
21
33
|
*/
|
|
22
34
|
export type Pattern = string | RegExp | (string | RegExp)[];
|
|
23
35
|
export type Matcher = (path: string) => boolean;
|
|
24
|
-
export declare const toMatcher: (pattern: Pattern) => Matcher;
|
|
36
|
+
export declare const toMatcher: (pattern: Pattern, options?: GlobOptions) => Matcher;
|
package/lib/index.js
CHANGED
|
@@ -2,6 +2,68 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.toMatcher = exports.toRegex = void 0;
|
|
4
4
|
const escapeRe = (ch) => (/[.^$+{}()|\\]/.test(ch) ? `\\${ch}` : ch);
|
|
5
|
+
/**
|
|
6
|
+
* Parse an extended glob pattern like ?(a|b|c)
|
|
7
|
+
* Returns the regex string equivalent and the new index position
|
|
8
|
+
*/
|
|
9
|
+
const parseExtGlob = (pattern, startIdx, prefix, options) => {
|
|
10
|
+
let i = startIdx; // startIdx should be pointing at the character after '('
|
|
11
|
+
const parts = [];
|
|
12
|
+
let cur = '';
|
|
13
|
+
let depth = 1; // Track parenthesis depth for nested patterns
|
|
14
|
+
while (i < pattern.length && depth > 0) {
|
|
15
|
+
const ch = pattern[i];
|
|
16
|
+
if (ch === '(') {
|
|
17
|
+
depth++;
|
|
18
|
+
cur += ch;
|
|
19
|
+
i++;
|
|
20
|
+
}
|
|
21
|
+
else if (ch === ')') {
|
|
22
|
+
depth--;
|
|
23
|
+
if (depth === 0) {
|
|
24
|
+
// Found the closing parenthesis
|
|
25
|
+
parts.push(cur);
|
|
26
|
+
i++; // consume ')'
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
cur += ch;
|
|
31
|
+
i++;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else if (ch === '|' && depth === 1) {
|
|
35
|
+
// Pipe separator at top level of this extglob
|
|
36
|
+
parts.push(cur);
|
|
37
|
+
cur = '';
|
|
38
|
+
i++;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
cur += ch;
|
|
42
|
+
i++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (depth !== 0)
|
|
46
|
+
return; // Unclosed parenthesis
|
|
47
|
+
let alternatives = '';
|
|
48
|
+
const length = parts.length;
|
|
49
|
+
for (let j = 0; j < length; j++)
|
|
50
|
+
alternatives += (alternatives ? '|' : '') + (0, exports.toRegex)(parts[j], options).source.replace(/^\^/, '').replace(/\$$/, '');
|
|
51
|
+
switch (prefix) {
|
|
52
|
+
case '?': // zero or one
|
|
53
|
+
return [`(?:${alternatives})?`, i];
|
|
54
|
+
case '*': // zero or more
|
|
55
|
+
return [`(?:${alternatives})*`, i];
|
|
56
|
+
case '+': // one or more
|
|
57
|
+
return [`(?:${alternatives})+`, i];
|
|
58
|
+
case '@': // exactly one
|
|
59
|
+
return [`(?:${alternatives})`, i];
|
|
60
|
+
case '!': // none of (negative match)
|
|
61
|
+
// For negation, we need to match anything that doesn't match the pattern
|
|
62
|
+
// Use negative lookahead without consuming characters after
|
|
63
|
+
return [`(?!${alternatives})[^/]*`, i];
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
};
|
|
5
67
|
/**
|
|
6
68
|
* Convert a glob pattern to a regular expression
|
|
7
69
|
*
|
|
@@ -12,8 +74,14 @@ const escapeRe = (ch) => (/[.^$+{}()|\\]/.test(ch) ? `\\${ch}` : ch);
|
|
|
12
74
|
* - `**` to match any number of path segments, including none
|
|
13
75
|
* - `{}` to group conditions (e.g. `{html,txt}`)
|
|
14
76
|
* - `[abc]`, `[a-z]`, `[!a-z]`, `[!abc]` character classes
|
|
77
|
+
* - Extended globbing (when `extglob: true` option is set):
|
|
78
|
+
* - `?(pattern-list)` zero or one occurrence
|
|
79
|
+
* - `*(pattern-list)` zero or more occurrences
|
|
80
|
+
* - `+(pattern-list)` one or more occurrences
|
|
81
|
+
* - `@(pattern-list)` exactly one of the patterns
|
|
82
|
+
* - `!(pattern-list)` anything except the patterns
|
|
15
83
|
*/
|
|
16
|
-
const toRegex = (pattern) => {
|
|
84
|
+
const toRegex = (pattern, options) => {
|
|
17
85
|
let regexStr = '';
|
|
18
86
|
let i = 0;
|
|
19
87
|
// Helper to parse a brace group like {a,b,c}. No nesting support.
|
|
@@ -45,11 +113,24 @@ const toRegex = (pattern) => {
|
|
|
45
113
|
return '\\{' + escapeRe(cur);
|
|
46
114
|
}
|
|
47
115
|
// Convert each part recursively to support globs inside braces
|
|
48
|
-
const alt = parts.map((p) => (0, exports.toRegex)(p).source.replace(/^\^/, '').replace(/\$$/, '')).join('|');
|
|
116
|
+
const alt = parts.map((p) => (0, exports.toRegex)(p, options).source.replace(/^\^/, '').replace(/\$$/, '')).join('|');
|
|
49
117
|
return `(?:${alt})`;
|
|
50
118
|
};
|
|
119
|
+
const extglob = !!options?.extglob;
|
|
51
120
|
while (i < pattern.length) {
|
|
52
121
|
const char = pattern[i];
|
|
122
|
+
// Check for extended glob patterns when extglob is enabled
|
|
123
|
+
if (extglob && pattern[i + 1] === '(') {
|
|
124
|
+
if (char === '?' || char === '*' || char === '+' || char === '@' || char === '!') {
|
|
125
|
+
const result = parseExtGlob(pattern, i + 2, char, options);
|
|
126
|
+
if (result) {
|
|
127
|
+
regexStr += result[0];
|
|
128
|
+
i = result[1];
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
// If parse failed, fall through to normal handling
|
|
132
|
+
}
|
|
133
|
+
}
|
|
53
134
|
switch (char) {
|
|
54
135
|
case '*': {
|
|
55
136
|
// Check for double star **
|
|
@@ -134,11 +215,12 @@ const toRegex = (pattern) => {
|
|
|
134
215
|
break;
|
|
135
216
|
}
|
|
136
217
|
}
|
|
137
|
-
|
|
218
|
+
const flags = options?.nocase ? 'i' : '';
|
|
219
|
+
return new RegExp('^' + regexStr + '$', flags);
|
|
138
220
|
};
|
|
139
221
|
exports.toRegex = toRegex;
|
|
140
222
|
const isRegExp = /^\/(.{1,4096})\/([gimsuy]{0,6})$/;
|
|
141
|
-
const toMatcher = (pattern) => {
|
|
223
|
+
const toMatcher = (pattern, options) => {
|
|
142
224
|
const regexes = [];
|
|
143
225
|
const patterns = Array.isArray(pattern) ? pattern : [pattern];
|
|
144
226
|
for (const pat of patterns) {
|
|
@@ -149,19 +231,15 @@ const toMatcher = (pattern) => {
|
|
|
149
231
|
regexes.push(new RegExp(expr, flags));
|
|
150
232
|
}
|
|
151
233
|
else {
|
|
152
|
-
regexes.push((0, exports.toRegex)(pat));
|
|
234
|
+
regexes.push((0, exports.toRegex)(pat, options));
|
|
153
235
|
}
|
|
154
236
|
}
|
|
155
237
|
else {
|
|
156
238
|
regexes.push(pat);
|
|
157
239
|
}
|
|
158
240
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (regexes[i].test(path))
|
|
163
|
-
return true;
|
|
164
|
-
return false;
|
|
165
|
-
};
|
|
241
|
+
return regexes.length
|
|
242
|
+
? new Function('p', 'return ' + regexes.map((r) => r + '.test(p)').join('||'))
|
|
243
|
+
: () => false;
|
|
166
244
|
};
|
|
167
245
|
exports.toMatcher = toMatcher;
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glob-to-regex.js",
|
|
3
|
-
"packageManager": "yarn@4.9.
|
|
3
|
+
"packageManager": "yarn@4.9.4",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "1.0
|
|
7
|
+
"version": "1.2.0",
|
|
8
8
|
"description": "Transform GLOB patterns to JavaScript regular expressions for fast file path matching.",
|
|
9
9
|
"author": {
|
|
10
10
|
"name": "streamich",
|