shfs 0.1.1 → 0.1.3
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/dist/{fs-DSmBon4m.d.mts → fs-rz7EnuDE.d.mts} +2 -1
- package/dist/fs.d.mts +2 -1
- package/dist/fs.mjs +50 -18
- package/dist/fs.mjs.map +1 -1
- package/dist/index.d.mts +17 -16
- package/dist/index.mjs +1057 -321
- package/dist/index.mjs.map +1 -1
- package/dist/path-BR0nl4aX.mjs +12 -0
- package/dist/path-BR0nl4aX.mjs.map +1 -0
- package/dist/util/path.d.mts +5 -0
- package/dist/util/path.mjs +3 -0
- package/package.json +12 -6
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { map, pipe } from "remeda";
|
|
2
|
-
|
|
3
1
|
//#region ../compiler/dist/index.mjs
|
|
4
2
|
/**
|
|
5
3
|
* Create a literal ExpandedWord.
|
|
@@ -62,9 +60,9 @@ function extractPathsFromExpandedWords(words) {
|
|
|
62
60
|
}
|
|
63
61
|
});
|
|
64
62
|
}
|
|
65
|
-
const NEGATIVE_NUMBER_REGEX
|
|
63
|
+
const NEGATIVE_NUMBER_REGEX = /^(?:-\d+(?:\.\d+)?|-\.\d+)$/;
|
|
66
64
|
function isNegativeNumberToken(token) {
|
|
67
|
-
return NEGATIVE_NUMBER_REGEX
|
|
65
|
+
return NEGATIVE_NUMBER_REGEX.test(token);
|
|
68
66
|
}
|
|
69
67
|
function startsWithLongPrefix(token) {
|
|
70
68
|
return token.length >= 2 && token[0] === "-" && token[1] === "-";
|
|
@@ -81,68 +79,203 @@ function splitNameBeforeEquals(token) {
|
|
|
81
79
|
}
|
|
82
80
|
const SHORT_NAME_REGEX = /^[A-Za-z]$/;
|
|
83
81
|
const LONG_NAME_REGEX = /^[A-Za-z0-9][A-Za-z0-9-]*$/;
|
|
84
|
-
|
|
82
|
+
const UNKNOWN_FLAG_PREFIX$2 = "Unknown flag: ";
|
|
83
|
+
function createArgParser(flagDefs) {
|
|
85
84
|
const index = buildFlagIndex(flagDefs);
|
|
86
|
-
|
|
85
|
+
return (args, options) => {
|
|
86
|
+
return parseArgsWithIndex(args, index, options);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function createWordParser(flagDefs, wordToString) {
|
|
90
|
+
const parseWithIndex = createArgParser(flagDefs);
|
|
91
|
+
return (words, options) => {
|
|
92
|
+
const parsed = parseWithIndex(words.map(wordToString), options);
|
|
93
|
+
const positionalWords = parsed.positionalIndices.flatMap((index) => {
|
|
94
|
+
const word = words[index];
|
|
95
|
+
return word === void 0 ? [] : [word];
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
...parsed,
|
|
99
|
+
positionalWords
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function parseArgsWithIndex(args, index, options) {
|
|
104
|
+
const normalizedOptions = normalizeOptions$1(options);
|
|
105
|
+
const negativeNumberValueEntry = getNegativeNumberValueEntry(normalizedOptions, index);
|
|
106
|
+
let consumedValueIndices = Object.create(null);
|
|
107
|
+
let flags = Object.create(null);
|
|
87
108
|
const positional = [];
|
|
109
|
+
const positionalIndices = [];
|
|
88
110
|
let endOfFlags = false;
|
|
89
111
|
for (let i = 0; i < args.length; i++) {
|
|
90
112
|
const token = args[i];
|
|
91
113
|
if (token === void 0) continue;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
const result = processToken({
|
|
115
|
+
args,
|
|
116
|
+
consumedValueIndices,
|
|
117
|
+
endOfFlags,
|
|
118
|
+
flags,
|
|
119
|
+
flagsIndex: index,
|
|
120
|
+
index: i,
|
|
121
|
+
negativeNumberValueEntry,
|
|
122
|
+
positional,
|
|
123
|
+
positionalIndices,
|
|
124
|
+
token,
|
|
125
|
+
unknownFlagPolicy: normalizedOptions.unknownFlagPolicy
|
|
126
|
+
});
|
|
127
|
+
consumedValueIndices = result.consumedValueIndices;
|
|
128
|
+
endOfFlags = result.endOfFlags;
|
|
129
|
+
flags = result.flags;
|
|
130
|
+
i = result.newIndex;
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
consumedValueIndices,
|
|
134
|
+
flags,
|
|
135
|
+
positional,
|
|
136
|
+
positionalIndices
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function processToken(params) {
|
|
140
|
+
const { args, consumedValueIndices, endOfFlags, flags, flagsIndex, index, negativeNumberValueEntry, positional, positionalIndices, token, unknownFlagPolicy } = params;
|
|
141
|
+
if (endOfFlags || token === "-") {
|
|
142
|
+
appendPositional(positional, positionalIndices, token, index);
|
|
143
|
+
return {
|
|
144
|
+
consumedValueIndices,
|
|
145
|
+
endOfFlags,
|
|
146
|
+
flags,
|
|
147
|
+
newIndex: index
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (token === "--") return {
|
|
151
|
+
consumedValueIndices,
|
|
152
|
+
endOfFlags: true,
|
|
153
|
+
flags,
|
|
154
|
+
newIndex: index
|
|
155
|
+
};
|
|
156
|
+
if (isNegativeNumberToken(token)) {
|
|
157
|
+
if (!negativeNumberValueEntry) {
|
|
158
|
+
appendPositional(positional, positionalIndices, token, index);
|
|
159
|
+
return {
|
|
160
|
+
consumedValueIndices,
|
|
161
|
+
endOfFlags,
|
|
162
|
+
flags,
|
|
163
|
+
newIndex: index
|
|
164
|
+
};
|
|
115
165
|
}
|
|
116
|
-
|
|
166
|
+
setValue(flags, consumedValueIndices, negativeNumberValueEntry, token.slice(1), index);
|
|
167
|
+
return {
|
|
168
|
+
consumedValueIndices,
|
|
169
|
+
endOfFlags,
|
|
170
|
+
flags,
|
|
171
|
+
newIndex: index
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const parser = getTokenParser(token);
|
|
175
|
+
if (!parser) {
|
|
176
|
+
appendPositional(positional, positionalIndices, token, index);
|
|
177
|
+
return {
|
|
178
|
+
consumedValueIndices,
|
|
179
|
+
endOfFlags,
|
|
180
|
+
flags,
|
|
181
|
+
newIndex: index
|
|
182
|
+
};
|
|
117
183
|
}
|
|
184
|
+
const parsed = parsePotentialFlagToken(args, index, token, flagsIndex, flags, consumedValueIndices, unknownFlagPolicy, parser);
|
|
185
|
+
if (!parsed) {
|
|
186
|
+
handleUnrecognizedToken(unknownFlagPolicy, positional, positionalIndices, token, index);
|
|
187
|
+
return {
|
|
188
|
+
consumedValueIndices,
|
|
189
|
+
endOfFlags,
|
|
190
|
+
flags,
|
|
191
|
+
newIndex: index
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
consumedValueIndices: parsed.consumedValueIndices,
|
|
196
|
+
endOfFlags,
|
|
197
|
+
flags: parsed.flags,
|
|
198
|
+
newIndex: parsed.newIndex
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function getTokenParser(token) {
|
|
202
|
+
if (startsWithLongPrefix(token)) return parseLongToken;
|
|
203
|
+
if (startsWithShortPrefix(token)) return parseShortToken;
|
|
204
|
+
}
|
|
205
|
+
function normalizeOptions$1(options) {
|
|
118
206
|
return {
|
|
119
|
-
|
|
120
|
-
|
|
207
|
+
negativeNumberPolicy: options?.negativeNumberPolicy ?? "positional",
|
|
208
|
+
negativeNumberFlag: options?.negativeNumberFlag,
|
|
209
|
+
unknownFlagPolicy: options?.unknownFlagPolicy ?? "error"
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function getNegativeNumberValueEntry(options, index) {
|
|
213
|
+
if (options.negativeNumberPolicy === "positional") return;
|
|
214
|
+
if (!options.negativeNumberFlag) throw new Error("negativeNumberFlag is required when negativeNumberPolicy is \"value\".");
|
|
215
|
+
const entry = index.canonical.get(options.negativeNumberFlag);
|
|
216
|
+
if (!entry) throw new Error(`Unknown negativeNumberFlag: "${options.negativeNumberFlag}".`);
|
|
217
|
+
if (!entry.def.takesValue) throw new Error(`negativeNumberFlag "${options.negativeNumberFlag}" must reference a flag that takes a value.`);
|
|
218
|
+
return entry;
|
|
219
|
+
}
|
|
220
|
+
function appendPositional(positional, positionalIndices, token, index) {
|
|
221
|
+
positional.push(token);
|
|
222
|
+
positionalIndices.push(index);
|
|
223
|
+
}
|
|
224
|
+
function parsePotentialFlagToken(args, index, token, flagsIndex, currentFlags, currentConsumedValueIndices, unknownFlagPolicy, parser) {
|
|
225
|
+
if (unknownFlagPolicy === "error") return {
|
|
226
|
+
consumedValueIndices: currentConsumedValueIndices,
|
|
227
|
+
flags: currentFlags,
|
|
228
|
+
newIndex: parser(args, index, token, flagsIndex, currentFlags, currentConsumedValueIndices)
|
|
121
229
|
};
|
|
230
|
+
const candidateFlags = cloneFlags(currentFlags);
|
|
231
|
+
const candidateConsumedValueIndices = cloneConsumedValueIndices(currentConsumedValueIndices);
|
|
232
|
+
try {
|
|
233
|
+
return {
|
|
234
|
+
consumedValueIndices: candidateConsumedValueIndices,
|
|
235
|
+
flags: candidateFlags,
|
|
236
|
+
newIndex: parser(args, index, token, flagsIndex, candidateFlags, candidateConsumedValueIndices)
|
|
237
|
+
};
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if (isUnknownFlagError(error)) return null;
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function handleUnrecognizedToken(policy, positional, positionalIndices, token, index) {
|
|
244
|
+
if (policy === "positional") appendPositional(positional, positionalIndices, token, index);
|
|
245
|
+
}
|
|
246
|
+
function cloneFlags(source) {
|
|
247
|
+
const cloned = Object.create(null);
|
|
248
|
+
for (const [key, value] of Object.entries(source)) cloned[key] = Array.isArray(value) ? [...value] : value;
|
|
249
|
+
return cloned;
|
|
250
|
+
}
|
|
251
|
+
function cloneConsumedValueIndices(source) {
|
|
252
|
+
const cloned = Object.create(null);
|
|
253
|
+
for (const [key, value] of Object.entries(source)) cloned[key] = [...value];
|
|
254
|
+
return cloned;
|
|
122
255
|
}
|
|
123
256
|
function buildFlagIndex(flagDefs) {
|
|
257
|
+
const canonical = /* @__PURE__ */ new Map();
|
|
124
258
|
const short = /* @__PURE__ */ new Map();
|
|
125
259
|
const long = /* @__PURE__ */ new Map();
|
|
126
|
-
const add = (map
|
|
127
|
-
const prev = map
|
|
260
|
+
const add = (map, token, entry) => {
|
|
261
|
+
const prev = map.get(token);
|
|
128
262
|
if (!prev) {
|
|
129
|
-
map
|
|
263
|
+
map.set(token, entry);
|
|
130
264
|
return;
|
|
131
265
|
}
|
|
132
266
|
throw new Error(`Duplicate flag token "${token}" for "${entry.canonical}" and "${prev.canonical}"`);
|
|
133
267
|
};
|
|
134
|
-
for (const [
|
|
135
|
-
if (!SHORT_NAME_REGEX.test(def.short)) throw new Error(`Invalid short flag for "${
|
|
136
|
-
|
|
137
|
-
canonical,
|
|
268
|
+
for (const [canonicalName, def] of Object.entries(flagDefs)) {
|
|
269
|
+
if (!SHORT_NAME_REGEX.test(def.short)) throw new Error(`Invalid short flag for "${canonicalName}": "${def.short}". Expected a single letter [A-Za-z].`);
|
|
270
|
+
const entry = {
|
|
271
|
+
canonical: canonicalName,
|
|
138
272
|
def
|
|
139
|
-
}
|
|
273
|
+
};
|
|
274
|
+
canonical.set(canonicalName, entry);
|
|
275
|
+
add(short, `-${def.short}`, entry);
|
|
140
276
|
if (def.long) {
|
|
141
|
-
if (!LONG_NAME_REGEX.test(def.long)) throw new Error(`Invalid long flag for "${
|
|
142
|
-
add(long, `--${def.long}`,
|
|
143
|
-
canonical,
|
|
144
|
-
def
|
|
145
|
-
});
|
|
277
|
+
if (!LONG_NAME_REGEX.test(def.long)) throw new Error(`Invalid long flag for "${canonicalName}": "${def.long}". Expected [A-Za-z0-9][A-Za-z0-9-]*.`);
|
|
278
|
+
add(long, `--${def.long}`, entry);
|
|
146
279
|
}
|
|
147
280
|
}
|
|
148
281
|
const isFlagToken = (token) => {
|
|
@@ -166,16 +299,17 @@ function buildFlagIndex(flagDefs) {
|
|
|
166
299
|
return false;
|
|
167
300
|
};
|
|
168
301
|
return {
|
|
302
|
+
canonical,
|
|
169
303
|
short,
|
|
170
304
|
long,
|
|
171
305
|
isFlagToken
|
|
172
306
|
};
|
|
173
307
|
}
|
|
174
|
-
function parseLongToken(args, index, token, flagsIndex, out) {
|
|
308
|
+
function parseLongToken(args, index, token, flagsIndex, out, consumedValueIndices) {
|
|
175
309
|
if (startsWithNoLongPrefix(token) && !token.includes("=")) {
|
|
176
310
|
const base = `--${token.slice(5)}`;
|
|
177
311
|
const entry$1 = flagsIndex.long.get(base);
|
|
178
|
-
if (!entry$1)
|
|
312
|
+
if (!entry$1) throwUnknownFlag(token);
|
|
179
313
|
if (entry$1.def.takesValue) throw new Error(`Flag ${base} takes a value; "${token}" is invalid.`);
|
|
180
314
|
setBoolean(out, entry$1.canonical, false);
|
|
181
315
|
return index;
|
|
@@ -185,69 +319,91 @@ function parseLongToken(args, index, token, flagsIndex, out) {
|
|
|
185
319
|
const name = token.slice(0, eq);
|
|
186
320
|
const value$1 = token.slice(eq + 1);
|
|
187
321
|
const entry$1 = flagsIndex.long.get(name);
|
|
188
|
-
if (!entry$1)
|
|
322
|
+
if (!entry$1) throwUnknownFlag(name);
|
|
189
323
|
if (!entry$1.def.takesValue) throw new Error(`Flag ${name} does not take a value.`);
|
|
190
|
-
setValue(out, entry$1, value$1);
|
|
324
|
+
setValue(out, consumedValueIndices, entry$1, value$1, index);
|
|
191
325
|
return index;
|
|
192
326
|
}
|
|
193
327
|
const entry = flagsIndex.long.get(token);
|
|
194
|
-
if (!entry)
|
|
328
|
+
if (!entry) throwUnknownFlag(token);
|
|
195
329
|
if (!entry.def.takesValue) {
|
|
196
330
|
setBoolean(out, entry.canonical, true);
|
|
197
331
|
return index;
|
|
198
332
|
}
|
|
199
|
-
const { value,
|
|
200
|
-
setValue(out, entry, value);
|
|
333
|
+
const { newIndex, value, valueIndex } = consumeValue(args, index, token, flagsIndex);
|
|
334
|
+
setValue(out, consumedValueIndices, entry, value, valueIndex);
|
|
201
335
|
return newIndex;
|
|
202
336
|
}
|
|
203
|
-
function parseShortToken(args, index, token, flagsIndex, out) {
|
|
204
|
-
if (token.length >= 3 && token[2] === "=")
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
337
|
+
function parseShortToken(args, index, token, flagsIndex, out, consumedValueIndices) {
|
|
338
|
+
if (token.length >= 3 && token[2] === "=") return parseShortEqualsToken(index, token, flagsIndex, out, consumedValueIndices);
|
|
339
|
+
if (token.length === 2) return parseSingleShortToken(args, index, token, flagsIndex, out, consumedValueIndices);
|
|
340
|
+
return parseShortClusterToken(args, index, token, flagsIndex, out, consumedValueIndices);
|
|
341
|
+
}
|
|
342
|
+
function parseShortEqualsToken(index, token, flagsIndex, out, consumedValueIndices) {
|
|
343
|
+
const name = token.slice(0, 2);
|
|
344
|
+
const value = token.slice(3);
|
|
345
|
+
const entry = getRequiredShortEntry(flagsIndex, name);
|
|
346
|
+
assertTakesValue(entry, name);
|
|
347
|
+
setValue(out, consumedValueIndices, entry, value, index);
|
|
348
|
+
return index;
|
|
349
|
+
}
|
|
350
|
+
function parseSingleShortToken(args, index, token, flagsIndex, out, consumedValueIndices) {
|
|
351
|
+
const entry = getRequiredShortEntry(flagsIndex, token);
|
|
352
|
+
if (!entry.def.takesValue) {
|
|
353
|
+
setBoolean(out, entry.canonical, true);
|
|
211
354
|
return index;
|
|
212
355
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return index;
|
|
219
|
-
}
|
|
220
|
-
const { value, newIndex } = consumeValue(args, index, token, flagsIndex);
|
|
221
|
-
setValue(out, entry, value);
|
|
222
|
-
return newIndex;
|
|
223
|
-
}
|
|
356
|
+
const { newIndex, value, valueIndex } = consumeValue(args, index, token, flagsIndex);
|
|
357
|
+
setValue(out, consumedValueIndices, entry, value, valueIndex);
|
|
358
|
+
return newIndex;
|
|
359
|
+
}
|
|
360
|
+
function parseShortClusterToken(args, index, token, flagsIndex, out, consumedValueIndices) {
|
|
224
361
|
for (let j = 1; j < token.length; j++) {
|
|
225
362
|
const ch = token[j] ?? "";
|
|
226
|
-
|
|
363
|
+
assertValidShortCharacter(token, ch);
|
|
227
364
|
const name = `-${ch}`;
|
|
228
|
-
const entry = flagsIndex
|
|
229
|
-
if (!entry) throw new Error(`Unknown flag: ${name}`);
|
|
365
|
+
const entry = getRequiredShortEntry(flagsIndex, name);
|
|
230
366
|
if (!entry.def.takesValue) {
|
|
231
367
|
setBoolean(out, entry.canonical, true);
|
|
232
368
|
continue;
|
|
233
369
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return newIndex;
|
|
243
|
-
}
|
|
244
|
-
const first = rest[0] ?? "";
|
|
245
|
-
if (SHORT_NAME_REGEX.test(first) && flagsIndex.short.has(`-${first}`)) throw new Error(`Ambiguous short flag cluster "${token}": ${name} takes a value, but "${rest}" begins with "-${first}" which is also a flag. Use "${name}=${rest}" or pass the value as a separate argument.`);
|
|
246
|
-
setValue(out, entry, rest);
|
|
370
|
+
return parseValueFlagInShortCluster(args, index, token, j, name, entry, flagsIndex, out, consumedValueIndices);
|
|
371
|
+
}
|
|
372
|
+
return index;
|
|
373
|
+
}
|
|
374
|
+
function parseValueFlagInShortCluster(args, index, token, flagPosition, name, entry, flagsIndex, out, consumedValueIndices) {
|
|
375
|
+
const rest = token.slice(flagPosition + 1);
|
|
376
|
+
if (rest.startsWith("=")) {
|
|
377
|
+
setValue(out, consumedValueIndices, entry, rest.slice(1), index);
|
|
247
378
|
return index;
|
|
248
379
|
}
|
|
380
|
+
if (rest.length === 0) {
|
|
381
|
+
const { newIndex, value, valueIndex } = consumeValue(args, index, name, flagsIndex);
|
|
382
|
+
setValue(out, consumedValueIndices, entry, value, valueIndex);
|
|
383
|
+
return newIndex;
|
|
384
|
+
}
|
|
385
|
+
assertNotAmbiguousShortValue(token, name, rest, flagsIndex);
|
|
386
|
+
setValue(out, consumedValueIndices, entry, rest, index);
|
|
249
387
|
return index;
|
|
250
388
|
}
|
|
389
|
+
function getRequiredShortEntry(flagsIndex, name) {
|
|
390
|
+
const entry = flagsIndex.short.get(name);
|
|
391
|
+
if (!entry) throwUnknownFlag(name);
|
|
392
|
+
return entry;
|
|
393
|
+
}
|
|
394
|
+
function assertValidShortCharacter(token, ch) {
|
|
395
|
+
if (SHORT_NAME_REGEX.test(ch)) return;
|
|
396
|
+
throw new Error(`Invalid short flag character "${ch}" in "${token}". Short flags must be letters.`);
|
|
397
|
+
}
|
|
398
|
+
function assertTakesValue(entry, token) {
|
|
399
|
+
if (entry.def.takesValue) return;
|
|
400
|
+
throw new Error(`Flag ${token} does not take a value.`);
|
|
401
|
+
}
|
|
402
|
+
function assertNotAmbiguousShortValue(token, name, rest, flagsIndex) {
|
|
403
|
+
const first = rest[0] ?? "";
|
|
404
|
+
if (!(SHORT_NAME_REGEX.test(first) && flagsIndex.short.has(`-${first}`))) return;
|
|
405
|
+
throw new Error(`Ambiguous short flag cluster "${token}": ${name} takes a value, but "${rest}" begins with "-${first}" which is also a flag. Use "${name}=${rest}" or pass the value as a separate argument.`);
|
|
406
|
+
}
|
|
251
407
|
function consumeValue(args, index, flagToken, flagsIndex) {
|
|
252
408
|
const nextIndex = index + 1;
|
|
253
409
|
if (nextIndex >= args.length) throw new Error(`Flag ${flagToken} requires a value.`);
|
|
@@ -257,34 +413,53 @@ function consumeValue(args, index, flagToken, flagsIndex) {
|
|
|
257
413
|
if (flagsIndex.isFlagToken(next)) throw new Error(`Flag ${flagToken} requires a value (got "${next}").`);
|
|
258
414
|
return {
|
|
259
415
|
value: next,
|
|
260
|
-
newIndex: nextIndex
|
|
416
|
+
newIndex: nextIndex,
|
|
417
|
+
valueIndex: nextIndex
|
|
261
418
|
};
|
|
262
419
|
}
|
|
263
420
|
function setBoolean(out, canonical, value) {
|
|
264
421
|
out[canonical] = value;
|
|
265
422
|
}
|
|
266
|
-
function setValue(out, entry, value) {
|
|
423
|
+
function setValue(out, consumedValueIndices, entry, value, valueIndex) {
|
|
267
424
|
const { canonical, def } = entry;
|
|
268
425
|
const existing = out[canonical];
|
|
269
426
|
if (existing === void 0) {
|
|
270
427
|
out[canonical] = value;
|
|
428
|
+
recordConsumedValueIndex(consumedValueIndices, canonical, valueIndex);
|
|
271
429
|
return;
|
|
272
430
|
}
|
|
273
431
|
if (!def.multiple) throw new Error(`Duplicate flag "${canonical}". If it is intended to repeat, set { multiple: true } in its definition.`);
|
|
274
432
|
if (Array.isArray(existing)) {
|
|
275
433
|
existing.push(value);
|
|
434
|
+
recordConsumedValueIndex(consumedValueIndices, canonical, valueIndex);
|
|
276
435
|
return;
|
|
277
436
|
}
|
|
278
437
|
if (typeof existing === "string") {
|
|
279
438
|
out[canonical] = [existing, value];
|
|
439
|
+
recordConsumedValueIndex(consumedValueIndices, canonical, valueIndex);
|
|
280
440
|
return;
|
|
281
441
|
}
|
|
282
442
|
throw new Error(`Invalid state for flag "${canonical}".`);
|
|
283
443
|
}
|
|
444
|
+
function recordConsumedValueIndex(consumedValueIndices, canonical, valueIndex) {
|
|
445
|
+
const existing = consumedValueIndices[canonical];
|
|
446
|
+
if (!existing) {
|
|
447
|
+
consumedValueIndices[canonical] = [valueIndex];
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
existing.push(valueIndex);
|
|
451
|
+
}
|
|
452
|
+
function throwUnknownFlag(token) {
|
|
453
|
+
throw new Error(`${UNKNOWN_FLAG_PREFIX$2}${token}`);
|
|
454
|
+
}
|
|
455
|
+
function isUnknownFlagError(error) {
|
|
456
|
+
if (!(error instanceof Error)) return false;
|
|
457
|
+
return error.message.startsWith(UNKNOWN_FLAG_PREFIX$2);
|
|
458
|
+
}
|
|
284
459
|
/**
|
|
285
460
|
* cat command handler for the AST-based compiler.
|
|
286
461
|
*/
|
|
287
|
-
const
|
|
462
|
+
const parseCatArgs = createArgParser({
|
|
288
463
|
number: {
|
|
289
464
|
short: "n",
|
|
290
465
|
takesValue: false
|
|
@@ -313,14 +488,17 @@ const flags = {
|
|
|
313
488
|
short: "s",
|
|
314
489
|
takesValue: false
|
|
315
490
|
}
|
|
316
|
-
};
|
|
491
|
+
});
|
|
317
492
|
/**
|
|
318
493
|
* Compile a cat command from SimpleCommandIR to StepIR.
|
|
319
494
|
*/
|
|
320
495
|
function compileCat(cmd$1) {
|
|
321
|
-
const parsed =
|
|
496
|
+
const parsed = parseCatArgs(cmd$1.args.map(expandedWordToString));
|
|
322
497
|
const fileArgs = [];
|
|
323
|
-
for (const
|
|
498
|
+
for (const positionalIndex of parsed.positionalIndices) {
|
|
499
|
+
const arg = cmd$1.args[positionalIndex];
|
|
500
|
+
if (arg !== void 0) fileArgs.push(arg);
|
|
501
|
+
}
|
|
324
502
|
const hasInputRedirection = cmd$1.redirections.some((redirection) => redirection.kind === "input");
|
|
325
503
|
if (fileArgs.length === 0 && !hasInputRedirection) throw new Error("cat requires at least one file");
|
|
326
504
|
return {
|
|
@@ -338,19 +516,45 @@ function compileCat(cmd$1) {
|
|
|
338
516
|
};
|
|
339
517
|
}
|
|
340
518
|
/**
|
|
519
|
+
* cd command handler for the AST-based compiler.
|
|
520
|
+
*/
|
|
521
|
+
const ROOT_DIRECTORY$3 = "/";
|
|
522
|
+
/**
|
|
523
|
+
* Compile a cd command from SimpleCommandIR to StepIR.
|
|
524
|
+
*/
|
|
525
|
+
function compileCd(cmd$1) {
|
|
526
|
+
if (cmd$1.args.length > 1) throw new Error("cd accepts at most one path");
|
|
527
|
+
return {
|
|
528
|
+
cmd: "cd",
|
|
529
|
+
args: { path: cmd$1.args[0] ?? literal(ROOT_DIRECTORY$3) }
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
341
533
|
* cp command handler for the AST-based compiler.
|
|
342
534
|
*/
|
|
535
|
+
const parseCpArgs = createWordParser({
|
|
536
|
+
force: {
|
|
537
|
+
short: "f",
|
|
538
|
+
takesValue: false
|
|
539
|
+
},
|
|
540
|
+
interactive: {
|
|
541
|
+
short: "i",
|
|
542
|
+
takesValue: false
|
|
543
|
+
},
|
|
544
|
+
recursive: {
|
|
545
|
+
short: "r",
|
|
546
|
+
takesValue: false
|
|
547
|
+
}
|
|
548
|
+
}, expandedWordToString);
|
|
343
549
|
/**
|
|
344
550
|
* Compile a cp command from SimpleCommandIR to StepIR.
|
|
345
551
|
*/
|
|
346
552
|
function compileCp(cmd$1) {
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
else if (argStr !== "-f" && argStr !== "-i") filteredArgs.push(arg);
|
|
353
|
-
}
|
|
553
|
+
const parsed = parseCpArgs(cmd$1.args, { unknownFlagPolicy: "positional" });
|
|
554
|
+
const recursive = parsed.flags.recursive === true;
|
|
555
|
+
const force = parsed.flags.force === true;
|
|
556
|
+
const interactive = parsed.flags.interactive === true;
|
|
557
|
+
const filteredArgs = parsed.positionalWords;
|
|
354
558
|
if (filteredArgs.length < 2) throw new Error("cp requires source and destination");
|
|
355
559
|
const dest = filteredArgs.pop();
|
|
356
560
|
if (!dest) throw new Error("cp requires source and destination");
|
|
@@ -358,6 +562,8 @@ function compileCp(cmd$1) {
|
|
|
358
562
|
cmd: "cp",
|
|
359
563
|
args: {
|
|
360
564
|
dest,
|
|
565
|
+
force,
|
|
566
|
+
interactive,
|
|
361
567
|
recursive,
|
|
362
568
|
srcs: filteredArgs
|
|
363
569
|
}
|
|
@@ -366,67 +572,102 @@ function compileCp(cmd$1) {
|
|
|
366
572
|
/**
|
|
367
573
|
* head command handler for the AST-based compiler.
|
|
368
574
|
*/
|
|
369
|
-
const
|
|
575
|
+
const DEFAULT_LINE_COUNT$1 = 10;
|
|
576
|
+
const parseHeadArgs = createWordParser({ lines: {
|
|
577
|
+
multiple: true,
|
|
578
|
+
short: "n",
|
|
579
|
+
takesValue: true
|
|
580
|
+
} }, expandedWordToString);
|
|
581
|
+
const MISSING_N_VALUE_PREFIX$1 = "Flag -n requires a value";
|
|
582
|
+
const UNKNOWN_FLAG_PREFIX$1 = "Unknown flag:";
|
|
370
583
|
/**
|
|
371
584
|
* Compile a head command from SimpleCommandIR to StepIR.
|
|
372
585
|
*/
|
|
373
586
|
function compileHead(cmd$1) {
|
|
374
|
-
|
|
375
|
-
const
|
|
376
|
-
let skipNext = false;
|
|
377
|
-
for (let i = 0; i < cmd$1.args.length; i++) {
|
|
378
|
-
if (skipNext) {
|
|
379
|
-
skipNext = false;
|
|
380
|
-
continue;
|
|
381
|
-
}
|
|
382
|
-
const arg = cmd$1.args[i];
|
|
383
|
-
if (!arg) continue;
|
|
384
|
-
const argStr = expandedWordToString(arg);
|
|
385
|
-
if (argStr === "-n") {
|
|
386
|
-
const numArg = cmd$1.args[i + 1];
|
|
387
|
-
if (!numArg) throw new Error("head -n requires a number");
|
|
388
|
-
n = Number(expandedWordToString(numArg));
|
|
389
|
-
if (!Number.isFinite(n)) throw new Error("Invalid head count");
|
|
390
|
-
skipNext = true;
|
|
391
|
-
} else if (argStr.startsWith("-") && NEGATIVE_NUMBER_REGEX$1.test(argStr)) n = Number(argStr.slice(1));
|
|
392
|
-
else if (argStr.startsWith("-")) throw new Error("Unknown head option");
|
|
393
|
-
else files$1.push(arg);
|
|
394
|
-
}
|
|
587
|
+
const parsed = parseHeadArgsOrThrow(cmd$1.args);
|
|
588
|
+
const n = parseHeadCount(parsed.flags.lines);
|
|
395
589
|
return {
|
|
396
590
|
cmd: "head",
|
|
397
591
|
args: {
|
|
398
|
-
files:
|
|
592
|
+
files: parsed.positionalWords,
|
|
399
593
|
n
|
|
400
594
|
}
|
|
401
595
|
};
|
|
402
596
|
}
|
|
597
|
+
function parseHeadCount(value) {
|
|
598
|
+
const lastValue = getLastValueToken$1(value);
|
|
599
|
+
if (lastValue === void 0) return DEFAULT_LINE_COUNT$1;
|
|
600
|
+
const parsedValue = Number(lastValue);
|
|
601
|
+
if (!Number.isFinite(parsedValue)) throw new Error("Invalid head count");
|
|
602
|
+
return parsedValue;
|
|
603
|
+
}
|
|
604
|
+
function getLastValueToken$1(value) {
|
|
605
|
+
if (value === void 0) return;
|
|
606
|
+
if (typeof value === "string") return value;
|
|
607
|
+
if (Array.isArray(value)) return value.at(-1);
|
|
608
|
+
throw new Error("Invalid head count");
|
|
609
|
+
}
|
|
610
|
+
function parseHeadArgsOrThrow(args) {
|
|
611
|
+
try {
|
|
612
|
+
return parseHeadArgs(args, {
|
|
613
|
+
negativeNumberFlag: "lines",
|
|
614
|
+
negativeNumberPolicy: "value"
|
|
615
|
+
});
|
|
616
|
+
} catch (error) {
|
|
617
|
+
if (!(error instanceof Error)) throw new Error("Unknown head option");
|
|
618
|
+
if (error.message.startsWith(MISSING_N_VALUE_PREFIX$1)) throw new Error("head -n requires a number");
|
|
619
|
+
if (error.message.startsWith(UNKNOWN_FLAG_PREFIX$1)) throw new Error("Unknown head option");
|
|
620
|
+
throw error;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
403
623
|
/**
|
|
404
624
|
* ls command handler for the AST-based compiler.
|
|
405
625
|
*/
|
|
626
|
+
const parseLsArgs = createWordParser({
|
|
627
|
+
longFormat: {
|
|
628
|
+
short: "l",
|
|
629
|
+
takesValue: false
|
|
630
|
+
},
|
|
631
|
+
showAll: {
|
|
632
|
+
short: "a",
|
|
633
|
+
takesValue: false
|
|
634
|
+
}
|
|
635
|
+
}, expandedWordToString);
|
|
406
636
|
/**
|
|
407
637
|
* Compile an ls command from SimpleCommandIR to StepIR.
|
|
408
638
|
*/
|
|
409
639
|
function compileLs(cmd$1) {
|
|
640
|
+
const parsed = parseLsArgs(cmd$1.args, { unknownFlagPolicy: "positional" });
|
|
641
|
+
const paths = parsed.positionalWords.length === 0 ? [literal(".")] : parsed.positionalWords;
|
|
410
642
|
return {
|
|
411
643
|
cmd: "ls",
|
|
412
|
-
args: {
|
|
644
|
+
args: {
|
|
645
|
+
longFormat: parsed.flags.longFormat === true,
|
|
646
|
+
paths,
|
|
647
|
+
showAll: parsed.flags.showAll === true
|
|
648
|
+
}
|
|
413
649
|
};
|
|
414
650
|
}
|
|
415
651
|
/**
|
|
416
652
|
* mkdir command handler for the AST-based compiler.
|
|
417
653
|
*/
|
|
654
|
+
const parseMkdirArgs = createWordParser({ parents: {
|
|
655
|
+
short: "p",
|
|
656
|
+
takesValue: false
|
|
657
|
+
} }, expandedWordToString);
|
|
418
658
|
/**
|
|
419
659
|
* Compile a mkdir command from SimpleCommandIR to StepIR.
|
|
420
660
|
*/
|
|
421
661
|
function compileMkdir(cmd$1) {
|
|
422
|
-
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
662
|
+
const parsed = parseMkdirArgs(cmd$1.args, { unknownFlagPolicy: "positional" });
|
|
663
|
+
const parents = parsed.flags.parents === true;
|
|
664
|
+
const recursive = parents;
|
|
665
|
+
const paths = parsed.positionalWords;
|
|
426
666
|
if (paths.length === 0) throw new Error("mkdir requires at least one path");
|
|
427
667
|
return {
|
|
428
668
|
cmd: "mkdir",
|
|
429
669
|
args: {
|
|
670
|
+
parents,
|
|
430
671
|
paths,
|
|
431
672
|
recursive
|
|
432
673
|
}
|
|
@@ -435,15 +676,24 @@ function compileMkdir(cmd$1) {
|
|
|
435
676
|
/**
|
|
436
677
|
* mv command handler for the AST-based compiler.
|
|
437
678
|
*/
|
|
679
|
+
const parseMvArgs = createWordParser({
|
|
680
|
+
force: {
|
|
681
|
+
short: "f",
|
|
682
|
+
takesValue: false
|
|
683
|
+
},
|
|
684
|
+
interactive: {
|
|
685
|
+
short: "i",
|
|
686
|
+
takesValue: false
|
|
687
|
+
}
|
|
688
|
+
}, expandedWordToString);
|
|
438
689
|
/**
|
|
439
690
|
* Compile a mv command from SimpleCommandIR to StepIR.
|
|
440
691
|
*/
|
|
441
692
|
function compileMv(cmd$1) {
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
}
|
|
693
|
+
const parsed = parseMvArgs(cmd$1.args, { unknownFlagPolicy: "positional" });
|
|
694
|
+
const force = parsed.flags.force === true;
|
|
695
|
+
const interactive = parsed.flags.interactive === true;
|
|
696
|
+
const filteredArgs = parsed.positionalWords;
|
|
447
697
|
if (filteredArgs.length < 2) throw new Error("mv requires source and destination");
|
|
448
698
|
const dest = filteredArgs.pop();
|
|
449
699
|
if (!dest) throw new Error("mv requires source and destination");
|
|
@@ -451,28 +701,54 @@ function compileMv(cmd$1) {
|
|
|
451
701
|
cmd: "mv",
|
|
452
702
|
args: {
|
|
453
703
|
dest,
|
|
704
|
+
force,
|
|
705
|
+
interactive,
|
|
454
706
|
srcs: filteredArgs
|
|
455
707
|
}
|
|
456
708
|
};
|
|
457
709
|
}
|
|
458
710
|
/**
|
|
711
|
+
* Compile a pwd command from SimpleCommandIR to StepIR.
|
|
712
|
+
*/
|
|
713
|
+
function compilePwd(cmd$1) {
|
|
714
|
+
if (cmd$1.args.length > 0) throw new Error("pwd does not take any arguments");
|
|
715
|
+
return {
|
|
716
|
+
cmd: "pwd",
|
|
717
|
+
args: {}
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
459
721
|
* rm command handler for the AST-based compiler.
|
|
460
722
|
*/
|
|
723
|
+
const parseRmArgs = createWordParser({
|
|
724
|
+
force: {
|
|
725
|
+
short: "f",
|
|
726
|
+
takesValue: false
|
|
727
|
+
},
|
|
728
|
+
interactive: {
|
|
729
|
+
short: "i",
|
|
730
|
+
takesValue: false
|
|
731
|
+
},
|
|
732
|
+
recursive: {
|
|
733
|
+
short: "r",
|
|
734
|
+
takesValue: false
|
|
735
|
+
}
|
|
736
|
+
}, expandedWordToString);
|
|
461
737
|
/**
|
|
462
738
|
* Compile a rm command from SimpleCommandIR to StepIR.
|
|
463
739
|
*/
|
|
464
740
|
function compileRm(cmd$1) {
|
|
465
|
-
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
else if (argStr !== "-f" && argStr !== "-i") paths.push(arg);
|
|
471
|
-
}
|
|
741
|
+
const parsed = parseRmArgs(cmd$1.args, { unknownFlagPolicy: "positional" });
|
|
742
|
+
const recursive = parsed.flags.recursive === true;
|
|
743
|
+
const force = parsed.flags.force === true;
|
|
744
|
+
const interactive = parsed.flags.interactive === true;
|
|
745
|
+
const paths = parsed.positionalWords;
|
|
472
746
|
if (paths.length === 0) throw new Error("rm requires at least one path");
|
|
473
747
|
return {
|
|
474
748
|
cmd: "rm",
|
|
475
749
|
args: {
|
|
750
|
+
force,
|
|
751
|
+
interactive,
|
|
476
752
|
paths,
|
|
477
753
|
recursive
|
|
478
754
|
}
|
|
@@ -481,64 +757,101 @@ function compileRm(cmd$1) {
|
|
|
481
757
|
/**
|
|
482
758
|
* tail command handler for the AST-based compiler.
|
|
483
759
|
*/
|
|
484
|
-
const
|
|
760
|
+
const DEFAULT_LINE_COUNT = 10;
|
|
761
|
+
const parseTailArgs = createWordParser({ lines: {
|
|
762
|
+
multiple: true,
|
|
763
|
+
short: "n",
|
|
764
|
+
takesValue: true
|
|
765
|
+
} }, expandedWordToString);
|
|
766
|
+
const MISSING_N_VALUE_PREFIX = "Flag -n requires a value";
|
|
767
|
+
const UNKNOWN_FLAG_PREFIX = "Unknown flag:";
|
|
485
768
|
/**
|
|
486
769
|
* Compile a tail command from SimpleCommandIR to StepIR.
|
|
487
770
|
*/
|
|
488
771
|
function compileTail(cmd$1) {
|
|
489
|
-
|
|
490
|
-
const
|
|
491
|
-
let skipNext = false;
|
|
492
|
-
for (let i = 0; i < cmd$1.args.length; i++) {
|
|
493
|
-
if (skipNext) {
|
|
494
|
-
skipNext = false;
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
const arg = cmd$1.args[i];
|
|
498
|
-
if (!arg) continue;
|
|
499
|
-
const argStr = expandedWordToString(arg);
|
|
500
|
-
if (argStr === "-n") {
|
|
501
|
-
const numArg = cmd$1.args[i + 1];
|
|
502
|
-
if (!numArg) throw new Error("tail -n requires a number");
|
|
503
|
-
n = Number(expandedWordToString(numArg));
|
|
504
|
-
if (!Number.isFinite(n)) throw new Error("Invalid tail count");
|
|
505
|
-
skipNext = true;
|
|
506
|
-
} else if (argStr.startsWith("-") && NEGATIVE_NUMBER_REGEX.test(argStr)) n = Number(argStr.slice(1));
|
|
507
|
-
else if (argStr.startsWith("-")) throw new Error("Unknown tail option");
|
|
508
|
-
else files$1.push(arg);
|
|
509
|
-
}
|
|
772
|
+
const parsed = parseTailArgsOrThrow(cmd$1.args);
|
|
773
|
+
const n = parseTailCount(parsed.flags.lines);
|
|
510
774
|
return {
|
|
511
775
|
cmd: "tail",
|
|
512
776
|
args: {
|
|
513
|
-
files:
|
|
777
|
+
files: parsed.positionalWords,
|
|
514
778
|
n
|
|
515
779
|
}
|
|
516
780
|
};
|
|
517
781
|
}
|
|
782
|
+
function parseTailCount(value) {
|
|
783
|
+
const lastValue = getLastValueToken(value);
|
|
784
|
+
if (lastValue === void 0) return DEFAULT_LINE_COUNT;
|
|
785
|
+
const parsedValue = Number(lastValue);
|
|
786
|
+
if (!Number.isFinite(parsedValue)) throw new Error("Invalid tail count");
|
|
787
|
+
return parsedValue;
|
|
788
|
+
}
|
|
789
|
+
function getLastValueToken(value) {
|
|
790
|
+
if (value === void 0) return;
|
|
791
|
+
if (typeof value === "string") return value;
|
|
792
|
+
if (Array.isArray(value)) return value.at(-1);
|
|
793
|
+
throw new Error("Invalid tail count");
|
|
794
|
+
}
|
|
795
|
+
function parseTailArgsOrThrow(args) {
|
|
796
|
+
try {
|
|
797
|
+
return parseTailArgs(args, {
|
|
798
|
+
negativeNumberFlag: "lines",
|
|
799
|
+
negativeNumberPolicy: "value"
|
|
800
|
+
});
|
|
801
|
+
} catch (error) {
|
|
802
|
+
throw normalizeTailParseError(error);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
function normalizeTailParseError(error) {
|
|
806
|
+
if (!(error instanceof Error)) return /* @__PURE__ */ new Error("Unknown tail option");
|
|
807
|
+
if (error.message.startsWith(MISSING_N_VALUE_PREFIX)) return /* @__PURE__ */ new Error("tail -n requires a number");
|
|
808
|
+
if (error.message.startsWith(UNKNOWN_FLAG_PREFIX)) return /* @__PURE__ */ new Error("Unknown tail option");
|
|
809
|
+
return error;
|
|
810
|
+
}
|
|
518
811
|
/**
|
|
519
812
|
* touch command handler for the AST-based compiler.
|
|
520
813
|
*/
|
|
814
|
+
const parseTouchArgs = createWordParser({
|
|
815
|
+
accessTimeOnly: {
|
|
816
|
+
short: "a",
|
|
817
|
+
takesValue: false
|
|
818
|
+
},
|
|
819
|
+
modificationTimeOnly: {
|
|
820
|
+
short: "m",
|
|
821
|
+
takesValue: false
|
|
822
|
+
}
|
|
823
|
+
}, expandedWordToString);
|
|
521
824
|
/**
|
|
522
825
|
* Compile a touch command from SimpleCommandIR to StepIR.
|
|
523
826
|
*/
|
|
524
827
|
function compileTouch(cmd$1) {
|
|
525
|
-
const
|
|
526
|
-
|
|
828
|
+
const parsed = parseTouchArgs(cmd$1.args, { unknownFlagPolicy: "positional" });
|
|
829
|
+
const accessTimeOnly = parsed.flags.accessTimeOnly === true;
|
|
830
|
+
const modificationTimeOnly = parsed.flags.modificationTimeOnly === true;
|
|
831
|
+
const files$1 = parsed.positionalWords.filter((arg) => {
|
|
832
|
+
return !expandedWordToString(arg).startsWith("-");
|
|
833
|
+
});
|
|
527
834
|
if (files$1.length === 0) throw new Error("touch requires at least one file");
|
|
528
835
|
return {
|
|
529
836
|
cmd: "touch",
|
|
530
|
-
args: {
|
|
837
|
+
args: {
|
|
838
|
+
accessTimeOnly,
|
|
839
|
+
files: files$1,
|
|
840
|
+
modificationTimeOnly
|
|
841
|
+
}
|
|
531
842
|
};
|
|
532
843
|
}
|
|
533
844
|
let CommandHandler;
|
|
534
845
|
(function(_CommandHandler) {
|
|
535
846
|
const handlers = {
|
|
536
847
|
cat: compileCat,
|
|
848
|
+
cd: compileCd,
|
|
537
849
|
cp: compileCp,
|
|
538
850
|
head: compileHead,
|
|
539
851
|
ls: compileLs,
|
|
540
852
|
mkdir: compileMkdir,
|
|
541
853
|
mv: compileMv,
|
|
854
|
+
pwd: compilePwd,
|
|
542
855
|
rm: compileRm,
|
|
543
856
|
tail: compileTail,
|
|
544
857
|
touch: compileTouch
|
|
@@ -880,8 +1193,8 @@ function createEmptyFlags() {
|
|
|
880
1193
|
/**
|
|
881
1194
|
* Check if any quote flag is set.
|
|
882
1195
|
*/
|
|
883
|
-
function isQuoted(flags
|
|
884
|
-
return flags
|
|
1196
|
+
function isQuoted(flags) {
|
|
1197
|
+
return flags.quoted || flags.singleQuoted || flags.doubleQuoted;
|
|
885
1198
|
}
|
|
886
1199
|
/**
|
|
887
1200
|
* Human-readable names for token kinds.
|
|
@@ -923,11 +1236,11 @@ var Token = class Token$1 {
|
|
|
923
1236
|
spelling;
|
|
924
1237
|
span;
|
|
925
1238
|
flags;
|
|
926
|
-
constructor(kind, spelling, span, flags
|
|
1239
|
+
constructor(kind, spelling, span, flags = createEmptyFlags()) {
|
|
927
1240
|
this.kind = kind;
|
|
928
1241
|
this.spelling = spelling;
|
|
929
1242
|
this.span = span;
|
|
930
|
-
this.flags = flags
|
|
1243
|
+
this.flags = flags;
|
|
931
1244
|
}
|
|
932
1245
|
/**
|
|
933
1246
|
* Get the canonical spelling for a token kind.
|
|
@@ -1184,16 +1497,16 @@ var Scanner = class {
|
|
|
1184
1497
|
readComplexWord(start) {
|
|
1185
1498
|
this.stateCtx.reset();
|
|
1186
1499
|
let spelling = "";
|
|
1187
|
-
let flags
|
|
1500
|
+
let flags = createEmptyFlags();
|
|
1188
1501
|
while (!this.source.eof) {
|
|
1189
1502
|
const c = this.source.peek();
|
|
1190
1503
|
if (!this.stateCtx.inQuotes && this.isWordBoundary(c)) break;
|
|
1191
1504
|
const result = this.processChar(c);
|
|
1192
1505
|
spelling += result.chars;
|
|
1193
|
-
flags
|
|
1506
|
+
flags = mergeFlags(flags, result.flags);
|
|
1194
1507
|
if (result.done) break;
|
|
1195
1508
|
}
|
|
1196
|
-
return this.classifyWord(spelling, start, flags
|
|
1509
|
+
return this.classifyWord(spelling, start, flags);
|
|
1197
1510
|
}
|
|
1198
1511
|
processChar(c) {
|
|
1199
1512
|
if (c === "'" && !this.stateCtx.inDoubleQuote) return this.handleSingleQuote();
|
|
@@ -1211,11 +1524,11 @@ var Scanner = class {
|
|
|
1211
1524
|
}
|
|
1212
1525
|
handleGlobChar(c) {
|
|
1213
1526
|
this.source.advance();
|
|
1214
|
-
const flags
|
|
1215
|
-
flags
|
|
1527
|
+
const flags = createEmptyFlags();
|
|
1528
|
+
flags.containsGlob = true;
|
|
1216
1529
|
return {
|
|
1217
1530
|
chars: c,
|
|
1218
|
-
flags
|
|
1531
|
+
flags,
|
|
1219
1532
|
done: false
|
|
1220
1533
|
};
|
|
1221
1534
|
}
|
|
@@ -1231,12 +1544,12 @@ var Scanner = class {
|
|
|
1231
1544
|
}
|
|
1232
1545
|
this.stateCtx.push(LexerState.SINGLE_QUOTED);
|
|
1233
1546
|
this.source.advance();
|
|
1234
|
-
const flags
|
|
1235
|
-
flags
|
|
1236
|
-
flags
|
|
1547
|
+
const flags = createEmptyFlags();
|
|
1548
|
+
flags.singleQuoted = true;
|
|
1549
|
+
flags.quoted = true;
|
|
1237
1550
|
return {
|
|
1238
1551
|
chars: "",
|
|
1239
|
-
flags
|
|
1552
|
+
flags,
|
|
1240
1553
|
done: false
|
|
1241
1554
|
};
|
|
1242
1555
|
}
|
|
@@ -1252,12 +1565,12 @@ var Scanner = class {
|
|
|
1252
1565
|
}
|
|
1253
1566
|
this.stateCtx.push(LexerState.DOUBLE_QUOTED);
|
|
1254
1567
|
this.source.advance();
|
|
1255
|
-
const flags
|
|
1256
|
-
flags
|
|
1257
|
-
flags
|
|
1568
|
+
const flags = createEmptyFlags();
|
|
1569
|
+
flags.doubleQuoted = true;
|
|
1570
|
+
flags.quoted = true;
|
|
1258
1571
|
return {
|
|
1259
1572
|
chars: "",
|
|
1260
|
-
flags
|
|
1573
|
+
flags,
|
|
1261
1574
|
done: false
|
|
1262
1575
|
};
|
|
1263
1576
|
}
|
|
@@ -1317,11 +1630,11 @@ var Scanner = class {
|
|
|
1317
1630
|
if (!this.source.eof) result += this.source.advance();
|
|
1318
1631
|
} else result += this.source.advance();
|
|
1319
1632
|
}
|
|
1320
|
-
const flags
|
|
1321
|
-
flags
|
|
1633
|
+
const flags = createEmptyFlags();
|
|
1634
|
+
flags.containsExpansion = true;
|
|
1322
1635
|
return {
|
|
1323
1636
|
chars: result,
|
|
1324
|
-
flags
|
|
1637
|
+
flags,
|
|
1325
1638
|
done: false
|
|
1326
1639
|
};
|
|
1327
1640
|
}
|
|
@@ -1332,11 +1645,11 @@ var Scanner = class {
|
|
|
1332
1645
|
if (this.source.peek() === "]") result += this.source.advance();
|
|
1333
1646
|
while (!this.source.eof && this.source.peek() !== "]") result += this.source.advance();
|
|
1334
1647
|
if (this.source.peek() === "]") result += this.source.advance();
|
|
1335
|
-
const flags
|
|
1336
|
-
flags
|
|
1648
|
+
const flags = createEmptyFlags();
|
|
1649
|
+
flags.containsGlob = true;
|
|
1337
1650
|
return {
|
|
1338
1651
|
chars: result,
|
|
1339
|
-
flags
|
|
1652
|
+
flags,
|
|
1340
1653
|
done: false
|
|
1341
1654
|
};
|
|
1342
1655
|
}
|
|
@@ -1351,10 +1664,10 @@ var Scanner = class {
|
|
|
1351
1664
|
if (this.source.peek() === quoteChar) result += this.source.advance();
|
|
1352
1665
|
return result;
|
|
1353
1666
|
}
|
|
1354
|
-
classifyWord(spelling, start, flags
|
|
1355
|
-
if (NUMBER_PATTERN.test(spelling)) return this.makeToken(TokenKind.NUMBER, spelling, start, flags
|
|
1356
|
-
if (NAME_PATTERN.test(spelling)) return this.makeToken(TokenKind.NAME, spelling, start, flags
|
|
1357
|
-
return this.makeToken(TokenKind.WORD, spelling, start, flags
|
|
1667
|
+
classifyWord(spelling, start, flags) {
|
|
1668
|
+
if (NUMBER_PATTERN.test(spelling)) return this.makeToken(TokenKind.NUMBER, spelling, start, flags);
|
|
1669
|
+
if (NAME_PATTERN.test(spelling)) return this.makeToken(TokenKind.NAME, spelling, start, flags);
|
|
1670
|
+
return this.makeToken(TokenKind.WORD, spelling, start, flags);
|
|
1358
1671
|
}
|
|
1359
1672
|
skipWhitespace() {
|
|
1360
1673
|
while (!this.source.eof) {
|
|
@@ -1377,8 +1690,8 @@ var Scanner = class {
|
|
|
1377
1690
|
isWordBoundary(c) {
|
|
1378
1691
|
return WORD_BOUNDARY_CHARS.has(c) || c === "\0";
|
|
1379
1692
|
}
|
|
1380
|
-
makeToken(kind, spelling, start, flags
|
|
1381
|
-
return new Token(kind, spelling, start.span(this.source.position), flags
|
|
1693
|
+
makeToken(kind, spelling, start, flags = createEmptyFlags()) {
|
|
1694
|
+
return new Token(kind, spelling, start.span(this.source.position), flags);
|
|
1382
1695
|
}
|
|
1383
1696
|
};
|
|
1384
1697
|
/**
|
|
@@ -2153,31 +2466,187 @@ function collect() {
|
|
|
2153
2466
|
|
|
2154
2467
|
//#endregion
|
|
2155
2468
|
//#region src/operator/cat/cat.ts
|
|
2156
|
-
function
|
|
2469
|
+
function isLineRecord(record) {
|
|
2470
|
+
return record.kind === "line";
|
|
2471
|
+
}
|
|
2472
|
+
function formatNonPrinting(text) {
|
|
2473
|
+
let formatted = "";
|
|
2474
|
+
for (const char of text) {
|
|
2475
|
+
const code = char.charCodeAt(0);
|
|
2476
|
+
if (code === 9) {
|
|
2477
|
+
formatted += " ";
|
|
2478
|
+
continue;
|
|
2479
|
+
}
|
|
2480
|
+
if (code < 32) {
|
|
2481
|
+
formatted += `^${String.fromCharCode(code + 64)}`;
|
|
2482
|
+
continue;
|
|
2483
|
+
}
|
|
2484
|
+
if (code === 127) {
|
|
2485
|
+
formatted += "^?";
|
|
2486
|
+
continue;
|
|
2487
|
+
}
|
|
2488
|
+
formatted += char;
|
|
2489
|
+
}
|
|
2490
|
+
return formatted;
|
|
2491
|
+
}
|
|
2492
|
+
function renderLineText(text, lineNumber, options) {
|
|
2493
|
+
let rendered = text;
|
|
2494
|
+
const showNonprinting = options.showAll || options.showNonprinting;
|
|
2495
|
+
const showTabs = options.showAll || options.showTabs;
|
|
2496
|
+
const showEnds = options.showAll || options.showEnds;
|
|
2497
|
+
if (showNonprinting) rendered = formatNonPrinting(rendered);
|
|
2498
|
+
if (showTabs) rendered = rendered.replaceAll(" ", "^I");
|
|
2499
|
+
if (showEnds) rendered = `${rendered}$`;
|
|
2500
|
+
if (lineNumber !== null) rendered = `${lineNumber.toString().padStart(6, " ")}\t${rendered}`;
|
|
2501
|
+
return rendered;
|
|
2502
|
+
}
|
|
2503
|
+
function normalizeOptions(options) {
|
|
2504
|
+
return {
|
|
2505
|
+
numberLines: options?.numberLines ?? false,
|
|
2506
|
+
numberNonBlank: options?.numberNonBlank ?? false,
|
|
2507
|
+
showAll: options?.showAll ?? false,
|
|
2508
|
+
showEnds: options?.showEnds ?? false,
|
|
2509
|
+
showNonprinting: options?.showNonprinting ?? false,
|
|
2510
|
+
showTabs: options?.showTabs ?? false,
|
|
2511
|
+
squeezeBlank: options?.squeezeBlank ?? false
|
|
2512
|
+
};
|
|
2513
|
+
}
|
|
2514
|
+
function nextRenderedLine(text, state, options) {
|
|
2515
|
+
const isBlank = text.length === 0;
|
|
2516
|
+
if (options.squeezeBlank && isBlank && state.previousWasBlank) return {
|
|
2517
|
+
isSkipped: true,
|
|
2518
|
+
lineNumber: null,
|
|
2519
|
+
text
|
|
2520
|
+
};
|
|
2521
|
+
state.previousWasBlank = isBlank;
|
|
2522
|
+
return {
|
|
2523
|
+
isSkipped: false,
|
|
2524
|
+
lineNumber: (options.numberNonBlank ? !isBlank : options.numberLines) ? state.renderedLineNumber++ : null,
|
|
2525
|
+
text
|
|
2526
|
+
};
|
|
2527
|
+
}
|
|
2528
|
+
async function* emitLineRecord(record, state, options) {
|
|
2529
|
+
const rendered = nextRenderedLine(record.text, state, options);
|
|
2530
|
+
if (rendered.isSkipped) return;
|
|
2531
|
+
yield {
|
|
2532
|
+
...record,
|
|
2533
|
+
text: renderLineText(rendered.text, rendered.lineNumber, options)
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
async function* emitJsonRecord(value, state, options) {
|
|
2537
|
+
const rendered = nextRenderedLine(JSON.stringify(value), state, options);
|
|
2538
|
+
if (rendered.isSkipped) return;
|
|
2539
|
+
yield {
|
|
2540
|
+
kind: "line",
|
|
2541
|
+
text: renderLineText(rendered.text, rendered.lineNumber, options)
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
async function* emitFileLines(fs, path, state, options) {
|
|
2545
|
+
let sourceLineNum = 1;
|
|
2546
|
+
for await (const rawText of fs.readLines(path)) {
|
|
2547
|
+
const rendered = nextRenderedLine(rawText, state, options);
|
|
2548
|
+
if (rendered.isSkipped) continue;
|
|
2549
|
+
yield {
|
|
2550
|
+
file: path,
|
|
2551
|
+
kind: "line",
|
|
2552
|
+
lineNum: sourceLineNum++,
|
|
2553
|
+
text: renderLineText(rendered.text, rendered.lineNumber, options)
|
|
2554
|
+
};
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
function cat(fs, options) {
|
|
2558
|
+
const normalized = normalizeOptions(options);
|
|
2559
|
+
const state = {
|
|
2560
|
+
previousWasBlank: false,
|
|
2561
|
+
renderedLineNumber: 1
|
|
2562
|
+
};
|
|
2157
2563
|
return async function* (input) {
|
|
2158
|
-
for await (const
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2564
|
+
for await (const record of input) {
|
|
2565
|
+
if (isLineRecord(record)) {
|
|
2566
|
+
yield* emitLineRecord(record, state, normalized);
|
|
2567
|
+
continue;
|
|
2568
|
+
}
|
|
2569
|
+
if (record.kind === "json") {
|
|
2570
|
+
yield* emitJsonRecord(record.value, state, normalized);
|
|
2571
|
+
continue;
|
|
2572
|
+
}
|
|
2573
|
+
yield* emitFileLines(fs, record.path, state, normalized);
|
|
2166
2574
|
}
|
|
2167
2575
|
};
|
|
2168
2576
|
}
|
|
2169
2577
|
|
|
2170
2578
|
//#endregion
|
|
2171
2579
|
//#region src/operator/cp/cp.ts
|
|
2580
|
+
const TRAILING_SLASH_REGEX$3 = /\/+$/;
|
|
2581
|
+
const MULTIPLE_SLASH_REGEX$3 = /\/+/g;
|
|
2582
|
+
function trimTrailingSlash$2(path) {
|
|
2583
|
+
return path.replace(TRAILING_SLASH_REGEX$3, "");
|
|
2584
|
+
}
|
|
2585
|
+
function joinPath$1(base, suffix) {
|
|
2586
|
+
return `${trimTrailingSlash$2(base)}/${suffix}`.replace(MULTIPLE_SLASH_REGEX$3, "/");
|
|
2587
|
+
}
|
|
2588
|
+
function basename$1(path) {
|
|
2589
|
+
const normalized = trimTrailingSlash$2(path);
|
|
2590
|
+
const slashIndex = normalized.lastIndexOf("/");
|
|
2591
|
+
if (slashIndex === -1) return normalized;
|
|
2592
|
+
return normalized.slice(slashIndex + 1);
|
|
2593
|
+
}
|
|
2594
|
+
async function isDirectory$1(fs, path) {
|
|
2595
|
+
try {
|
|
2596
|
+
return (await fs.stat(path)).isDirectory;
|
|
2597
|
+
} catch {
|
|
2598
|
+
return false;
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
async function assertCanWriteDestination(fs, path, force, interactive) {
|
|
2602
|
+
if (!await fs.exists(path)) return;
|
|
2603
|
+
if (interactive) throw new Error(`cp: destination exists (interactive): ${path}`);
|
|
2604
|
+
if (!force) throw new Error(`cp: destination exists (use -f to overwrite): ${path}`);
|
|
2605
|
+
}
|
|
2606
|
+
async function copyFileWithPolicy(fs, src, dest, force, interactive) {
|
|
2607
|
+
await assertCanWriteDestination(fs, dest, force, interactive);
|
|
2608
|
+
const content = await fs.readFile(src);
|
|
2609
|
+
await fs.writeFile(dest, content);
|
|
2610
|
+
}
|
|
2611
|
+
async function copyDirectoryRecursive(fs, srcDir, destDir, force, interactive) {
|
|
2612
|
+
const normalizedSrc = trimTrailingSlash$2(srcDir);
|
|
2613
|
+
const glob$1 = `${normalizedSrc}/**/*`;
|
|
2614
|
+
for await (const srcPath of fs.readdir(glob$1)) {
|
|
2615
|
+
const targetPath = joinPath$1(destDir, srcPath.slice(normalizedSrc.length + 1));
|
|
2616
|
+
if ((await fs.stat(srcPath)).isDirectory) continue;
|
|
2617
|
+
await copyFileWithPolicy(fs, srcPath, targetPath, force, interactive);
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2172
2620
|
function cp(fs) {
|
|
2173
|
-
return async ({
|
|
2174
|
-
|
|
2175
|
-
await fs
|
|
2621
|
+
return async ({ srcs, dest, force = false, interactive = false, recursive }) => {
|
|
2622
|
+
if (srcs.length === 0) throw new Error("cp requires at least one source");
|
|
2623
|
+
const destinationIsDirectory = await isDirectory$1(fs, dest);
|
|
2624
|
+
if (srcs.length > 1 && !destinationIsDirectory) throw new Error("cp destination must be a directory for multiple sources");
|
|
2625
|
+
for (const src of srcs) {
|
|
2626
|
+
const srcStat = await fs.stat(src);
|
|
2627
|
+
const targetPath = destinationIsDirectory || srcs.length > 1 ? joinPath$1(dest, basename$1(src)) : dest;
|
|
2628
|
+
if (srcStat.isDirectory) {
|
|
2629
|
+
if (!recursive) throw new Error(`cp: omitting directory "${src}" (use -r)`);
|
|
2630
|
+
await copyDirectoryRecursive(fs, src, targetPath, force, interactive);
|
|
2631
|
+
continue;
|
|
2632
|
+
}
|
|
2633
|
+
await copyFileWithPolicy(fs, src, targetPath, force, interactive);
|
|
2634
|
+
}
|
|
2176
2635
|
};
|
|
2177
2636
|
}
|
|
2178
2637
|
|
|
2179
2638
|
//#endregion
|
|
2180
2639
|
//#region src/operator/head/head.ts
|
|
2640
|
+
function headLines(n) {
|
|
2641
|
+
return async function* (input) {
|
|
2642
|
+
let emitted = 0;
|
|
2643
|
+
for await (const line of input) {
|
|
2644
|
+
if (emitted >= n) break;
|
|
2645
|
+
emitted++;
|
|
2646
|
+
yield line;
|
|
2647
|
+
}
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2181
2650
|
function headWithN(fs, n) {
|
|
2182
2651
|
return async function* (input) {
|
|
2183
2652
|
for await (const file of input) {
|
|
@@ -2197,11 +2666,21 @@ function headWithN(fs, n) {
|
|
|
2197
2666
|
|
|
2198
2667
|
//#endregion
|
|
2199
2668
|
//#region src/operator/ls/ls.ts
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2669
|
+
function basename(path) {
|
|
2670
|
+
const normalized = path.replace(/\/+$/g, "");
|
|
2671
|
+
const slashIndex = normalized.lastIndexOf("/");
|
|
2672
|
+
if (slashIndex === -1) return normalized;
|
|
2673
|
+
return normalized.slice(slashIndex + 1);
|
|
2674
|
+
}
|
|
2675
|
+
async function* ls(fs, path, options) {
|
|
2676
|
+
const showAll = options?.showAll ?? false;
|
|
2677
|
+
for await (const listedPath of fs.readdir(path)) {
|
|
2678
|
+
if (!showAll && basename(listedPath).startsWith(".")) continue;
|
|
2679
|
+
yield {
|
|
2680
|
+
kind: "file",
|
|
2681
|
+
path: listedPath
|
|
2682
|
+
};
|
|
2683
|
+
}
|
|
2205
2684
|
}
|
|
2206
2685
|
|
|
2207
2686
|
//#endregion
|
|
@@ -2214,27 +2693,42 @@ function mkdir(fs) {
|
|
|
2214
2693
|
|
|
2215
2694
|
//#endregion
|
|
2216
2695
|
//#region src/operator/mv/mv.ts
|
|
2217
|
-
const
|
|
2696
|
+
const TRAILING_SLASH_REGEX$2 = /\/+$/;
|
|
2697
|
+
const MULTIPLE_SLASH_REGEX$2 = /\/+/g;
|
|
2698
|
+
function trimTrailingSlash$1(path) {
|
|
2699
|
+
return path.replace(TRAILING_SLASH_REGEX$2, "");
|
|
2700
|
+
}
|
|
2218
2701
|
function extractFileName(path) {
|
|
2219
|
-
const
|
|
2220
|
-
|
|
2221
|
-
|
|
2702
|
+
const normalized = trimTrailingSlash$1(path);
|
|
2703
|
+
const lastSlashIndex = normalized.lastIndexOf("/");
|
|
2704
|
+
if (lastSlashIndex === -1) return normalized;
|
|
2705
|
+
return normalized.slice(lastSlashIndex + 1);
|
|
2706
|
+
}
|
|
2707
|
+
function joinPath(base, suffix) {
|
|
2708
|
+
return `${trimTrailingSlash$1(base)}/${suffix}`.replace(MULTIPLE_SLASH_REGEX$2, "/");
|
|
2709
|
+
}
|
|
2710
|
+
async function isDirectory(fs, path) {
|
|
2711
|
+
try {
|
|
2712
|
+
return (await fs.stat(path)).isDirectory;
|
|
2713
|
+
} catch {
|
|
2714
|
+
return false;
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
async function assertCanMoveToDestination(fs, dest, force, interactive) {
|
|
2718
|
+
if (!await fs.exists(dest)) return;
|
|
2719
|
+
if (interactive) throw new Error(`mv: destination exists (interactive): ${dest}`);
|
|
2720
|
+
if (!force) throw new Error(`mv: destination exists (use -f to overwrite): ${dest}`);
|
|
2222
2721
|
}
|
|
2223
2722
|
function mv(fs) {
|
|
2224
|
-
return async ({ srcs, dest }) => {
|
|
2225
|
-
if (srcs.length ===
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
await moveFile(fs, src, dest);
|
|
2234
|
-
}
|
|
2235
|
-
} else for (const src of srcs) {
|
|
2236
|
-
const fileName = extractFileName(src);
|
|
2237
|
-
await moveFile(fs, src, (dest.endsWith("/") ? dest + fileName : `${dest}/${fileName}`).replace(MULTIPLE_SLASH_REGEX, "/"));
|
|
2723
|
+
return async ({ srcs, dest, force = false, interactive = false }) => {
|
|
2724
|
+
if (srcs.length === 0) throw new Error("mv requires at least one source");
|
|
2725
|
+
const destinationIsDirectory = await isDirectory(fs, dest);
|
|
2726
|
+
if (srcs.length > 1 && !destinationIsDirectory) throw new Error("mv destination must be a directory for multiple sources");
|
|
2727
|
+
for (const src of srcs) {
|
|
2728
|
+
if ((await fs.stat(src)).isDirectory) throw new Error(`mv: directory moves are not supported: ${src}`);
|
|
2729
|
+
const targetPath = destinationIsDirectory || srcs.length > 1 ? joinPath(dest, extractFileName(src)) : dest;
|
|
2730
|
+
await assertCanMoveToDestination(fs, targetPath, force, interactive);
|
|
2731
|
+
await moveFile(fs, src, targetPath);
|
|
2238
2732
|
}
|
|
2239
2733
|
};
|
|
2240
2734
|
}
|
|
@@ -2244,11 +2738,34 @@ async function moveFile(fs, src, dest) {
|
|
|
2244
2738
|
await fs.deleteFile(src);
|
|
2245
2739
|
}
|
|
2246
2740
|
|
|
2741
|
+
//#endregion
|
|
2742
|
+
//#region src/operator/pwd/pwd.ts
|
|
2743
|
+
const ROOT_DIRECTORY$2 = "/";
|
|
2744
|
+
async function* pwd(cwd = ROOT_DIRECTORY$2) {
|
|
2745
|
+
yield {
|
|
2746
|
+
kind: "line",
|
|
2747
|
+
text: cwd
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2247
2751
|
//#endregion
|
|
2248
2752
|
//#region src/operator/rm/rm.ts
|
|
2249
2753
|
function rm(fs) {
|
|
2250
|
-
return async ({ path }) => {
|
|
2251
|
-
|
|
2754
|
+
return async ({ path, recursive, force = false, interactive = false }) => {
|
|
2755
|
+
if (interactive) throw new Error(`rm: interactive mode is not supported: ${path}`);
|
|
2756
|
+
let stat = null;
|
|
2757
|
+
try {
|
|
2758
|
+
stat = await fs.stat(path);
|
|
2759
|
+
} catch {
|
|
2760
|
+
if (force) return;
|
|
2761
|
+
throw new Error(`File not found: ${path}`);
|
|
2762
|
+
}
|
|
2763
|
+
if (!stat.isDirectory) {
|
|
2764
|
+
await fs.deleteFile(path);
|
|
2765
|
+
return;
|
|
2766
|
+
}
|
|
2767
|
+
if (!recursive) throw new Error(`rm: cannot remove '${path}': Is a directory`);
|
|
2768
|
+
await fs.deleteDirectory(path, true);
|
|
2252
2769
|
};
|
|
2253
2770
|
}
|
|
2254
2771
|
|
|
@@ -2268,8 +2785,18 @@ function tail(n) {
|
|
|
2268
2785
|
//#endregion
|
|
2269
2786
|
//#region src/operator/touch/touch.ts
|
|
2270
2787
|
function touch(fs) {
|
|
2271
|
-
return async ({ files: files$1 }) => {
|
|
2272
|
-
|
|
2788
|
+
return async ({ files: files$1, accessTimeOnly = false, modificationTimeOnly = false }) => {
|
|
2789
|
+
const shouldUpdateMtime = !accessTimeOnly || modificationTimeOnly;
|
|
2790
|
+
for (const file of files$1) {
|
|
2791
|
+
if (!await fs.exists(file)) {
|
|
2792
|
+
await fs.writeFile(file, new Uint8Array());
|
|
2793
|
+
continue;
|
|
2794
|
+
}
|
|
2795
|
+
if (shouldUpdateMtime) {
|
|
2796
|
+
const content = await fs.readFile(file);
|
|
2797
|
+
await fs.writeFile(file, content);
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2273
2800
|
};
|
|
2274
2801
|
}
|
|
2275
2802
|
|
|
@@ -2285,115 +2812,274 @@ async function* files(...paths) {
|
|
|
2285
2812
|
//#endregion
|
|
2286
2813
|
//#region src/execute/execute.ts
|
|
2287
2814
|
const textEncoder = new TextEncoder();
|
|
2815
|
+
const EFFECT_COMMANDS = new Set([
|
|
2816
|
+
"cd",
|
|
2817
|
+
"cp",
|
|
2818
|
+
"mkdir",
|
|
2819
|
+
"mv",
|
|
2820
|
+
"rm",
|
|
2821
|
+
"touch"
|
|
2822
|
+
]);
|
|
2823
|
+
const LS_GLOB_PATTERN_REGEX = /[*?]/;
|
|
2824
|
+
const MULTIPLE_SLASH_REGEX$1 = /\/+/g;
|
|
2825
|
+
const TRAILING_SLASH_REGEX$1 = /\/+$/;
|
|
2826
|
+
const ROOT_DIRECTORY$1 = "/";
|
|
2827
|
+
function isEffectStep(step) {
|
|
2828
|
+
return EFFECT_COMMANDS.has(step.cmd);
|
|
2829
|
+
}
|
|
2830
|
+
async function* emptyStream() {}
|
|
2288
2831
|
/**
|
|
2289
2832
|
* Execute compiles a PipelineIR into an executable result.
|
|
2290
2833
|
* Returns either a stream (for producers/transducers) or a promise (for sinks).
|
|
2291
2834
|
*/
|
|
2292
|
-
function execute(ir, fs) {
|
|
2293
|
-
const
|
|
2294
|
-
if (
|
|
2835
|
+
function execute(ir, fs, context = { cwd: ROOT_DIRECTORY$1 }) {
|
|
2836
|
+
const normalizedContext = normalizeContext(context);
|
|
2837
|
+
if (ir.steps.length === 0) return {
|
|
2838
|
+
kind: "stream",
|
|
2839
|
+
value: emptyStream()
|
|
2840
|
+
};
|
|
2841
|
+
const lastStep = ir.steps.at(-1);
|
|
2842
|
+
if (!lastStep) return {
|
|
2295
2843
|
kind: "stream",
|
|
2296
|
-
value: (
|
|
2844
|
+
value: emptyStream()
|
|
2297
2845
|
};
|
|
2298
|
-
|
|
2846
|
+
if (isEffectStep(lastStep)) {
|
|
2847
|
+
for (const [index, step] of ir.steps.entries()) if (isEffectStep(step) && index !== ir.steps.length - 1) throw new Error(`Unsupported pipeline: "${step.cmd}" must be the final command`);
|
|
2848
|
+
return applyOutputRedirect({
|
|
2849
|
+
kind: "sink",
|
|
2850
|
+
value: executePipelineToSink(ir.steps, fs, normalizedContext)
|
|
2851
|
+
}, lastStep, fs);
|
|
2852
|
+
}
|
|
2853
|
+
return applyOutputRedirect({
|
|
2854
|
+
kind: "stream",
|
|
2855
|
+
value: executePipelineToStream(ir.steps, fs, normalizedContext)
|
|
2856
|
+
}, lastStep, fs);
|
|
2857
|
+
}
|
|
2858
|
+
function executePipelineToStream(steps, fs, context) {
|
|
2859
|
+
return (async function* () {
|
|
2860
|
+
let stream = null;
|
|
2861
|
+
for (const step of steps) {
|
|
2862
|
+
if (isEffectStep(step)) throw new Error(`Unsupported pipeline: "${step.cmd}" requires being the final command`);
|
|
2863
|
+
stream = executeStreamStep(step, fs, stream, context);
|
|
2864
|
+
}
|
|
2865
|
+
if (!stream) return;
|
|
2866
|
+
yield* stream;
|
|
2867
|
+
})();
|
|
2868
|
+
}
|
|
2869
|
+
async function executePipelineToSink(steps, fs, context) {
|
|
2870
|
+
const finalStep = steps.at(-1);
|
|
2871
|
+
if (!(finalStep && isEffectStep(finalStep))) return;
|
|
2872
|
+
if (steps.length > 1) {
|
|
2873
|
+
const stream = executePipelineToStream(steps.slice(0, -1), fs, context);
|
|
2874
|
+
for await (const _record of stream);
|
|
2875
|
+
}
|
|
2876
|
+
await executeEffectStep(finalStep, fs, context);
|
|
2877
|
+
}
|
|
2878
|
+
function executeStreamStep(step, fs, input, context) {
|
|
2299
2879
|
switch (step.cmd) {
|
|
2300
2880
|
case "cat": {
|
|
2301
|
-
const
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2881
|
+
const options = {
|
|
2882
|
+
numberLines: step.args.numberLines,
|
|
2883
|
+
numberNonBlank: step.args.numberNonBlank,
|
|
2884
|
+
showAll: step.args.showAll,
|
|
2885
|
+
showEnds: step.args.showEnds,
|
|
2886
|
+
showNonprinting: step.args.showNonprinting,
|
|
2887
|
+
showTabs: step.args.showTabs,
|
|
2888
|
+
squeezeBlank: step.args.squeezeBlank
|
|
2305
2889
|
};
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
result = {
|
|
2312
|
-
kind: "sink",
|
|
2313
|
-
value: Promise.all(map(srcPaths, (src) => cp(fs)({
|
|
2314
|
-
src,
|
|
2315
|
-
dest: destPath,
|
|
2316
|
-
recursive: step.args.recursive
|
|
2317
|
-
}))).then()
|
|
2318
|
-
};
|
|
2319
|
-
break;
|
|
2890
|
+
const inputPath = getRedirectPath(step.redirections, "input");
|
|
2891
|
+
const filePaths = withInputRedirect(extractPathsFromExpandedWords(step.args.files), inputPath);
|
|
2892
|
+
if (filePaths.length > 0) return cat(fs, options)(files(...filePaths));
|
|
2893
|
+
if (input) return cat(fs, options)(input);
|
|
2894
|
+
return emptyStream();
|
|
2320
2895
|
}
|
|
2321
2896
|
case "head": {
|
|
2322
2897
|
const inputPath = getRedirectPath(step.redirections, "input");
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
break;
|
|
2898
|
+
const filePaths = withInputRedirect(extractPathsFromExpandedWords(step.args.files), inputPath);
|
|
2899
|
+
if (filePaths.length > 0) return headWithN(fs, step.args.n)(files(...filePaths));
|
|
2900
|
+
if (!input) return emptyStream();
|
|
2901
|
+
return headLines(step.args.n)(toLineStream(fs, input));
|
|
2328
2902
|
}
|
|
2329
2903
|
case "ls": {
|
|
2330
2904
|
const paths = extractPathsFromExpandedWords(step.args.paths);
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2905
|
+
return (async function* () {
|
|
2906
|
+
for (const inputPath of paths) {
|
|
2907
|
+
const resolvedPath = await resolveLsPath(fs, inputPath);
|
|
2908
|
+
for await (const fileRecord of ls(fs, resolvedPath, { showAll: step.args.showAll })) {
|
|
2909
|
+
if (step.args.longFormat) {
|
|
2910
|
+
const stat = await fs.stat(fileRecord.path);
|
|
2911
|
+
yield {
|
|
2912
|
+
kind: "line",
|
|
2913
|
+
text: formatLongListing(fileRecord.path, stat)
|
|
2914
|
+
};
|
|
2915
|
+
continue;
|
|
2916
|
+
}
|
|
2917
|
+
yield fileRecord;
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
})();
|
|
2921
|
+
}
|
|
2922
|
+
case "tail": {
|
|
2923
|
+
const inputPath = getRedirectPath(step.redirections, "input");
|
|
2924
|
+
const filePaths = withInputRedirect(extractPathsFromExpandedWords(step.args.files), inputPath);
|
|
2925
|
+
if (filePaths.length > 0) return (async function* () {
|
|
2926
|
+
for (const filePath of filePaths) yield* tail(step.args.n)(cat(fs)(files(filePath)));
|
|
2927
|
+
})();
|
|
2928
|
+
if (!input) return emptyStream();
|
|
2929
|
+
return tail(step.args.n)(toLineStream(fs, input));
|
|
2930
|
+
}
|
|
2931
|
+
case "pwd": return pwd(context.cwd);
|
|
2932
|
+
default: {
|
|
2933
|
+
const _exhaustive = step;
|
|
2934
|
+
throw new Error(`Unknown command: ${String(_exhaustive.cmd)}`);
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
function normalizeAbsolutePath$1(path) {
|
|
2939
|
+
const segments = (path.startsWith(ROOT_DIRECTORY$1) ? path : `${ROOT_DIRECTORY$1}${path}`).replace(MULTIPLE_SLASH_REGEX$1, "/").split(ROOT_DIRECTORY$1);
|
|
2940
|
+
const normalizedSegments = [];
|
|
2941
|
+
for (const segment of segments) {
|
|
2942
|
+
if (segment === "" || segment === ".") continue;
|
|
2943
|
+
if (segment === "..") {
|
|
2944
|
+
normalizedSegments.pop();
|
|
2945
|
+
continue;
|
|
2946
|
+
}
|
|
2947
|
+
normalizedSegments.push(segment);
|
|
2948
|
+
}
|
|
2949
|
+
const normalizedPath = `${ROOT_DIRECTORY$1}${normalizedSegments.join(ROOT_DIRECTORY$1)}`;
|
|
2950
|
+
return normalizedPath === "" ? ROOT_DIRECTORY$1 : normalizedPath;
|
|
2951
|
+
}
|
|
2952
|
+
function normalizeCwd$1(cwd) {
|
|
2953
|
+
if (cwd === "") return ROOT_DIRECTORY$1;
|
|
2954
|
+
const trimmed = normalizeAbsolutePath$1(cwd).replace(TRAILING_SLASH_REGEX$1, "");
|
|
2955
|
+
return trimmed === "" ? ROOT_DIRECTORY$1 : trimmed;
|
|
2956
|
+
}
|
|
2957
|
+
function normalizeContext(context) {
|
|
2958
|
+
context.cwd = normalizeCwd$1(context.cwd);
|
|
2959
|
+
return context;
|
|
2960
|
+
}
|
|
2961
|
+
function resolvePathFromCwd(cwd, path) {
|
|
2962
|
+
if (path === "") return cwd;
|
|
2963
|
+
if (path.startsWith(ROOT_DIRECTORY$1)) return normalizeAbsolutePath$1(path);
|
|
2964
|
+
return normalizeAbsolutePath$1(`${cwd}/${path}`);
|
|
2965
|
+
}
|
|
2966
|
+
async function executeEffectStep(step, fs, context) {
|
|
2967
|
+
switch (step.cmd) {
|
|
2968
|
+
case "cd": {
|
|
2969
|
+
const requestedPath = expandedWordToString(step.args.path);
|
|
2970
|
+
const resolvedPath = resolvePathFromCwd(context.cwd, requestedPath);
|
|
2971
|
+
let stat;
|
|
2972
|
+
try {
|
|
2973
|
+
stat = await fs.stat(resolvedPath);
|
|
2974
|
+
} catch {
|
|
2975
|
+
throw new Error(`cd: no such file or directory: ${requestedPath}`);
|
|
2976
|
+
}
|
|
2977
|
+
if (!stat.isDirectory) throw new Error(`cd: not a directory: ${requestedPath}`);
|
|
2978
|
+
context.cwd = resolvedPath;
|
|
2979
|
+
break;
|
|
2980
|
+
}
|
|
2981
|
+
case "cp": {
|
|
2982
|
+
const srcPaths = extractPathsFromExpandedWords(step.args.srcs);
|
|
2983
|
+
const destPath = expandedWordToString(step.args.dest);
|
|
2984
|
+
await cp(fs)({
|
|
2985
|
+
srcs: srcPaths,
|
|
2986
|
+
dest: destPath,
|
|
2987
|
+
force: step.args.force,
|
|
2988
|
+
interactive: step.args.interactive,
|
|
2989
|
+
recursive: step.args.recursive
|
|
2990
|
+
});
|
|
2338
2991
|
break;
|
|
2339
2992
|
}
|
|
2340
2993
|
case "mkdir": {
|
|
2341
2994
|
const paths = extractPathsFromExpandedWords(step.args.paths);
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
recursive: step.args.recursive
|
|
2347
|
-
}))).then()
|
|
2348
|
-
};
|
|
2995
|
+
for (const path of paths) await mkdir(fs)({
|
|
2996
|
+
path,
|
|
2997
|
+
recursive: step.args.recursive
|
|
2998
|
+
});
|
|
2349
2999
|
break;
|
|
2350
3000
|
}
|
|
2351
3001
|
case "mv": {
|
|
2352
3002
|
const srcPaths = extractPathsFromExpandedWords(step.args.srcs);
|
|
2353
3003
|
const destPath = expandedWordToString(step.args.dest);
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
};
|
|
3004
|
+
await mv(fs)({
|
|
3005
|
+
srcs: srcPaths,
|
|
3006
|
+
dest: destPath,
|
|
3007
|
+
force: step.args.force,
|
|
3008
|
+
interactive: step.args.interactive
|
|
3009
|
+
});
|
|
2361
3010
|
break;
|
|
2362
3011
|
}
|
|
2363
3012
|
case "rm": {
|
|
2364
3013
|
const paths = extractPathsFromExpandedWords(step.args.paths);
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
};
|
|
2372
|
-
break;
|
|
2373
|
-
}
|
|
2374
|
-
case "tail": {
|
|
2375
|
-
const inputPath = getRedirectPath(step.redirections, "input");
|
|
2376
|
-
const filePaths = withInputRedirect(extractPathsFromExpandedWords(step.args.files), inputPath);
|
|
2377
|
-
result = {
|
|
2378
|
-
kind: "stream",
|
|
2379
|
-
value: (async function* () {
|
|
2380
|
-
const results = await Promise.all(map(filePaths, (file) => pipe(files(file), cat(fs), tail(step.args.n))));
|
|
2381
|
-
for (const lines of results) yield* lines;
|
|
2382
|
-
})()
|
|
2383
|
-
};
|
|
3014
|
+
for (const path of paths) await rm(fs)({
|
|
3015
|
+
path,
|
|
3016
|
+
force: step.args.force,
|
|
3017
|
+
interactive: step.args.interactive,
|
|
3018
|
+
recursive: step.args.recursive
|
|
3019
|
+
});
|
|
2384
3020
|
break;
|
|
2385
3021
|
}
|
|
2386
3022
|
case "touch": {
|
|
2387
3023
|
const filePaths = extractPathsFromExpandedWords(step.args.files);
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
3024
|
+
await touch(fs)({
|
|
3025
|
+
files: filePaths,
|
|
3026
|
+
accessTimeOnly: step.args.accessTimeOnly,
|
|
3027
|
+
modificationTimeOnly: step.args.modificationTimeOnly
|
|
3028
|
+
});
|
|
2392
3029
|
break;
|
|
2393
3030
|
}
|
|
2394
|
-
default:
|
|
3031
|
+
default: {
|
|
3032
|
+
const _exhaustive = step;
|
|
3033
|
+
throw new Error(`Unknown command: ${String(_exhaustive.cmd)}`);
|
|
3034
|
+
}
|
|
2395
3035
|
}
|
|
2396
|
-
|
|
3036
|
+
}
|
|
3037
|
+
async function* toLineStream(fs, input) {
|
|
3038
|
+
for await (const record of input) {
|
|
3039
|
+
if (record.kind === "line") {
|
|
3040
|
+
yield record;
|
|
3041
|
+
continue;
|
|
3042
|
+
}
|
|
3043
|
+
if (record.kind === "file") {
|
|
3044
|
+
let lineNum = 1;
|
|
3045
|
+
for await (const text of fs.readLines(record.path)) yield {
|
|
3046
|
+
kind: "line",
|
|
3047
|
+
text,
|
|
3048
|
+
file: record.path,
|
|
3049
|
+
lineNum: lineNum++
|
|
3050
|
+
};
|
|
3051
|
+
continue;
|
|
3052
|
+
}
|
|
3053
|
+
yield {
|
|
3054
|
+
kind: "line",
|
|
3055
|
+
text: JSON.stringify(record.value)
|
|
3056
|
+
};
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
function formatLongListing(path, stat) {
|
|
3060
|
+
return `${stat.isDirectory ? "d" : "-"} ${String(stat.size).padStart(8, " ")} ${stat.mtime.toISOString()} ${path}`;
|
|
3061
|
+
}
|
|
3062
|
+
function normalizeLsPath(path) {
|
|
3063
|
+
if (path === "." || path === "./") return "/";
|
|
3064
|
+
if (path.startsWith("./")) return `/${path.slice(2)}`;
|
|
3065
|
+
if (path.startsWith("/")) return path;
|
|
3066
|
+
return `/${path}`;
|
|
3067
|
+
}
|
|
3068
|
+
function trimTrailingSlash(path) {
|
|
3069
|
+
if (path === "/") return path;
|
|
3070
|
+
return path.replace(TRAILING_SLASH_REGEX$1, "");
|
|
3071
|
+
}
|
|
3072
|
+
async function resolveLsPath(fs, path) {
|
|
3073
|
+
const normalizedPath = normalizeLsPath(path);
|
|
3074
|
+
if (LS_GLOB_PATTERN_REGEX.test(normalizedPath)) return normalizedPath;
|
|
3075
|
+
try {
|
|
3076
|
+
if (!(await fs.stat(normalizedPath)).isDirectory) return normalizedPath;
|
|
3077
|
+
} catch {
|
|
3078
|
+
return normalizedPath;
|
|
3079
|
+
}
|
|
3080
|
+
const directoryPath = trimTrailingSlash(normalizedPath);
|
|
3081
|
+
if (directoryPath === "/") return "/*";
|
|
3082
|
+
return `${directoryPath}/*`;
|
|
2397
3083
|
}
|
|
2398
3084
|
function getRedirectPath(redirections, kind) {
|
|
2399
3085
|
if (!redirections) return null;
|
|
@@ -2448,6 +3134,27 @@ function lazy(fn) {
|
|
|
2448
3134
|
|
|
2449
3135
|
//#endregion
|
|
2450
3136
|
//#region src/shell/shell.ts
|
|
3137
|
+
const ROOT_DIRECTORY = "/";
|
|
3138
|
+
const MULTIPLE_SLASH_REGEX = /\/+/g;
|
|
3139
|
+
const TRAILING_SLASH_REGEX = /\/+$/;
|
|
3140
|
+
function normalizeAbsolutePath(path) {
|
|
3141
|
+
const segments = (path.startsWith(ROOT_DIRECTORY) ? path : `${ROOT_DIRECTORY}${path}`).replace(MULTIPLE_SLASH_REGEX, "/").split(ROOT_DIRECTORY);
|
|
3142
|
+
const normalizedSegments = [];
|
|
3143
|
+
for (const segment of segments) {
|
|
3144
|
+
if (segment === "" || segment === ".") continue;
|
|
3145
|
+
if (segment === "..") {
|
|
3146
|
+
normalizedSegments.pop();
|
|
3147
|
+
continue;
|
|
3148
|
+
}
|
|
3149
|
+
normalizedSegments.push(segment);
|
|
3150
|
+
}
|
|
3151
|
+
return `${ROOT_DIRECTORY}${normalizedSegments.join(ROOT_DIRECTORY)}`;
|
|
3152
|
+
}
|
|
3153
|
+
function normalizeCwd(cwd) {
|
|
3154
|
+
if (cwd === "") return ROOT_DIRECTORY;
|
|
3155
|
+
const trimmed = normalizeAbsolutePath(cwd).replace(TRAILING_SLASH_REGEX, "");
|
|
3156
|
+
return trimmed === "" ? ROOT_DIRECTORY : trimmed;
|
|
3157
|
+
}
|
|
2451
3158
|
async function collectRecords(result) {
|
|
2452
3159
|
if (result.kind === "sink") {
|
|
2453
3160
|
await result.value;
|
|
@@ -2457,8 +3164,10 @@ async function collectRecords(result) {
|
|
|
2457
3164
|
}
|
|
2458
3165
|
var Shell = class {
|
|
2459
3166
|
fs;
|
|
2460
|
-
|
|
3167
|
+
currentCwd;
|
|
3168
|
+
constructor(fs, options = {}) {
|
|
2461
3169
|
this.fs = fs;
|
|
3170
|
+
this.currentCwd = normalizeCwd(options.cwd ?? ROOT_DIRECTORY);
|
|
2462
3171
|
}
|
|
2463
3172
|
$ = (strings, ...exprs) => {
|
|
2464
3173
|
return this._exec(strings, ...exprs);
|
|
@@ -2466,32 +3175,58 @@ var Shell = class {
|
|
|
2466
3175
|
exec(strings, ...exprs) {
|
|
2467
3176
|
return this._exec(strings, ...exprs);
|
|
2468
3177
|
}
|
|
3178
|
+
cwd(newCwd) {
|
|
3179
|
+
this.currentCwd = normalizeCwd(newCwd);
|
|
3180
|
+
}
|
|
2469
3181
|
_exec(strings, ...exprs) {
|
|
2470
3182
|
const source = String.raw(strings, ...exprs);
|
|
2471
3183
|
const fs = this.fs;
|
|
3184
|
+
let cwdOverride;
|
|
3185
|
+
const runWithContext = async () => {
|
|
3186
|
+
const commandStartCwd = normalizeCwd(cwdOverride ?? this.currentCwd);
|
|
3187
|
+
const context = { cwd: commandStartCwd };
|
|
3188
|
+
try {
|
|
3189
|
+
return await collectRecords(execute(ir(), fs, context));
|
|
3190
|
+
} finally {
|
|
3191
|
+
if (cwdOverride === void 0 || context.cwd !== commandStartCwd) this.currentCwd = context.cwd;
|
|
3192
|
+
}
|
|
3193
|
+
};
|
|
3194
|
+
const runStdoutWithContext = async () => {
|
|
3195
|
+
const commandStartCwd = normalizeCwd(cwdOverride ?? this.currentCwd);
|
|
3196
|
+
const context = { cwd: commandStartCwd };
|
|
3197
|
+
try {
|
|
3198
|
+
const result = execute(ir(), fs, context);
|
|
3199
|
+
if (result.kind === "sink") {
|
|
3200
|
+
await result.value;
|
|
3201
|
+
return;
|
|
3202
|
+
}
|
|
3203
|
+
for await (const r of result.value) if (r.kind === "line") process.stdout.write(`${r.text}\n`);
|
|
3204
|
+
} finally {
|
|
3205
|
+
if (cwdOverride === void 0 || context.cwd !== commandStartCwd) this.currentCwd = context.cwd;
|
|
3206
|
+
}
|
|
3207
|
+
};
|
|
2472
3208
|
const ir = lazy(() => {
|
|
2473
3209
|
return compile(parse(source));
|
|
2474
3210
|
});
|
|
2475
|
-
|
|
3211
|
+
const command = {
|
|
3212
|
+
cwd(path) {
|
|
3213
|
+
cwdOverride = normalizeCwd(path);
|
|
3214
|
+
return command;
|
|
3215
|
+
},
|
|
2476
3216
|
async json() {
|
|
2477
|
-
return (await
|
|
3217
|
+
return (await runWithContext()).filter((r) => r.kind === "json").map((r) => r.value);
|
|
2478
3218
|
},
|
|
2479
3219
|
async lines() {
|
|
2480
|
-
return (await
|
|
3220
|
+
return (await runWithContext()).filter((r) => r.kind === "line").map((r) => r.text);
|
|
2481
3221
|
},
|
|
2482
3222
|
async raw() {
|
|
2483
|
-
return await
|
|
3223
|
+
return await runWithContext();
|
|
2484
3224
|
},
|
|
2485
3225
|
async stdout() {
|
|
2486
|
-
|
|
2487
|
-
if (result.kind === "sink") {
|
|
2488
|
-
await result.value;
|
|
2489
|
-
return;
|
|
2490
|
-
}
|
|
2491
|
-
for await (const r of result.value) if (r.kind === "line") process.stdout.write(`${r.text}\n`);
|
|
3226
|
+
await runStdoutWithContext();
|
|
2492
3227
|
},
|
|
2493
3228
|
async text() {
|
|
2494
|
-
return (await
|
|
3229
|
+
return (await runWithContext()).map((r) => {
|
|
2495
3230
|
if (r.kind === "line") return r.text;
|
|
2496
3231
|
if (r.kind === "file") return r.path;
|
|
2497
3232
|
if (r.kind === "json") return JSON.stringify(r.value);
|
|
@@ -2499,6 +3234,7 @@ var Shell = class {
|
|
|
2499
3234
|
}).join("\n");
|
|
2500
3235
|
}
|
|
2501
3236
|
};
|
|
3237
|
+
return command;
|
|
2502
3238
|
}
|
|
2503
3239
|
};
|
|
2504
3240
|
|