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 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
+ }