@descent-vtt/spec-harness 0.5.0 → 0.7.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +148 -0
- package/README.md +273 -24
- package/dist/audit.d.ts +71 -1
- package/dist/audit.js +140 -17
- package/dist/audit.js.map +1 -1
- package/dist/branch.d.ts +13 -0
- package/dist/branch.js +19 -0
- package/dist/branch.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +51 -13
- package/dist/cli.js.map +1 -1
- package/dist/commands.js +13 -4
- package/dist/commands.js.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/configure.d.ts +50 -3
- package/dist/configure.js +78 -5
- package/dist/configure.js.map +1 -1
- package/dist/context.d.ts +21 -7
- package/dist/context.js +104 -31
- package/dist/context.js.map +1 -1
- package/dist/document.d.ts +2 -1
- package/dist/document.js.map +1 -1
- package/dist/formats.d.ts +31 -0
- package/dist/formats.js +127 -0
- package/dist/formats.js.map +1 -0
- package/dist/git.d.ts +6 -0
- package/dist/git.js +8 -0
- package/dist/git.js.map +1 -1
- package/dist/guard.d.ts +8 -0
- package/dist/guard.js +19 -1
- package/dist/guard.js.map +1 -1
- package/dist/host.d.ts +14 -0
- package/dist/host.js +50 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/manifests.d.ts +8 -1
- package/dist/manifests.js +16 -6
- package/dist/manifests.js.map +1 -1
- package/dist/premises.d.ts +3 -0
- package/dist/premises.js +43 -13
- package/dist/premises.js.map +1 -1
- package/dist/reader.js +10 -2
- package/dist/reader.js.map +1 -1
- package/dist/round.d.ts +8 -0
- package/dist/round.js +73 -22
- package/dist/round.js.map +1 -1
- package/dist/server.js +11 -3
- package/dist/server.js.map +1 -1
- package/dist/setup.d.ts +10 -0
- package/dist/setup.js +68 -32
- package/dist/setup.js.map +1 -1
- package/dist/signers.d.ts +43 -0
- package/dist/signers.js +105 -0
- package/dist/signers.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.js.map +1 -1
- package/dist/vendor/spec-core/markdown/frontmatter.d.ts +11 -1
- package/dist/vendor/spec-core/markdown/frontmatter.js +20 -3
- package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -1
- package/dist/vendor/spec-core/pattern/glob.d.ts +88 -2
- package/dist/vendor/spec-core/pattern/glob.js +234 -32
- package/dist/vendor/spec-core/pattern/glob.js.map +1 -1
- package/dist/vendor/spec-core/pattern/index.d.ts +2 -2
- package/dist/vendor/spec-core/pattern/index.js +1 -1
- package/dist/vendor/spec-core/pattern/index.js.map +1 -1
- package/dist/vendor/spec-core/text/index.d.ts +1 -0
- package/dist/vendor/spec-core/text/index.js +1 -0
- package/dist/vendor/spec-core/text/index.js.map +1 -1
- package/dist/vendor/spec-core/text/width.d.ts +30 -0
- package/dist/vendor/spec-core/text/width.js +113 -0
- package/dist/vendor/spec-core/text/width.js.map +1 -0
- package/dist/versions.d.ts +27 -1
- package/dist/versions.js +23 -1
- package/dist/versions.js.map +1 -1
- package/dist/workspace.d.ts +6 -1
- package/dist/workspace.js +17 -6
- package/dist/workspace.js.map +1 -1
- package/package.json +1 -1
- package/skills/close-round/SKILL.md +4 -1
|
@@ -29,8 +29,11 @@
|
|
|
29
29
|
* - Braces expand first, and each alternative reads as it would written on
|
|
30
30
|
* its own: `{src/,lib}` is `src/` or `lib`, and a trailing `/` means the
|
|
31
31
|
* directory's contents - in an exclusion, the directory and its contents.
|
|
32
|
-
*
|
|
33
|
-
* the
|
|
32
|
+
* `{/docs,lib}` is `/docs` or `lib`, the leading `/` rooting or anchoring
|
|
33
|
+
* that alternative as it would the pattern. One that names no path,
|
|
34
|
+
* `{./,lib}` or `{,lib}`, is refused, as `./` and the empty pattern are.
|
|
35
|
+
* - A leading `./` is the current directory, and goes with the slashes after
|
|
36
|
+
* it, as POSIX reads them: `.//docs` is `docs`, and never `/docs`.
|
|
34
37
|
* - Case is the caller's decision, stated every time. A result must not
|
|
35
38
|
* depend on the host it ran on.
|
|
36
39
|
* - A malformed pattern is an error, never a literal. An unclosed `[` or `{`,
|
|
@@ -133,3 +136,86 @@ export type GlobListParse = {
|
|
|
133
136
|
* deciding the paths it matches, a leading `!` taking them back out.
|
|
134
137
|
*/
|
|
135
138
|
export declare function parseGlobList(patterns: readonly string[], options: GlobOptions): GlobListParse;
|
|
139
|
+
/** One text a pattern's braces give, as every dialect reads its start. */
|
|
140
|
+
export interface GlobAlternative {
|
|
141
|
+
/**
|
|
142
|
+
* Led by a `/`, the pattern's or its own with no `./` of the pattern's
|
|
143
|
+
* before it: rooted at the filesystem's root in the `path` and `ripgrep`
|
|
144
|
+
* dialects, and anchored at the repository root in `gitignore`.
|
|
145
|
+
*/
|
|
146
|
+
readonly rooted: boolean;
|
|
147
|
+
/**
|
|
148
|
+
* The text without the `./` and the slashes it starts with, the pattern's
|
|
149
|
+
* included, and with each `}` and `,` no group took written as a class of
|
|
150
|
+
* that one character, so that it reads the same inside braces again. Alone,
|
|
151
|
+
* it reads the same behind a `/` when rooted and behind a `./` when not,
|
|
152
|
+
* which keeps a `!` it starts with a character. A trailing `/` stays: what
|
|
153
|
+
* it means is the dialect's.
|
|
154
|
+
*/
|
|
155
|
+
readonly text: string;
|
|
156
|
+
}
|
|
157
|
+
export type GlobAlternatives = {
|
|
158
|
+
readonly ok: true;
|
|
159
|
+
/** Led by a `/` of its own, before any `./`, which roots every alternative. */
|
|
160
|
+
readonly rooted: boolean;
|
|
161
|
+
/** In the order the braces give them. */
|
|
162
|
+
readonly alternatives: readonly GlobAlternative[];
|
|
163
|
+
} | {
|
|
164
|
+
readonly ok: false;
|
|
165
|
+
readonly error: string;
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* The alternatives a pattern's braces give, each read at its start as
|
|
169
|
+
* {@link parseGlob} reads it, for a tool that reads a pattern's alternatives
|
|
170
|
+
* one by one - to anchor a rooted one, to hand each to another engine - and
|
|
171
|
+
* would otherwise expand braces and read a leading `./` and `/` again itself.
|
|
172
|
+
*
|
|
173
|
+
* Braces expand first, and an alternative is rooted by a leading `/` on the
|
|
174
|
+
* pattern, or on the text the braces give when no `./` of the pattern's stands
|
|
175
|
+
* before it: `{/docs,x}` is `docs` rooted and `x`; `./{/docs,x}` and
|
|
176
|
+
* `{.//docs,x}` are `docs` and `x`, neither rooted; `a/{/b,c}` is `a//b` and
|
|
177
|
+
* `a/c`. A `!` after a `./` is a character, as it is to `parseGlob`: `./!a` is
|
|
178
|
+
* the name `!a`.
|
|
179
|
+
*
|
|
180
|
+
* A reading, not a verdict: refused are only what `parseGlob` refuses before
|
|
181
|
+
* it reads an alternative, in its words, and braces that do not expand. An
|
|
182
|
+
* alternative `parseGlob` refuses - `{/,x}` gives `/` - is given as read.
|
|
183
|
+
*/
|
|
184
|
+
export declare function globAlternatives(source: string, options?: Pick<GlobOptions, 'backslash'>): GlobAlternatives;
|
|
185
|
+
export type GlobRebase = {
|
|
186
|
+
readonly ok: true;
|
|
187
|
+
readonly pattern: string;
|
|
188
|
+
} | {
|
|
189
|
+
readonly ok: false;
|
|
190
|
+
readonly error: string;
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* A list entry typed in a directory below the root, rewritten to read from
|
|
194
|
+
* the root as the `path` dialect reads it: for a tool run in a subdirectory
|
|
195
|
+
* that matches whole paths from the root, where what is typed means what it
|
|
196
|
+
* names from where it was typed.
|
|
197
|
+
*
|
|
198
|
+
* `directory` is where the entry was typed, a path under the root; at the
|
|
199
|
+
* root itself, `''`, there is nothing to rewrite. The entry is read as
|
|
200
|
+
* {@link parseGlobList} reads one: a leading `!` stays in front, and what
|
|
201
|
+
* follows is the pattern. Braces expand first, and each alternative is
|
|
202
|
+
* rebased as that text written alone would be:
|
|
203
|
+
*
|
|
204
|
+
* - one with a leading `/` is rooted at the filesystem's root, which no
|
|
205
|
+
* directory moves, and is kept: `/docs` is `/docs` typed anywhere;
|
|
206
|
+
* - any other gets the directory in front, a leading `..` climbing out of
|
|
207
|
+
* it: `docs/*.md` typed in `sub` is `sub/docs/*.md`, and `../*.md` typed in
|
|
208
|
+
* `docs/deep` is `docs/*.md`.
|
|
209
|
+
*
|
|
210
|
+
* When every alternative is rebased alike, the braces stay as written, `{a,b}`
|
|
211
|
+
* typed in `sub` being `sub/{a,b}`; when they are not, each is written out:
|
|
212
|
+
* `{/docs,x}` is `{/docs,sub/x}`.
|
|
213
|
+
*
|
|
214
|
+
* Refused, with the reason: what {@link parseGlob} refuses before it reads an
|
|
215
|
+
* alternative, in its words; braces that do not expand; a `..` that climbs
|
|
216
|
+
* above the root; a directory no pattern can name; and alternatives written
|
|
217
|
+
* out that hold a `,` or a `}`, which would read as braces. Anything else a
|
|
218
|
+
* rebased pattern holds is for `parseGlob` to refuse, as it would the same
|
|
219
|
+
* pattern typed at the root.
|
|
220
|
+
*/
|
|
221
|
+
export declare function rebaseGlob(entry: string, directory: string, options?: Pick<GlobOptions, 'backslash'>): GlobRebase;
|
|
@@ -29,8 +29,11 @@
|
|
|
29
29
|
* - Braces expand first, and each alternative reads as it would written on
|
|
30
30
|
* its own: `{src/,lib}` is `src/` or `lib`, and a trailing `/` means the
|
|
31
31
|
* directory's contents - in an exclusion, the directory and its contents.
|
|
32
|
-
*
|
|
33
|
-
* the
|
|
32
|
+
* `{/docs,lib}` is `/docs` or `lib`, the leading `/` rooting or anchoring
|
|
33
|
+
* that alternative as it would the pattern. One that names no path,
|
|
34
|
+
* `{./,lib}` or `{,lib}`, is refused, as `./` and the empty pattern are.
|
|
35
|
+
* - A leading `./` is the current directory, and goes with the slashes after
|
|
36
|
+
* it, as POSIX reads them: `.//docs` is `docs`, and never `/docs`.
|
|
34
37
|
* - Case is the caller's decision, stated every time. A result must not
|
|
35
38
|
* depend on the host it ran on.
|
|
36
39
|
* - A malformed pattern is an error, never a literal. An unclosed `[` or `{`,
|
|
@@ -74,34 +77,35 @@ export function compileGlob(source, options) {
|
|
|
74
77
|
throw new GlobError(source, result);
|
|
75
78
|
return result;
|
|
76
79
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
/**
|
|
81
|
+
* A pattern as every dialect takes it - trimmed, and each `\` a separator
|
|
82
|
+
* when the caller asks - or the reason it is refused before anything in it is
|
|
83
|
+
* read.
|
|
84
|
+
*/
|
|
85
|
+
function prepare(source, escapes) {
|
|
86
|
+
const trimmed = source.trim();
|
|
87
|
+
const pattern = escapes ? trimmed : trimmed.replace(/\\/g, '/');
|
|
83
88
|
if (pattern.length === 0)
|
|
84
89
|
return 'the pattern is empty';
|
|
85
90
|
if (pattern.startsWith('!'))
|
|
86
91
|
return 'a negated pattern is a list entry, not a glob; narrow the positive pattern';
|
|
87
92
|
if (/(?:^|[^\\])[?*+@!]\([^)]*\|/.test(pattern))
|
|
88
93
|
return EXTGLOB;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (pattern.length === 0)
|
|
94
|
+
return { pattern };
|
|
95
|
+
}
|
|
96
|
+
function build(source, options) {
|
|
97
|
+
const escapes = options.backslash !== 'separator';
|
|
98
|
+
const prepared = prepare(source, escapes);
|
|
99
|
+
if (typeof prepared === 'string')
|
|
100
|
+
return prepared;
|
|
101
|
+
const { pattern } = prepared;
|
|
102
|
+
// The pattern's own leading slash is taken off before the braces expand, so
|
|
103
|
+
// that the root itself, and a text the braces give, are refused in the
|
|
104
|
+
// words they always were: `/{./,a}` is told `./`.
|
|
105
|
+
const whole = unrooted(pattern);
|
|
106
|
+
if (whole.text.length === 0)
|
|
103
107
|
return 'the pattern names the root itself, not a path under it';
|
|
104
|
-
const expanded = expandBraces(
|
|
108
|
+
const expanded = expandBraces(whole.text, escapes);
|
|
105
109
|
if (typeof expanded === 'string')
|
|
106
110
|
return expanded;
|
|
107
111
|
const alternatives = [];
|
|
@@ -111,13 +115,20 @@ function build(source, options) {
|
|
|
111
115
|
// Each text the braces give is shorter than the pattern, so only a
|
|
112
116
|
// pattern without braces is its own text.
|
|
113
117
|
if (namesNoPath(text))
|
|
114
|
-
return text ===
|
|
115
|
-
|
|
118
|
+
return text === whole.text ? 'the pattern names no path' : bracesNamingNoPath(text);
|
|
119
|
+
// Its leading slash is read as the pattern's is, since braces expand
|
|
120
|
+
// before anything else is decided: `{/docs,x}` is `/docs` or `x`. Left to
|
|
121
|
+
// `parseAlternative`, the slash would be an empty segment, dropped, and
|
|
122
|
+
// `/docs` would read as `docs`. A slash after a segment, as in
|
|
123
|
+
// `a/{/b,c}`, starts no text, and is the empty segment of `a//b`; one
|
|
124
|
+
// after the pattern's `./` roots nothing either.
|
|
125
|
+
const alone = unrooted(text);
|
|
126
|
+
const parsed = parseAlternative(trailingSlash(alone.text, options.dialect), escapes);
|
|
116
127
|
if (parsed === GLOBSTAR_IN_NAME)
|
|
117
|
-
return `${GLOBSTAR_IN_NAME}: ${globstarAdvice(
|
|
128
|
+
return `${GLOBSTAR_IN_NAME}: ${globstarAdvice(pattern)}`;
|
|
118
129
|
if (typeof parsed === 'string')
|
|
119
130
|
return parsed;
|
|
120
|
-
alternatives.push(parsed);
|
|
131
|
+
alternatives.push({ ...parsed, rooted: rootedIn(whole, alone) });
|
|
121
132
|
}
|
|
122
133
|
const builder = new Builder();
|
|
123
134
|
const fragments = [];
|
|
@@ -125,7 +136,7 @@ function build(source, options) {
|
|
|
125
136
|
let automaton;
|
|
126
137
|
try {
|
|
127
138
|
for (const alternative of alternatives) {
|
|
128
|
-
const compiled = compileAlternative(builder, alternative, options
|
|
139
|
+
const compiled = compileAlternative(builder, alternative, options);
|
|
129
140
|
fragments.push(compiled.fragment);
|
|
130
141
|
bases.push(compiled.base);
|
|
131
142
|
}
|
|
@@ -261,6 +272,33 @@ function classEnd(pattern, open, escapes) {
|
|
|
261
272
|
return -1;
|
|
262
273
|
}
|
|
263
274
|
/* ---------------------------------------------------------------- segments */
|
|
275
|
+
/**
|
|
276
|
+
* A pattern, or a text its braces give, without the `./` and the slashes it
|
|
277
|
+
* starts with; whether a slash led it, which roots it; and whether a `./`
|
|
278
|
+
* did, which leaves every slash after it rootless. What a leading slash means
|
|
279
|
+
* is the dialect's: it roots a pattern at the filesystem's root in `path` and
|
|
280
|
+
* `ripgrep`, and only anchors it at the repository root in `gitignore`, as
|
|
281
|
+
* git reads one.
|
|
282
|
+
*
|
|
283
|
+
* The slashes after a `./` go with it, as POSIX reads `.//docs` as `./docs`:
|
|
284
|
+
* the current directory, then `docs`. Left behind, they would lead what is
|
|
285
|
+
* left and root it, and `.//docs` would be `/docs`. A slash the braces give
|
|
286
|
+
* after a pattern's `./` is one of them, so `./{/docs,x}`, which is
|
|
287
|
+
* `.//docs` or `./x`, roots neither.
|
|
288
|
+
*/
|
|
289
|
+
function unrooted(text) {
|
|
290
|
+
let rest = text;
|
|
291
|
+
while (rest.startsWith('./'))
|
|
292
|
+
rest = rest.slice(2).replace(/^\/+/, '');
|
|
293
|
+
return { text: rest.replace(/^\/+/, ''), rooted: rest.startsWith('/'), dotted: rest !== text };
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Whether a text the braces give is rooted: by the pattern's own leading
|
|
297
|
+
* slash, or by its own when the pattern's `./` does not stand before it.
|
|
298
|
+
*/
|
|
299
|
+
function rootedIn(whole, alone) {
|
|
300
|
+
return whole.rooted || (alone.rooted && !whole.dotted);
|
|
301
|
+
}
|
|
264
302
|
/** Whether a pattern's text names no path: nothing in it but `/` and `.` segments. */
|
|
265
303
|
function namesNoPath(text) {
|
|
266
304
|
return text.split('/').every((part) => part === '' || part === '.');
|
|
@@ -302,7 +340,7 @@ function parseAlternative(text, escapes) {
|
|
|
302
340
|
if (part === '' || part === '.')
|
|
303
341
|
continue;
|
|
304
342
|
if (part === '..')
|
|
305
|
-
return
|
|
343
|
+
return CLIMBS_OUT;
|
|
306
344
|
if (part === '**') {
|
|
307
345
|
if (segments[segments.length - 1]?.kind !== 'globstar')
|
|
308
346
|
segments.push({ kind: 'globstar' });
|
|
@@ -315,6 +353,7 @@ function parseAlternative(text, escapes) {
|
|
|
315
353
|
}
|
|
316
354
|
return { segments, slashed: raw.filter((part) => part !== '' && part !== '.').length > 1 };
|
|
317
355
|
}
|
|
356
|
+
const CLIMBS_OUT = 'a pattern cannot climb out of its root with ".."';
|
|
318
357
|
const EXTGLOB = 'extended globs such as "+(a|b)" are not supported: write alternatives as "{a,b}", and a literal parenthesis as "[(]"';
|
|
319
358
|
const GLOBSTAR_IN_NAME = '"**" means any number of directories only as a whole segment';
|
|
320
359
|
/**
|
|
@@ -504,14 +543,14 @@ function segmentsFragment(b, segments) {
|
|
|
504
543
|
});
|
|
505
544
|
return b.sequence(parts);
|
|
506
545
|
}
|
|
507
|
-
function compileAlternative(b, alternative, options
|
|
508
|
-
const { segments } = alternative;
|
|
546
|
+
function compileAlternative(b, alternative, options) {
|
|
547
|
+
const { segments, rooted } = alternative;
|
|
509
548
|
const body = segmentsFragment(b, segments);
|
|
510
549
|
const root = rooted ? '/' : '';
|
|
511
550
|
const literal = literalText(segments);
|
|
512
551
|
const leading = leadingLiteral(segments);
|
|
513
552
|
if (options.dialect === 'gitignore') {
|
|
514
|
-
const floating = !
|
|
553
|
+
const floating = !rooted && !alternative.slashed;
|
|
515
554
|
const parts = [body, b.optional(beneath(b))];
|
|
516
555
|
if (floating)
|
|
517
556
|
parts.unshift(anyDirectories(b));
|
|
@@ -597,4 +636,167 @@ export function parseGlobList(patterns, options) {
|
|
|
597
636
|
},
|
|
598
637
|
};
|
|
599
638
|
}
|
|
639
|
+
/**
|
|
640
|
+
* The alternatives a pattern's braces give, each read at its start as
|
|
641
|
+
* {@link parseGlob} reads it, for a tool that reads a pattern's alternatives
|
|
642
|
+
* one by one - to anchor a rooted one, to hand each to another engine - and
|
|
643
|
+
* would otherwise expand braces and read a leading `./` and `/` again itself.
|
|
644
|
+
*
|
|
645
|
+
* Braces expand first, and an alternative is rooted by a leading `/` on the
|
|
646
|
+
* pattern, or on the text the braces give when no `./` of the pattern's stands
|
|
647
|
+
* before it: `{/docs,x}` is `docs` rooted and `x`; `./{/docs,x}` and
|
|
648
|
+
* `{.//docs,x}` are `docs` and `x`, neither rooted; `a/{/b,c}` is `a//b` and
|
|
649
|
+
* `a/c`. A `!` after a `./` is a character, as it is to `parseGlob`: `./!a` is
|
|
650
|
+
* the name `!a`.
|
|
651
|
+
*
|
|
652
|
+
* A reading, not a verdict: refused are only what `parseGlob` refuses before
|
|
653
|
+
* it reads an alternative, in its words, and braces that do not expand. An
|
|
654
|
+
* alternative `parseGlob` refuses - `{/,x}` gives `/` - is given as read.
|
|
655
|
+
*/
|
|
656
|
+
export function globAlternatives(source, options = {}) {
|
|
657
|
+
const escapes = options.backslash !== 'separator';
|
|
658
|
+
const prepared = prepare(source, escapes);
|
|
659
|
+
if (typeof prepared === 'string')
|
|
660
|
+
return { ok: false, error: prepared };
|
|
661
|
+
const whole = unrooted(prepared.pattern);
|
|
662
|
+
const texts = expandBraces(whole.text, escapes);
|
|
663
|
+
if (typeof texts === 'string')
|
|
664
|
+
return { ok: false, error: texts };
|
|
665
|
+
return {
|
|
666
|
+
ok: true,
|
|
667
|
+
rooted: whole.rooted,
|
|
668
|
+
alternatives: texts.map((text) => {
|
|
669
|
+
const alone = unrooted(text);
|
|
670
|
+
// Past its root, a text may start with a `./` of its own, as `/./docs`
|
|
671
|
+
// does, and names the same without it.
|
|
672
|
+
return { rooted: rootedIn(whole, alone), text: asCharacters(unrooted(alone.text).text) };
|
|
673
|
+
}),
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* A text the braces gave, with each `}` and `,` no group took written as the
|
|
678
|
+
* class of that one character: once the braces have expanded, that is every
|
|
679
|
+
* one outside a class and not escaped. Escapes and classes are copied as
|
|
680
|
+
* written. A `\` is always an escape here, since one read as a separator was
|
|
681
|
+
* a `/` before the braces expanded. A step past the end reads `''`, which
|
|
682
|
+
* adds nothing, so the bound could be one further.
|
|
683
|
+
*/
|
|
684
|
+
function asCharacters(text) {
|
|
685
|
+
let out = '';
|
|
686
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
687
|
+
const ch = text.charAt(i);
|
|
688
|
+
let next = i + 1;
|
|
689
|
+
if (ch === '\\')
|
|
690
|
+
next = i + 2;
|
|
691
|
+
else if (ch === '[')
|
|
692
|
+
next = Math.max(next, classEnd(text, i, true) + 1);
|
|
693
|
+
out += ch === '}' || ch === ',' ? `[${ch}]` : text.slice(i, next);
|
|
694
|
+
i = next - 1;
|
|
695
|
+
}
|
|
696
|
+
return out;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* A list entry typed in a directory below the root, rewritten to read from
|
|
700
|
+
* the root as the `path` dialect reads it: for a tool run in a subdirectory
|
|
701
|
+
* that matches whole paths from the root, where what is typed means what it
|
|
702
|
+
* names from where it was typed.
|
|
703
|
+
*
|
|
704
|
+
* `directory` is where the entry was typed, a path under the root; at the
|
|
705
|
+
* root itself, `''`, there is nothing to rewrite. The entry is read as
|
|
706
|
+
* {@link parseGlobList} reads one: a leading `!` stays in front, and what
|
|
707
|
+
* follows is the pattern. Braces expand first, and each alternative is
|
|
708
|
+
* rebased as that text written alone would be:
|
|
709
|
+
*
|
|
710
|
+
* - one with a leading `/` is rooted at the filesystem's root, which no
|
|
711
|
+
* directory moves, and is kept: `/docs` is `/docs` typed anywhere;
|
|
712
|
+
* - any other gets the directory in front, a leading `..` climbing out of
|
|
713
|
+
* it: `docs/*.md` typed in `sub` is `sub/docs/*.md`, and `../*.md` typed in
|
|
714
|
+
* `docs/deep` is `docs/*.md`.
|
|
715
|
+
*
|
|
716
|
+
* When every alternative is rebased alike, the braces stay as written, `{a,b}`
|
|
717
|
+
* typed in `sub` being `sub/{a,b}`; when they are not, each is written out:
|
|
718
|
+
* `{/docs,x}` is `{/docs,sub/x}`.
|
|
719
|
+
*
|
|
720
|
+
* Refused, with the reason: what {@link parseGlob} refuses before it reads an
|
|
721
|
+
* alternative, in its words; braces that do not expand; a `..` that climbs
|
|
722
|
+
* above the root; a directory no pattern can name; and alternatives written
|
|
723
|
+
* out that hold a `,` or a `}`, which would read as braces. Anything else a
|
|
724
|
+
* rebased pattern holds is for `parseGlob` to refuse, as it would the same
|
|
725
|
+
* pattern typed at the root.
|
|
726
|
+
*/
|
|
727
|
+
export function rebaseGlob(entry, directory, options = {}) {
|
|
728
|
+
if (directory === '')
|
|
729
|
+
return { ok: true, pattern: entry };
|
|
730
|
+
const directories = directory.split('/');
|
|
731
|
+
if (directories.some((name) => name === '' || name === '.' || name === '..')) {
|
|
732
|
+
throw new Error(`"${directory}" is not a directory under the root`);
|
|
733
|
+
}
|
|
734
|
+
const escapes = options.backslash !== 'separator';
|
|
735
|
+
const trimmed = entry.trim();
|
|
736
|
+
const negation = trimmed.startsWith('!') ? '!' : '';
|
|
737
|
+
const prepared = prepare(trimmed.slice(negation.length), escapes);
|
|
738
|
+
if (typeof prepared === 'string')
|
|
739
|
+
return { ok: false, error: prepared };
|
|
740
|
+
const { pattern } = prepared;
|
|
741
|
+
const whole = unrooted(pattern);
|
|
742
|
+
if (whole.rooted)
|
|
743
|
+
return { ok: true, pattern: negation + pattern };
|
|
744
|
+
const texts = expandBraces(whole.text, escapes);
|
|
745
|
+
if (typeof texts === 'string')
|
|
746
|
+
return { ok: false, error: texts };
|
|
747
|
+
const pieces = [];
|
|
748
|
+
const depths = new Set();
|
|
749
|
+
let rooted = false;
|
|
750
|
+
for (const text of texts) {
|
|
751
|
+
// Rooted as `build` roots it, the pattern's own slash having been read.
|
|
752
|
+
if (rootedIn(whole, unrooted(text))) {
|
|
753
|
+
rooted = true;
|
|
754
|
+
pieces.push(text);
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
const moved = climb(text, directories);
|
|
758
|
+
if (moved === null)
|
|
759
|
+
return { ok: false, error: CLIMBS_OUT };
|
|
760
|
+
depths.add(moved.directories.length);
|
|
761
|
+
pieces.push(spell(moved));
|
|
762
|
+
}
|
|
763
|
+
if (depths.size === 0)
|
|
764
|
+
return { ok: true, pattern: negation + pattern };
|
|
765
|
+
if (/^[!\s]|[*?[{\\]/.test(directory)) {
|
|
766
|
+
return {
|
|
767
|
+
ok: false,
|
|
768
|
+
error: `the directory "${directory}" cannot be named in a pattern: a "*", "?", "[", "{" or "\\" in it, or a "!" or a space it starts with, would be read as syntax`,
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
// Every text the braces give starts with the pattern's own leading `..`
|
|
772
|
+
// segments, and one of them has just climbed without leaving the root.
|
|
773
|
+
const lead = climb(whole.text, directories);
|
|
774
|
+
if (!rooted && depths.size === 1 && depths.has(lead.directories.length))
|
|
775
|
+
return { ok: true, pattern: negation + spell(lead) };
|
|
776
|
+
const split = pieces.find((piece) => /[,}]/.test(piece));
|
|
777
|
+
if (split !== undefined) {
|
|
778
|
+
return { ok: false, error: `the braces cannot be rebased one alternative at a time: "${split}" holds a "," or a "}"` };
|
|
779
|
+
}
|
|
780
|
+
return { ok: true, pattern: `${negation}{${pieces.join(',')}}` };
|
|
781
|
+
}
|
|
782
|
+
/** A relative text's leading `..` segments taken off the directories it was typed in, or `null` past the root. */
|
|
783
|
+
function climb(text, directories) {
|
|
784
|
+
let kept = directories.length;
|
|
785
|
+
let rest = unrooted(text).text;
|
|
786
|
+
while (rest === '..' || rest.startsWith('../')) {
|
|
787
|
+
if (kept === 0)
|
|
788
|
+
return null;
|
|
789
|
+
kept -= 1;
|
|
790
|
+
rest = unrooted(rest.slice(3)).text;
|
|
791
|
+
}
|
|
792
|
+
return { directories: directories.slice(0, kept), rest };
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* A rebased relative text. At the root itself it is read from there behind a
|
|
796
|
+
* `./`, which keeps a slash the braces give after it from rooting it, and
|
|
797
|
+
* leaves the root refused as `./` is.
|
|
798
|
+
*/
|
|
799
|
+
function spell(moved) {
|
|
800
|
+
return moved.directories.length > 0 ? `${moved.directories.join('/')}/${moved.rest}` : `./${moved.rest}`;
|
|
801
|
+
}
|
|
600
802
|
//# sourceMappingURL=glob.js.map
|