glob-to-regex.js 1.0.1
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 +87 -0
- package/lib/index.d.ts +24 -0
- package/lib/index.js +167 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# glob-to-regex.js
|
|
2
|
+
|
|
3
|
+
Transform GLOB patterns to JavaScript regular expressions for fast file path matching.
|
|
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.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
yarn add glob-to-regex.js
|
|
11
|
+
# or
|
|
12
|
+
npm i glob-to-regex.js
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import {toRegex, toMatcher} from 'glob-to-regex.js';
|
|
19
|
+
|
|
20
|
+
// Build a RegExp from a glob
|
|
21
|
+
const re = toRegex('src/**/test.ts');
|
|
22
|
+
re.test('src/a/b/test.ts'); // true
|
|
23
|
+
re.test('src/test.ts'); // true
|
|
24
|
+
re.test('src/test.tsx'); // false
|
|
25
|
+
|
|
26
|
+
// Build a predicate function from a pattern or an array of patterns
|
|
27
|
+
const match = toMatcher(['**/*.ts', '!**/*.d.ts']); // negative patterns are not special; use a RegExp if needed
|
|
28
|
+
match('index.ts'); // true
|
|
29
|
+
match('types.d.ts'); // true (negation is not parsed specially)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## API
|
|
33
|
+
|
|
34
|
+
- toRegex(pattern: string): RegExp
|
|
35
|
+
- Converts a glob pattern to an anchored regular expression (`^...$`).
|
|
36
|
+
|
|
37
|
+
- toMatcher(pattern: string | RegExp | Array<string | RegExp>): (path: string) => boolean
|
|
38
|
+
- 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
|
+
- Strings starting with `/` and ending with `/flags?` are treated as regular expressions (e.g. `"/\\.test\\.ts$/"`).
|
|
40
|
+
|
|
41
|
+
## Supported glob features
|
|
42
|
+
|
|
43
|
+
- `/` separates path segments
|
|
44
|
+
- `*` matches zero or more characters within a single segment (does not cross `/`)
|
|
45
|
+
- `?` matches exactly one character within a single segment
|
|
46
|
+
- `**` matches across path segments, including none
|
|
47
|
+
- `{a,b,c}` alternation groups (no nesting). Each item inside can itself contain glob syntax
|
|
48
|
+
- Character classes: `[abc]`, `[a-z]`, `[!a-z]`, `[!abc]`
|
|
49
|
+
|
|
50
|
+
Notes:
|
|
51
|
+
- The produced RegExp is anchored at start and end (`^...$`).
|
|
52
|
+
- Character classes are copied through to the output regex. Use standard JavaScript class syntax.
|
|
53
|
+
- Brace groups are not nestable. If an unmatched `{` is found, it is treated literally.
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
toRegex('a/b/c.txt').test('a/b/c.txt'); // true
|
|
59
|
+
toRegex('a/*.txt').test('a/file.txt'); // true
|
|
60
|
+
toRegex('a/*.txt').test('a/x/y.txt'); // false
|
|
61
|
+
toRegex('file?.js').test('file1.js'); // true
|
|
62
|
+
toRegex('src/**/test.ts').test('src/a/b/test.ts'); // true
|
|
63
|
+
toRegex('assets/**').test('assets/a/b.png'); // true
|
|
64
|
+
toRegex('*.{html,txt}').test('page.html'); // true
|
|
65
|
+
toRegex('src/{a,b}/**/*.ts').test('src/b/x/y.ts'); // true
|
|
66
|
+
toRegex('file[0-9].txt').test('file5.txt'); // true
|
|
67
|
+
toRegex('file[!0-9].txt').test('filea.txt'); // true
|
|
68
|
+
toRegex('**/*.[jt]s{,x}').test('dir/a/b.jsx'); // true
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## TypeScript
|
|
72
|
+
|
|
73
|
+
Types are bundled. The library targets modern Node.js and browsers.
|
|
74
|
+
|
|
75
|
+
## Performance
|
|
76
|
+
|
|
77
|
+
`toRegex` performs a single pass over the pattern and creates a native RegExp. Matching is then performed by V8's highly optimized engine.
|
|
78
|
+
|
|
79
|
+
## Limitations
|
|
80
|
+
|
|
81
|
+
- Brace groups are not nested.
|
|
82
|
+
- Negated globs like `!**/*.d.ts` are not parsed specially. If you need exclusion, combine multiple matchers or filter results separately.
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
Apache-2.0 © streamich
|
|
87
|
+
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a glob pattern to a regular expression
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - `/` to separate path segments
|
|
6
|
+
* - `*` to match zero or more characters in a path segment
|
|
7
|
+
* - `?` to match one character in a path segment
|
|
8
|
+
* - `**` to match any number of path segments, including none
|
|
9
|
+
* - `{}` to group conditions (e.g. `{html,txt}`)
|
|
10
|
+
* - `[abc]`, `[a-z]`, `[!a-z]`, `[!abc]` character classes
|
|
11
|
+
*/
|
|
12
|
+
export declare const toRegex: (pattern: string) => RegExp;
|
|
13
|
+
/**
|
|
14
|
+
* A glob pattern to match files paths against. An array or a single pattern
|
|
15
|
+
* can be provided, if an array is given, then individual patterns will be
|
|
16
|
+
* tested in order until one matches (OR short-circuits).
|
|
17
|
+
*
|
|
18
|
+
* For each pattern a string or a regular expression can be provided. If the
|
|
19
|
+
* string starts with `/` and ends with `/<flags>?` it is treated as a regular
|
|
20
|
+
* expression.
|
|
21
|
+
*/
|
|
22
|
+
export type Pattern = string | RegExp | (string | RegExp)[];
|
|
23
|
+
export type Matcher = (path: string) => boolean;
|
|
24
|
+
export declare const toMatcher: (pattern: Pattern) => Matcher;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toMatcher = exports.toRegex = void 0;
|
|
4
|
+
const escapeRe = (ch) => (/[.^$+{}()|\\]/.test(ch) ? `\\${ch}` : ch);
|
|
5
|
+
/**
|
|
6
|
+
* Convert a glob pattern to a regular expression
|
|
7
|
+
*
|
|
8
|
+
* Supports:
|
|
9
|
+
* - `/` to separate path segments
|
|
10
|
+
* - `*` to match zero or more characters in a path segment
|
|
11
|
+
* - `?` to match one character in a path segment
|
|
12
|
+
* - `**` 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
|
|
15
|
+
*/
|
|
16
|
+
const toRegex = (pattern) => {
|
|
17
|
+
let regexStr = '';
|
|
18
|
+
let i = 0;
|
|
19
|
+
// Helper to parse a brace group like {a,b,c}. No nesting support.
|
|
20
|
+
const parseBraceGroup = () => {
|
|
21
|
+
// Assume current char is '{'
|
|
22
|
+
i++; // skip '{'
|
|
23
|
+
const parts = [];
|
|
24
|
+
let cur = '';
|
|
25
|
+
let closed = false;
|
|
26
|
+
while (i < pattern.length) {
|
|
27
|
+
const ch = pattern[i];
|
|
28
|
+
if (ch === '}') {
|
|
29
|
+
parts.push(cur);
|
|
30
|
+
i++; // consume '}'
|
|
31
|
+
closed = true;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
if (ch === ',') {
|
|
35
|
+
parts.push(cur);
|
|
36
|
+
cur = '';
|
|
37
|
+
i++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
cur += ch;
|
|
41
|
+
i++;
|
|
42
|
+
}
|
|
43
|
+
if (!closed) {
|
|
44
|
+
// treat as literal '{...'
|
|
45
|
+
return '\\{' + escapeRe(cur);
|
|
46
|
+
}
|
|
47
|
+
// Convert each part recursively to support globs inside braces
|
|
48
|
+
const alt = parts.map((p) => (0, exports.toRegex)(p).source.replace(/^\^/, '').replace(/\$$/, '')).join('|');
|
|
49
|
+
return `(?:${alt})`;
|
|
50
|
+
};
|
|
51
|
+
while (i < pattern.length) {
|
|
52
|
+
const char = pattern[i];
|
|
53
|
+
switch (char) {
|
|
54
|
+
case '*': {
|
|
55
|
+
// Check for double star **
|
|
56
|
+
if (pattern[i + 1] === '*') {
|
|
57
|
+
// Collapse consecutive * beyond two (e.g., *** -> **)
|
|
58
|
+
let j = i + 2;
|
|
59
|
+
while (pattern[j] === '*')
|
|
60
|
+
j++;
|
|
61
|
+
// If followed by a slash, make it optional to allow zero segments
|
|
62
|
+
if (pattern[j] === '/') {
|
|
63
|
+
regexStr += '(?:.*/)?';
|
|
64
|
+
i = j + 1; // consume **/
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
regexStr += '.*';
|
|
68
|
+
i = j; // consume **
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
regexStr += '[^/]*';
|
|
73
|
+
i++;
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case '?':
|
|
78
|
+
regexStr += '[^/]';
|
|
79
|
+
i++;
|
|
80
|
+
break;
|
|
81
|
+
case '[': {
|
|
82
|
+
// Copy character class as-is with support for leading '!'
|
|
83
|
+
let cls = '[';
|
|
84
|
+
i++;
|
|
85
|
+
if (i < pattern.length && pattern[i] === '!') {
|
|
86
|
+
cls += '^';
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
// if first after [ or [^ is ']' include it literally
|
|
90
|
+
if (i < pattern.length && pattern[i] === ']') {
|
|
91
|
+
cls += ']';
|
|
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;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case '{': {
|
|
113
|
+
regexStr += parseBraceGroup();
|
|
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}`;
|
|
129
|
+
i++;
|
|
130
|
+
break;
|
|
131
|
+
default:
|
|
132
|
+
regexStr += char;
|
|
133
|
+
i++;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return new RegExp('^' + regexStr + '$');
|
|
138
|
+
};
|
|
139
|
+
exports.toRegex = toRegex;
|
|
140
|
+
const isRegExp = /^\/(.{1,4096})\/([gimsuy]{0,6})$/;
|
|
141
|
+
const toMatcher = (pattern) => {
|
|
142
|
+
const regexes = [];
|
|
143
|
+
const patterns = Array.isArray(pattern) ? pattern : [pattern];
|
|
144
|
+
for (const pat of patterns) {
|
|
145
|
+
if (typeof pat === 'string') {
|
|
146
|
+
const match = isRegExp.exec(pat);
|
|
147
|
+
if (match) {
|
|
148
|
+
const [, expr, flags] = match;
|
|
149
|
+
regexes.push(new RegExp(expr, flags));
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
regexes.push((0, exports.toRegex)(pat));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
regexes.push(pat);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const length = regexes.length;
|
|
160
|
+
return (path) => {
|
|
161
|
+
for (let i = 0; i < length; i++)
|
|
162
|
+
if (regexes[i].test(path))
|
|
163
|
+
return true;
|
|
164
|
+
return false;
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
exports.toMatcher = toMatcher;
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "glob-to-regex.js",
|
|
3
|
+
"packageManager": "yarn@4.9.3",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"version": "1.0.1",
|
|
8
|
+
"description": "Transform GLOB patterns to JavaScript regular expressions for fast file path matching.",
|
|
9
|
+
"author": {
|
|
10
|
+
"name": "streamich",
|
|
11
|
+
"url": "https://github.com/streamich"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/streamich/glob-to-regex",
|
|
14
|
+
"repository": "streamich/glob-to-regex",
|
|
15
|
+
"funding": {
|
|
16
|
+
"type": "github",
|
|
17
|
+
"url": "https://github.com/sponsors/streamich"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"glob",
|
|
21
|
+
"regex",
|
|
22
|
+
"regexp",
|
|
23
|
+
"pattern",
|
|
24
|
+
"matcher",
|
|
25
|
+
"path",
|
|
26
|
+
"filesystem",
|
|
27
|
+
"wildcard"
|
|
28
|
+
],
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=10.0"
|
|
31
|
+
},
|
|
32
|
+
"main": "lib/index.js",
|
|
33
|
+
"types": "lib/index.d.ts",
|
|
34
|
+
"typings": "lib/index.d.ts",
|
|
35
|
+
"files": [
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"lib/"
|
|
38
|
+
],
|
|
39
|
+
"license": "Apache-2.0",
|
|
40
|
+
"scripts": {
|
|
41
|
+
"format": "biome format ./src",
|
|
42
|
+
"format:fix": "biome format --write ./src",
|
|
43
|
+
"lint": "biome lint ./src",
|
|
44
|
+
"lint:fix": "biome lint --apply ./src",
|
|
45
|
+
"clean": "npx rimraf@6.0.1 lib typedocs coverage gh-pages yarn-error.log",
|
|
46
|
+
"build": "tsc --project tsconfig.build.json --module commonjs --target es2020 --outDir lib",
|
|
47
|
+
"test": "vitest ./src",
|
|
48
|
+
"coverage": "vitest run --coverage",
|
|
49
|
+
"typedoc": "npx typedoc@0.25.13 --tsconfig tsconfig.build.json",
|
|
50
|
+
"build:pages": "npx rimraf@6.0.1 gh-pages && mkdir -p gh-pages && cp -r typedocs/* gh-pages && cp -r coverage gh-pages/coverage",
|
|
51
|
+
"deploy:pages": "gh-pages -d gh-pages",
|
|
52
|
+
"publish-coverage-and-typedocs": "yarn typedoc && yarn coverage && yarn build:pages && yarn deploy:pages"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"tslib": "2"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@biomejs/biome": "^2.1.2",
|
|
59
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
60
|
+
"config-galore": "^1.0.0",
|
|
61
|
+
"tslib": "^2.8.1",
|
|
62
|
+
"typescript": "^5.8.3",
|
|
63
|
+
"vitest": "^3.2.4"
|
|
64
|
+
}
|
|
65
|
+
}
|