fauxnix-cli 0.1.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/LICENSE +21 -0
- package/README.md +179 -0
- package/dist/ast.d.ts +72 -0
- package/dist/ast.js +43 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +91 -0
- package/dist/commands/archive.d.ts +2 -0
- package/dist/commands/archive.js +385 -0
- package/dist/commands/files.d.ts +2 -0
- package/dist/commands/files.js +721 -0
- package/dist/commands/install-all.d.ts +2 -0
- package/dist/commands/install-all.js +17 -0
- package/dist/commands/net.d.ts +2 -0
- package/dist/commands/net.js +533 -0
- package/dist/commands/sysinfo.d.ts +2 -0
- package/dist/commands/sysinfo.js +1138 -0
- package/dist/commands/text-filters.d.ts +2 -0
- package/dist/commands/text-filters.js +2281 -0
- package/dist/commands/text-io.d.ts +2 -0
- package/dist/commands/text-io.js +1164 -0
- package/dist/encoding.d.ts +7 -0
- package/dist/encoding.js +29 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +103 -0
- package/dist/executor.d.ts +27 -0
- package/dist/executor.js +299 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/mcp.d.ts +4 -0
- package/dist/mcp.js +79 -0
- package/dist/parser.d.ts +11 -0
- package/dist/parser.js +400 -0
- package/dist/registry.d.ts +79 -0
- package/dist/registry.js +145 -0
- package/dist/translator.d.ts +55 -0
- package/dist/translator.js +318 -0
- package/package.json +57 -0
|
@@ -0,0 +1,1164 @@
|
|
|
1
|
+
import { wordToString } from '../ast.js';
|
|
2
|
+
import { lookup, parseWords, psStr } from '../registry.js';
|
|
3
|
+
import { exprOfWord, operandExpr } from '../translator.js';
|
|
4
|
+
/* ------------------------------------------------------------------ */
|
|
5
|
+
/* Shared PS snippets (same shape as files.ts / text-filters.ts) */
|
|
6
|
+
/* ------------------------------------------------------------------ */
|
|
7
|
+
const PS_GLOB_FN = [
|
|
8
|
+
'function fx-glob($p) {',
|
|
9
|
+
" if ($p -notlike '*[*?]*') { return @($p) }",
|
|
10
|
+
' $m = @(Get-Item -Path $p -ErrorAction SilentlyContinue)',
|
|
11
|
+
' if ($m.Count -eq 0) { return @($p) }',
|
|
12
|
+
' return @($m | ForEach-Object { $_.FullName })',
|
|
13
|
+
'}',
|
|
14
|
+
].join('\n');
|
|
15
|
+
const PS_READTEXT_FN = [
|
|
16
|
+
'function fx-read($p) {',
|
|
17
|
+
' $b = [IO.File]::ReadAllBytes($p)',
|
|
18
|
+
' try { return (New-Object System.Text.UTF8Encoding($false, $true)).GetString($b) }',
|
|
19
|
+
' catch { try { return [System.Text.Encoding]::GetEncoding(936).GetString($b) } catch { return [System.Text.Encoding]::ASCII.GetString($b) } }',
|
|
20
|
+
'}',
|
|
21
|
+
].join('\n');
|
|
22
|
+
/** Split text into lines, GNU-style (trailing newline makes no extra line). */
|
|
23
|
+
const PS_SPLITLINES_FN = [
|
|
24
|
+
'function fx-splitlines($t) {',
|
|
25
|
+
' $t = $t.Replace([string][char]13 + [string][char]10, [string][char]10).Replace([string][char]13, [string][char]10)',
|
|
26
|
+
" if ($t -eq '') { return @() }",
|
|
27
|
+
' if ($t.EndsWith([string][char]10)) { $t = $t.Substring(0, $t.Length - 1) }',
|
|
28
|
+
' return @($t.Split([char]10))',
|
|
29
|
+
'}',
|
|
30
|
+
].join('\n');
|
|
31
|
+
/** stdin → flat line array (multi-line items from printf-style stages split). */
|
|
32
|
+
const STDIN_INLINES = [
|
|
33
|
+
'$fx_in = New-Object System.Collections.Generic.List[string]',
|
|
34
|
+
'foreach ($fx_it in @($input | ForEach-Object { [string]$_ })) { $fx_in.AddRange([string[]]@(fx-splitlines $fx_it)) }',
|
|
35
|
+
'$fx_in = @($fx_in)',
|
|
36
|
+
].join('\n');
|
|
37
|
+
/** stdin → raw item array (the 1-item rule keeps printf's exact byte tail). */
|
|
38
|
+
const STDIN_ITEMS = '$fx_items = @($input | ForEach-Object { [string]$_ })';
|
|
39
|
+
/**
|
|
40
|
+
* Reconstruct the upstream byte stream: one item = raw printf-style payload
|
|
41
|
+
* (no synthetic trailing newline); several items = line-oriented upstream
|
|
42
|
+
* (every line was newline-terminated).
|
|
43
|
+
*/
|
|
44
|
+
const PS_STDINRAW_FN = [
|
|
45
|
+
'function fx-stdinraw($items) {',
|
|
46
|
+
' if ($items.Count -eq 1) { return [string]$items[0] }',
|
|
47
|
+
' if ($items.Count -gt 1) { return (($items -join [string][char]10) + [string][char]10) }',
|
|
48
|
+
" return ''",
|
|
49
|
+
'}',
|
|
50
|
+
].join('\n');
|
|
51
|
+
/** Interpret \n \t \r \\ escapes (echo -e, printf %b). */
|
|
52
|
+
const PS_UNESQ_FN = [
|
|
53
|
+
'function fx-unesq($s) {',
|
|
54
|
+
' $sb = New-Object System.Text.StringBuilder',
|
|
55
|
+
' $i = 0',
|
|
56
|
+
' while ($i -lt $s.Length) {',
|
|
57
|
+
" if ($s[$i] -eq '\\' -and $i + 1 -lt $s.Length) {",
|
|
58
|
+
' $c = $s[$i + 1]',
|
|
59
|
+
" if ($c -eq 'n') { [void]$sb.Append([char]10); $i += 2; continue }",
|
|
60
|
+
" if ($c -eq 't') { [void]$sb.Append([char]9); $i += 2; continue }",
|
|
61
|
+
" if ($c -eq 'r') { [void]$sb.Append([char]13); $i += 2; continue }",
|
|
62
|
+
" if ($c -eq '\\') { [void]$sb.Append('\\'); $i += 2; continue }",
|
|
63
|
+
' }',
|
|
64
|
+
' [void]$sb.Append($s[$i]); $i++',
|
|
65
|
+
' }',
|
|
66
|
+
' $sb.ToString()',
|
|
67
|
+
'}',
|
|
68
|
+
].join('\n');
|
|
69
|
+
/**
|
|
70
|
+
* Terminal-aware write. When this block's output goes straight to the console
|
|
71
|
+
* (a lone `& { }` scriptblock — $MyInvocation name is empty — or the last
|
|
72
|
+
* stage of a pipeline):
|
|
73
|
+
* - a string ending in \n is emitted as line items (the console formatter
|
|
74
|
+
* appends the final newline; still correct inside $(...) substitution);
|
|
75
|
+
* - a string without a trailing newline is written with the exact bytes so
|
|
76
|
+
* `echo -n`, `printf 'x'`, `base64 -w0`, `head -c N` stay GNU-exact.
|
|
77
|
+
* Inside a pipeline stage emit one string item instead (line semantics
|
|
78
|
+
* downstream; embedded \n is preserved for consumers that re-split).
|
|
79
|
+
*/
|
|
80
|
+
const PS_WRITE_FN = [
|
|
81
|
+
'function fx-write($s, $term) {',
|
|
82
|
+
" if ($s -eq '') { return }",
|
|
83
|
+
' if (-not $term) { $s; return }',
|
|
84
|
+
' if (-not $s.EndsWith([string][char]10)) { [Console]::Out.Write($s); return }',
|
|
85
|
+
' $t = $s.Substring(0, $s.Length - 1)',
|
|
86
|
+
' foreach ($fx_l in $t.Split([char]10)) { $fx_l }',
|
|
87
|
+
'}',
|
|
88
|
+
].join('\n');
|
|
89
|
+
/** $fx_term: is this block's output console-terminal? (see PS_WRITE_FN) */
|
|
90
|
+
function fxTermLine(position) {
|
|
91
|
+
return ("$fx_term = (($MyInvocation.MyCommand.Name -eq '') -or " +
|
|
92
|
+
(position === 'last' ? '$true' : '$false') +
|
|
93
|
+
')');
|
|
94
|
+
}
|
|
95
|
+
/** PS boolean literal. */
|
|
96
|
+
function pb(v) {
|
|
97
|
+
return v ? '$true' : '$false';
|
|
98
|
+
}
|
|
99
|
+
/** GNU-style stderr line + exit flag, message given as a PS expression. */
|
|
100
|
+
function psErrExpr(msgExpr, code = '1') {
|
|
101
|
+
return '[Console]::Error.WriteLine(' + msgExpr + '); $script:fx_exit = ' + code;
|
|
102
|
+
}
|
|
103
|
+
/** `cmd: <g>: message` as a PS expression (single-quoted concat style). */
|
|
104
|
+
function sErr(cmd, g, msg) {
|
|
105
|
+
return "'" + cmd + ": ' + " + g + " + ': " + msg + "'";
|
|
106
|
+
}
|
|
107
|
+
/** `cmd: lead '<g>': message` as a PS expression (double-quoted style). */
|
|
108
|
+
function qErr(cmd, g, msg, lead = 'cannot open ') {
|
|
109
|
+
return ('"' + cmd + ': ' + lead + "'" + '" + ' + g + ' + "' + "'" + ': ' + msg + '"');
|
|
110
|
+
}
|
|
111
|
+
/** Operand Words → PS array expression of string exprs. */
|
|
112
|
+
function psArray(words, fn = operandExpr) {
|
|
113
|
+
if (words.length === 0)
|
|
114
|
+
return '@()';
|
|
115
|
+
return '@(' + words.map(fn).join(', ') + ')';
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Collect file operands through fx-glob into `$fx_srcs`. A literal `-`
|
|
119
|
+
* operand is passed through as a stdin marker. Missing files / directories
|
|
120
|
+
* emit the given GNU-style error and continue processing (exit flag set).
|
|
121
|
+
* When there are no operands and stdinDefault is set, `$fx_srcs` starts as
|
|
122
|
+
* the stdin marker (GNU: read standard input).
|
|
123
|
+
* Missing-file errors quote the operand as the user typed it ($fx_d) —
|
|
124
|
+
* GNU prints the argument text, not a resolved Windows path.
|
|
125
|
+
*/
|
|
126
|
+
function psCollectFiles(operandWords, missErr, dirErr, stdinDefault = true) {
|
|
127
|
+
const out = [
|
|
128
|
+
operandWords.length === 0 && stdinDefault ? "$fx_srcs = @('-')" : '$fx_srcs = @()',
|
|
129
|
+
operandWords.length === 0 && stdinDefault ? '$fx_names = @($null)' : '$fx_names = @()',
|
|
130
|
+
];
|
|
131
|
+
for (const w of operandWords) {
|
|
132
|
+
if (wordToString(w) === '-') {
|
|
133
|
+
out.push("$fx_srcs += '-'", "$fx_names += '-'");
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const lit = w.every((p) => p.kind === 'Text' || p.kind === 'SingleQuoted')
|
|
137
|
+
? w.map((p) => p.text).join('')
|
|
138
|
+
: null;
|
|
139
|
+
out.push('$fx_d = ' + (lit !== null ? psStr(lit) : exprOfWord(w)));
|
|
140
|
+
out.push('foreach ($fx_g in (fx-glob ' + operandExpr(w) + ')) {');
|
|
141
|
+
if (dirErr) {
|
|
142
|
+
const dis = lit !== null && /[*?]/.test(lit) ? '$fx_g' : '$fx_d';
|
|
143
|
+
out.push(' if (Test-Path -LiteralPath $fx_g -PathType Container) { ' +
|
|
144
|
+
psErrExpr(dirErr(dis)) +
|
|
145
|
+
'; continue }');
|
|
146
|
+
}
|
|
147
|
+
out.push(' if (-not (Test-Path -LiteralPath $fx_g -PathType Leaf)) { ' +
|
|
148
|
+
psErrExpr(missErr('$fx_d')) +
|
|
149
|
+
'; continue }');
|
|
150
|
+
out.push(' $fx_srcs += $fx_g');
|
|
151
|
+
// glob operands display their expansion; plain operands the text as typed
|
|
152
|
+
out.push(' ' +
|
|
153
|
+
(lit !== null && /[*?]/.test(lit)
|
|
154
|
+
? '$fx_names += $fx_g'
|
|
155
|
+
: '$fx_names += $fx_d'));
|
|
156
|
+
out.push('}');
|
|
157
|
+
}
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
/* ------------------------------------------------------------------ */
|
|
161
|
+
/* echo */
|
|
162
|
+
/* ------------------------------------------------------------------ */
|
|
163
|
+
const echo = (args, ctx) => {
|
|
164
|
+
// GNU echo: options only before the first operand
|
|
165
|
+
let noNewline = false;
|
|
166
|
+
let esc = false;
|
|
167
|
+
let i = 0;
|
|
168
|
+
while (i < args.length) {
|
|
169
|
+
const t = wordToString(args[i]);
|
|
170
|
+
if (t === '-n')
|
|
171
|
+
noNewline = true;
|
|
172
|
+
else if (t === '-e')
|
|
173
|
+
esc = true;
|
|
174
|
+
else if (t === '-E')
|
|
175
|
+
esc = false;
|
|
176
|
+
else if (/^-[neE]{2,}$/.test(t)) {
|
|
177
|
+
if (t.includes('n'))
|
|
178
|
+
noNewline = true;
|
|
179
|
+
if (t.includes('e'))
|
|
180
|
+
esc = true;
|
|
181
|
+
if (t.includes('E'))
|
|
182
|
+
esc = false;
|
|
183
|
+
}
|
|
184
|
+
else
|
|
185
|
+
break;
|
|
186
|
+
i++;
|
|
187
|
+
}
|
|
188
|
+
const rest = args.slice(i);
|
|
189
|
+
return [
|
|
190
|
+
PS_UNESQ_FN,
|
|
191
|
+
PS_WRITE_FN,
|
|
192
|
+
fxTermLine(ctx.position),
|
|
193
|
+
'$fx_parts = ' + psArray(rest, exprOfWord),
|
|
194
|
+
"$fx_s = $fx_parts -join ' '",
|
|
195
|
+
...(esc ? ['$fx_s = fx-unesq $fx_s'] : []),
|
|
196
|
+
noNewline
|
|
197
|
+
? 'fx-write $fx_s $fx_term'
|
|
198
|
+
: 'fx-write ($fx_s + [string][char]10) $fx_term',
|
|
199
|
+
].join('\n');
|
|
200
|
+
};
|
|
201
|
+
/* ------------------------------------------------------------------ */
|
|
202
|
+
/* printf — runtime formatter (GNU repeat-until-consumed semantics) */
|
|
203
|
+
/* ------------------------------------------------------------------ */
|
|
204
|
+
const PS_PRINTF_FNS = [
|
|
205
|
+
PS_UNESQ_FN,
|
|
206
|
+
'function fx-pf-int($s) {',
|
|
207
|
+
' $t = [string]$s',
|
|
208
|
+
" if ($t -match '^[+-]?0[xX][0-9a-fA-F]+$') { return [double][Convert]::ToUInt64($t, 16) }",
|
|
209
|
+
" if ($t -match '^[+-]?[0-9]+') { return [double]::Parse($Matches[0], [System.Globalization.CultureInfo]::InvariantCulture) }",
|
|
210
|
+
' return [double]0',
|
|
211
|
+
'}',
|
|
212
|
+
'function fx-pf-float($s) {',
|
|
213
|
+
' $t = [string]$s',
|
|
214
|
+
" if ($t -match '^[+-]?0[xX][0-9a-fA-F]+$') { return [double][Convert]::ToUInt64($t, 16) }",
|
|
215
|
+
" if ($t -match '^[+-]?([0-9]*\\.[0-9]+|[0-9]+\\.?)([eE][+-]?[0-9]+)?') { try { return [double]::Parse($Matches[0], [System.Globalization.CultureInfo]::InvariantCulture) } catch { return [double]0 } }",
|
|
216
|
+
' return [double]0',
|
|
217
|
+
'}',
|
|
218
|
+
'function fx-pf-pad($s, $w, $left, $zero) {',
|
|
219
|
+
' if ($w -le 0 -or $s.Length -ge $w) { return $s }',
|
|
220
|
+
" $pc = ' '",
|
|
221
|
+
" if ($zero) { $pc = '0' }",
|
|
222
|
+
" if ($zero -and $s.Length -gt 0 -and $s[0] -eq '-') {",
|
|
223
|
+
" return '-' + $s.Substring(1).PadLeft($w - 1, $pc)",
|
|
224
|
+
' }',
|
|
225
|
+
' if ($left) { return $s.PadRight($w, $pc) }',
|
|
226
|
+
' return $s.PadLeft($w, $pc)',
|
|
227
|
+
'}',
|
|
228
|
+
'function fx-printf($fmt, $av) {',
|
|
229
|
+
' $sb = New-Object System.Text.StringBuilder',
|
|
230
|
+
' $n = $av.Count',
|
|
231
|
+
' $ai = 0',
|
|
232
|
+
' while ($true) {',
|
|
233
|
+
' $i = 0',
|
|
234
|
+
' $used = $false',
|
|
235
|
+
' while ($i -lt $fmt.Length) {',
|
|
236
|
+
" $c = $fmt[$i]",
|
|
237
|
+
" if ($c -eq '%') {",
|
|
238
|
+
' $j = $i + 1',
|
|
239
|
+
' $left = $false; $zero = $false; $plus = $false',
|
|
240
|
+
" while ($j -lt $fmt.Length -and @('-','0','+',' ') -contains [string]$fmt[$j]) {",
|
|
241
|
+
" if ($fmt[$j] -eq '-') { $left = $true }",
|
|
242
|
+
" if ($fmt[$j] -eq '0') { $zero = $true }",
|
|
243
|
+
" if ($fmt[$j] -eq '+') { $plus = $true }",
|
|
244
|
+
' $j++',
|
|
245
|
+
' }',
|
|
246
|
+
' $w = 0',
|
|
247
|
+
" while ($j -lt $fmt.Length -and $fmt[$j] -ge '0' -and $fmt[$j] -le '9') { $w = $w * 10 + ([int]$fmt[$j] - 48); $j++ }",
|
|
248
|
+
' $p = -1',
|
|
249
|
+
" if ($j -lt $fmt.Length -and $fmt[$j] -eq '.') {",
|
|
250
|
+
' $j++; $p = 0',
|
|
251
|
+
" while ($j -lt $fmt.Length -and $fmt[$j] -ge '0' -and $fmt[$j] -le '9') { $p = $p * 10 + ([int]$fmt[$j] - 48); $j++ }",
|
|
252
|
+
' }',
|
|
253
|
+
" $conv = [char]0",
|
|
254
|
+
' if ($j -lt $fmt.Length) { $conv = $fmt[$j] }',
|
|
255
|
+
" if ($conv -ceq '%') {",
|
|
256
|
+
" [void]$sb.Append('%')",
|
|
257
|
+
' $i = $j + 1',
|
|
258
|
+
' continue',
|
|
259
|
+
' }',
|
|
260
|
+
" if (@('s','b','c','d','i','f','x','X') -ccontains [string]$conv) {",
|
|
261
|
+
" $a = ''",
|
|
262
|
+
' if ($ai -lt $n) { $a = [string]$av[$ai]; $ai++; $used = $true }',
|
|
263
|
+
" if ($conv -ceq 's') {",
|
|
264
|
+
' $s = $a',
|
|
265
|
+
' if ($p -ge 0 -and $s.Length -gt $p) { $s = $s.Substring(0, $p) }',
|
|
266
|
+
' $s = fx-pf-pad $s $w $left $false',
|
|
267
|
+
' [void]$sb.Append($s)',
|
|
268
|
+
" } elseif ($conv -ceq 'b') {",
|
|
269
|
+
' $s = fx-unesq $a',
|
|
270
|
+
' if ($p -ge 0 -and $s.Length -gt $p) { $s = $s.Substring(0, $p) }',
|
|
271
|
+
' $s = fx-pf-pad $s $w $left $false',
|
|
272
|
+
' [void]$sb.Append($s)',
|
|
273
|
+
" } elseif ($conv -ceq 'c') {",
|
|
274
|
+
" $s = ''",
|
|
275
|
+
' if ($a.Length -gt 0) { $s = [string]$a[0] }',
|
|
276
|
+
' $s = fx-pf-pad $s $w $left $false',
|
|
277
|
+
' [void]$sb.Append($s)',
|
|
278
|
+
" } elseif ($conv -ceq 'd' -or $conv -ceq 'i') {",
|
|
279
|
+
' $v = fx-pf-int $a',
|
|
280
|
+
' $s = [string][long]$v',
|
|
281
|
+
" if ($p -ge 1 -and $s.TrimStart('-').Length -lt $p) {",
|
|
282
|
+
" $neg = $s.StartsWith('-')",
|
|
283
|
+
" $digits = $s.TrimStart('-')",
|
|
284
|
+
" $digits = $digits.PadLeft($p, '0')",
|
|
285
|
+
" if ($neg) { $s = '-' + $digits } else { $s = $digits }",
|
|
286
|
+
' }',
|
|
287
|
+
" if ($plus -and -not $s.StartsWith('-')) { $s = '+' + $s }",
|
|
288
|
+
' $s = fx-pf-pad $s $w $left $zero',
|
|
289
|
+
' [void]$sb.Append($s)',
|
|
290
|
+
" } elseif ($conv -ceq 'x' -or $conv -ceq 'X') {",
|
|
291
|
+
' $v = [long](fx-pf-int $a)',
|
|
292
|
+
' if ($v -lt 0) { $v = $v -band 0xFFFFFFFFFFFFFFF }',
|
|
293
|
+
' $s = [Convert]::ToString($v, 16)',
|
|
294
|
+
" if ($conv -ceq 'X') { $s = $s.ToUpper() }",
|
|
295
|
+
" if ($p -ge 1 -and $s.Length -lt $p) { $s = $s.PadLeft($p, '0') }",
|
|
296
|
+
' $s = fx-pf-pad $s $w $left $zero',
|
|
297
|
+
' [void]$sb.Append($s)',
|
|
298
|
+
' } else {',
|
|
299
|
+
' $v = fx-pf-float $a',
|
|
300
|
+
' $pp = 6',
|
|
301
|
+
' if ($p -ge 0) { $pp = $p }',
|
|
302
|
+
" $s = $v.ToString('F' + $pp, [System.Globalization.CultureInfo]::InvariantCulture)",
|
|
303
|
+
" if ($plus -and -not $s.StartsWith('-')) { $s = '+' + $s }",
|
|
304
|
+
' $s = fx-pf-pad $s $w $left $zero',
|
|
305
|
+
' [void]$sb.Append($s)',
|
|
306
|
+
' }',
|
|
307
|
+
' $i = $j + 1',
|
|
308
|
+
' continue',
|
|
309
|
+
' }',
|
|
310
|
+
' # unknown directive: emit literally (GNU errors; we stay lenient)',
|
|
311
|
+
" [void]$sb.Append('%')",
|
|
312
|
+
' $i = $i + 1',
|
|
313
|
+
' continue',
|
|
314
|
+
' }',
|
|
315
|
+
" if ($c -eq '\\' -and $i + 1 -lt $fmt.Length) {",
|
|
316
|
+
' $e = $fmt[$i + 1]',
|
|
317
|
+
" if ($e -eq 'n') { [void]$sb.Append([char]10); $i += 2; continue }",
|
|
318
|
+
" if ($e -eq 't') { [void]$sb.Append([char]9); $i += 2; continue }",
|
|
319
|
+
" if ($e -eq 'r') { [void]$sb.Append([char]13); $i += 2; continue }",
|
|
320
|
+
" if ($e -eq '\\') { [void]$sb.Append('\\'); $i += 2; continue }",
|
|
321
|
+
' }',
|
|
322
|
+
' [void]$sb.Append($c)',
|
|
323
|
+
' $i++',
|
|
324
|
+
' }',
|
|
325
|
+
' if ($ai -ge $n) { break }',
|
|
326
|
+
' if (-not $used) { break }',
|
|
327
|
+
' }',
|
|
328
|
+
' $sb.ToString()',
|
|
329
|
+
'}',
|
|
330
|
+
].join('\n');
|
|
331
|
+
const printf = (args, ctx) => {
|
|
332
|
+
// no options; skip a single leading `--` separator
|
|
333
|
+
const ops = [];
|
|
334
|
+
for (const w of args) {
|
|
335
|
+
if (ops.length === 0 && wordToString(w) === '--')
|
|
336
|
+
continue;
|
|
337
|
+
ops.push(w);
|
|
338
|
+
}
|
|
339
|
+
if (ops.length === 0) {
|
|
340
|
+
return psErrExpr(psStr('printf: usage: printf format [arguments]'), '2');
|
|
341
|
+
}
|
|
342
|
+
return [
|
|
343
|
+
PS_PRINTF_FNS,
|
|
344
|
+
PS_WRITE_FN,
|
|
345
|
+
fxTermLine(ctx.position),
|
|
346
|
+
'$fx_fmt = ' + exprOfWord(ops[0]),
|
|
347
|
+
'$fx_av = ' + psArray(ops.slice(1), exprOfWord),
|
|
348
|
+
'fx-write (fx-printf $fx_fmt $fx_av) $fx_term',
|
|
349
|
+
].join('\n');
|
|
350
|
+
};
|
|
351
|
+
/* ------------------------------------------------------------------ */
|
|
352
|
+
/* cat */
|
|
353
|
+
/* ------------------------------------------------------------------ */
|
|
354
|
+
const cat = (args) => {
|
|
355
|
+
const { flags, operandWords } = parseWords(args);
|
|
356
|
+
const numberAll = flags.has('n');
|
|
357
|
+
const numberNonBlank = flags.has('b');
|
|
358
|
+
const squeeze = flags.has('s');
|
|
359
|
+
const showEnds = flags.has('E') || flags.has('A');
|
|
360
|
+
const showTabs = flags.has('T') || flags.has('A');
|
|
361
|
+
// GNU: -b overrides -n (nonblank numbering wins)
|
|
362
|
+
const mode = numberNonBlank ? 'nonblank' : numberAll ? 'all' : 'none';
|
|
363
|
+
return [
|
|
364
|
+
PS_GLOB_FN,
|
|
365
|
+
PS_READTEXT_FN,
|
|
366
|
+
PS_SPLITLINES_FN,
|
|
367
|
+
STDIN_INLINES,
|
|
368
|
+
...psCollectFiles(operandWords, (g) => sErr('cat', g, 'No such file or directory'), (g) => sErr('cat', g, 'Is a directory')),
|
|
369
|
+
'$fx_no = 1',
|
|
370
|
+
'$fx_blank = $false',
|
|
371
|
+
'foreach ($fx_g in $fx_srcs) {',
|
|
372
|
+
" if ($fx_g -eq '-') { $fx_ls = @($fx_in) }",
|
|
373
|
+
' else { $fx_ls = @(fx-splitlines (fx-read $fx_g)) }',
|
|
374
|
+
' for ($fx_i = 0; $fx_i -lt $fx_ls.Count; $fx_i++) {',
|
|
375
|
+
' $fx_l = $fx_ls[$fx_i]',
|
|
376
|
+
' if (' + pb(squeeze) + ') {',
|
|
377
|
+
" if ($fx_l -eq '') {",
|
|
378
|
+
' if ($fx_blank) { continue }',
|
|
379
|
+
' $fx_blank = $true',
|
|
380
|
+
' } else { $fx_blank = $false }',
|
|
381
|
+
' }',
|
|
382
|
+
' $fx_o = $fx_l',
|
|
383
|
+
' if (' + pb(showTabs) + ") { $fx_o = $fx_o.Replace([string][char]9, '^I') }",
|
|
384
|
+
" if ('" + mode + "' -eq 'all') {",
|
|
385
|
+
" $fx_o = ('{0,6}' -f $fx_no) + [string][char]9 + $fx_o; $fx_no++",
|
|
386
|
+
" } elseif ('" + mode + "' -eq 'nonblank' -and $fx_l -ne '') {",
|
|
387
|
+
" $fx_o = ('{0,6}' -f $fx_no) + [string][char]9 + $fx_o; $fx_no++",
|
|
388
|
+
' }',
|
|
389
|
+
' if (' + pb(showEnds) + ") { $fx_o = $fx_o + '$' }",
|
|
390
|
+
' $fx_o',
|
|
391
|
+
' }',
|
|
392
|
+
'}',
|
|
393
|
+
].join('\n');
|
|
394
|
+
};
|
|
395
|
+
/* ------------------------------------------------------------------ */
|
|
396
|
+
/* head */
|
|
397
|
+
/* ------------------------------------------------------------------ */
|
|
398
|
+
const head = (args, ctx) => {
|
|
399
|
+
// option scan (legacy `head -N` supported; -n/-c take values, incl. negative)
|
|
400
|
+
let nLines = null;
|
|
401
|
+
let nBytes = null;
|
|
402
|
+
const operandWords = [];
|
|
403
|
+
let quiet = false;
|
|
404
|
+
let verbose = false;
|
|
405
|
+
{
|
|
406
|
+
let i = 0;
|
|
407
|
+
let onlyOps = false;
|
|
408
|
+
while (i < args.length) {
|
|
409
|
+
const t = wordToString(args[i]);
|
|
410
|
+
if (onlyOps) {
|
|
411
|
+
operandWords.push(args[i]);
|
|
412
|
+
i++;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (t === '--') {
|
|
416
|
+
onlyOps = true;
|
|
417
|
+
i++;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (t.startsWith('--')) {
|
|
421
|
+
i++;
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
let m;
|
|
425
|
+
if (t === '-n' || t === '-c') {
|
|
426
|
+
const val = i + 1 < args.length ? wordToString(args[i + 1]) : null;
|
|
427
|
+
if (val === null) {
|
|
428
|
+
return psErrExpr(psStr('head: option requires an argument -- ' + t.slice(1)));
|
|
429
|
+
}
|
|
430
|
+
if (t === '-c')
|
|
431
|
+
nBytes = val;
|
|
432
|
+
else
|
|
433
|
+
nLines = val;
|
|
434
|
+
i += 2;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
if ((m = t.match(/^-[nc](.+)$/)) !== null) {
|
|
438
|
+
if (t[1] === 'c')
|
|
439
|
+
nBytes = m[1];
|
|
440
|
+
else
|
|
441
|
+
nLines = m[1];
|
|
442
|
+
i++;
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if ((m = t.match(/^-(\d+)$/)) !== null) {
|
|
446
|
+
nLines = m[1];
|
|
447
|
+
i++;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (t.startsWith('-') && t.length > 1) {
|
|
451
|
+
for (const ch of t.slice(1)) {
|
|
452
|
+
if (ch === 'q')
|
|
453
|
+
quiet = true;
|
|
454
|
+
else if (ch === 'v')
|
|
455
|
+
verbose = true;
|
|
456
|
+
}
|
|
457
|
+
i++;
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
operandWords.push(args[i]);
|
|
461
|
+
i++;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const bytesMode = nBytes !== null;
|
|
465
|
+
const countLit = bytesMode ? nBytes : nLines !== null ? nLines : '10';
|
|
466
|
+
const lines = [
|
|
467
|
+
PS_GLOB_FN,
|
|
468
|
+
PS_READTEXT_FN,
|
|
469
|
+
PS_SPLITLINES_FN,
|
|
470
|
+
PS_WRITE_FN,
|
|
471
|
+
fxTermLine(ctx.position),
|
|
472
|
+
];
|
|
473
|
+
if (bytesMode)
|
|
474
|
+
lines.push(STDIN_ITEMS, PS_STDINRAW_FN);
|
|
475
|
+
else
|
|
476
|
+
lines.push(STDIN_INLINES);
|
|
477
|
+
lines.push(...psCollectFiles(operandWords, (g) => qErr('head', g, 'No such file or directory'), (g) => qErr('head', g, 'Is a directory', 'error reading ')), '$fx_count = [int](' + countLit + ')', '$fx_hdr = ((($fx_srcs.Count -gt 1) -and ' + pb(!quiet) + ') -or ' + pb(verbose) + ')', '$fx_first = $true');
|
|
478
|
+
if (bytesMode) {
|
|
479
|
+
lines.push('$fx_out = New-Object System.Text.StringBuilder', 'for ($fx_k = 0; $fx_k -lt $fx_srcs.Count; $fx_k++) {', ' $fx_g = $fx_srcs[$fx_k]', " if ($fx_g -eq '-') { $fx_txt = (fx-stdinraw $fx_items); $fx_disp = 'standard input' }", ' else { $fx_txt = fx-read $fx_g; $fx_disp = $fx_names[$fx_k] }', ' if ($fx_hdr) {', ' if (-not $fx_first) { [void]$fx_out.Append([string][char]10) }', " [void]$fx_out.Append('==> ' + $fx_disp + ' <==' + [string][char]10)", ' }', ' $fx_first = $false', ' $fx_len = [math]::Min($fx_count, $fx_txt.Length)', ' if ($fx_len -lt 0) { $fx_len = 0 }', ' if ($fx_len -gt 0) { [void]$fx_out.Append($fx_txt.Substring(0, $fx_len)) }', '}', 'fx-write $fx_out.ToString() $fx_term');
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
lines.push('for ($fx_k = 0; $fx_k -lt $fx_srcs.Count; $fx_k++) {', ' $fx_g = $fx_srcs[$fx_k]', " if ($fx_g -eq '-') { $fx_ls = @($fx_in); $fx_disp = 'standard input' }", ' else { $fx_ls = @(fx-splitlines (fx-read $fx_g)); $fx_disp = $fx_names[$fx_k] }', ' if ($fx_hdr) {', " if (-not $fx_first) { '' }", " '==> ' + $fx_disp + ' <=='", ' }', ' $fx_first = $false', ' $fx_lim = $fx_ls.Count', ' if ($fx_count -lt 0) { $fx_lim = $fx_ls.Count + $fx_count }', ' elseif ($fx_lim -gt $fx_count) { $fx_lim = $fx_count }', ' if ($fx_lim -lt 0) { $fx_lim = 0 }', ' for ($fx_i = 0; $fx_i -lt $fx_lim; $fx_i++) { $fx_ls[$fx_i] }', '}');
|
|
483
|
+
}
|
|
484
|
+
return lines.join('\n');
|
|
485
|
+
};
|
|
486
|
+
/* ------------------------------------------------------------------ */
|
|
487
|
+
/* tail */
|
|
488
|
+
/* ------------------------------------------------------------------ */
|
|
489
|
+
const tail = (args, ctx) => {
|
|
490
|
+
const pre = parseWords(args);
|
|
491
|
+
if (pre.flags.has('f') || pre.flags.has('F')) {
|
|
492
|
+
return psErrExpr(psStr('tail: -f is not supported by fauxnix (no persistent tty)'));
|
|
493
|
+
}
|
|
494
|
+
// option scan (legacy `tail -N` / `tail +N` supported; -n/-c take values,
|
|
495
|
+
// including `+N` = "from line N")
|
|
496
|
+
let nLines = null;
|
|
497
|
+
let fromLine = null;
|
|
498
|
+
let nBytes = null;
|
|
499
|
+
const operandWords = [];
|
|
500
|
+
let quiet = false;
|
|
501
|
+
let verbose = false;
|
|
502
|
+
{
|
|
503
|
+
let i = 0;
|
|
504
|
+
let onlyOps = false;
|
|
505
|
+
while (i < args.length) {
|
|
506
|
+
const t = wordToString(args[i]);
|
|
507
|
+
if (onlyOps) {
|
|
508
|
+
operandWords.push(args[i]);
|
|
509
|
+
i++;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
if (t === '--') {
|
|
513
|
+
onlyOps = true;
|
|
514
|
+
i++;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (t.startsWith('--')) {
|
|
518
|
+
i++;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
let m;
|
|
522
|
+
if (t === '-n' || t === '-c') {
|
|
523
|
+
const val = i + 1 < args.length ? wordToString(args[i + 1]) : null;
|
|
524
|
+
if (val === null) {
|
|
525
|
+
return psErrExpr(psStr('tail: option requires an argument -- ' + t.slice(1)));
|
|
526
|
+
}
|
|
527
|
+
if (t === '-c')
|
|
528
|
+
nBytes = val;
|
|
529
|
+
else if (val.startsWith('+'))
|
|
530
|
+
fromLine = val.slice(1);
|
|
531
|
+
else
|
|
532
|
+
nLines = val.replace(/^-/, ''); // -n -N ≡ -n N (last N)
|
|
533
|
+
i += 2;
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
if ((m = t.match(/^-[nc](.*)$/)) !== null) {
|
|
537
|
+
const val = m[1];
|
|
538
|
+
if (t[1] === 'c')
|
|
539
|
+
nBytes = val;
|
|
540
|
+
else if (val.startsWith('+'))
|
|
541
|
+
fromLine = val.slice(1);
|
|
542
|
+
else
|
|
543
|
+
nLines = val.replace(/^-/, '');
|
|
544
|
+
i++;
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
if ((m = t.match(/^-(\d+)$/)) !== null) {
|
|
548
|
+
nLines = m[1];
|
|
549
|
+
i++;
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
if ((m = t.match(/^\+(\d+)$/)) !== null) {
|
|
553
|
+
fromLine = m[1];
|
|
554
|
+
i++;
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
if (t.startsWith('-') && t.length > 1) {
|
|
558
|
+
for (const ch of t.slice(1)) {
|
|
559
|
+
if (ch === 'q')
|
|
560
|
+
quiet = true;
|
|
561
|
+
else if (ch === 'v')
|
|
562
|
+
verbose = true;
|
|
563
|
+
}
|
|
564
|
+
i++;
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
operandWords.push(args[i]);
|
|
568
|
+
i++;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const bytesMode = nBytes !== null;
|
|
572
|
+
const countLit = bytesMode
|
|
573
|
+
? nBytes
|
|
574
|
+
: fromLine !== null
|
|
575
|
+
? fromLine
|
|
576
|
+
: nLines !== null
|
|
577
|
+
? nLines
|
|
578
|
+
: '10';
|
|
579
|
+
const lines = [
|
|
580
|
+
PS_GLOB_FN,
|
|
581
|
+
PS_READTEXT_FN,
|
|
582
|
+
PS_SPLITLINES_FN,
|
|
583
|
+
PS_WRITE_FN,
|
|
584
|
+
fxTermLine(ctx.position),
|
|
585
|
+
];
|
|
586
|
+
if (bytesMode)
|
|
587
|
+
lines.push(STDIN_ITEMS, PS_STDINRAW_FN);
|
|
588
|
+
else
|
|
589
|
+
lines.push(STDIN_INLINES);
|
|
590
|
+
lines.push(...psCollectFiles(operandWords, (g) => qErr('tail', g, 'No such file or directory'), (g) => qErr('tail', g, 'Is a directory', 'error reading ')), '$fx_count = [int](' + countLit + ')', '$fx_from = ' + pb(fromLine !== null && !bytesMode), '$fx_hdr = ((($fx_srcs.Count -gt 1) -and ' + pb(!quiet) + ') -or ' + pb(verbose) + ')', '$fx_first = $true');
|
|
591
|
+
if (bytesMode) {
|
|
592
|
+
lines.push('$fx_out = New-Object System.Text.StringBuilder', 'for ($fx_k = 0; $fx_k -lt $fx_srcs.Count; $fx_k++) {', ' $fx_g = $fx_srcs[$fx_k]', " if ($fx_g -eq '-') { $fx_txt = (fx-stdinraw $fx_items); $fx_disp = 'standard input' }", ' else { $fx_txt = fx-read $fx_g; $fx_disp = $fx_names[$fx_k] }', ' if ($fx_hdr) {', ' if (-not $fx_first) { [void]$fx_out.Append([string][char]10) }', " [void]$fx_out.Append('==> ' + $fx_disp + ' <==' + [string][char]10)", ' }', ' $fx_first = $false', ' $fx_st = $fx_txt.Length - $fx_count', ' if ($fx_st -lt 0) { $fx_st = 0 }', ' if ($fx_st -lt $fx_txt.Length) { [void]$fx_out.Append($fx_txt.Substring($fx_st)) }', '}', 'fx-write $fx_out.ToString() $fx_term');
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
lines.push('for ($fx_k = 0; $fx_k -lt $fx_srcs.Count; $fx_k++) {', ' $fx_g = $fx_srcs[$fx_k]', " if ($fx_g -eq '-') { $fx_ls = @($fx_in); $fx_disp = 'standard input' }", ' else { $fx_ls = @(fx-splitlines (fx-read $fx_g)); $fx_disp = $fx_names[$fx_k] }', ' if ($fx_hdr) {', " if (-not $fx_first) { '' }", " '==> ' + $fx_disp + ' <=='", ' }', ' $fx_first = $false', ' if ($fx_from) {', ' $fx_st = $fx_count - 1', ' if ($fx_st -lt 0) { $fx_st = 0 }', ' } else {', ' $fx_st = $fx_ls.Count - $fx_count', ' if ($fx_st -lt 0) { $fx_st = 0 }', ' }', ' for ($fx_i = $fx_st; $fx_i -lt $fx_ls.Count; $fx_i++) { $fx_ls[$fx_i] }', '}');
|
|
596
|
+
}
|
|
597
|
+
return lines.join('\n');
|
|
598
|
+
};
|
|
599
|
+
/* ------------------------------------------------------------------ */
|
|
600
|
+
/* wc */
|
|
601
|
+
/* ------------------------------------------------------------------ */
|
|
602
|
+
const wc = (args) => {
|
|
603
|
+
const { flags, operandWords } = parseWords(args);
|
|
604
|
+
const wantL = flags.has('l');
|
|
605
|
+
const wantW = flags.has('w');
|
|
606
|
+
const wantC = flags.has('c');
|
|
607
|
+
const wantM = flags.has('m');
|
|
608
|
+
const anyFlag = wantL || wantW || wantC || wantM;
|
|
609
|
+
const pl = wantL || !anyFlag;
|
|
610
|
+
const pw = wantW || !anyFlag;
|
|
611
|
+
const pc = wantC || !anyFlag;
|
|
612
|
+
const pm = wantM;
|
|
613
|
+
// GNU: stdin (implicit or `-`) prints the classic 7-wide columns; real
|
|
614
|
+
// file operands use dynamic widths; `-` displays the name '-'.
|
|
615
|
+
const fromFiles = operandWords.some((w) => wordToString(w) !== '-');
|
|
616
|
+
return [
|
|
617
|
+
PS_GLOB_FN,
|
|
618
|
+
PS_READTEXT_FN,
|
|
619
|
+
PS_SPLITLINES_FN,
|
|
620
|
+
STDIN_ITEMS,
|
|
621
|
+
PS_STDINRAW_FN,
|
|
622
|
+
...psCollectFiles(operandWords, (g) => sErr('wc', g, 'No such file or directory'), (g) => sErr('wc', g, 'Is a directory')),
|
|
623
|
+
'function fx-wdcount($ls) {',
|
|
624
|
+
' $t = 0',
|
|
625
|
+
' foreach ($fx_l in $ls) {',
|
|
626
|
+
' $fx_x = $fx_l.Trim()',
|
|
627
|
+
" if ($fx_x -ne '') { $t += @($fx_x -split '\\s+').Count }",
|
|
628
|
+
' }',
|
|
629
|
+
' return $t',
|
|
630
|
+
'}',
|
|
631
|
+
'function fx-digits($n) { return ([string]$n).Length }',
|
|
632
|
+
'$fx_rows = @()',
|
|
633
|
+
'for ($fx_k = 0; $fx_k -lt $fx_srcs.Count; $fx_k++) {',
|
|
634
|
+
' $fx_g = $fx_srcs[$fx_k]',
|
|
635
|
+
" if ($fx_g -eq '-') {",
|
|
636
|
+
' # stdin — single raw item keeps a printf-style tail exact for bytes,',
|
|
637
|
+
' # but the LINE count treats the last line as terminated (bash mental',
|
|
638
|
+
' # model: `find | wc -l` counts result rows)',
|
|
639
|
+
' $fx_txt = fx-stdinraw $fx_items',
|
|
640
|
+
" $fx_nl = [regex]::Matches($fx_txt, '\\n').Count",
|
|
641
|
+
" if ($fx_txt -ne '' -and -not $fx_txt.EndsWith([string][char]10)) { $fx_nl = $fx_nl + 1 }",
|
|
642
|
+
' $fx_ww = fx-wdcount @(fx-splitlines $fx_txt)',
|
|
643
|
+
' $fx_bc = [System.Text.Encoding]::UTF8.GetByteCount($fx_txt)',
|
|
644
|
+
" $fx_rows += ,@($fx_nl, $fx_ww, $fx_bc, $fx_txt.Length, $fx_names[$fx_k])",
|
|
645
|
+
' } else {',
|
|
646
|
+
' $fx_txt = fx-read $fx_g',
|
|
647
|
+
' $fx_txt2 = $fx_txt.Replace([string][char]13 + [string][char]10, [string][char]10)',
|
|
648
|
+
" $fx_nl = [regex]::Matches($fx_txt2, '\\n').Count",
|
|
649
|
+
' $fx_bytes = [IO.File]::ReadAllBytes($fx_g).Length',
|
|
650
|
+
' $fx_ww = fx-wdcount @(fx-splitlines $fx_txt)',
|
|
651
|
+
' $fx_rows += ,@($fx_nl, $fx_ww, $fx_bytes, $fx_txt.Length, $fx_names[$fx_k])',
|
|
652
|
+
' }',
|
|
653
|
+
'}',
|
|
654
|
+
'$fx_tot = @(0, 0, 0, 0)',
|
|
655
|
+
'foreach ($fx_r in $fx_rows) {',
|
|
656
|
+
' $fx_tot[0] += $fx_r[0]; $fx_tot[1] += $fx_r[1]; $fx_tot[2] += $fx_r[2]; $fx_tot[3] += $fx_r[3]',
|
|
657
|
+
'}',
|
|
658
|
+
'$fx_showtotal = ($fx_rows.Count -gt 1)',
|
|
659
|
+
// column width = max digits over every printed count; stdin keeps the classic 7
|
|
660
|
+
'$fx_wd = 1',
|
|
661
|
+
...(fromFiles ? [] : ['$fx_wd = 7']),
|
|
662
|
+
'foreach ($fx_r in $fx_rows) {',
|
|
663
|
+
' if (' + pb(pl) + ' -and (fx-digits $fx_r[0]) -gt $fx_wd) { $fx_wd = fx-digits $fx_r[0] }',
|
|
664
|
+
' if (' + pb(pw) + ' -and (fx-digits $fx_r[1]) -gt $fx_wd) { $fx_wd = fx-digits $fx_r[1] }',
|
|
665
|
+
' if (' + pb(pc) + ' -and (fx-digits $fx_r[2]) -gt $fx_wd) { $fx_wd = fx-digits $fx_r[2] }',
|
|
666
|
+
' if (' + pb(pm) + ' -and (fx-digits $fx_r[3]) -gt $fx_wd) { $fx_wd = fx-digits $fx_r[3] }',
|
|
667
|
+
'}',
|
|
668
|
+
'if ($fx_showtotal) {',
|
|
669
|
+
' if (' + pb(pl) + ' -and (fx-digits $fx_tot[0]) -gt $fx_wd) { $fx_wd = fx-digits $fx_tot[0] }',
|
|
670
|
+
' if (' + pb(pw) + ' -and (fx-digits $fx_tot[1]) -gt $fx_wd) { $fx_wd = fx-digits $fx_tot[1] }',
|
|
671
|
+
' if (' + pb(pc) + ' -and (fx-digits $fx_tot[2]) -gt $fx_wd) { $fx_wd = fx-digits $fx_tot[2] }',
|
|
672
|
+
' if (' + pb(pm) + ' -and (fx-digits $fx_tot[3]) -gt $fx_wd) { $fx_wd = fx-digits $fx_tot[3] }',
|
|
673
|
+
'}',
|
|
674
|
+
'function fx-wcline($r, $name) {',
|
|
675
|
+
' $fx_f = @()',
|
|
676
|
+
' if (' + pb(pl) + ') { $fx_f += [string]$r[0] }',
|
|
677
|
+
' if (' + pb(pw) + ') { $fx_f += [string]$r[1] }',
|
|
678
|
+
' if (' + pb(pm) + ') { $fx_f += [string]$r[3] }',
|
|
679
|
+
' if (' + pb(pc) + ') { $fx_f += [string]$r[2] }',
|
|
680
|
+
' if ($fx_f.Count -eq 1) { $fx_line = $fx_f[0] }',
|
|
681
|
+
" else { $fx_f = @($fx_f | ForEach-Object { $_.PadLeft($fx_wd) }); $fx_line = ($fx_f -join ' ') }",
|
|
682
|
+
" if ($null -ne $name) { $fx_line = $fx_line + ' ' + $name }",
|
|
683
|
+
' $fx_line',
|
|
684
|
+
'}',
|
|
685
|
+
'foreach ($fx_r in $fx_rows) { fx-wcline $fx_r $fx_r[4] }',
|
|
686
|
+
"if ($fx_showtotal) { fx-wcline $fx_tot 'total' }",
|
|
687
|
+
].join('\n');
|
|
688
|
+
};
|
|
689
|
+
/* ------------------------------------------------------------------ */
|
|
690
|
+
/* tee */
|
|
691
|
+
/* ------------------------------------------------------------------ */
|
|
692
|
+
const tee = (args, ctx) => {
|
|
693
|
+
const { flags, operandWords } = parseWords(args);
|
|
694
|
+
const append = flags.has('a') || flags.has('append');
|
|
695
|
+
return [
|
|
696
|
+
PS_WRITE_FN,
|
|
697
|
+
fxTermLine(ctx.position),
|
|
698
|
+
STDIN_ITEMS,
|
|
699
|
+
PS_STDINRAW_FN,
|
|
700
|
+
'$fx_files = ' + psArray(operandWords),
|
|
701
|
+
'$fx_s = fx-stdinraw $fx_items',
|
|
702
|
+
'foreach ($fx_f in $fx_files) {',
|
|
703
|
+
' try {',
|
|
704
|
+
' if (' + pb(append) + ') {',
|
|
705
|
+
' [IO.File]::AppendAllText($fx_f, $fx_s, (New-Object System.Text.UTF8Encoding($false)))',
|
|
706
|
+
' } else {',
|
|
707
|
+
' [IO.File]::WriteAllText($fx_f, $fx_s, (New-Object System.Text.UTF8Encoding($false)))',
|
|
708
|
+
' }',
|
|
709
|
+
' } catch { ' + psErrExpr(sErr('tee', '$fx_f', 'No such file or directory')) + ' }',
|
|
710
|
+
'}',
|
|
711
|
+
'fx-write $fx_s $fx_term',
|
|
712
|
+
].join('\n');
|
|
713
|
+
};
|
|
714
|
+
/* ------------------------------------------------------------------ */
|
|
715
|
+
/* nl */
|
|
716
|
+
/* ------------------------------------------------------------------ */
|
|
717
|
+
const nl = (args) => {
|
|
718
|
+
const { values, operandWords } = parseWords(args, ['b']);
|
|
719
|
+
const bodyMode = values.get('-b') ?? 't';
|
|
720
|
+
if (bodyMode !== 'a' && bodyMode !== 'n' && bodyMode !== 't') {
|
|
721
|
+
return psErrExpr(psStr("nl: invalid body numbering mode: '" + bodyMode + "'"));
|
|
722
|
+
}
|
|
723
|
+
return [
|
|
724
|
+
PS_GLOB_FN,
|
|
725
|
+
PS_READTEXT_FN,
|
|
726
|
+
PS_SPLITLINES_FN,
|
|
727
|
+
STDIN_INLINES,
|
|
728
|
+
...psCollectFiles(operandWords, (g) => sErr('nl', g, 'No such file or directory'), (g) => sErr('nl', g, 'Is a directory')),
|
|
729
|
+
'$fx_no = 1',
|
|
730
|
+
"$fx_pad = ' '.PadLeft(7)",
|
|
731
|
+
'foreach ($fx_g in $fx_srcs) {',
|
|
732
|
+
" if ($fx_g -eq '-') { $fx_ls = @($fx_in) }",
|
|
733
|
+
' else { $fx_ls = @(fx-splitlines (fx-read $fx_g)) }',
|
|
734
|
+
' for ($fx_i = 0; $fx_i -lt $fx_ls.Count; $fx_i++) {',
|
|
735
|
+
' $fx_l = $fx_ls[$fx_i]',
|
|
736
|
+
" if ('" + bodyMode + "' -eq 'a' -or ('" + bodyMode + "' -eq 't' -and $fx_l -ne '')) {",
|
|
737
|
+
" ('{0,6}' -f $fx_no) + [string][char]9 + $fx_l",
|
|
738
|
+
' $fx_no++',
|
|
739
|
+
' } else {',
|
|
740
|
+
' $fx_pad + $fx_l',
|
|
741
|
+
' }',
|
|
742
|
+
' }',
|
|
743
|
+
'}',
|
|
744
|
+
].join('\n');
|
|
745
|
+
};
|
|
746
|
+
/* ------------------------------------------------------------------ */
|
|
747
|
+
/* tac */
|
|
748
|
+
/* ------------------------------------------------------------------ */
|
|
749
|
+
const tac = (args) => {
|
|
750
|
+
const { operandWords } = parseWords(args);
|
|
751
|
+
return [
|
|
752
|
+
PS_GLOB_FN,
|
|
753
|
+
PS_READTEXT_FN,
|
|
754
|
+
PS_SPLITLINES_FN,
|
|
755
|
+
STDIN_INLINES,
|
|
756
|
+
...psCollectFiles(operandWords, (g) => qErr('tac', g, 'No such file or directory', 'failed to open '), (g) => sErr('tac', g, 'read error: Is a directory')),
|
|
757
|
+
'$fx_all = @()',
|
|
758
|
+
'foreach ($fx_g in $fx_srcs) {',
|
|
759
|
+
" if ($fx_g -eq '-') { $fx_all += @($fx_in) }",
|
|
760
|
+
' else { $fx_all += @(fx-splitlines (fx-read $fx_g)) }',
|
|
761
|
+
'}',
|
|
762
|
+
'for ($fx_i = $fx_all.Count - 1; $fx_i -ge 0; $fx_i--) { $fx_all[$fx_i] }',
|
|
763
|
+
].join('\n');
|
|
764
|
+
};
|
|
765
|
+
/* ------------------------------------------------------------------ */
|
|
766
|
+
/* md5sum / sha1sum / sha256sum */
|
|
767
|
+
/* ------------------------------------------------------------------ */
|
|
768
|
+
function hashSum(cmd, createExpr, hexLen, algoName) {
|
|
769
|
+
const hashFn = [
|
|
770
|
+
'function fx-hash($b) {',
|
|
771
|
+
' $h = ' + createExpr + '.ComputeHash($b)',
|
|
772
|
+
" return (($h | ForEach-Object { $_.ToString('x2') }) -join '')",
|
|
773
|
+
'}',
|
|
774
|
+
].join('\n');
|
|
775
|
+
return (args) => {
|
|
776
|
+
const { flags, longs, operandWords } = parseWords(args);
|
|
777
|
+
const check = flags.has('c') || longs.has('--check');
|
|
778
|
+
if (check) {
|
|
779
|
+
const chk = operandWords.length ? operandExpr(operandWords[0]) : "''";
|
|
780
|
+
return [
|
|
781
|
+
PS_READTEXT_FN,
|
|
782
|
+
PS_SPLITLINES_FN,
|
|
783
|
+
hashFn,
|
|
784
|
+
'function fx-path($p) {',
|
|
785
|
+
" if ($p -eq '/dev/null') { return 'NUL' }",
|
|
786
|
+
" if ($p -eq '/tmp') { return $env:TEMP }",
|
|
787
|
+
" if ($p.StartsWith('/tmp/')) { return ($env:TEMP + '\\' + ($p.Substring(5) -replace '/', '\\')) }",
|
|
788
|
+
" if ($p -match '^/([a-zA-Z])/(.+)$') { return ($Matches[1].ToUpper() + ':\\' + ($Matches[2] -replace '/', '\\')) }",
|
|
789
|
+
" if ($p -match '^/([a-zA-Z])$') { return ($Matches[1].ToUpper() + ':\\') }",
|
|
790
|
+
' return $p',
|
|
791
|
+
'}',
|
|
792
|
+
'$fx_chk = ' + chk,
|
|
793
|
+
"if ($fx_chk -eq '') { " + psErrExpr(psStr(cmd + ': missing operand')) + ' }',
|
|
794
|
+
'elseif (-not (Test-Path -LiteralPath $fx_chk -PathType Leaf)) {',
|
|
795
|
+
' ' + psErrExpr(sErr(cmd, '$fx_chk', 'No such file or directory')),
|
|
796
|
+
'}',
|
|
797
|
+
'else {',
|
|
798
|
+
' $fx_bad = 0',
|
|
799
|
+
' $fx_openfail = 0',
|
|
800
|
+
' $fx_parsed = 0',
|
|
801
|
+
' foreach ($fx_l in @(fx-splitlines (fx-read $fx_chk))) {',
|
|
802
|
+
" if ($fx_l -match '^([0-9a-fA-F]{" + hexLen + '})( | \\*)(.*)$' + "') {",
|
|
803
|
+
' $fx_want = $Matches[1].ToLower()',
|
|
804
|
+
' $fx_name = $Matches[3]',
|
|
805
|
+
' $fx_target = fx-path $fx_name',
|
|
806
|
+
' $fx_parsed++',
|
|
807
|
+
' if (-not (Test-Path -LiteralPath $fx_target -PathType Leaf)) {',
|
|
808
|
+
' ' + psErrExpr(sErr(cmd, '$fx_name', 'No such file or directory')),
|
|
809
|
+
" $fx_name + ': FAILED open or read'",
|
|
810
|
+
' $fx_openfail++',
|
|
811
|
+
' continue',
|
|
812
|
+
' }',
|
|
813
|
+
' try { $fx_got = fx-hash ([IO.File]::ReadAllBytes($fx_target)) }',
|
|
814
|
+
" catch { $fx_got = '' }",
|
|
815
|
+
' if ($fx_got -eq $fx_want) {',
|
|
816
|
+
" $fx_name + ': OK'",
|
|
817
|
+
' } else {',
|
|
818
|
+
" $fx_name + ': FAILED'",
|
|
819
|
+
' $fx_bad++',
|
|
820
|
+
' }',
|
|
821
|
+
' }',
|
|
822
|
+
' }',
|
|
823
|
+
' if ($fx_parsed -eq 0) {',
|
|
824
|
+
' ' + psErrExpr(sErr(cmd, '$fx_chk', 'no properly formatted ' + algoName + ' checksum lines found')),
|
|
825
|
+
' }',
|
|
826
|
+
' if ($fx_bad -gt 0) {',
|
|
827
|
+
" $fx_sfx = 's'; if ($fx_bad -eq 1) { $fx_sfx = '' }",
|
|
828
|
+
' [Console]::Error.WriteLine(' +
|
|
829
|
+
psStr(cmd + ': WARNING: ') +
|
|
830
|
+
" + $fx_bad + ' computed checksum' + $fx_sfx + ' did NOT match')",
|
|
831
|
+
' }',
|
|
832
|
+
' if ($fx_openfail -gt 0) {',
|
|
833
|
+
" $fx_sfx2 = 's'; if ($fx_openfail -eq 1) { $fx_sfx2 = '' }",
|
|
834
|
+
' [Console]::Error.WriteLine(' +
|
|
835
|
+
psStr(cmd + ': WARNING: ') +
|
|
836
|
+
" + $fx_openfail + ' listed file' + $fx_sfx2 + ' could not be read')",
|
|
837
|
+
' }',
|
|
838
|
+
' if ($fx_bad -gt 0 -or $fx_openfail -gt 0) { $script:fx_exit = 1 }',
|
|
839
|
+
'}',
|
|
840
|
+
].join('\n');
|
|
841
|
+
}
|
|
842
|
+
return [
|
|
843
|
+
PS_GLOB_FN,
|
|
844
|
+
PS_STDINRAW_FN,
|
|
845
|
+
STDIN_ITEMS,
|
|
846
|
+
hashFn,
|
|
847
|
+
...psCollectFiles(operandWords, (g) => sErr(cmd, g, 'No such file or directory'), (g) => sErr(cmd, g, 'Is a directory')),
|
|
848
|
+
'for ($fx_k = 0; $fx_k -lt $fx_srcs.Count; $fx_k++) {',
|
|
849
|
+
' $fx_g = $fx_srcs[$fx_k]',
|
|
850
|
+
" if ($fx_g -eq '-') { $fx_b = [System.Text.Encoding]::UTF8.GetBytes((fx-stdinraw $fx_items)); $fx_disp = '-' }",
|
|
851
|
+
' else { $fx_b = [IO.File]::ReadAllBytes($fx_g); $fx_disp = $fx_names[$fx_k] }',
|
|
852
|
+
" (fx-hash $fx_b) + ' ' + $fx_disp",
|
|
853
|
+
'}',
|
|
854
|
+
].join('\n');
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
const md5sum = hashSum('md5sum', '[System.Security.Cryptography.MD5]::Create()', 32, 'MD5');
|
|
858
|
+
const sha1sum = hashSum('sha1sum', '[System.Security.Cryptography.SHA1]::Create()', 40, 'SHA1');
|
|
859
|
+
const sha256sum = hashSum('sha256sum', '[System.Security.Cryptography.SHA256]::Create()', 64, 'SHA256');
|
|
860
|
+
/* ------------------------------------------------------------------ */
|
|
861
|
+
/* base64 */
|
|
862
|
+
/* ------------------------------------------------------------------ */
|
|
863
|
+
const base64 = (args, ctx) => {
|
|
864
|
+
const { flags, longs, values, operandWords } = parseWords(args, ['w'], ['--wrap']);
|
|
865
|
+
const decode = flags.has('d') || longs.has('--decode');
|
|
866
|
+
let wrap = 76;
|
|
867
|
+
const wv = values.get('-w') ?? values.get('--wrap');
|
|
868
|
+
if (wv !== undefined && /^\d+$/.test(wv))
|
|
869
|
+
wrap = parseInt(wv, 10);
|
|
870
|
+
const pre = [
|
|
871
|
+
PS_GLOB_FN,
|
|
872
|
+
PS_READTEXT_FN,
|
|
873
|
+
STDIN_ITEMS,
|
|
874
|
+
PS_STDINRAW_FN,
|
|
875
|
+
PS_WRITE_FN,
|
|
876
|
+
fxTermLine(ctx.position),
|
|
877
|
+
...psCollectFiles(operandWords, (g) => sErr('base64', g, 'No such file or directory'), null, false),
|
|
878
|
+
];
|
|
879
|
+
if (decode) {
|
|
880
|
+
return [
|
|
881
|
+
...pre,
|
|
882
|
+
"$fx_enc = ''",
|
|
883
|
+
'if ($fx_srcs.Count -gt 0) { $fx_enc = fx-read $fx_srcs[0] }',
|
|
884
|
+
'else { $fx_enc = fx-stdinraw $fx_items }',
|
|
885
|
+
'try {',
|
|
886
|
+
" $fx_b = [Convert]::FromBase64String(($fx_enc -replace '[\\s\\r\\n]+', ''))",
|
|
887
|
+
'} catch { ' + psErrExpr(psStr('base64: invalid input')) + "; $fx_b = $null }",
|
|
888
|
+
'if ($null -ne $fx_b) {',
|
|
889
|
+
' fx-write ([System.Text.Encoding]::UTF8.GetString($fx_b)) $fx_term',
|
|
890
|
+
'}',
|
|
891
|
+
].join('\n');
|
|
892
|
+
}
|
|
893
|
+
return [
|
|
894
|
+
...pre,
|
|
895
|
+
'if (' + pb(wrap > 0) + ') {',
|
|
896
|
+
' if ($fx_srcs.Count -gt 0) { $fx_b = [IO.File]::ReadAllBytes($fx_srcs[0]) }',
|
|
897
|
+
' else { $fx_b = [System.Text.Encoding]::UTF8.GetBytes((fx-stdinraw $fx_items)) }',
|
|
898
|
+
' $fx_s = [Convert]::ToBase64String($fx_b)',
|
|
899
|
+
' $fx_i = 0',
|
|
900
|
+
' while ($fx_i -lt $fx_s.Length) {',
|
|
901
|
+
' $fx_len = [math]::Min(' + wrap + ", $fx_s.Length - $fx_i)",
|
|
902
|
+
' $fx_s.Substring($fx_i, $fx_len)',
|
|
903
|
+
' $fx_i += ' + wrap,
|
|
904
|
+
' }',
|
|
905
|
+
'} else {',
|
|
906
|
+
' if ($fx_srcs.Count -gt 0) { $fx_b = [IO.File]::ReadAllBytes($fx_srcs[0]) }',
|
|
907
|
+
' else { $fx_b = [System.Text.Encoding]::UTF8.GetBytes((fx-stdinraw $fx_items)) }',
|
|
908
|
+
' fx-write ([Convert]::ToBase64String($fx_b)) $fx_term',
|
|
909
|
+
'}',
|
|
910
|
+
].join('\n');
|
|
911
|
+
};
|
|
912
|
+
/* ------------------------------------------------------------------ */
|
|
913
|
+
/* seq */
|
|
914
|
+
/* ------------------------------------------------------------------ */
|
|
915
|
+
const seq = (args, ctx) => {
|
|
916
|
+
const { flags, values, operandWords } = parseWords(args, ['s']);
|
|
917
|
+
const eq = flags.has('w');
|
|
918
|
+
const nums = operandWords.map((w) => exprOfWord(w));
|
|
919
|
+
if (nums.length === 0) {
|
|
920
|
+
return psErrExpr(psStr('seq: missing operand'));
|
|
921
|
+
}
|
|
922
|
+
if (nums.length > 3) {
|
|
923
|
+
return psErrExpr(psStr('seq: extra operand ') + ' + ' + nums[3]);
|
|
924
|
+
}
|
|
925
|
+
const first = nums.length >= 2 ? nums[0] : '1';
|
|
926
|
+
const inc = nums.length === 3 ? nums[1] : '1';
|
|
927
|
+
const last = nums.length === 3 ? nums[2] : nums[nums.length - 1];
|
|
928
|
+
const sepExpr = values.has('-s') ? psStr(values.get('-s')) : '[string][char]10';
|
|
929
|
+
return [
|
|
930
|
+
PS_WRITE_FN,
|
|
931
|
+
fxTermLine(ctx.position),
|
|
932
|
+
'function fx-tod($s) {',
|
|
933
|
+
' try { return [double]::Parse([string]$s, [System.Globalization.CultureInfo]::InvariantCulture) }',
|
|
934
|
+
' catch { return [double]0 }',
|
|
935
|
+
'}',
|
|
936
|
+
'function fx-dec($s) {',
|
|
937
|
+
" if ([string]$s -match '^[+-]?[0-9]*\\.([0-9]+)') { return $Matches[1].Length }",
|
|
938
|
+
' return 0',
|
|
939
|
+
'}',
|
|
940
|
+
'$fx_a = [string](' + first + ')',
|
|
941
|
+
'$fx_b = [string](' + inc + ')',
|
|
942
|
+
'$fx_c = [string](' + last + ')',
|
|
943
|
+
'$fx_first = fx-tod $fx_a',
|
|
944
|
+
'$fx_inc = fx-tod $fx_b',
|
|
945
|
+
'$fx_last = fx-tod $fx_c',
|
|
946
|
+
'if ($fx_inc -eq 0) { ' + psErrExpr(psStr('seq: invalid Zero increment value: ') + ' + $fx_b') + ' }',
|
|
947
|
+
'else {',
|
|
948
|
+
' $fx_p = 0',
|
|
949
|
+
' foreach ($fx_o in @($fx_a, $fx_b, $fx_c)) { $fx_d = fx-dec $fx_o; if ($fx_d -gt $fx_p) { $fx_p = $fx_d } }',
|
|
950
|
+
' $fx_buf = New-Object System.Collections.Generic.List[string]',
|
|
951
|
+
' $fx_i = 0',
|
|
952
|
+
' while ($fx_i -lt 1000000) {',
|
|
953
|
+
' $v = $fx_first + $fx_i * $fx_inc',
|
|
954
|
+
' if ($fx_inc -ge 0 -and $v -gt $fx_last + 0.0000001) { break }',
|
|
955
|
+
' if ($fx_inc -lt 0 -and $v -lt $fx_last - 0.0000001) { break }',
|
|
956
|
+
" $fx_buf.Add($v.ToString('F' + $fx_p, [System.Globalization.CultureInfo]::InvariantCulture))",
|
|
957
|
+
' $fx_i++',
|
|
958
|
+
' }',
|
|
959
|
+
' $fx_strs = @($fx_buf)',
|
|
960
|
+
' if (' + pb(eq) + ') {',
|
|
961
|
+
' $fx_wn = 1',
|
|
962
|
+
' if ($fx_strs.Count -gt 0) {',
|
|
963
|
+
" $fx_l0 = $fx_strs[0].TrimStart('-').Length",
|
|
964
|
+
" $fx_l1 = $fx_strs[$fx_strs.Count - 1].TrimStart('-').Length",
|
|
965
|
+
' $fx_wn = [math]::Max($fx_l0, $fx_l1)',
|
|
966
|
+
' }',
|
|
967
|
+
" $fx_strs = @($fx_strs | ForEach-Object { if ($_.StartsWith('-')) { '-' + $_.Substring(1).PadLeft($fx_wn, '0') } else { $_.PadLeft($fx_wn, '0') } })",
|
|
968
|
+
' }',
|
|
969
|
+
' if ($fx_strs.Count -eq 0) { }',
|
|
970
|
+
' else { fx-write (($fx_strs -join (' + sepExpr + ')) + [string][char]10) $fx_term }',
|
|
971
|
+
'}',
|
|
972
|
+
].join('\n');
|
|
973
|
+
};
|
|
974
|
+
/* ------------------------------------------------------------------ */
|
|
975
|
+
/* yes */
|
|
976
|
+
/* ------------------------------------------------------------------ */
|
|
977
|
+
const yes = (args) => {
|
|
978
|
+
return [
|
|
979
|
+
'$fx_parts = ' + psArray(args, exprOfWord),
|
|
980
|
+
"if ($fx_parts.Count -eq 0) { $fx_parts = @('y') }",
|
|
981
|
+
"$fx_s = $fx_parts -join ' '",
|
|
982
|
+
'# CAPPED: real `yes` repeats forever, but PS 5.1 pipelines cannot send a',
|
|
983
|
+
'# stop signal upstream (no SIGPIPE), so `yes | head -3` would hang. 65536',
|
|
984
|
+
'# lines is far beyond what any consumer keeps.',
|
|
985
|
+
'$fx_i = 0',
|
|
986
|
+
'while ($fx_i -lt 65536) {',
|
|
987
|
+
' $fx_s',
|
|
988
|
+
' $fx_i++',
|
|
989
|
+
'}',
|
|
990
|
+
].join('\n');
|
|
991
|
+
};
|
|
992
|
+
/* ------------------------------------------------------------------ */
|
|
993
|
+
/* xargs */
|
|
994
|
+
/* ------------------------------------------------------------------ */
|
|
995
|
+
const XARGS_BUILTIN_MSG = 'fauxnix: xargs currently passes arguments to native commands (git, node, npm, python, cargo...); ' +
|
|
996
|
+
'for built-ins like grep use direct invocation with globs or grep -r';
|
|
997
|
+
const xargs = (args) => {
|
|
998
|
+
// custom parse: options only until the first non-option word (the command)
|
|
999
|
+
let chunkN = null;
|
|
1000
|
+
let repl = null;
|
|
1001
|
+
let noRunIfEmpty = false;
|
|
1002
|
+
let trace = false;
|
|
1003
|
+
const target = [];
|
|
1004
|
+
let i = 0;
|
|
1005
|
+
let seenCmd = false;
|
|
1006
|
+
while (i < args.length) {
|
|
1007
|
+
const t = wordToString(args[i]);
|
|
1008
|
+
if (!seenCmd && t === '--') {
|
|
1009
|
+
i++;
|
|
1010
|
+
seenCmd = true;
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
if (!seenCmd && t.startsWith('--')) {
|
|
1014
|
+
if (t === '--no-run-if-empty')
|
|
1015
|
+
noRunIfEmpty = true;
|
|
1016
|
+
i++;
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
if (!seenCmd && t.startsWith('-') && t.length > 1 && !/^-/.test(t.slice(1, 2))) {
|
|
1020
|
+
const body = t.slice(1);
|
|
1021
|
+
for (let c = 0; c < body.length; c++) {
|
|
1022
|
+
const ch = body[c];
|
|
1023
|
+
if (ch === 'r')
|
|
1024
|
+
noRunIfEmpty = true;
|
|
1025
|
+
else if (ch === 't')
|
|
1026
|
+
trace = true;
|
|
1027
|
+
else if (ch === 'n' || ch === 'I' || ch === 'L') {
|
|
1028
|
+
const restv = body.slice(c + 1);
|
|
1029
|
+
let val;
|
|
1030
|
+
if (restv)
|
|
1031
|
+
val = restv;
|
|
1032
|
+
else if (i + 1 < args.length) {
|
|
1033
|
+
val = wordToString(args[i + 1]);
|
|
1034
|
+
i++;
|
|
1035
|
+
}
|
|
1036
|
+
else {
|
|
1037
|
+
return psErrExpr(psStr('xargs: option requires an argument -- ' + ch));
|
|
1038
|
+
}
|
|
1039
|
+
if (ch === 'n' || ch === 'L')
|
|
1040
|
+
chunkN = parseInt(val, 10);
|
|
1041
|
+
else
|
|
1042
|
+
repl = val;
|
|
1043
|
+
break;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
i++;
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
seenCmd = true;
|
|
1050
|
+
target.push(args[i]);
|
|
1051
|
+
i++;
|
|
1052
|
+
}
|
|
1053
|
+
// no command → GNU runs /bin/echo with the collected args
|
|
1054
|
+
if (target.length === 0) {
|
|
1055
|
+
const skipLine = noRunIfEmpty
|
|
1056
|
+
? "if ($fx_args.Count -gt 0) { ($fx_args -join ' ') }"
|
|
1057
|
+
: "($fx_args -join ' ')";
|
|
1058
|
+
return [
|
|
1059
|
+
PS_SPLITLINES_FN,
|
|
1060
|
+
STDIN_INLINES,
|
|
1061
|
+
"$fx_args = @($fx_in | Where-Object { $_ -ne '' })",
|
|
1062
|
+
skipLine,
|
|
1063
|
+
].join('\n');
|
|
1064
|
+
}
|
|
1065
|
+
// fauxnix built-ins cannot be invoked natively (they are PS code, not exes)
|
|
1066
|
+
const firstLit = target[0].every((p) => p.kind === 'Text' || p.kind === 'SingleQuoted')
|
|
1067
|
+
? target[0].map((p) => p.text).join('')
|
|
1068
|
+
: null;
|
|
1069
|
+
if (firstLit !== null && lookup(firstLit) !== undefined) {
|
|
1070
|
+
return psErrExpr(psStr(XARGS_BUILTIN_MSG));
|
|
1071
|
+
}
|
|
1072
|
+
const cmdExpr = exprOfWord(target[0]);
|
|
1073
|
+
const baseArgs = psArray(target.slice(1), exprOfWord);
|
|
1074
|
+
const n = chunkN !== null && Number.isFinite(chunkN) && chunkN > 0 ? chunkN : 0;
|
|
1075
|
+
const replExpr = repl !== null ? psStr(repl) : null;
|
|
1076
|
+
const invoke = [
|
|
1077
|
+
' if (' +
|
|
1078
|
+
pb(trace) +
|
|
1079
|
+
") { [Console]::Error.WriteLine(((@($fx_cmd) + @($fx_argv)) -join ' ')) }",
|
|
1080
|
+
' & $fx_cmd @fx_argv',
|
|
1081
|
+
' if ($LASTEXITCODE -ne 0 -and $script:fx_exit -eq 0) { $script:fx_exit = $LASTEXITCODE }',
|
|
1082
|
+
];
|
|
1083
|
+
let dispatch;
|
|
1084
|
+
const guard = ' if (' + pb(noRunIfEmpty) + ' -and $fx_args.Count -eq 0) { }' + '\n' + ' else {';
|
|
1085
|
+
if (replExpr !== null) {
|
|
1086
|
+
// -I REPL: one invocation per line, REPL substituted inside the args
|
|
1087
|
+
dispatch = [
|
|
1088
|
+
guard,
|
|
1089
|
+
' foreach ($fx_l in $fx_args) {',
|
|
1090
|
+
' $fx_argv = @()',
|
|
1091
|
+
' $fx_hit = $false',
|
|
1092
|
+
' foreach ($fx_a in $fx_base) {',
|
|
1093
|
+
' if ($fx_a.Contains(' + replExpr + ')) {',
|
|
1094
|
+
' $fx_hit = $true',
|
|
1095
|
+
' $fx_argv += $fx_a.Replace(' + replExpr + ', $fx_l)',
|
|
1096
|
+
' } else { $fx_argv += $fx_a }',
|
|
1097
|
+
' }',
|
|
1098
|
+
' if (-not $fx_hit) { $fx_argv += $fx_l }',
|
|
1099
|
+
...invoke,
|
|
1100
|
+
' }',
|
|
1101
|
+
' }',
|
|
1102
|
+
];
|
|
1103
|
+
}
|
|
1104
|
+
else if (n > 0) {
|
|
1105
|
+
// -n N: N arguments per invocation
|
|
1106
|
+
dispatch = [
|
|
1107
|
+
guard,
|
|
1108
|
+
' $fx_i = 0',
|
|
1109
|
+
' $fx_ran = $false',
|
|
1110
|
+
' while ($fx_i -lt $fx_args.Count) {',
|
|
1111
|
+
' $fx_argv = @($fx_base)',
|
|
1112
|
+
' $fx_j = 0',
|
|
1113
|
+
' while ($fx_j -lt ' + n + ' -and $fx_i -lt $fx_args.Count) {',
|
|
1114
|
+
' $fx_argv += $fx_args[$fx_i]',
|
|
1115
|
+
' $fx_i++; $fx_j++',
|
|
1116
|
+
' }',
|
|
1117
|
+
' $fx_ran = $true',
|
|
1118
|
+
...invoke,
|
|
1119
|
+
' }',
|
|
1120
|
+
' if (-not $fx_ran) {',
|
|
1121
|
+
' $fx_argv = @($fx_base)',
|
|
1122
|
+
' ' + invoke[0],
|
|
1123
|
+
' ' + invoke[1],
|
|
1124
|
+
' ' + invoke[2],
|
|
1125
|
+
' }',
|
|
1126
|
+
' }',
|
|
1127
|
+
];
|
|
1128
|
+
}
|
|
1129
|
+
else {
|
|
1130
|
+
dispatch = [
|
|
1131
|
+
guard,
|
|
1132
|
+
' $fx_argv = @($fx_base) + @($fx_args)',
|
|
1133
|
+
...invoke,
|
|
1134
|
+
' }',
|
|
1135
|
+
];
|
|
1136
|
+
}
|
|
1137
|
+
return [
|
|
1138
|
+
PS_SPLITLINES_FN,
|
|
1139
|
+
STDIN_INLINES,
|
|
1140
|
+
'$fx_cmd = ' + cmdExpr,
|
|
1141
|
+
'$fx_base = ' + baseArgs,
|
|
1142
|
+
"$fx_args = @($fx_in | Where-Object { $_ -ne '' })",
|
|
1143
|
+
...dispatch,
|
|
1144
|
+
].join('\n');
|
|
1145
|
+
};
|
|
1146
|
+
/* ------------------------------------------------------------------ */
|
|
1147
|
+
export const handlers = {
|
|
1148
|
+
echo,
|
|
1149
|
+
printf,
|
|
1150
|
+
cat,
|
|
1151
|
+
head,
|
|
1152
|
+
tail,
|
|
1153
|
+
wc,
|
|
1154
|
+
tee,
|
|
1155
|
+
nl,
|
|
1156
|
+
tac,
|
|
1157
|
+
md5sum,
|
|
1158
|
+
sha1sum,
|
|
1159
|
+
sha256sum,
|
|
1160
|
+
base64,
|
|
1161
|
+
seq,
|
|
1162
|
+
yes,
|
|
1163
|
+
xargs,
|
|
1164
|
+
};
|