@descent-vtt/spec-brief 0.1.0 → 0.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/CHANGELOG.md +264 -0
- package/README.md +90 -20
- package/dist/archive.d.ts +52 -2
- package/dist/archive.js +205 -37
- package/dist/archive.js.map +1 -1
- package/dist/brief.d.ts +2 -0
- package/dist/brief.js +5 -1
- package/dist/brief.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +48 -14
- package/dist/cli.js.map +1 -1
- package/dist/collisions.d.ts +43 -12
- package/dist/collisions.js +90 -58
- package/dist/collisions.js.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.js +58 -10
- package/dist/config.js.map +1 -1
- package/dist/corpus.d.ts +21 -1
- package/dist/corpus.js +51 -2
- package/dist/corpus.js.map +1 -1
- package/dist/engine.d.ts +33 -3
- package/dist/engine.js +112 -9
- package/dist/engine.js.map +1 -1
- package/dist/frontmatter.d.ts +18 -61
- package/dist/frontmatter.js +31 -300
- package/dist/frontmatter.js.map +1 -1
- package/dist/glob.d.ts +77 -54
- package/dist/glob.js +104 -433
- package/dist/glob.js.map +1 -1
- package/dist/index.d.ts +10 -8
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/links.d.ts +1 -1
- package/dist/links.js +3 -4
- package/dist/links.js.map +1 -1
- package/dist/lint.d.ts +33 -2
- package/dist/lint.js +9 -2
- package/dist/lint.js.map +1 -1
- package/dist/markdown.d.ts +26 -23
- package/dist/markdown.js +60 -237
- package/dist/markdown.js.map +1 -1
- package/dist/plugins.d.ts +14 -0
- package/dist/plugins.js +142 -6
- package/dist/plugins.js.map +1 -1
- package/dist/report.d.ts +40 -3
- package/dist/report.js +227 -11
- package/dist/report.js.map +1 -1
- package/dist/rules.d.ts +22 -4
- package/dist/rules.js +175 -45
- package/dist/rules.js.map +1 -1
- package/dist/scaffold.d.ts +1 -1
- package/dist/scaffold.js +10 -9
- package/dist/scaffold.js.map +1 -1
- package/dist/schedule.d.ts +133 -0
- package/dist/schedule.js +265 -0
- package/dist/schedule.js.map +1 -0
- package/dist/scope.d.ts +52 -0
- package/dist/scope.js +56 -0
- package/dist/scope.js.map +1 -0
- package/dist/text.d.ts +13 -0
- package/dist/text.js +23 -0
- package/dist/text.js.map +1 -1
- package/dist/trigger.d.ts +17 -0
- package/dist/trigger.js +49 -0
- package/dist/trigger.js.map +1 -0
- package/dist/types.d.ts +14 -5
- package/dist/types.js.map +1 -1
- package/dist/vendor/spec-core/markdown/frontmatter.d.ts +103 -0
- package/dist/vendor/spec-core/markdown/frontmatter.js +459 -0
- package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -0
- package/dist/vendor/spec-core/markdown/headings.d.ts +45 -0
- package/dist/vendor/spec-core/markdown/headings.js +199 -0
- package/dist/vendor/spec-core/markdown/headings.js.map +1 -0
- package/dist/vendor/spec-core/markdown/index.d.ts +5 -0
- package/dist/vendor/spec-core/markdown/index.js +4 -0
- package/dist/vendor/spec-core/markdown/index.js.map +1 -0
- package/dist/vendor/spec-core/markdown/layout.d.ts +39 -0
- package/dist/vendor/spec-core/markdown/layout.js +28 -0
- package/dist/vendor/spec-core/markdown/layout.js.map +1 -0
- package/dist/vendor/spec-core/markdown/links.d.ts +22 -0
- package/dist/vendor/spec-core/markdown/links.js +364 -0
- package/dist/vendor/spec-core/markdown/links.js.map +1 -0
- package/dist/vendor/spec-core/markdown/lists.d.ts +22 -0
- package/dist/vendor/spec-core/markdown/lists.js +127 -0
- package/dist/vendor/spec-core/markdown/lists.js.map +1 -0
- package/dist/vendor/spec-core/markdown/scan.d.ts +36 -0
- package/dist/vendor/spec-core/markdown/scan.js +439 -0
- package/dist/vendor/spec-core/markdown/scan.js.map +1 -0
- package/dist/vendor/spec-core/markdown/syntax.d.ts +75 -0
- package/dist/vendor/spec-core/markdown/syntax.js +181 -0
- package/dist/vendor/spec-core/markdown/syntax.js.map +1 -0
- package/dist/vendor/spec-core/markdown/tables.d.ts +18 -0
- package/dist/vendor/spec-core/markdown/tables.js +86 -0
- package/dist/vendor/spec-core/markdown/tables.js.map +1 -0
- package/dist/vendor/spec-core/markdown/types.d.ts +287 -0
- package/dist/vendor/spec-core/markdown/types.js +9 -0
- package/dist/vendor/spec-core/markdown/types.js.map +1 -0
- package/dist/vendor/spec-core/path/index.d.ts +1 -0
- package/dist/vendor/spec-core/path/index.js +2 -0
- package/dist/vendor/spec-core/path/index.js.map +1 -0
- package/dist/vendor/spec-core/path/posix.d.ts +83 -0
- package/dist/vendor/spec-core/path/posix.js +150 -0
- package/dist/vendor/spec-core/path/posix.js.map +1 -0
- package/dist/vendor/spec-core/pattern/automaton.d.ts +129 -0
- package/dist/vendor/spec-core/pattern/automaton.js +340 -0
- package/dist/vendor/spec-core/pattern/automaton.js.map +1 -0
- package/dist/vendor/spec-core/pattern/charset.d.ts +58 -0
- package/dist/vendor/spec-core/pattern/charset.js +116 -0
- package/dist/vendor/spec-core/pattern/charset.js.map +1 -0
- package/dist/vendor/spec-core/pattern/glob.d.ts +121 -0
- package/dist/vendor/spec-core/pattern/glob.js +481 -0
- package/dist/vendor/spec-core/pattern/glob.js.map +1 -0
- package/dist/vendor/spec-core/pattern/index.d.ts +6 -0
- package/dist/vendor/spec-core/pattern/index.js +4 -0
- package/dist/vendor/spec-core/pattern/index.js.map +1 -0
- package/dist/vendor/spec-core/pattern/regex.d.ts +67 -0
- package/dist/vendor/spec-core/pattern/regex.js +813 -0
- package/dist/vendor/spec-core/pattern/regex.js.map +1 -0
- package/dist/vendor/spec-core/text/index.d.ts +2 -0
- package/dist/vendor/spec-core/text/index.js +2 -0
- package/dist/vendor/spec-core/text/index.js.map +1 -0
- package/dist/vendor/spec-core/text/lines.d.ts +72 -0
- package/dist/vendor/spec-core/text/lines.js +156 -0
- package/dist/vendor/spec-core/text/lines.js.map +1 -0
- package/package.json +3 -2
- package/schema.json +17 -0
- package/src/vendor/spec-core/LICENSE +21 -0
- package/src/vendor/spec-core/README.md +8 -0
package/dist/glob.js
CHANGED
|
@@ -1,465 +1,136 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Scopes as globs: spec-core's `path` dialect, read the way spec-brief reads a
|
|
3
|
+
* brief.
|
|
3
4
|
*
|
|
4
|
-
* The
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* an extension, which matches only itself. A trailing `/` always means a
|
|
9
|
-
* directory. Paths
|
|
10
|
-
* are repository-relative and POSIX, and matching is case-sensitive on every
|
|
11
|
-
* host, because git's paths are and a result should not depend on who ran it.
|
|
12
|
-
* `*` matches a leading dot; a scope that forgot its dotfiles is not a scope
|
|
13
|
-
* that excludes them.
|
|
5
|
+
* The syntax, the automaton and the witness search are spec-core's, copied
|
|
6
|
+
* into `src/vendor/` and verified by hash, so the family's tools read one
|
|
7
|
+
* pattern the same way (spec-core ADR-0003). What is decided here is what
|
|
8
|
+
* spec-brief adds around it:
|
|
14
9
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
10
|
+
* - A pattern is relative to the repository root, so a leading `/` is refused
|
|
11
|
+
* rather than rooted at the filesystem; and a list entry's `!` has no list to
|
|
12
|
+
* belong to in a scope, so it is refused rather than read.
|
|
13
|
+
* - Paths compare case-sensitively on every host, as git's do.
|
|
14
|
+
* - A path with no glob syntax is read as the tree reads it: a file the tree
|
|
15
|
+
* holds is that file, a directory it holds is everything beneath it, and a
|
|
16
|
+
* path it does not hold is a file, unless it is written with a trailing `/`.
|
|
17
|
+
* The tree, not the spelling, knows that `Dockerfile` is a file and
|
|
18
|
+
* `docs/v1.2` a directory (ADR-0005, amended 2026-09-26).
|
|
19
|
+
* - A trailing `/**` is at least one segment, so `docs/**` is the directory's
|
|
20
|
+
* contents and a collision witness is always a file.
|
|
21
|
+
*
|
|
22
|
+
* Nothing here compiles to a `RegExp`. Matching costs at most the pattern's
|
|
23
|
+
* size times the path's length, and a question about two scopes that cannot be
|
|
24
|
+
* answered within the search's budget answers `undecided`, never a guess.
|
|
20
25
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
/** A name with an extension, `login.ts` or `.eslintrc.json`, and not a dot-directory such as `.github`. */
|
|
26
|
-
function hasExtension(name) {
|
|
27
|
-
return /.\.[^.]+$/.test(name);
|
|
28
|
-
}
|
|
26
|
+
import { AutomatonTooLarge, globCovers as coreCovers, globWitness as coreWitness, isGlobSyntax, parseGlob as coreParse, WITNESS_BUDGET, } from './vendor/spec-core/pattern/index.js';
|
|
27
|
+
import { segments } from './vendor/spec-core/path/index.js';
|
|
28
|
+
export { isGlobSyntax, WITNESS_BUDGET };
|
|
29
|
+
/** Parses a scope pattern, or says why it cannot. */
|
|
29
30
|
export function parseGlob(source, options = {}) {
|
|
30
|
-
|
|
31
|
-
if (pattern.length === 0)
|
|
32
|
-
return { ok: false, error: 'the pattern is empty' };
|
|
31
|
+
const pattern = source.trim();
|
|
33
32
|
if (pattern.startsWith('!'))
|
|
34
33
|
return { ok: false, error: 'negated patterns are not supported; narrow the positive pattern' };
|
|
35
34
|
if (pattern.startsWith('/'))
|
|
36
35
|
return { ok: false, error: 'a pattern is relative to the repository root and cannot start with "/"' };
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const segments = [];
|
|
48
|
-
for (const raw of alternative.split('/')) {
|
|
49
|
-
if (raw === '' || raw === '.')
|
|
50
|
-
continue;
|
|
51
|
-
if (raw === '..')
|
|
52
|
-
return { ok: false, error: 'a pattern cannot leave the repository with ".."' };
|
|
53
|
-
if (raw === '**') {
|
|
54
|
-
if (segments[segments.length - 1]?.kind !== 'globstar')
|
|
55
|
-
segments.push(GLOBSTAR);
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
const tokens = tokenize(raw);
|
|
59
|
-
if (typeof tokens === 'string')
|
|
60
|
-
return { ok: false, error: tokens };
|
|
61
|
-
segments.push({ kind: 'pattern', tokens, literal: tokens.every((t) => t.kind === 'literal') });
|
|
62
|
-
}
|
|
63
|
-
if (segments.length === 0)
|
|
64
|
-
return { ok: false, error: 'the pattern names no path' };
|
|
65
|
-
// A path with no glob syntax names a directory and everything beneath it,
|
|
66
|
-
// unless it names a file: one the tree has, or one with an extension.
|
|
67
|
-
// Treating a file as a directory would let "src/a.ts" overlap
|
|
68
|
-
// "**/session.ts" through a "src/a.ts/session.ts" nobody can create.
|
|
69
|
-
if (segments.every((s) => s.kind === 'pattern' && s.literal)) {
|
|
70
|
-
const path = segments
|
|
71
|
-
.map((s) => (s.kind === 'pattern' ? s.tokens.map((t) => (t.kind === 'literal' ? t.char : '')).join('') : ''))
|
|
72
|
-
.join('/');
|
|
73
|
-
const last = path.slice(path.lastIndexOf('/') + 1);
|
|
74
|
-
if (options.isFile?.(path) !== true && !hasExtension(last))
|
|
75
|
-
segments.push(GLOBSTAR);
|
|
76
|
-
}
|
|
77
|
-
alternatives.push(segments);
|
|
78
|
-
}
|
|
79
|
-
return { ok: true, glob: { source, alternatives } };
|
|
80
|
-
}
|
|
81
|
-
/** Expands `{a,b}` groups, innermost last, or says why the pattern is malformed. */
|
|
82
|
-
function expandBraces(pattern) {
|
|
83
|
-
let open = -1;
|
|
84
|
-
let depth = 0;
|
|
85
|
-
for (let i = 0; i < pattern.length; i += 1) {
|
|
86
|
-
const ch = pattern.charAt(i);
|
|
87
|
-
if (ch === '\\') {
|
|
88
|
-
i += 1;
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
if (ch === '[') {
|
|
92
|
-
const close = classEnd(pattern, i);
|
|
93
|
-
if (close > 0)
|
|
94
|
-
i = close;
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
if (ch === '{') {
|
|
98
|
-
if (depth === 0)
|
|
99
|
-
open = i;
|
|
100
|
-
depth += 1;
|
|
101
|
-
}
|
|
102
|
-
else if (ch === '}') {
|
|
103
|
-
if (depth === 0)
|
|
104
|
-
return 'a "}" closes no "{"';
|
|
105
|
-
depth -= 1;
|
|
106
|
-
if (depth === 0) {
|
|
107
|
-
const options = splitTopLevel(pattern.slice(open + 1, i));
|
|
108
|
-
const prefix = pattern.slice(0, open);
|
|
109
|
-
const suffix = pattern.slice(i + 1);
|
|
110
|
-
const results = [];
|
|
111
|
-
for (const option of options) {
|
|
112
|
-
const expanded = expandBraces(`${prefix}${option}${suffix}`);
|
|
113
|
-
if (typeof expanded === 'string')
|
|
114
|
-
return expanded;
|
|
115
|
-
results.push(...expanded);
|
|
116
|
-
if (results.length > MAX_ALTERNATIVES)
|
|
117
|
-
return `the braces expand to more than ${MAX_ALTERNATIVES} patterns`;
|
|
118
|
-
}
|
|
119
|
-
return results;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (depth > 0)
|
|
124
|
-
return 'a "{" is never closed';
|
|
125
|
-
return [pattern];
|
|
126
|
-
}
|
|
127
|
-
function splitTopLevel(body) {
|
|
128
|
-
const parts = [];
|
|
129
|
-
let depth = 0;
|
|
130
|
-
let start = 0;
|
|
131
|
-
for (let i = 0; i < body.length; i += 1) {
|
|
132
|
-
const ch = body.charAt(i);
|
|
133
|
-
if (ch === '\\')
|
|
134
|
-
i += 1;
|
|
135
|
-
else if (ch === '{')
|
|
136
|
-
depth += 1;
|
|
137
|
-
else if (ch === '}')
|
|
138
|
-
depth -= 1;
|
|
139
|
-
else if (ch === ',' && depth === 0) {
|
|
140
|
-
parts.push(body.slice(start, i));
|
|
141
|
-
start = i + 1;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
parts.push(body.slice(start));
|
|
145
|
-
return parts;
|
|
146
|
-
}
|
|
147
|
-
/** The index of the `]` closing a class opened at `open`, or -1. */
|
|
148
|
-
function classEnd(pattern, open) {
|
|
149
|
-
let i = open + 1;
|
|
150
|
-
if (pattern.charAt(i) === '!' || pattern.charAt(i) === '^')
|
|
151
|
-
i += 1;
|
|
152
|
-
if (pattern.charAt(i) === ']')
|
|
153
|
-
i += 1;
|
|
154
|
-
for (; i < pattern.length; i += 1) {
|
|
155
|
-
const ch = pattern.charAt(i);
|
|
156
|
-
if (ch === '/')
|
|
157
|
-
return -1;
|
|
158
|
-
if (ch === ']')
|
|
159
|
-
return i;
|
|
160
|
-
}
|
|
161
|
-
return -1;
|
|
162
|
-
}
|
|
163
|
-
function tokenize(segment) {
|
|
164
|
-
const tokens = [];
|
|
165
|
-
const chars = Array.from(segment);
|
|
166
|
-
for (let i = 0; i < chars.length; i += 1) {
|
|
167
|
-
const ch = chars[i];
|
|
168
|
-
if (ch === '\\') {
|
|
169
|
-
const next = chars[i + 1];
|
|
170
|
-
// A backslash before a letter, a digit or nothing is a Windows separator, not an escape.
|
|
171
|
-
if (next === undefined || /[A-Za-z0-9]/.test(next))
|
|
172
|
-
return '"\\" escapes glob syntax; separate directories with "/"';
|
|
173
|
-
tokens.push({ kind: 'literal', char: next });
|
|
174
|
-
i += 1;
|
|
175
|
-
}
|
|
176
|
-
else if (ch === '*') {
|
|
177
|
-
if (tokens[tokens.length - 1]?.kind !== 'star')
|
|
178
|
-
tokens.push({ kind: 'star' });
|
|
179
|
-
}
|
|
180
|
-
else if (ch === '?') {
|
|
181
|
-
tokens.push({ kind: 'any' });
|
|
182
|
-
}
|
|
183
|
-
else if (ch === '[') {
|
|
184
|
-
const parsed = readClass(chars, i);
|
|
185
|
-
if (typeof parsed === 'string')
|
|
186
|
-
return parsed;
|
|
187
|
-
tokens.push(parsed.token);
|
|
188
|
-
i = parsed.end;
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
tokens.push({ kind: 'literal', char: ch });
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return tokens;
|
|
195
|
-
}
|
|
196
|
-
function readClass(chars, open) {
|
|
197
|
-
let i = open + 1;
|
|
198
|
-
let negated = false;
|
|
199
|
-
if (chars[i] === '!' || chars[i] === '^') {
|
|
200
|
-
negated = true;
|
|
201
|
-
i += 1;
|
|
202
|
-
}
|
|
203
|
-
const ranges = [];
|
|
204
|
-
let first = true;
|
|
205
|
-
for (; i < chars.length; i += 1) {
|
|
206
|
-
const ch = chars[i];
|
|
207
|
-
// Never empty: the first character is always a member, even "]".
|
|
208
|
-
if (ch === ']' && !first)
|
|
209
|
-
return { token: { kind: 'class', negated, ranges }, end: i };
|
|
210
|
-
first = false;
|
|
211
|
-
let lo = ch;
|
|
212
|
-
if (ch === '\\' && chars[i + 1] !== undefined) {
|
|
213
|
-
i += 1;
|
|
214
|
-
lo = chars[i];
|
|
215
|
-
}
|
|
216
|
-
let hi = lo;
|
|
217
|
-
if (chars[i + 1] === '-' && chars[i + 2] !== undefined && chars[i + 2] !== ']') {
|
|
218
|
-
hi = chars[i + 2];
|
|
219
|
-
i += 2;
|
|
220
|
-
}
|
|
221
|
-
const from = lo.codePointAt(0);
|
|
222
|
-
const to = hi.codePointAt(0);
|
|
223
|
-
if (to < from)
|
|
224
|
-
return `the range "${lo}-${hi}" runs backwards`;
|
|
225
|
-
ranges.push([from, to]);
|
|
226
|
-
}
|
|
227
|
-
return 'a "[" is never closed';
|
|
228
|
-
}
|
|
229
|
-
function splitPath(path) {
|
|
230
|
-
return path.split('/').filter((s) => s !== '' && s !== '.');
|
|
231
|
-
}
|
|
232
|
-
/** Membership of a class. A slash is never asked about: paths are matched a segment at a time. */
|
|
233
|
-
function inClass(token, point) {
|
|
234
|
-
const inside = token.ranges.some(([lo, hi]) => point >= lo && point <= hi);
|
|
235
|
-
return token.negated ? !inside : inside;
|
|
236
|
-
}
|
|
237
|
-
function charMatches(token, char) {
|
|
238
|
-
switch (token.kind) {
|
|
239
|
-
case 'literal':
|
|
240
|
-
return token.char === char;
|
|
241
|
-
case 'any':
|
|
242
|
-
return true;
|
|
243
|
-
case 'class':
|
|
244
|
-
return inClass(token, char.codePointAt(0));
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
/** Whether one segment of a path matches a pattern segment's tokens. */
|
|
248
|
-
function tokensMatch(tokens, subject) {
|
|
249
|
-
const chars = Array.from(subject);
|
|
250
|
-
// next[j]: tokens[i+1..] match chars[j..]; filled from the end of both.
|
|
251
|
-
let next = new Array(chars.length + 1).fill(false);
|
|
252
|
-
next[chars.length] = true;
|
|
253
|
-
for (let i = tokens.length - 1; i >= 0; i -= 1) {
|
|
254
|
-
const token = tokens[i];
|
|
255
|
-
const row = new Array(chars.length + 1).fill(false);
|
|
256
|
-
for (let j = chars.length; j >= 0; j -= 1) {
|
|
257
|
-
row[j] =
|
|
258
|
-
token.kind === 'star'
|
|
259
|
-
? next[j] || (j < chars.length && row[j + 1])
|
|
260
|
-
: j < chars.length && charMatches(token, chars[j]) && next[j + 1];
|
|
261
|
-
}
|
|
262
|
-
next = row;
|
|
36
|
+
const given = options.literal ?? 'file';
|
|
37
|
+
const literals = [];
|
|
38
|
+
const literal = (path) => {
|
|
39
|
+
const reading = typeof given === 'function' ? given(path) : given;
|
|
40
|
+
literals.push({ path, reading });
|
|
41
|
+
return reading;
|
|
42
|
+
};
|
|
43
|
+
try {
|
|
44
|
+
const parsed = coreParse(pattern, { dialect: 'path', caseSensitive: true, literal });
|
|
45
|
+
return parsed.ok ? { ok: true, glob: { source, compiled: parsed.glob, literals } } : parsed;
|
|
263
46
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
for (let i = segments.length - 1; i >= 0; i -= 1) {
|
|
270
|
-
const segment = segments[i];
|
|
271
|
-
const row = new Array(path.length + 1).fill(false);
|
|
272
|
-
for (let j = path.length; j >= 0; j -= 1) {
|
|
273
|
-
row[j] =
|
|
274
|
-
segment.kind === 'globstar'
|
|
275
|
-
? next[j] || (j < path.length && row[j + 1])
|
|
276
|
-
: j < path.length && tokensMatch(segment.tokens, path[j]) && next[j + 1];
|
|
277
|
-
}
|
|
278
|
-
next = row;
|
|
47
|
+
catch (error) {
|
|
48
|
+
// Too many states is a pattern nobody meant; anything else is a defect to surface.
|
|
49
|
+
if (error instanceof AutomatonTooLarge)
|
|
50
|
+
return { ok: false, error: error.message };
|
|
51
|
+
throw error;
|
|
279
52
|
}
|
|
280
|
-
return next[0];
|
|
281
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Whether a path matches. The path is read as a repository path: empty and
|
|
56
|
+
* `.` segments are dropped, so `./src//a.ts` is `src/a.ts`.
|
|
57
|
+
*/
|
|
282
58
|
export function matchGlob(glob, path) {
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
return false;
|
|
286
|
-
return glob.alternatives.some((alternative) => segmentsMatch(alternative, segments));
|
|
59
|
+
const canonical = segments(path).join('/');
|
|
60
|
+
return canonical !== '' && glob.compiled.match(canonical);
|
|
287
61
|
}
|
|
288
62
|
/**
|
|
289
|
-
* A path
|
|
290
|
-
*
|
|
291
|
-
*
|
|
63
|
+
* A shortest path every glob in `include` matches and none in `exclude` does,
|
|
64
|
+
* `none` as a proof that there is no such path, or `undecided` when the search
|
|
65
|
+
* met its budget first.
|
|
292
66
|
*/
|
|
293
|
-
export function
|
|
294
|
-
|
|
295
|
-
for (const right of b.alternatives) {
|
|
296
|
-
const witness = intersectSegments(left, right);
|
|
297
|
-
if (witness !== null)
|
|
298
|
-
return witness.length === 0 ? 'x' : witness.join('/');
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return null;
|
|
67
|
+
export function globWitness(include, exclude = [], budget = WITNESS_BUDGET) {
|
|
68
|
+
return coreWitness(include.map((g) => g.compiled), exclude.map((g) => g.compiled), budget);
|
|
302
69
|
}
|
|
303
|
-
|
|
304
|
-
function
|
|
305
|
-
|
|
306
|
-
const width = q.length + 1;
|
|
307
|
-
const visit = (i, j) => {
|
|
308
|
-
const key = i * width + j;
|
|
309
|
-
const cached = memo.get(key);
|
|
310
|
-
if (cached !== undefined)
|
|
311
|
-
return cached;
|
|
312
|
-
const result = step(i, j);
|
|
313
|
-
memo.set(key, result);
|
|
314
|
-
return result;
|
|
315
|
-
};
|
|
316
|
-
const prepend = (head, tail) => head === null || tail === null ? null : [head, ...tail];
|
|
317
|
-
const step = (i, j) => {
|
|
318
|
-
if (i === p.length && j === q.length)
|
|
319
|
-
return [];
|
|
320
|
-
const x = p[i];
|
|
321
|
-
const y = q[j];
|
|
322
|
-
if (x?.kind === 'globstar') {
|
|
323
|
-
const skip = visit(i + 1, j);
|
|
324
|
-
if (skip !== null)
|
|
325
|
-
return skip;
|
|
326
|
-
if (y === undefined)
|
|
327
|
-
return null;
|
|
328
|
-
if (y.kind === 'globstar')
|
|
329
|
-
return visit(i, j + 1);
|
|
330
|
-
return prepend(intersectTokens(y.tokens, ANY_SEGMENT), visit(i, j + 1));
|
|
331
|
-
}
|
|
332
|
-
if (y?.kind === 'globstar') {
|
|
333
|
-
const skip = visit(i, j + 1);
|
|
334
|
-
if (skip !== null)
|
|
335
|
-
return skip;
|
|
336
|
-
if (x === undefined)
|
|
337
|
-
return null;
|
|
338
|
-
return prepend(intersectTokens(x.tokens, ANY_SEGMENT), visit(i + 1, j));
|
|
339
|
-
}
|
|
340
|
-
if (x === undefined || y === undefined)
|
|
341
|
-
return null;
|
|
342
|
-
const head = intersectTokens(x.tokens, y.tokens);
|
|
343
|
-
return head === null ? null : prepend(head, visit(i + 1, j + 1));
|
|
344
|
-
};
|
|
345
|
-
return visit(0, 0);
|
|
70
|
+
/** Whether every path `inner` matches is matched by one of `outer`, or `undecided`. */
|
|
71
|
+
export function globCovers(outer, inner, budget = WITNESS_BUDGET) {
|
|
72
|
+
return coreCovers(outer.map((g) => g.compiled), inner.compiled, budget);
|
|
346
73
|
}
|
|
347
74
|
/**
|
|
348
|
-
* A
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* character is absorbed by a star on both sides at once (dropping it would
|
|
352
|
-
* leave a shorter one), so every character advances at least one sequence,
|
|
353
|
-
* and the five moves below are all the ways that can happen.
|
|
75
|
+
* A path both globs match, or `null` when there is none: the 0.1 interface,
|
|
76
|
+
* kept for the library's callers. It cannot say `undecided`, so a search that
|
|
77
|
+
* meets its budget throws; {@link globWitness} answers in three ways.
|
|
354
78
|
*/
|
|
355
|
-
export function
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const key = i * width + j;
|
|
360
|
-
const cached = memo.get(key);
|
|
361
|
-
if (cached !== undefined)
|
|
362
|
-
return cached;
|
|
363
|
-
const result = step(i, j);
|
|
364
|
-
memo.set(key, result);
|
|
365
|
-
return result;
|
|
366
|
-
};
|
|
367
|
-
const prepend = (head, tail) => head === null || tail === null ? null : head + tail;
|
|
368
|
-
const step = (i, j) => {
|
|
369
|
-
if (i === x.length && j === y.length)
|
|
370
|
-
return '';
|
|
371
|
-
const a = x[i];
|
|
372
|
-
const b = y[j];
|
|
373
|
-
if (a?.kind === 'star') {
|
|
374
|
-
const skip = visit(i + 1, j);
|
|
375
|
-
if (skip !== null)
|
|
376
|
-
return skip;
|
|
377
|
-
if (b === undefined)
|
|
378
|
-
return null;
|
|
379
|
-
if (b.kind === 'star')
|
|
380
|
-
return visit(i, j + 1);
|
|
381
|
-
return prepend(witnessChar(b), visit(i, j + 1));
|
|
382
|
-
}
|
|
383
|
-
if (b?.kind === 'star') {
|
|
384
|
-
const skip = visit(i, j + 1);
|
|
385
|
-
if (skip !== null)
|
|
386
|
-
return skip;
|
|
387
|
-
if (a === undefined)
|
|
388
|
-
return null;
|
|
389
|
-
return prepend(witnessChar(a), visit(i + 1, j));
|
|
390
|
-
}
|
|
391
|
-
if (a === undefined || b === undefined)
|
|
392
|
-
return null;
|
|
393
|
-
return prepend(meet(a, b), visit(i + 1, j + 1));
|
|
394
|
-
};
|
|
395
|
-
const witness = visit(0, 0);
|
|
396
|
-
// Empty only when both sides are nothing but stars, and then any name will do.
|
|
397
|
-
return witness === '' ? 'x' : witness;
|
|
398
|
-
}
|
|
399
|
-
function accepts(token, point) {
|
|
400
|
-
if (point === SLASH)
|
|
401
|
-
return false;
|
|
402
|
-
switch (token.kind) {
|
|
403
|
-
case 'literal':
|
|
404
|
-
return token.char.codePointAt(0) === point;
|
|
405
|
-
case 'any':
|
|
406
|
-
return true;
|
|
407
|
-
case 'class':
|
|
408
|
-
return inClass(token, point);
|
|
79
|
+
export function intersectGlobs(a, b, budget = WITNESS_BUDGET) {
|
|
80
|
+
const witness = globWitness([a, b], [], budget);
|
|
81
|
+
if (witness.kind === 'undecided') {
|
|
82
|
+
throw new Error(`whether "${a.source}" and "${b.source}" meet is undecided within the search's budget`);
|
|
409
83
|
}
|
|
84
|
+
return witness.kind === 'found' ? witness.path : null;
|
|
410
85
|
}
|
|
411
86
|
/**
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
* class its ranges, a negated class and `?` everything outside theirs. Less the
|
|
416
|
-
* slash. The least character of an intersection of such sets is the left end
|
|
417
|
-
* of one of its intervals, so it is one of these points, and checking them
|
|
418
|
-
* all decides the intersection exactly. `0` is also the point after the
|
|
419
|
-
* slash, and readable characters go first, so that a witness is one a person
|
|
420
|
-
* would type.
|
|
87
|
+
* The directories a walk must enter to find every match, one per brace
|
|
88
|
+
* alternative: the leading literal segments, or a literal file's directory.
|
|
89
|
+
* `''` for an alternative that can match at the root.
|
|
421
90
|
*/
|
|
422
|
-
function
|
|
423
|
-
|
|
424
|
-
for (const token of tokens) {
|
|
425
|
-
if (token.kind === 'literal')
|
|
426
|
-
points.push(token.char.codePointAt(0));
|
|
427
|
-
if (token.kind === 'class') {
|
|
428
|
-
for (const [lo, hi] of token.ranges)
|
|
429
|
-
points.push(lo, hi + 1);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
return points;
|
|
91
|
+
export function globBases(glob) {
|
|
92
|
+
return glob.compiled.bases;
|
|
433
93
|
}
|
|
434
|
-
/**
|
|
435
|
-
function
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
return String.fromCodePoint(point);
|
|
439
|
-
}
|
|
440
|
-
return null;
|
|
94
|
+
/** The first of {@link globBases}: the 0.1 interface, kept for the library's callers. */
|
|
95
|
+
export function globBase(glob) {
|
|
96
|
+
// One base per alternative, and a pattern has at least one.
|
|
97
|
+
return glob.compiled.bases[0];
|
|
441
98
|
}
|
|
442
|
-
|
|
443
|
-
|
|
99
|
+
/** A name with an extension, `login.ts` or `.eslintrc.json`, and not a dot-name such as `.github`. */
|
|
100
|
+
export function hasExtension(name) {
|
|
101
|
+
return /.\.[^.]+$/.test(name);
|
|
102
|
+
}
|
|
103
|
+
const trees = new WeakMap();
|
|
104
|
+
/** The tree a list of files describes. Built once per list, which every brief of a run shares. */
|
|
105
|
+
export function treeOf(files) {
|
|
106
|
+
const known = trees.get(files);
|
|
107
|
+
if (known !== undefined)
|
|
108
|
+
return known;
|
|
109
|
+
const directories = new Set();
|
|
110
|
+
for (const file of files) {
|
|
111
|
+
for (let slash = file.indexOf('/'); slash !== -1; slash = file.indexOf('/', slash + 1))
|
|
112
|
+
directories.add(file.slice(0, slash));
|
|
113
|
+
}
|
|
114
|
+
const tree = { files: new Set(files), directories };
|
|
115
|
+
trees.set(files, tree);
|
|
116
|
+
return tree;
|
|
444
117
|
}
|
|
445
|
-
|
|
446
|
-
|
|
118
|
+
/** What the tree holds at a path: a file, a directory, or nothing. */
|
|
119
|
+
export function held(tree, path) {
|
|
120
|
+
if (tree.files.has(path))
|
|
121
|
+
return 'file';
|
|
122
|
+
return tree.directories.has(path) ? 'directory' : null;
|
|
447
123
|
}
|
|
448
124
|
/**
|
|
449
|
-
*
|
|
450
|
-
*
|
|
125
|
+
* How a tree reads a literal path: a directory it holds is a directory, and
|
|
126
|
+
* anything else is a file. Without a tree nothing is known to be a directory,
|
|
127
|
+
* so every literal is a file; a trailing `/` says otherwise, and never reaches
|
|
128
|
+
* this question.
|
|
451
129
|
*/
|
|
452
|
-
export function
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
break;
|
|
458
|
-
literal.push(segment.tokens.map((t) => (t.kind === 'literal' ? t.char : '')).join(''));
|
|
459
|
-
}
|
|
460
|
-
// A literal file path is rooted in its directory; a literal directory in itself.
|
|
461
|
-
if (literal.length === first.length)
|
|
462
|
-
literal.pop();
|
|
463
|
-
return literal.join('/');
|
|
130
|
+
export function readingIn(files) {
|
|
131
|
+
if (files === null)
|
|
132
|
+
return () => 'file';
|
|
133
|
+
const tree = treeOf(files);
|
|
134
|
+
return (path) => (held(tree, path) === 'directory' ? 'directory' : 'file');
|
|
464
135
|
}
|
|
465
136
|
//# sourceMappingURL=glob.js.map
|