fauxnix-cli 0.9.1 → 0.9.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/README.md +15 -4
- package/dist/cli.js +4 -0
- package/dist/commands/archive.js +3 -2
- package/dist/commands/files.js +29 -14
- package/dist/commands/net.js +3 -4
- package/dist/commands/text-filters.js +166 -46
- package/dist/commands/text-io.js +92 -11
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +18 -5
- package/dist/executor.js +116 -93
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +4 -1
- package/dist/translator.d.ts +5 -1
- package/dist/translator.js +189 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,6 +58,14 @@ Linux command line — file ops, text processing, process management, archives,
|
|
|
58
58
|
maps cleanly onto PowerShell + .NET. fauxnix implements that subset faithfully and *fails loudly
|
|
59
59
|
and helpfully* on what it can't translate, so the agent never gets silently-wrong results.
|
|
60
60
|
|
|
61
|
+
Labs now train computer-use agents on fleets of real desktops. Reporting in 2026 (*The
|
|
62
|
+
Information*, widely repeated) has OpenAI buying tens of thousands of Mac mini / Mac Studio
|
|
63
|
+
boxes — no screen, no keyboard — to reinforcement-learn agents that click, edit, test, and
|
|
64
|
+
run bash workflows, and Anthropic renting Mac minis through AWS for the same class of work.
|
|
65
|
+
That scoring environment is macOS. Windows users should not have to install a guest Unix to
|
|
66
|
+
keep up: the agent keeps writing bash; fauxnix makes the Windows box answer like the box the
|
|
67
|
+
agent was trained on. See [`docs/rfc-computer-use-windows.md`](docs/rfc-computer-use-windows.md).
|
|
68
|
+
|
|
61
69
|
## Install
|
|
62
70
|
|
|
63
71
|
```bash
|
|
@@ -154,10 +162,13 @@ development:
|
|
|
154
162
|
translate time (unsupported constructs throw named errors, never silently misbehave)
|
|
155
163
|
- **text I/O**: `echo printf cat head tail wc tee nl tac md5sum sha1sum sha256sum base64 seq yes xargs`
|
|
156
164
|
|
|
157
|
-
`cp` / `mv` / `rm` / `touch` / `
|
|
158
|
-
|
|
159
|
-
`
|
|
160
|
-
|
|
165
|
+
`cp` / `mv` / `rm` / `touch` / `du` / `ls` / `ll` / `mkdir` / `rmdir` / `mktemp` / `ln` /
|
|
166
|
+
`readlink` / `realpath` / `basename` / `dirname` / `stat` / `file` / `df` / `chmod` / `chown` /
|
|
167
|
+
`diff` / `tee` / `grep` / `head` / `echo` / `printf` / `cat` / `tail` / `wc` carry a `CommandSpec`: unknown options fail with a GNU-style
|
|
168
|
+
usage error instead of being ignored (`find` stays unspec'd so predicates like `-name` still
|
|
169
|
+
compile). Implemented GNU holes: `cp -n` / `mv -n` / `touch -c` / `tee --append` / `grep -m` /
|
|
170
|
+
`head --lines` / `du --max-depth`. `fauxnix list --json` and `docs/command-specs.md` dump the
|
|
171
|
+
same metadata.
|
|
161
172
|
- **shell/system**: `cd pwd export unset env printenv ps kill pkill pgrep sleep which type whoami
|
|
162
173
|
id groups date uname hostname uptime free nproc clear true false test [ [[ : pushd popd dirs sudo
|
|
163
174
|
timeout man history less more source . eval exit alias set`
|
package/dist/cli.js
CHANGED
|
@@ -87,6 +87,10 @@ async function runCheck() {
|
|
|
87
87
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
88
88
|
windowsHide: true,
|
|
89
89
|
});
|
|
90
|
+
probe.on('error', (e) => {
|
|
91
|
+
console.error('status : FAILED to run powershell.exe: ' + e.message);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
});
|
|
90
94
|
let out = '';
|
|
91
95
|
probe.stdout.on('data', (d) => (out += d.toString('utf8')));
|
|
92
96
|
const code = await new Promise((resolve) => probe.on('close', (c) => resolve(c ?? 1)));
|
package/dist/commands/archive.js
CHANGED
|
@@ -214,8 +214,9 @@ const tar = (args) => {
|
|
|
214
214
|
' if ($fx_c) { $fx_tar = $fx_c.Source } else { $fx_tar = $null }',
|
|
215
215
|
'}',
|
|
216
216
|
'if ($fx_tar) {',
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
// fx-native captures stdout as pipeline strings and sets fx_exit.
|
|
218
|
+
// [object[]]@(...) keeps an empty argv from unwrapping to $null on PS 5.1.
|
|
219
|
+
' fx-native $fx_tar ([object[]]@($fx_args))',
|
|
219
220
|
'} else {',
|
|
220
221
|
" [Console]::Error.WriteLine('tar: fauxnix: tar.exe not found (Windows 10+ ships bsdtar as tar.exe)')",
|
|
221
222
|
' $script:fx_exit = 1',
|
package/dist/commands/files.js
CHANGED
|
@@ -47,11 +47,15 @@ const PS_HSIZE_FN = [
|
|
|
47
47
|
function psArray(words, fn = operandExpr) {
|
|
48
48
|
return argListExpr(words, fn);
|
|
49
49
|
}
|
|
50
|
+
/** GNU-style link: symlink or Windows junction. HardLink is a regular file. */
|
|
51
|
+
function psIsLink(it) {
|
|
52
|
+
return `(${it}.LinkType -eq 'SymbolicLink' -or ${it}.LinkType -eq 'Junction')`;
|
|
53
|
+
}
|
|
50
54
|
/* ------------------------------------------------------------------ */
|
|
51
55
|
/* ls */
|
|
52
56
|
/* ------------------------------------------------------------------ */
|
|
53
57
|
const ls = (args) => {
|
|
54
|
-
const { flags, longs, values, operandWords } = parseWords(args, [], ['--format']);
|
|
58
|
+
const { flags, longs, values, operandWords } = parseWords(args, [], ['--format', '--color']);
|
|
55
59
|
const long = flags.has('l') || longs.has('--long') || values.get('--format') === 'long';
|
|
56
60
|
const all = flags.has('a') || longs.has('--all');
|
|
57
61
|
const almost = flags.has('A') || longs.has('--almost-all');
|
|
@@ -66,7 +70,7 @@ const ls = (args) => {
|
|
|
66
70
|
PS_GLOB_FN,
|
|
67
71
|
PS_FTIME_FN,
|
|
68
72
|
'function fx-mode($it) {',
|
|
69
|
-
" $t = '-'; if ($it.PSIsContainer) { $t = 'd' } elseif ($it
|
|
73
|
+
" $t = '-'; if ($it.PSIsContainer) { $t = 'd' } elseif " + psIsLink('$it') + " { $t = 'l' }",
|
|
70
74
|
" if ($it.PSIsContainer) { return ($t + 'rwxr-xr-x') }",
|
|
71
75
|
" $ro = $it.Attributes.ToString().Contains('ReadOnly')",
|
|
72
76
|
' $ex = $false',
|
|
@@ -78,7 +82,9 @@ const ls = (args) => {
|
|
|
78
82
|
'function fx-name($it) {',
|
|
79
83
|
' $n = $it.Name',
|
|
80
84
|
' if (' + (classify ? '$true' : '$false') + ') {',
|
|
81
|
-
" if ($it.PSIsContainer) { $n = $n + '/' } elseif (@('.exe','.bat','.cmd','.com','.msi','.ps1') -contains $it.Extension.ToLower()) { $n = $n + '*' } elseif
|
|
85
|
+
" if ($it.PSIsContainer) { $n = $n + '/' } elseif (@('.exe','.bat','.cmd','.com','.msi','.ps1') -contains $it.Extension.ToLower()) { $n = $n + '*' } elseif " +
|
|
86
|
+
psIsLink('$it') +
|
|
87
|
+
" { $n = $n + '@' }",
|
|
82
88
|
' }',
|
|
83
89
|
' return $n',
|
|
84
90
|
'}',
|
|
@@ -298,7 +304,9 @@ const readlink = (args) => {
|
|
|
298
304
|
' if (' + (canon ? '$true' : '$false') + ') { (Resolve-Path -LiteralPath $fx_p).ProviderPath }',
|
|
299
305
|
' else {',
|
|
300
306
|
' $fx_it = Get-Item -LiteralPath $fx_p -Force',
|
|
301
|
-
' if
|
|
307
|
+
' if ' +
|
|
308
|
+
psIsLink('$fx_it') +
|
|
309
|
+
' { $fx_tgt = \'\'; try { $fx_tgt = [string](@($fx_it.Target)[0]) } catch {}; $fx_tgt }',
|
|
302
310
|
' else { $script:fx_exit = 1 }',
|
|
303
311
|
' }',
|
|
304
312
|
'}',
|
|
@@ -367,7 +375,9 @@ const stat = (args) => {
|
|
|
367
375
|
' if (-not (Test-Path -LiteralPath $fx_g)) { [Console]::Error.WriteLine("stat: cannot statx \'" + $fx_g + "\': No such file or directory"); $script:fx_exit = 1; continue }',
|
|
368
376
|
' $fx_it = Get-Item -LiteralPath $fx_g -Force',
|
|
369
377
|
' $fx_size = 0; if (-not $fx_it.PSIsContainer) { try { $fx_size = $fx_it.Length } catch {} }',
|
|
370
|
-
" $fx_ft = 'regular file'; if ($fx_it.PSIsContainer) { $fx_ft = 'directory' } elseif
|
|
378
|
+
" $fx_ft = 'regular file'; if ($fx_it.PSIsContainer) { $fx_ft = 'directory' } elseif " +
|
|
379
|
+
psIsLink('$fx_it') +
|
|
380
|
+
" { $fx_ft = 'symbolic link' }",
|
|
371
381
|
" $fx_ro = $fx_it.Attributes.ToString().Contains('ReadOnly')",
|
|
372
382
|
" $fx_mode = '0664'; if ($fx_it.PSIsContainer) { $fx_mode = '0775' } elseif ($fx_ro) { $fx_mode = '0444' }",
|
|
373
383
|
" $fx_epoch = [int](($fx_it.LastWriteTime.ToUniversalTime() - [datetime]'1970-01-01').TotalSeconds)",
|
|
@@ -397,22 +407,25 @@ const file = (args) => {
|
|
|
397
407
|
' foreach ($fx_g in (fx-glob $fx_f)) {',
|
|
398
408
|
' if (-not (Test-Path -LiteralPath $fx_g)) { [Console]::Error.WriteLine($fx_g + ": cannot open (No such file or directory)"); $script:fx_exit = 1; continue }',
|
|
399
409
|
' $fx_it = Get-Item -LiteralPath $fx_g -Force',
|
|
400
|
-
|
|
401
|
-
' if ($fx_it.
|
|
410
|
+
// `"$fx_g: …"` is a PS scope (`$fx_g:symbolic`), not "name: text".
|
|
411
|
+
' if ($fx_it.PSIsContainer) { $fx_g + \': directory\'; continue }',
|
|
412
|
+
' if ' +
|
|
413
|
+
psIsLink('$fx_it') +
|
|
414
|
+
' { $fx_tgt = \'\'; try { $fx_tgt = [string](@($fx_it.Target)[0]) } catch {}; $fx_g + \': symbolic link to \' + $fx_tgt; continue }',
|
|
402
415
|
' $fx_ext = $fx_it.Extension.ToLower()',
|
|
403
|
-
' if (@(\'.exe\', \'.dll\', \'.sys\') -contains $fx_ext) {
|
|
416
|
+
' if (@(\'.exe\', \'.dll\', \'.sys\') -contains $fx_ext) { $fx_g + \': PE32+ executable (console) Intel 80386, for MS Windows\'; continue }',
|
|
404
417
|
' $fx_bytes = [IO.File]::ReadAllBytes($fx_g)',
|
|
405
|
-
' if ($fx_bytes.Length -eq 0) {
|
|
418
|
+
' if ($fx_bytes.Length -eq 0) { $fx_g + \': empty\'; continue }',
|
|
406
419
|
' $fx_nul = $false',
|
|
407
420
|
' $fx_lim = [math]::Min(8192, $fx_bytes.Length)',
|
|
408
421
|
' for ($fx_i = 0; $fx_i -lt $fx_lim; $fx_i++) { if ($fx_bytes[$fx_i] -eq 0) { $fx_nul = $true; break } }',
|
|
409
|
-
' if ($fx_nul) {
|
|
422
|
+
' if ($fx_nul) { $fx_g + \': data\'; continue }',
|
|
410
423
|
' $fx_txt = fx-read $fx_g',
|
|
411
|
-
' if ($fx_txt.StartsWith(\'#!/\')) {
|
|
424
|
+
' if ($fx_txt.StartsWith(\'#!/\')) { $fx_g + \': \' + $fx_txt.Split("`n")[0].Trim() + \' a /bin/sh script text executable\' }',
|
|
412
425
|
' else {',
|
|
413
426
|
' $fx_nonascii = $false',
|
|
414
427
|
' foreach ($fx_c in $fx_txt.ToCharArray()) { if ([int]$fx_c -gt 127) { $fx_nonascii = $true; break } }',
|
|
415
|
-
' if ($fx_nonascii) {
|
|
428
|
+
' if ($fx_nonascii) { $fx_g + \': UTF-8 Unicode text\' } else { $fx_g + \': ASCII text\' }',
|
|
416
429
|
' }',
|
|
417
430
|
' }',
|
|
418
431
|
'}',
|
|
@@ -689,7 +702,7 @@ function emitFind(n) {
|
|
|
689
702
|
return '(-not $fx_i.PSIsContainer)';
|
|
690
703
|
if (n.t === 'd')
|
|
691
704
|
return '($fx_i.PSIsContainer)';
|
|
692
|
-
return '
|
|
705
|
+
return psIsLink('$fx_i');
|
|
693
706
|
case 'size':
|
|
694
707
|
return '(' + n.ps + ')';
|
|
695
708
|
case 'mtime':
|
|
@@ -712,7 +725,7 @@ const find = (args) => {
|
|
|
712
725
|
const preds = raw.slice(pathEnd);
|
|
713
726
|
if (preds.includes('-exec') || preds.includes('-execdir')) {
|
|
714
727
|
return ('[Console]::Error.WriteLine(' +
|
|
715
|
-
psStr('find: -exec is not supported by fauxnix;
|
|
728
|
+
psStr('find: -exec is not supported by fauxnix; use `find . -name "*.log" -delete` or grep -r instead') +
|
|
716
729
|
'); $script:fx_exit = 1');
|
|
717
730
|
}
|
|
718
731
|
const plan = parseFindPreds(preds);
|
|
@@ -954,6 +967,8 @@ export const specs = [
|
|
|
954
967
|
opt('S', undefined),
|
|
955
968
|
opt('r', undefined),
|
|
956
969
|
opt('R', '--recursive', 'unsupported', { reason: 'recursive listing' }),
|
|
970
|
+
// GNU optional WHEN; takesValue so --color=auto is valid. No ANSI.
|
|
971
|
+
opt(undefined, '--color', 'implemented', { takesValue: true }),
|
|
957
972
|
], ls),
|
|
958
973
|
fileSpec(['mkdir'], ['write'], [opt('p', '--parents'), opt('v', '--verbose')], mkdir),
|
|
959
974
|
fileSpec(['rmdir'], ['delete'], [], rmdir),
|
package/dist/commands/net.js
CHANGED
|
@@ -79,10 +79,9 @@ function synthWord(text) {
|
|
|
79
79
|
}
|
|
80
80
|
/** A native-exe invocation obeying the fauxnix contract (string lines + exit code). */
|
|
81
81
|
function nativeCall(exe, argArray) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
].join('\n');
|
|
82
|
+
// fx-native captures stdout as pipeline strings and sets fx_exit.
|
|
83
|
+
// [object[]]@(...) keeps an empty argv from unwrapping to $null on PS 5.1.
|
|
84
|
+
return 'fx-native ' + psStr(exe) + ' ([object[]]@(' + argArray + '))';
|
|
86
85
|
}
|
|
87
86
|
/* ------------------------------------------------------------------ */
|
|
88
87
|
/* curl */
|
|
@@ -31,7 +31,12 @@ const PS_SPLITLINES_FN = [
|
|
|
31
31
|
' return @($t.Split([char]10))',
|
|
32
32
|
'}',
|
|
33
33
|
].join('\n');
|
|
34
|
-
|
|
34
|
+
/** stdin → flat line array (multi-line items from printf-style stages split). */
|
|
35
|
+
const STDIN_LINES = [
|
|
36
|
+
'$fx_in = New-Object System.Collections.Generic.List[string]',
|
|
37
|
+
'foreach ($fx_it in @($input | ForEach-Object { [string]$_ })) { $fx_in.AddRange([string[]]@(fx-splitlines $fx_it)) }',
|
|
38
|
+
'$fx_in = @($fx_in)',
|
|
39
|
+
].join('\n');
|
|
35
40
|
/** Operand Words → PS array expression of string exprs. */
|
|
36
41
|
function psArray(words, fn = operandExpr) {
|
|
37
42
|
return argListExpr(words, fn);
|
|
@@ -126,6 +131,55 @@ function collectLongValues(args, names) {
|
|
|
126
131
|
}
|
|
127
132
|
return out;
|
|
128
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Collect EVERY value of a short option and its long aliases, in argv order.
|
|
136
|
+
* parseWords keeps only the last; grep -e/--regexp must OR-accumulate.
|
|
137
|
+
* Handles -e PAT, -ePAT, -ie PAT (bundled), --regexp PAT, --regexp=PAT.
|
|
138
|
+
*/
|
|
139
|
+
function collectRepeatOptionValues(args, short, longs) {
|
|
140
|
+
const out = [];
|
|
141
|
+
let onlyOps = false;
|
|
142
|
+
for (let i = 0; i < args.length; i++) {
|
|
143
|
+
const t = wordToString(args[i]);
|
|
144
|
+
if (t === '--') {
|
|
145
|
+
onlyOps = true;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (onlyOps)
|
|
149
|
+
continue;
|
|
150
|
+
if (t.startsWith('--')) {
|
|
151
|
+
const eq = t.indexOf('=');
|
|
152
|
+
const name = eq >= 0 ? t.slice(0, eq) : t;
|
|
153
|
+
if (!longs.includes(name))
|
|
154
|
+
continue;
|
|
155
|
+
if (eq >= 0) {
|
|
156
|
+
out.push(t.slice(eq + 1));
|
|
157
|
+
}
|
|
158
|
+
else if (i + 1 < args.length) {
|
|
159
|
+
out.push(wordToString(args[i + 1]));
|
|
160
|
+
i++;
|
|
161
|
+
}
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (!(t.startsWith('-') && t.length > 1 && !/^-?\d/.test(t.slice(1, 2))))
|
|
165
|
+
continue;
|
|
166
|
+
const body = t.slice(1);
|
|
167
|
+
for (let c = 0; c < body.length; c++) {
|
|
168
|
+
if (body[c] !== short)
|
|
169
|
+
continue;
|
|
170
|
+
const rest = body.slice(c + 1);
|
|
171
|
+
if (rest) {
|
|
172
|
+
out.push(rest);
|
|
173
|
+
}
|
|
174
|
+
else if (i + 1 < args.length) {
|
|
175
|
+
out.push(wordToString(args[i + 1]));
|
|
176
|
+
i++;
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
129
183
|
/** Build the "collect file operands through fx-glob" PS prologue. */
|
|
130
184
|
function psCollectSources(filesExpr, cmdErr, leafOnly) {
|
|
131
185
|
const test = leafOnly
|
|
@@ -304,26 +358,43 @@ const grep = (args) => {
|
|
|
304
358
|
};
|
|
305
359
|
const ctxA = Math.max(toInt(values.get('-A')), toInt(values.get('-C')));
|
|
306
360
|
const ctxB = Math.max(toInt(values.get('-B')), toInt(values.get('-C')));
|
|
307
|
-
const
|
|
308
|
-
if (
|
|
361
|
+
const regexpPats = collectRepeatOptionValues(args, 'e', ['--regexp']);
|
|
362
|
+
if (regexpPats.length === 0 && operandWords.length === 0) {
|
|
309
363
|
return ("[Console]::Error.WriteLine('usage: grep [OPTION]... PATTERN [FILE]...'); $script:fx_exit = 2");
|
|
310
364
|
}
|
|
311
|
-
const
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
365
|
+
const fileWords = regexpPats.length > 0 ? operandWords : operandWords.slice(1);
|
|
366
|
+
const multiFixed = fixed && regexpPats.length > 1;
|
|
367
|
+
let patExpr = "''";
|
|
368
|
+
if (!multiFixed) {
|
|
369
|
+
if (regexpPats.length > 1) {
|
|
370
|
+
patExpr = psStr(regexpPats.map((p) => '(?:' + (ere ? ereToDotNet(p) : breToDotNet(p)) + ')').join('|'));
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
const patternWord = regexpPats.length === 1 ? [{ kind: 'Text', text: regexpPats[0] }] : operandWords[0];
|
|
374
|
+
const patLit = literalOfWord(patternWord);
|
|
375
|
+
if (fixed || patLit === null) {
|
|
376
|
+
patExpr = textExpr(patternWord);
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
patExpr = psStr(ere ? ereToDotNet(patLit) : breToDotNet(patLit));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
320
382
|
}
|
|
321
383
|
const lines = [PS_READTEXT_FN, PS_SPLITLINES_FN];
|
|
322
384
|
// --- pattern objects -------------------------------------------------
|
|
323
385
|
if (fixed) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
386
|
+
if (multiFixed) {
|
|
387
|
+
lines.push('$fx_needles = @(' +
|
|
388
|
+
regexpPats.map((p) => textExpr([{ kind: 'Text', text: p }])).join(', ') +
|
|
389
|
+
')');
|
|
390
|
+
if (ci)
|
|
391
|
+
lines.push('$fx_needles_ll = @($fx_needles | ForEach-Object { $_.ToLower() })');
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
lines.push('$fx_needle = ' + patExpr);
|
|
395
|
+
if (ci)
|
|
396
|
+
lines.push('$fx_needle_ll = $fx_needle.ToLower()');
|
|
397
|
+
}
|
|
327
398
|
}
|
|
328
399
|
else {
|
|
329
400
|
lines.push('$fx_pat = ' + patExpr);
|
|
@@ -333,21 +404,45 @@ const grep = (args) => {
|
|
|
333
404
|
? '$fx_re = New-Object System.Text.RegularExpressions.Regex($fx_pat, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)'
|
|
334
405
|
: '$fx_re = New-Object System.Text.RegularExpressions.Regex($fx_pat)');
|
|
335
406
|
}
|
|
336
|
-
// --- fx-gmatch: line test (-v applied)
|
|
407
|
+
// --- fx-gmatch: line test (-v applied once to the combined OR) --------
|
|
337
408
|
lines.push('function fx-gmatch($l) {');
|
|
338
409
|
if (fixed) {
|
|
339
410
|
if (word) {
|
|
340
411
|
if (ci)
|
|
341
412
|
lines.push(' $lx = $l.ToLower()');
|
|
342
413
|
const hay = ci ? '$lx' : '$l';
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
414
|
+
if (multiFixed) {
|
|
415
|
+
const arr = ci ? '$fx_needles_ll' : '$fx_needles';
|
|
416
|
+
lines.push(' foreach ($fx_needle in ' + arr + ') {');
|
|
417
|
+
lines.push(' $p = ' + hay + '.IndexOf($fx_needle)');
|
|
418
|
+
lines.push(' while ($p -ge 0) {');
|
|
419
|
+
lines.push(' $ok = $true');
|
|
420
|
+
lines.push(" if ($p -gt 0) { $c = " + hay + "[$p - 1]; $ok = -not ([char]::IsLetterOrDigit($c) -or $c -eq '_') }");
|
|
421
|
+
lines.push(' if ($ok) { $e = $p + $fx_needle.Length; if ($e -lt ' + hay + '.Length) { $c = ' + hay + "[$e]; $ok = -not ([char]::IsLetterOrDigit($c) -or $c -eq '_') } }");
|
|
422
|
+
lines.push(' if ($ok) { return ' + pb(!inv) + ' }');
|
|
423
|
+
lines.push(' $p = ' + hay + '.IndexOf($fx_needle, $p + 1)');
|
|
424
|
+
lines.push(' }');
|
|
425
|
+
lines.push(' }');
|
|
426
|
+
lines.push(' return ' + pb(inv));
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
const needle = ci ? '$fx_needle_ll' : '$fx_needle';
|
|
430
|
+
lines.push(' $p = ' + hay + '.IndexOf(' + needle + ')');
|
|
431
|
+
lines.push(' while ($p -ge 0) {');
|
|
432
|
+
lines.push(' $ok = $true');
|
|
433
|
+
lines.push(" if ($p -gt 0) { $c = " + hay + "[$p - 1]; $ok = -not ([char]::IsLetterOrDigit($c) -or $c -eq '_') }");
|
|
434
|
+
lines.push(' if ($ok) { $e = $p + ' + needle + '.Length; if ($e -lt ' + hay + '.Length) { $c = ' + hay + "[$e]; $ok = -not ([char]::IsLetterOrDigit($c) -or $c -eq '_') } }");
|
|
435
|
+
lines.push(' if ($ok) { return ' + pb(!inv) + ' }');
|
|
436
|
+
lines.push(' $p = ' + hay + '.IndexOf(' + needle + ', $p + 1)');
|
|
437
|
+
lines.push(' }');
|
|
438
|
+
lines.push(' return ' + pb(inv));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
else if (multiFixed) {
|
|
442
|
+
const hay = ci ? '$l.ToLower()' : '$l';
|
|
443
|
+
const arr = ci ? '$fx_needles_ll' : '$fx_needles';
|
|
444
|
+
lines.push(' foreach ($fx_needle in ' + arr + ') {');
|
|
445
|
+
lines.push(' if (' + hay + '.Contains($fx_needle)) { return ' + pb(!inv) + ' }');
|
|
351
446
|
lines.push(' }');
|
|
352
447
|
lines.push(' return ' + pb(inv));
|
|
353
448
|
}
|
|
@@ -488,26 +583,44 @@ const grep = (args) => {
|
|
|
488
583
|
scan.push(' $fx_mleft--');
|
|
489
584
|
if (onlyMatch && !inv) {
|
|
490
585
|
if (fixed) {
|
|
491
|
-
|
|
586
|
+
// GNU -o: emit leftmost-longest matches in input order, not per-needle.
|
|
587
|
+
if (ci)
|
|
492
588
|
scan.push(' $lx = $fx_l.ToLower()');
|
|
493
|
-
scan.push(' $p = $lx.IndexOf($fx_needle_ll)');
|
|
494
|
-
}
|
|
495
|
-
else {
|
|
496
|
-
scan.push(' $p = $fx_l.IndexOf($fx_needle)');
|
|
497
|
-
}
|
|
498
589
|
const hay = ci ? '$lx' : '$fx_l';
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
590
|
+
const needleArr = multiFixed
|
|
591
|
+
? ci
|
|
592
|
+
? '$fx_needles_ll'
|
|
593
|
+
: '$fx_needles'
|
|
594
|
+
: '@(' + (ci ? '$fx_needle_ll' : '$fx_needle') + ')';
|
|
595
|
+
scan.push(' $fx_cands = New-Object System.Collections.Generic.List[object]');
|
|
596
|
+
scan.push(' foreach ($fx_needle in ' + needleArr + ') {');
|
|
597
|
+
scan.push(' if ($fx_needle.Length -lt 1) { continue }');
|
|
598
|
+
scan.push(' $p = ' + hay + '.IndexOf($fx_needle)');
|
|
599
|
+
scan.push(' while ($p -ge 0) {');
|
|
502
600
|
if (word) {
|
|
503
|
-
scan.push(
|
|
504
|
-
scan.push(
|
|
505
|
-
|
|
601
|
+
scan.push(' $ok = $true');
|
|
602
|
+
scan.push(" if ($p -gt 0) { $c = " +
|
|
603
|
+
hay +
|
|
604
|
+
"[$p - 1]; $ok = -not ([char]::IsLetterOrDigit($c) -or $c -eq '_') }");
|
|
605
|
+
scan.push(' if ($ok) { $e = $p + $fx_needle.Length; if ($e -lt ' +
|
|
606
|
+
hay +
|
|
607
|
+
'.Length) { $c = ' +
|
|
608
|
+
hay +
|
|
609
|
+
"[$e]; $ok = -not ([char]::IsLetterOrDigit($c) -or $c -eq '_') } }");
|
|
610
|
+
scan.push(' if ($ok) { [void]$fx_cands.Add([pscustomobject]@{ Start = $p; Len = $fx_needle.Length }) }');
|
|
506
611
|
}
|
|
507
612
|
else {
|
|
508
|
-
scan.push('
|
|
613
|
+
scan.push(' [void]$fx_cands.Add([pscustomobject]@{ Start = $p; Len = $fx_needle.Length })');
|
|
509
614
|
}
|
|
510
|
-
scan.push('
|
|
615
|
+
scan.push(' $p = ' + hay + '.IndexOf($fx_needle, $p + 1)');
|
|
616
|
+
scan.push(' }');
|
|
617
|
+
scan.push(' }');
|
|
618
|
+
scan.push(' $fx_end = 0');
|
|
619
|
+
scan.push(' foreach ($fx_c in @($fx_cands | Sort-Object Start, @{ Expression = { $_.Len }; Descending = $true })) {');
|
|
620
|
+
scan.push(' if ($fx_c.Start -ge $fx_end) {');
|
|
621
|
+
scan.push(' fx-emitline $fx_i (' + hay + '.Substring($fx_c.Start, $fx_c.Len))');
|
|
622
|
+
scan.push(' $fx_end = $fx_c.Start + $fx_c.Len');
|
|
623
|
+
scan.push(' }');
|
|
511
624
|
scan.push(' }');
|
|
512
625
|
}
|
|
513
626
|
else {
|
|
@@ -558,7 +671,8 @@ const grep = (args) => {
|
|
|
558
671
|
else {
|
|
559
672
|
lines.push('$fx_pre = $false');
|
|
560
673
|
lines.push("$fx_disp = '(standard input)'");
|
|
561
|
-
lines.push(
|
|
674
|
+
lines.push(STDIN_LINES);
|
|
675
|
+
lines.push('$fx_ls = $fx_in');
|
|
562
676
|
for (const l of scan)
|
|
563
677
|
lines.push(l);
|
|
564
678
|
}
|
|
@@ -1130,7 +1244,8 @@ const sed = (args) => {
|
|
|
1130
1244
|
}
|
|
1131
1245
|
else {
|
|
1132
1246
|
lines.push('$fx_err = $false');
|
|
1133
|
-
lines.push(
|
|
1247
|
+
lines.push(STDIN_LINES);
|
|
1248
|
+
lines.push('$fx_lines = $fx_in');
|
|
1134
1249
|
lines.push('$fx_n = $fx_lines.Count');
|
|
1135
1250
|
lines.push('$fx_out = New-Object System.Collections.Generic.List[string]');
|
|
1136
1251
|
lines.push('$fx_stop = $false');
|
|
@@ -1881,7 +1996,8 @@ const awk = (args) => {
|
|
|
1881
1996
|
}
|
|
1882
1997
|
else {
|
|
1883
1998
|
lines.push('$fx_err = $false');
|
|
1884
|
-
lines.push(
|
|
1999
|
+
lines.push(STDIN_LINES);
|
|
2000
|
+
lines.push('$fx_lines = $fx_in');
|
|
1885
2001
|
}
|
|
1886
2002
|
const mainLoop = [];
|
|
1887
2003
|
if (fsMode === 'ws') {
|
|
@@ -1982,7 +2098,8 @@ const sort = (args) => {
|
|
|
1982
2098
|
lines.push('foreach ($fx_f in $fx_srcs) { $fx_lines += fx-splitlines (fx-read $fx_f) }');
|
|
1983
2099
|
}
|
|
1984
2100
|
else {
|
|
1985
|
-
lines.push(
|
|
2101
|
+
lines.push(STDIN_LINES);
|
|
2102
|
+
lines.push('$fx_lines = $fx_in');
|
|
1986
2103
|
}
|
|
1987
2104
|
const fastPath = specs.length === 0 && !globalN && !globalB;
|
|
1988
2105
|
if (fastPath) {
|
|
@@ -2102,7 +2219,8 @@ const uniq = (args) => {
|
|
|
2102
2219
|
}
|
|
2103
2220
|
else {
|
|
2104
2221
|
lines.push('$fx_err = $false');
|
|
2105
|
-
lines.push(
|
|
2222
|
+
lines.push(STDIN_LINES);
|
|
2223
|
+
lines.push('$fx_lines = $fx_in');
|
|
2106
2224
|
}
|
|
2107
2225
|
lines.push('function fx-ueq($a, $b) {');
|
|
2108
2226
|
if (ignoreCase) {
|
|
@@ -2208,7 +2326,8 @@ const cut = (args) => {
|
|
|
2208
2326
|
lines.push('foreach ($fx_f in $fx_srcs) { $fx_lines += fx-splitlines (fx-read $fx_f) }');
|
|
2209
2327
|
}
|
|
2210
2328
|
else {
|
|
2211
|
-
lines.push(
|
|
2329
|
+
lines.push(STDIN_LINES);
|
|
2330
|
+
lines.push('$fx_lines = $fx_in');
|
|
2212
2331
|
}
|
|
2213
2332
|
if (charsMode) {
|
|
2214
2333
|
lines.push('foreach ($fx_l in $fx_lines) {');
|
|
@@ -2339,7 +2458,7 @@ const tr = (args) => {
|
|
|
2339
2458
|
mapPairs.push([set1[i], set2[Math.min(i, set2.length - 1)]]);
|
|
2340
2459
|
}
|
|
2341
2460
|
}
|
|
2342
|
-
const lines = [];
|
|
2461
|
+
const lines = [PS_SPLITLINES_FN];
|
|
2343
2462
|
lines.push('$fx_dl = @{}');
|
|
2344
2463
|
if (del) {
|
|
2345
2464
|
lines.push('foreach ($c in [char[]](' + set1.join(', ') + ')) { $fx_dl[[int]$c] = $true }');
|
|
@@ -2352,7 +2471,8 @@ const tr = (args) => {
|
|
|
2352
2471
|
if (squeeze) {
|
|
2353
2472
|
lines.push('foreach ($c in [char[]](' + sqSet.join(', ') + ')) { $fx_sq[[int]$c] = $true }');
|
|
2354
2473
|
}
|
|
2355
|
-
lines.push(
|
|
2474
|
+
lines.push(STDIN_LINES);
|
|
2475
|
+
lines.push('foreach ($fx_line in $fx_in) {');
|
|
2356
2476
|
lines.push(' $fx_sb = New-Object System.Text.StringBuilder');
|
|
2357
2477
|
lines.push(' $fx_prev = -1');
|
|
2358
2478
|
lines.push(' foreach ($fx_ch in $fx_line.ToCharArray()) {');
|