fauxnix-cli 0.9.3 → 0.11.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/README.md +46 -12
- package/dist/ast.d.ts +38 -3
- package/dist/ast.js +22 -2
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +25 -1
- package/dist/commands/archive.d.ts +2 -1
- package/dist/commands/archive.js +82 -1
- package/dist/commands/install-all.js +2 -1
- package/dist/commands/sysinfo.js +48 -12
- package/dist/commands/text-filters.d.ts +1 -0
- package/dist/commands/text-filters.js +63 -2
- package/dist/doctor.d.ts +20 -0
- package/dist/doctor.js +251 -0
- package/dist/install.d.ts +15 -0
- package/dist/install.js +206 -0
- package/dist/mcp.d.ts +6 -0
- package/dist/mcp.js +34 -12
- package/dist/parser.js +422 -33
- package/dist/translator.d.ts +29 -3
- package/dist/translator.js +302 -25
- package/package.json +1 -1
package/dist/translator.d.ts
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
|
-
import { Assignment, CommandList, Redirect, SimpleCommand, IfCommand, ForCommand, Word, WordPart } from './ast.js';
|
|
1
|
+
import { Assignment, CommandList, Redirect, SimpleCommand, IfCommand, ForCommand, WhileCommand, CaseCommand, Word, WordPart } from './ast.js';
|
|
2
2
|
import { PipelineCtx } from './registry.js';
|
|
3
3
|
/** `${name:-word}` and friends using case-exact fx-scalar0. */
|
|
4
4
|
export declare function paramExpr(name: string, op: ':-' | ':=' | ':+' | ':?' | '-' | '+' | '?', word: string): string;
|
|
5
|
+
export declare function varExtraOf(p: WordPart): {
|
|
6
|
+
replace?: {
|
|
7
|
+
global: boolean;
|
|
8
|
+
pat: string;
|
|
9
|
+
repl: string;
|
|
10
|
+
};
|
|
11
|
+
slice?: {
|
|
12
|
+
offset: string;
|
|
13
|
+
length?: string;
|
|
14
|
+
};
|
|
15
|
+
} | undefined;
|
|
5
16
|
/** Map a bash $VAR name to a PowerShell expression (usable inside $(...)). */
|
|
6
17
|
export declare function varExpr(name: string, index?: string, param?: {
|
|
7
18
|
op: ':-' | ':=' | ':+' | ':?' | '-' | '+' | '?';
|
|
8
19
|
word: string;
|
|
9
|
-
}, length?: boolean
|
|
20
|
+
}, length?: boolean, extra?: {
|
|
21
|
+
replace?: {
|
|
22
|
+
global: boolean;
|
|
23
|
+
pat: string;
|
|
24
|
+
repl: string;
|
|
25
|
+
};
|
|
26
|
+
slice?: {
|
|
27
|
+
offset: string;
|
|
28
|
+
length?: string;
|
|
29
|
+
};
|
|
30
|
+
}): string;
|
|
31
|
+
/** `$?` `$$` `$0`–`$n` `$#` `$@` `$*` — not ordinary `$env:` names. */
|
|
32
|
+
export declare function isSpecialShellVar(name: string): boolean;
|
|
10
33
|
/** Escape text destined for the inside of a PS double-quoted string. */
|
|
11
34
|
export declare function escapeDq(s: string): string;
|
|
12
35
|
/** Normalize a literal POSIX-ish path to its Windows equivalent. */
|
|
@@ -39,6 +62,7 @@ export declare function operandExpr(w: Word): string;
|
|
|
39
62
|
/**
|
|
40
63
|
* `${name[@]}` / `"pre${name[@]}post"` — one argv per element.
|
|
41
64
|
* Unquoted `${name[*]}` also splats (bash); quoted `"${name[*]}"` stays one join.
|
|
65
|
+
* `$@` / unquoted `$*` splat like `${arr[@]}`; quoted `"$@"` still splats.
|
|
42
66
|
*/
|
|
43
67
|
export declare function splatSpec(w: Word): {
|
|
44
68
|
name: string;
|
|
@@ -53,6 +77,8 @@ export declare function argListExpr(words: Word[], fn?: (w: Word) => string): st
|
|
|
53
77
|
* Unquoted command words join non-empty lines with a space (IFS
|
|
54
78
|
* word-split approximation). Handlers often emit one string object, so
|
|
55
79
|
* a bare `$(…)` interpolation would keep those newlines.
|
|
80
|
+
* Lists (`;` `&&` `||`) reuse translateListInline inside the fx-csub
|
|
81
|
+
* scriptblock so the newline contract is unchanged.
|
|
56
82
|
*/
|
|
57
83
|
export declare function translateCmdSub(cmdText: string, keepNl?: boolean): string;
|
|
58
84
|
export declare function translateSimple(cmd: SimpleCommand, position: PipelineCtx['position'], hasStdin: boolean): string;
|
|
@@ -76,7 +102,7 @@ export interface PipelineParts {
|
|
|
76
102
|
call: string;
|
|
77
103
|
}
|
|
78
104
|
export declare function translatePipelineBody(p: {
|
|
79
|
-
commands: Array<SimpleCommand | IfCommand | ForCommand>;
|
|
105
|
+
commands: Array<SimpleCommand | IfCommand | ForCommand | WhileCommand | CaseCommand>;
|
|
80
106
|
}): PipelineParts;
|
|
81
107
|
export interface SegmentPlan {
|
|
82
108
|
op: ';' | '&&' | '||';
|
package/dist/translator.js
CHANGED
|
@@ -39,8 +39,43 @@ export function paramExpr(name, op, word) {
|
|
|
39
39
|
psStr('bash: ' + msg) +
|
|
40
40
|
'); $script:fx_exit = 1; \'\' } else { $fx_pv } )');
|
|
41
41
|
}
|
|
42
|
+
function sliceArgExpr(s) {
|
|
43
|
+
if (s.length > 0 && s[0] === '$') {
|
|
44
|
+
return '(fx-scalar0 ' + psStr(s.slice(1)) + ')';
|
|
45
|
+
}
|
|
46
|
+
return psStr(s);
|
|
47
|
+
}
|
|
48
|
+
export function varExtraOf(p) {
|
|
49
|
+
if (p.kind !== 'Var')
|
|
50
|
+
return undefined;
|
|
51
|
+
if (!p.replace && !p.slice)
|
|
52
|
+
return undefined;
|
|
53
|
+
const extra = {};
|
|
54
|
+
if (p.replace)
|
|
55
|
+
extra.replace = p.replace;
|
|
56
|
+
if (p.slice)
|
|
57
|
+
extra.slice = p.slice;
|
|
58
|
+
return extra;
|
|
59
|
+
}
|
|
42
60
|
/** Map a bash $VAR name to a PowerShell expression (usable inside $(...)). */
|
|
43
|
-
export function varExpr(name, index, param, length = false) {
|
|
61
|
+
export function varExpr(name, index, param, length = false, extra) {
|
|
62
|
+
if (extra && extra.replace) {
|
|
63
|
+
const r = extra.replace;
|
|
64
|
+
return ('(fx-subst (fx-scalar0 ' +
|
|
65
|
+
psStr(name) +
|
|
66
|
+
') ' +
|
|
67
|
+
psStr(r.pat) +
|
|
68
|
+
' ' +
|
|
69
|
+
psStr(r.repl) +
|
|
70
|
+
' ' +
|
|
71
|
+
(r.global ? '$true' : '$false') +
|
|
72
|
+
')');
|
|
73
|
+
}
|
|
74
|
+
if (extra && extra.slice) {
|
|
75
|
+
const off = sliceArgExpr(extra.slice.offset);
|
|
76
|
+
const len = extra.slice.length !== undefined ? sliceArgExpr(extra.slice.length) : '$null';
|
|
77
|
+
return '(fx-slice (fx-scalar0 ' + psStr(name) + ') ' + off + ' ' + len + ')';
|
|
78
|
+
}
|
|
44
79
|
if (param)
|
|
45
80
|
return paramExpr(name, param.op, param.word);
|
|
46
81
|
if (length) {
|
|
@@ -60,6 +95,12 @@ export function varExpr(name, index, param, length = false) {
|
|
|
60
95
|
if (index !== undefined) {
|
|
61
96
|
return '(fx-subget ' + psStr(name) + ' ' + psStr(index) + ')';
|
|
62
97
|
}
|
|
98
|
+
if (/^[0-9]+$/.test(name)) {
|
|
99
|
+
if (name === '0') {
|
|
100
|
+
return "$(if ($env:FAUXNIX_ARG0) { [string]$env:FAUXNIX_ARG0 } else { 'fauxnix' })";
|
|
101
|
+
}
|
|
102
|
+
return '(fx-posget ' + name + ')';
|
|
103
|
+
}
|
|
63
104
|
switch (name) {
|
|
64
105
|
case 'HOME':
|
|
65
106
|
return '$HOME';
|
|
@@ -82,10 +123,24 @@ export function varExpr(name, index, param, length = false) {
|
|
|
82
123
|
return '[string]$PID';
|
|
83
124
|
case 'HOSTNAME':
|
|
84
125
|
return '$env:COMPUTERNAME';
|
|
126
|
+
case '#':
|
|
127
|
+
return '@(fx-posload).Count';
|
|
128
|
+
case '@':
|
|
129
|
+
case '*':
|
|
130
|
+
return '((@(fx-posload) -join (fx-ifs1)))';
|
|
85
131
|
default:
|
|
86
132
|
return '$env:' + name;
|
|
87
133
|
}
|
|
88
134
|
}
|
|
135
|
+
/** `$?` `$$` `$0`–`$n` `$#` `$@` `$*` — not ordinary `$env:` names. */
|
|
136
|
+
export function isSpecialShellVar(name) {
|
|
137
|
+
return (name === '?' ||
|
|
138
|
+
name === '$' ||
|
|
139
|
+
name === '#' ||
|
|
140
|
+
name === '@' ||
|
|
141
|
+
name === '*' ||
|
|
142
|
+
/^[0-9]+$/.test(name));
|
|
143
|
+
}
|
|
89
144
|
/* ------------------------------------------------------------------ */
|
|
90
145
|
/* Word → PowerShell expression */
|
|
91
146
|
/* ------------------------------------------------------------------ */
|
|
@@ -155,7 +210,7 @@ export function exprOfWord(w, opts) {
|
|
|
155
210
|
// single bare variable → bare expression
|
|
156
211
|
if (expanded.length === 1 && expanded[0].kind === 'Var') {
|
|
157
212
|
const v = expanded[0];
|
|
158
|
-
return varExpr(v.name, v.index, v.param, v.length === true);
|
|
213
|
+
return varExpr(v.name, v.index, v.param, v.length === true, varExtraOf(v));
|
|
159
214
|
}
|
|
160
215
|
// Bare `$(...)` must not sit inside a PS expandable string: the
|
|
161
216
|
// substitution body contains `"` / `$_` that would break interpolation.
|
|
@@ -185,7 +240,7 @@ export function exprOfWord(w, opts) {
|
|
|
185
240
|
emitPart(q, true);
|
|
186
241
|
break;
|
|
187
242
|
case 'Var':
|
|
188
|
-
out += '$(' + varExpr(p.name, p.index, p.param, p.length === true) + ')';
|
|
243
|
+
out += '$(' + varExpr(p.name, p.index, p.param, p.length === true, varExtraOf(p)) + ')';
|
|
189
244
|
break;
|
|
190
245
|
case 'CmdSub':
|
|
191
246
|
out += '$(' + translateCmdSub(p.cmd, quoted || opts?.preserveCmdSub === true) + ')';
|
|
@@ -241,7 +296,7 @@ function arithSourceExpr(parts) {
|
|
|
241
296
|
emit(q);
|
|
242
297
|
break;
|
|
243
298
|
case 'Var':
|
|
244
|
-
out += '$(' + varExpr(p.name, p.index, p.param, p.length === true) + ')';
|
|
299
|
+
out += '$(' + varExpr(p.name, p.index, p.param, p.length === true, varExtraOf(p)) + ')';
|
|
245
300
|
break;
|
|
246
301
|
case 'CmdSub':
|
|
247
302
|
out += '$(' + translateCmdSub(p.cmd, true) + ')';
|
|
@@ -292,6 +347,7 @@ function wordPartsForSplat(w) {
|
|
|
292
347
|
/**
|
|
293
348
|
* `${name[@]}` / `"pre${name[@]}post"` — one argv per element.
|
|
294
349
|
* Unquoted `${name[*]}` also splats (bash); quoted `"${name[*]}"` stays one join.
|
|
350
|
+
* `$@` / unquoted `$*` splat like `${arr[@]}`; quoted `"$@"` still splats.
|
|
295
351
|
*/
|
|
296
352
|
export function splatSpec(w) {
|
|
297
353
|
const parts = wordPartsForSplat(w);
|
|
@@ -302,7 +358,10 @@ export function splatSpec(w) {
|
|
|
302
358
|
for (const { part: p, quoted } of parts) {
|
|
303
359
|
const splat = p.kind === 'Var' &&
|
|
304
360
|
!p.length &&
|
|
305
|
-
(p.
|
|
361
|
+
(p.name === '@' ||
|
|
362
|
+
(p.name === '*' && !quoted) ||
|
|
363
|
+
p.index === '@' ||
|
|
364
|
+
(p.index === '*' && !quoted));
|
|
306
365
|
if (splat) {
|
|
307
366
|
if (seen)
|
|
308
367
|
return null;
|
|
@@ -319,6 +378,12 @@ export function splatSpec(w) {
|
|
|
319
378
|
}
|
|
320
379
|
return name ? { name, prefix, suffix } : null;
|
|
321
380
|
}
|
|
381
|
+
/** Load the splat source: positionals (`@`/`*`) vs named arrays. */
|
|
382
|
+
function splatLoadCall(name) {
|
|
383
|
+
if (name === '@' || name === '*')
|
|
384
|
+
return 'fx-posload';
|
|
385
|
+
return 'fx-arrload ' + psStr(name);
|
|
386
|
+
}
|
|
322
387
|
/** PS expression of a string[]: `@` words splat, others stay one element. */
|
|
323
388
|
export function argListExpr(words, fn = exprOfWord) {
|
|
324
389
|
if (words.length === 0)
|
|
@@ -330,9 +395,9 @@ export function argListExpr(words, fn = exprOfWord) {
|
|
|
330
395
|
if (!s)
|
|
331
396
|
return '@(' + fn(w) + ')';
|
|
332
397
|
if (!s.prefix && !s.suffix)
|
|
333
|
-
return '@(
|
|
334
|
-
return ('@($( $fx_sp = @(
|
|
335
|
-
|
|
398
|
+
return '@(' + splatLoadCall(s.name) + ')';
|
|
399
|
+
return ('@($( $fx_sp = @(' +
|
|
400
|
+
splatLoadCall(s.name) +
|
|
336
401
|
'); if ($fx_sp.Count -eq 0) { $fx_sp = @(' +
|
|
337
402
|
psStr(s.prefix + s.suffix) +
|
|
338
403
|
') } else { $fx_sp[0] = ' +
|
|
@@ -353,14 +418,11 @@ export function argListExpr(words, fn = exprOfWord) {
|
|
|
353
418
|
* Unquoted command words join non-empty lines with a space (IFS
|
|
354
419
|
* word-split approximation). Handlers often emit one string object, so
|
|
355
420
|
* a bare `$(…)` interpolation would keep those newlines.
|
|
421
|
+
* Lists (`;` `&&` `||`) reuse translateListInline inside the fx-csub
|
|
422
|
+
* scriptblock so the newline contract is unchanged.
|
|
356
423
|
*/
|
|
357
424
|
export function translateCmdSub(cmdText, keepNl = false) {
|
|
358
|
-
const
|
|
359
|
-
if (list.segments.length !== 1) {
|
|
360
|
-
throw new FauxnixParseError('fauxnix: command substitution with ; && || is not supported yet');
|
|
361
|
-
}
|
|
362
|
-
const { defs, call } = translatePipelineBody(list.segments[0].pipeline);
|
|
363
|
-
const inner = defs ? defs + '\n' + call : call;
|
|
425
|
+
const inner = translateListInline(parseCommand(cmdText));
|
|
364
426
|
const collected = '(fx-csub { ' + inner + ' })';
|
|
365
427
|
if (keepNl)
|
|
366
428
|
return collected;
|
|
@@ -384,11 +446,21 @@ export function translateSimple(cmd, position, hasStdin) {
|
|
|
384
446
|
// deviation: shell var vs exported var are indistinguishable here).
|
|
385
447
|
if (cmd.name === null) {
|
|
386
448
|
const exportHandler = lookup('export');
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
449
|
+
const chunks = [];
|
|
450
|
+
for (const a of cmd.assignments) {
|
|
451
|
+
if (a.values) {
|
|
452
|
+
chunks.push('fx-arrput ' +
|
|
453
|
+
psStr(a.name) +
|
|
454
|
+
' ' +
|
|
455
|
+
argListExpr(a.values, (w) => exprOfWord(w, { preserveCmdSub: true })));
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
const words = [[{ kind: 'Text', text: a.name + '=' }, ...a.value]];
|
|
459
|
+
if (exportHandler)
|
|
460
|
+
chunks.push(exportHandler(words, { position, hasStdin }));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return chunks.join('\n');
|
|
392
464
|
}
|
|
393
465
|
const nameLit = literalOfWord(cmd.name);
|
|
394
466
|
const nameSplat = splatSpec(cmd.name);
|
|
@@ -405,7 +477,7 @@ export function translateSimple(cmd, position, hasStdin) {
|
|
|
405
477
|
redirects: cmd.redirects,
|
|
406
478
|
}, position, hasStdin);
|
|
407
479
|
const emptyCmdLines = [
|
|
408
|
-
'$fx_cw = @(
|
|
480
|
+
'$fx_cw = @(' + splatLoadCall(nameSplat.name) + ')',
|
|
409
481
|
];
|
|
410
482
|
if (hasAffix) {
|
|
411
483
|
emptyCmdLines.push('if ($fx_cw.Count -eq 0) { $fx_cw = @(' +
|
|
@@ -547,10 +619,20 @@ export function wrapTempEnv(sets, body, extra) {
|
|
|
547
619
|
lines.push(arrSave + '[' + psStr(n) + '] = (fx-arrpackget ' + psStr(n) + ')');
|
|
548
620
|
}
|
|
549
621
|
const valVars = [];
|
|
622
|
+
const valIsArr = [];
|
|
550
623
|
for (let i = 0; i < sets.length; i++) {
|
|
551
624
|
const vn = '$fx_ev' + id + '_' + i;
|
|
552
625
|
valVars.push(vn);
|
|
553
|
-
|
|
626
|
+
if (sets[i].values) {
|
|
627
|
+
valIsArr.push(true);
|
|
628
|
+
lines.push(vn +
|
|
629
|
+
' = ' +
|
|
630
|
+
argListExpr(sets[i].values, (w) => exprOfWord(w, { preserveCmdSub: true })));
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
valIsArr.push(false);
|
|
634
|
+
lines.push(vn + ' = ' + exprOfWord(sets[i].value, { preserveCmdSub: true }));
|
|
635
|
+
}
|
|
554
636
|
}
|
|
555
637
|
lines.push('try {');
|
|
556
638
|
for (const u of unsets) {
|
|
@@ -576,6 +658,10 @@ export function wrapTempEnv(sets, body, extra) {
|
|
|
576
658
|
for (let i = 0; i < sets.length; i++) {
|
|
577
659
|
const n = sets[i].name;
|
|
578
660
|
const nq = n.replace(/'/g, "''");
|
|
661
|
+
if (valIsArr[i]) {
|
|
662
|
+
lines.push(' fx-arrput ' + psStr(n) + ' ' + valVars[i]);
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
579
665
|
lines.push(' $env:' + n + ' = ' + valVars[i]);
|
|
580
666
|
lines.push(' fx-arrdrop ' + psStr(n));
|
|
581
667
|
lines.push(" $env:FAUXNIX_SETVARS = ((@($env:FAUXNIX_SETVARS -split ';' | Where-Object { $_ -ne '' -and $_ -cne '" +
|
|
@@ -753,6 +839,22 @@ function translateIf(cmd) {
|
|
|
753
839
|
lines.push('}');
|
|
754
840
|
return lines.join('\n');
|
|
755
841
|
}
|
|
842
|
+
function translateCase(cmd) {
|
|
843
|
+
const lines = ['$script:fx_exit = 0', '$fx_cw = ' + exprOfWord(cmd.word)];
|
|
844
|
+
for (let i = 0; i < cmd.arms.length; i++) {
|
|
845
|
+
const arm = cmd.arms[i];
|
|
846
|
+
const pats = arm.patterns.map((w) => exprOfWord(w)).join(',');
|
|
847
|
+
const head = i === 0 ? 'if' : 'elseif';
|
|
848
|
+
lines.push(head + ' (fx-casematch $fx_cw @(' + pats + ')) {');
|
|
849
|
+
const body = translateListInline(arm.body);
|
|
850
|
+
if (body) {
|
|
851
|
+
for (const l of body.split('\n'))
|
|
852
|
+
lines.push(l ? ' ' + l : l);
|
|
853
|
+
}
|
|
854
|
+
lines.push('}');
|
|
855
|
+
}
|
|
856
|
+
return lines.join('\n');
|
|
857
|
+
}
|
|
756
858
|
function translateFor(cmd) {
|
|
757
859
|
const n = cmd.name.replace(/'/g, "''");
|
|
758
860
|
const lines = [
|
|
@@ -780,6 +882,22 @@ function translateFor(cmd) {
|
|
|
780
882
|
lines.push('}');
|
|
781
883
|
return lines.join('\n');
|
|
782
884
|
}
|
|
885
|
+
function translateWhile(cmd) {
|
|
886
|
+
// Bash: last executed body owns status; a test that ends the loop does not.
|
|
887
|
+
// Never-entered loops (`while false; do …; done`, `until true; do …; done`)
|
|
888
|
+
// exit 0. Save the body status and restore it on the failing test.
|
|
889
|
+
const fail = cmd.until ? '$script:fx_exit -eq 0' : '$script:fx_exit -ne 0';
|
|
890
|
+
const lines = ['$fx_wst = 0', 'do {'];
|
|
891
|
+
for (const l of translateListInline(cmd.test).split('\n'))
|
|
892
|
+
lines.push(l ? ' ' + l : l);
|
|
893
|
+
lines.push(' if (' + fail + ') { $script:fx_exit = $fx_wst; break }');
|
|
894
|
+
lines.push(' $script:fx_exit = 0');
|
|
895
|
+
for (const l of translateListInline(cmd.body).split('\n'))
|
|
896
|
+
lines.push(l ? ' ' + l : l);
|
|
897
|
+
lines.push(' $fx_wst = $script:fx_exit');
|
|
898
|
+
lines.push('} while ($true)');
|
|
899
|
+
return lines.join('\n');
|
|
900
|
+
}
|
|
783
901
|
function stdinTarget(c) {
|
|
784
902
|
let t = null;
|
|
785
903
|
for (const r of c.redirects) {
|
|
@@ -794,7 +912,24 @@ function stdinReadExpr(target) {
|
|
|
794
912
|
return '@()';
|
|
795
913
|
return '@(fx-readlines ' + pathExpr(n) + ')';
|
|
796
914
|
}
|
|
915
|
+
/** `>` `>>` `&>` `&>>` — Node last-stage apply cannot honor these on earlier stages. */
|
|
916
|
+
function isStdoutFileRedirect(op) {
|
|
917
|
+
return op === '>' || op === '>>' || op === '&>' || op === '&>>';
|
|
918
|
+
}
|
|
919
|
+
const NONLAST_STDOUT_REDIRECT_MSG = 'fauxnix: stdout redirect on a non-last pipeline stage is not supported yet; write the file in a previous list segment (cmd >f; cat f) or wait for per-stage fds (#157)';
|
|
920
|
+
function rejectNonLastStdoutRedirects(commands) {
|
|
921
|
+
if (commands.length < 2)
|
|
922
|
+
return;
|
|
923
|
+
for (let i = 0; i < commands.length - 1; i++) {
|
|
924
|
+
if (commands[i].redirects.some((r) => isStdoutFileRedirect(r.op))) {
|
|
925
|
+
throw new FauxnixParseError(NONLAST_STDOUT_REDIRECT_MSG);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
797
929
|
export function translatePipelineBody(p) {
|
|
930
|
+
// Last-stage `>` is Node apply; a non-last `>` would truncate the file and
|
|
931
|
+
// still feed the pipe. Fail loud until in-stage writes (#157).
|
|
932
|
+
rejectNonLastStdoutRedirects(p.commands);
|
|
798
933
|
// Every pipeline stage needs its own status slot. Handlers deliberately use
|
|
799
934
|
// `$script:fx_exit` because their helper functions run in child scopes; in a
|
|
800
935
|
// pipeline that shared flag lets an earlier failure leak into a successful
|
|
@@ -810,6 +945,10 @@ export function translatePipelineBody(p) {
|
|
|
810
945
|
bodies.push(translateIf(c));
|
|
811
946
|
else if (c.kind === 'For')
|
|
812
947
|
bodies.push(translateFor(c));
|
|
948
|
+
else if (c.kind === 'While')
|
|
949
|
+
bodies.push(translateWhile(c));
|
|
950
|
+
else if (c.kind === 'Case')
|
|
951
|
+
bodies.push(translateCase(c));
|
|
813
952
|
else
|
|
814
953
|
bodies.push(translateSimple(c, position, hasStdin));
|
|
815
954
|
}
|
|
@@ -913,6 +1052,10 @@ const WRAP_HELPER_ORDER = [
|
|
|
913
1052
|
'fx-csub',
|
|
914
1053
|
'fx-svenc',
|
|
915
1054
|
'fx-svdec',
|
|
1055
|
+
'fx-posload',
|
|
1056
|
+
'fx-posset',
|
|
1057
|
+
'fx-posget',
|
|
1058
|
+
'fx-posshift',
|
|
916
1059
|
'fx-arrload',
|
|
917
1060
|
'fx-scalar0',
|
|
918
1061
|
'fx-ifs1',
|
|
@@ -923,6 +1066,9 @@ const WRAP_HELPER_ORDER = [
|
|
|
923
1066
|
'fx-arrput',
|
|
924
1067
|
'fx-arrclr',
|
|
925
1068
|
'fx-subget',
|
|
1069
|
+
'fx-casematch',
|
|
1070
|
+
'fx-subst',
|
|
1071
|
+
'fx-slice',
|
|
926
1072
|
'fx-winargv',
|
|
927
1073
|
'fx-native',
|
|
928
1074
|
];
|
|
@@ -931,6 +1077,10 @@ const WRAP_HELPER_DEPS = {
|
|
|
931
1077
|
'fx-csub': [],
|
|
932
1078
|
'fx-svenc': [],
|
|
933
1079
|
'fx-svdec': [],
|
|
1080
|
+
'fx-posload': ['fx-svdec'],
|
|
1081
|
+
'fx-posset': ['fx-svenc'],
|
|
1082
|
+
'fx-posget': ['fx-posload'],
|
|
1083
|
+
'fx-posshift': ['fx-posload', 'fx-posset'],
|
|
934
1084
|
'fx-arrload': ['fx-scalar0', 'fx-svdec'],
|
|
935
1085
|
'fx-scalar0': ['fx-svdec'],
|
|
936
1086
|
'fx-ifs1': ['fx-scalar0'],
|
|
@@ -941,6 +1091,9 @@ const WRAP_HELPER_DEPS = {
|
|
|
941
1091
|
'fx-arrput': ['fx-arrdrop', 'fx-svenc'],
|
|
942
1092
|
'fx-arrclr': ['fx-arrdrop'],
|
|
943
1093
|
'fx-subget': ['fx-arrload', 'fx-ifs1'],
|
|
1094
|
+
'fx-casematch': [],
|
|
1095
|
+
'fx-subst': [],
|
|
1096
|
+
'fx-slice': [],
|
|
944
1097
|
'fx-winargv': [],
|
|
945
1098
|
'fx-native': ['fx-winargv'],
|
|
946
1099
|
};
|
|
@@ -1058,7 +1211,16 @@ export function wrapScript(body, opts = {}) {
|
|
|
1058
1211
|
' $script:fx_csub = $true',
|
|
1059
1212
|
' try { $fx_o = @(& $b | ForEach-Object { [string]$_ }) }',
|
|
1060
1213
|
' finally { $script:fx_csub = $fx_prevcs }',
|
|
1061
|
-
|
|
1214
|
+
// Line items (no trailing NL) get a NL between them. Chunks that
|
|
1215
|
+
// already end in NL concatenate, so $(echo a; echo b) is a\nb.
|
|
1216
|
+
" $fx_s = ''",
|
|
1217
|
+
' foreach ($fx_x in $fx_o) {',
|
|
1218
|
+
' $fx_t = [string]$fx_x',
|
|
1219
|
+
' if ($fx_s.Length -gt 0 -and $fx_s[$fx_s.Length - 1] -ne [char]10) {',
|
|
1220
|
+
' $fx_s += [string][char]10',
|
|
1221
|
+
' }',
|
|
1222
|
+
' $fx_s += $fx_t',
|
|
1223
|
+
' }',
|
|
1062
1224
|
' while ($fx_s.Length -gt 0 -and $fx_s[$fx_s.Length - 1] -eq [char]10) {',
|
|
1063
1225
|
' $fx_s = $fx_s.Substring(0, $fx_s.Length - 1)',
|
|
1064
1226
|
' }',
|
|
@@ -1089,6 +1251,52 @@ export function wrapScript(body, opts = {}) {
|
|
|
1089
1251
|
' return [string]$sb',
|
|
1090
1252
|
'}',
|
|
1091
1253
|
],
|
|
1254
|
+
'fx-posload': [
|
|
1255
|
+
'function fx-posload {',
|
|
1256
|
+
' if ($null -eq $env:FAUXNIX_POS -or [string]$env:FAUXNIX_POS -eq \'\') { return @() }',
|
|
1257
|
+
' $out = @()',
|
|
1258
|
+
' foreach ($el in @($env:FAUXNIX_POS -split [string][char]30)) { $out += ,(fx-svdec $el) }',
|
|
1259
|
+
' return $out',
|
|
1260
|
+
'}',
|
|
1261
|
+
],
|
|
1262
|
+
'fx-posset': [
|
|
1263
|
+
'function fx-posset($vals) {',
|
|
1264
|
+
' if ($null -eq $vals) { $vals = @() }',
|
|
1265
|
+
' $vals = @($vals)',
|
|
1266
|
+
' if ($vals.Count -eq 0) { $env:FAUXNIX_POS = \'\'; return }',
|
|
1267
|
+
' $encs = @(); foreach ($v in $vals) { $encs += (fx-svenc $v) }',
|
|
1268
|
+
' $env:FAUXNIX_POS = ($encs -join [string][char]30)',
|
|
1269
|
+
'}',
|
|
1270
|
+
],
|
|
1271
|
+
'fx-posget': [
|
|
1272
|
+
'function fx-posget($i) {',
|
|
1273
|
+
' $arr = @(fx-posload)',
|
|
1274
|
+
' $n = 0',
|
|
1275
|
+
' if (-not [int]::TryParse([string]$i, [ref]$n)) { return \'\' }',
|
|
1276
|
+
' if ($n -lt 1 -or $n -gt $arr.Count) { return \'\' }',
|
|
1277
|
+
' return [string]$arr[$n - 1]',
|
|
1278
|
+
'}',
|
|
1279
|
+
],
|
|
1280
|
+
'fx-posshift': [
|
|
1281
|
+
'function fx-posshift($n) {',
|
|
1282
|
+
' $arr = @(fx-posload)',
|
|
1283
|
+
' $i = 1',
|
|
1284
|
+
' if ($null -ne $n -and [string]$n -ne \'\') {',
|
|
1285
|
+
' $parsed = 0',
|
|
1286
|
+
' if (-not [int]::TryParse([string]$n, [ref]$parsed)) {',
|
|
1287
|
+
' [Console]::Error.WriteLine(\'bash: shift: \' + [string]$n + \': numeric argument required\')',
|
|
1288
|
+
' $script:fx_exit = 1',
|
|
1289
|
+
' return',
|
|
1290
|
+
' }',
|
|
1291
|
+
' $i = $parsed',
|
|
1292
|
+
' }',
|
|
1293
|
+
' if ($i -lt 0 -or $i -gt $arr.Count) { $script:fx_exit = 1; return }',
|
|
1294
|
+
' if ($i -eq 0) { return }',
|
|
1295
|
+
' if ($i -eq $arr.Count) { fx-posset @(); return }',
|
|
1296
|
+
' $new = @($arr[$i..($arr.Count - 1)])',
|
|
1297
|
+
' fx-posset $new',
|
|
1298
|
+
'}',
|
|
1299
|
+
],
|
|
1092
1300
|
'fx-arrload': [
|
|
1093
1301
|
'function fx-arrload($n) {',
|
|
1094
1302
|
' $n = [string]$n',
|
|
@@ -1096,8 +1304,10 @@ export function wrapScript(body, opts = {}) {
|
|
|
1096
1304
|
' $fx_eq = $fx_pair.IndexOf([char]61)',
|
|
1097
1305
|
' if ($fx_eq -lt 1) { continue }',
|
|
1098
1306
|
' if ($fx_pair.Substring(0, $fx_eq) -cne $n) { continue }',
|
|
1307
|
+
' $pay = $fx_pair.Substring($fx_eq + 1)',
|
|
1308
|
+
' if ($pay -eq [string][char]1) { return @() }',
|
|
1099
1309
|
' $out = @()',
|
|
1100
|
-
' foreach ($el in @($
|
|
1310
|
+
' foreach ($el in @($pay -split [string][char]30)) { $out += ,(fx-svdec $el) }',
|
|
1101
1311
|
' return $out',
|
|
1102
1312
|
' }',
|
|
1103
1313
|
' $s0 = fx-scalar0 $n',
|
|
@@ -1179,9 +1389,12 @@ export function wrapScript(body, opts = {}) {
|
|
|
1179
1389
|
'fx-arrput': [
|
|
1180
1390
|
'function fx-arrput($n, $vals) {',
|
|
1181
1391
|
' $n = [string]$n',
|
|
1182
|
-
' $vals = @($vals)',
|
|
1392
|
+
' if ($null -eq $vals) { $vals = @() } else { $vals = @($vals) }',
|
|
1183
1393
|
' fx-arrdrop $n',
|
|
1184
|
-
' if ($vals.Count -eq 0) {
|
|
1394
|
+
' if ($vals.Count -eq 0) {',
|
|
1395
|
+
// SOH payload: empty array, distinct from scalar '' and from A=('')
|
|
1396
|
+
" $env:FAUXNIX_ARRS = ((@($env:FAUXNIX_ARRS -split [string][char]10 | Where-Object { $_ -ne '' }) + ($n + [string][char]61 + [string][char]1)) -join [string][char]10)",
|
|
1397
|
+
' } else {',
|
|
1185
1398
|
' $encs = @(); foreach ($v in $vals) { $encs += (fx-svenc $v) }',
|
|
1186
1399
|
" $env:FAUXNIX_ARRS = ((@($env:FAUXNIX_ARRS -split [string][char]10 | Where-Object { $_ -ne '' }) + ($n + [string][char]61 + ($encs -join [string][char]30))) -join [string][char]10)",
|
|
1187
1400
|
' }',
|
|
@@ -1216,6 +1429,70 @@ export function wrapScript(body, opts = {}) {
|
|
|
1216
1429
|
' return [string]$arr[$i]',
|
|
1217
1430
|
'}',
|
|
1218
1431
|
],
|
|
1432
|
+
'fx-casematch': [
|
|
1433
|
+
'function fx-casematch($w, $pats) {',
|
|
1434
|
+
' $w = [string]$w',
|
|
1435
|
+
' foreach ($p in @($pats)) {',
|
|
1436
|
+
' $pat = [string]$p',
|
|
1437
|
+
' try {',
|
|
1438
|
+
' $wp = [WildcardPattern]::new($pat, [System.Management.Automation.WildcardOptions]::None)',
|
|
1439
|
+
' if ($wp.IsMatch($w)) { return $true }',
|
|
1440
|
+
' } catch {}',
|
|
1441
|
+
' }',
|
|
1442
|
+
' return $false',
|
|
1443
|
+
'}',
|
|
1444
|
+
],
|
|
1445
|
+
'fx-subst': [
|
|
1446
|
+
'function fx-subst($s, $pat, $repl, $all) {',
|
|
1447
|
+
' if ($null -eq $s) { return \'\' }',
|
|
1448
|
+
' $s = [string]$s',
|
|
1449
|
+
' $pat = [string]$pat',
|
|
1450
|
+
' $repl = [string]$repl',
|
|
1451
|
+
' if ($pat -eq \'\') { return $s }',
|
|
1452
|
+
' $glob = $false',
|
|
1453
|
+
' foreach ($fx_c in $pat.ToCharArray()) {',
|
|
1454
|
+
' if ($fx_c -eq [char]42 -or $fx_c -eq [char]63) { $glob = $true; break }',
|
|
1455
|
+
' }',
|
|
1456
|
+
' if (-not $glob) {',
|
|
1457
|
+
' if ($all) { return $s.Replace($pat, $repl) }',
|
|
1458
|
+
' $i = $s.IndexOf($pat)',
|
|
1459
|
+
' if ($i -lt 0) { return $s }',
|
|
1460
|
+
' return $s.Substring(0, $i) + $repl + $s.Substring($i + $pat.Length)',
|
|
1461
|
+
' }',
|
|
1462
|
+
' $sb = New-Object System.Text.StringBuilder',
|
|
1463
|
+
' foreach ($fx_c in $pat.ToCharArray()) {',
|
|
1464
|
+
' if ($fx_c -eq [char]42) { [void]$sb.Append(\'.*\'); continue }',
|
|
1465
|
+
' if ($fx_c -eq [char]63) { [void]$sb.Append(\'.\'); continue }',
|
|
1466
|
+
' [void]$sb.Append([regex]::Escape([string]$fx_c))',
|
|
1467
|
+
' }',
|
|
1468
|
+
' $rx = New-Object System.Text.RegularExpressions.Regex($sb.ToString(), [System.Text.RegularExpressions.RegexOptions]::Singleline)',
|
|
1469
|
+
' $fx_rep = $repl.Replace([string][char]36, ([string][char]36 + [string][char]36))',
|
|
1470
|
+
' if ($all) { return $rx.Replace($s, $fx_rep) }',
|
|
1471
|
+
' return $rx.Replace($s, $fx_rep, 1)',
|
|
1472
|
+
'}',
|
|
1473
|
+
],
|
|
1474
|
+
'fx-slice': [
|
|
1475
|
+
'function fx-slice($s, $off, $len) {',
|
|
1476
|
+
' if ($null -eq $s) { return \'\' }',
|
|
1477
|
+
' $s = [string]$s',
|
|
1478
|
+
' $n = $s.Length',
|
|
1479
|
+
' $o = 0',
|
|
1480
|
+
' if (-not [int]::TryParse([string]$off, [ref]$o)) { return \'\' }',
|
|
1481
|
+
' if ($o -lt 0) { $o = $n + $o }',
|
|
1482
|
+
' if ($o -lt 0 -or $o -ge $n) { return \'\' }',
|
|
1483
|
+
' if ($null -eq $len -or [string]$len -eq \'\') { return $s.Substring($o) }',
|
|
1484
|
+
' $l = 0',
|
|
1485
|
+
' if (-not [int]::TryParse([string]$len, [ref]$l)) { return \'\' }',
|
|
1486
|
+
' if ($l -lt 0) {',
|
|
1487
|
+
' $fx_end = $n + $l',
|
|
1488
|
+
' if ($fx_end -lt $o) { return \'\' }',
|
|
1489
|
+
' $l = $fx_end - $o',
|
|
1490
|
+
' }',
|
|
1491
|
+
' if ($l -le 0) { return \'\' }',
|
|
1492
|
+
' if (($o + $l) -gt $n) { $l = $n - $o }',
|
|
1493
|
+
' return $s.Substring($o, $l)',
|
|
1494
|
+
'}',
|
|
1495
|
+
],
|
|
1219
1496
|
'fx-winargv': [
|
|
1220
1497
|
'function fx-winargv($argv, $cmdmeta) {',
|
|
1221
1498
|
// Empty [object[]] unwraps to $null on PS 5.1; @($null) is one empty arg.
|
package/package.json
CHANGED