@taliesinb/mash 0.0.0-stage → 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 +275 -2
- package/bin/mash.ts +253 -0
- package/dist/bin/mash.d.ts +39 -0
- package/dist/bin/mash.d.ts.map +1 -0
- package/dist/bin/mash.js +281 -0
- package/dist/bin/mash.js.map +1 -0
- package/dist/src/analysis/analyze.d.ts +97 -0
- package/dist/src/analysis/analyze.d.ts.map +1 -0
- package/dist/src/analysis/analyze.js +1406 -0
- package/dist/src/analysis/analyze.js.map +1 -0
- package/dist/src/analysis/fs.d.ts +39 -0
- package/dist/src/analysis/fs.d.ts.map +1 -0
- package/dist/src/analysis/fs.js +117 -0
- package/dist/src/analysis/fs.js.map +1 -0
- package/dist/src/analysis/outputs.d.ts +16 -0
- package/dist/src/analysis/outputs.d.ts.map +1 -0
- package/dist/src/analysis/outputs.js +277 -0
- package/dist/src/analysis/outputs.js.map +1 -0
- package/dist/src/analysis/value.d.ts +76 -0
- package/dist/src/analysis/value.d.ts.map +1 -0
- package/dist/src/analysis/value.js +248 -0
- package/dist/src/analysis/value.js.map +1 -0
- package/dist/src/behaviors/defs/core.d.ts +15 -0
- package/dist/src/behaviors/defs/core.d.ts.map +1 -0
- package/dist/src/behaviors/defs/core.js +1084 -0
- package/dist/src/behaviors/defs/core.js.map +1 -0
- package/dist/src/behaviors/defs/devtools.d.ts +9 -0
- package/dist/src/behaviors/defs/devtools.d.ts.map +1 -0
- package/dist/src/behaviors/defs/devtools.js +432 -0
- package/dist/src/behaviors/defs/devtools.js.map +1 -0
- package/dist/src/behaviors/defs/macos.d.ts +12 -0
- package/dist/src/behaviors/defs/macos.d.ts.map +1 -0
- package/dist/src/behaviors/defs/macos.js +454 -0
- package/dist/src/behaviors/defs/macos.js.map +1 -0
- package/dist/src/behaviors/defs/tools.d.ts +9 -0
- package/dist/src/behaviors/defs/tools.d.ts.map +1 -0
- package/dist/src/behaviors/defs/tools.js +639 -0
- package/dist/src/behaviors/defs/tools.js.map +1 -0
- package/dist/src/behaviors/dsl.d.ts +95 -0
- package/dist/src/behaviors/dsl.d.ts.map +1 -0
- package/dist/src/behaviors/dsl.js +267 -0
- package/dist/src/behaviors/dsl.js.map +1 -0
- package/dist/src/behaviors/index.d.ts +12 -0
- package/dist/src/behaviors/index.d.ts.map +1 -0
- package/dist/src/behaviors/index.js +18 -0
- package/dist/src/behaviors/index.js.map +1 -0
- package/dist/src/behaviors/registry.d.ts +45 -0
- package/dist/src/behaviors/registry.d.ts.map +1 -0
- package/dist/src/behaviors/registry.js +266 -0
- package/dist/src/behaviors/registry.js.map +1 -0
- package/dist/src/behaviors/roles.d.ts +29 -0
- package/dist/src/behaviors/roles.d.ts.map +1 -0
- package/dist/src/behaviors/roles.js +198 -0
- package/dist/src/behaviors/roles.js.map +1 -0
- package/dist/src/behaviors/types.d.ts +120 -0
- package/dist/src/behaviors/types.d.ts.map +1 -0
- package/dist/src/behaviors/types.js +12 -0
- package/dist/src/behaviors/types.js.map +1 -0
- package/dist/src/index.d.ts +27 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +24 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/integrations/deepsea.d.ts +121 -0
- package/dist/src/integrations/deepsea.d.ts.map +1 -0
- package/dist/src/integrations/deepsea.js +199 -0
- package/dist/src/integrations/deepsea.js.map +1 -0
- package/dist/src/machine.d.ts +139 -0
- package/dist/src/machine.d.ts.map +1 -0
- package/dist/src/machine.js +400 -0
- package/dist/src/machine.js.map +1 -0
- package/dist/src/parser/ast.d.ts +369 -0
- package/dist/src/parser/ast.d.ts.map +1 -0
- package/dist/src/parser/ast.js +17 -0
- package/dist/src/parser/ast.js.map +1 -0
- package/dist/src/parser/parser.d.ts +48 -0
- package/dist/src/parser/parser.d.ts.map +1 -0
- package/dist/src/parser/parser.js +1950 -0
- package/dist/src/parser/parser.js.map +1 -0
- package/dist/src/policy/effects.d.ts +86 -0
- package/dist/src/policy/effects.d.ts.map +1 -0
- package/dist/src/policy/effects.js +53 -0
- package/dist/src/policy/effects.js.map +1 -0
- package/dist/src/policy/policy.d.ts +130 -0
- package/dist/src/policy/policy.d.ts.map +1 -0
- package/dist/src/policy/policy.js +532 -0
- package/dist/src/policy/policy.js.map +1 -0
- package/dist/src/printer/doc.d.ts +151 -0
- package/dist/src/printer/doc.d.ts.map +1 -0
- package/dist/src/printer/doc.js +409 -0
- package/dist/src/printer/doc.js.map +1 -0
- package/dist/src/printer/highlight.d.ts +53 -0
- package/dist/src/printer/highlight.d.ts.map +1 -0
- package/dist/src/printer/highlight.js +339 -0
- package/dist/src/printer/highlight.js.map +1 -0
- package/dist/src/printer/html.d.ts +72 -0
- package/dist/src/printer/html.d.ts.map +1 -0
- package/dist/src/printer/html.js +423 -0
- package/dist/src/printer/html.js.map +1 -0
- package/dist/src/printer/print.d.ts +63 -0
- package/dist/src/printer/print.d.ts.map +1 -0
- package/dist/src/printer/print.js +484 -0
- package/dist/src/printer/print.js.map +1 -0
- package/dist/src/printer/summary.d.ts +50 -0
- package/dist/src/printer/summary.d.ts.map +1 -0
- package/dist/src/printer/summary.js +621 -0
- package/dist/src/printer/summary.js.map +1 -0
- package/dist/src/trace/events.d.ts +183 -0
- package/dist/src/trace/events.d.ts.map +1 -0
- package/dist/src/trace/events.js +9 -0
- package/dist/src/trace/events.js.map +1 -0
- package/dist/src/trace/progress.d.ts +58 -0
- package/dist/src/trace/progress.d.ts.map +1 -0
- package/dist/src/trace/progress.js +83 -0
- package/dist/src/trace/progress.js.map +1 -0
- package/dist/src/vm/arith.d.ts +29 -0
- package/dist/src/vm/arith.d.ts.map +1 -0
- package/dist/src/vm/arith.js +258 -0
- package/dist/src/vm/arith.js.map +1 -0
- package/dist/src/vm/builtins.d.ts +31 -0
- package/dist/src/vm/builtins.d.ts.map +1 -0
- package/dist/src/vm/builtins.js +1773 -0
- package/dist/src/vm/builtins.js.map +1 -0
- package/dist/src/vm/emulate.d.ts +75 -0
- package/dist/src/vm/emulate.d.ts.map +1 -0
- package/dist/src/vm/emulate.js +380 -0
- package/dist/src/vm/emulate.js.map +1 -0
- package/dist/src/vm/expand.d.ts +68 -0
- package/dist/src/vm/expand.d.ts.map +1 -0
- package/dist/src/vm/expand.js +744 -0
- package/dist/src/vm/expand.js.map +1 -0
- package/dist/src/vm/external.d.ts +34 -0
- package/dist/src/vm/external.d.ts.map +1 -0
- package/dist/src/vm/external.js +228 -0
- package/dist/src/vm/external.js.map +1 -0
- package/dist/src/vm/fifo.d.ts +66 -0
- package/dist/src/vm/fifo.d.ts.map +1 -0
- package/dist/src/vm/fifo.js +232 -0
- package/dist/src/vm/fifo.js.map +1 -0
- package/dist/src/vm/interp.d.ts +209 -0
- package/dist/src/vm/interp.d.ts.map +1 -0
- package/dist/src/vm/interp.js +1641 -0
- package/dist/src/vm/interp.js.map +1 -0
- package/dist/src/vm/io.d.ts +180 -0
- package/dist/src/vm/io.d.ts.map +1 -0
- package/dist/src/vm/io.js +502 -0
- package/dist/src/vm/io.js.map +1 -0
- package/dist/src/vm/pattern.d.ts +46 -0
- package/dist/src/vm/pattern.d.ts.map +1 -0
- package/dist/src/vm/pattern.js +498 -0
- package/dist/src/vm/pattern.js.map +1 -0
- package/dist/src/vm/proc.d.ts +94 -0
- package/dist/src/vm/proc.d.ts.map +1 -0
- package/dist/src/vm/proc.js +130 -0
- package/dist/src/vm/proc.js.map +1 -0
- package/dist/src/vm/state.d.ts +104 -0
- package/dist/src/vm/state.d.ts.map +1 -0
- package/dist/src/vm/state.js +233 -0
- package/dist/src/vm/state.js.map +1 -0
- package/dist/src/vm/test.d.ts +11 -0
- package/dist/src/vm/test.d.ts.map +1 -0
- package/dist/src/vm/test.js +277 -0
- package/dist/src/vm/test.js.map +1 -0
- package/package.json +63 -4
- package/src/analysis/analyze.ts +1395 -0
- package/src/analysis/fs.ts +121 -0
- package/src/analysis/outputs.ts +242 -0
- package/src/analysis/value.ts +264 -0
- package/src/behaviors/defs/core.ts +970 -0
- package/src/behaviors/defs/devtools.ts +400 -0
- package/src/behaviors/defs/macos.ts +426 -0
- package/src/behaviors/defs/tools.ts +596 -0
- package/src/behaviors/dsl.ts +293 -0
- package/src/behaviors/index.ts +19 -0
- package/src/behaviors/registry.ts +261 -0
- package/src/behaviors/roles.ts +187 -0
- package/src/behaviors/types.ts +127 -0
- package/src/index.ts +27 -0
- package/src/integrations/deepsea.ts +259 -0
- package/src/machine.ts +483 -0
- package/src/parser/ast.ts +472 -0
- package/src/parser/parser.ts +1826 -0
- package/src/policy/effects.ts +102 -0
- package/src/policy/policy.ts +578 -0
- package/src/printer/doc.ts +454 -0
- package/src/printer/highlight.ts +283 -0
- package/src/printer/html.ts +441 -0
- package/src/printer/print.ts +528 -0
- package/src/printer/summary.ts +601 -0
- package/src/trace/events.ts +102 -0
- package/src/trace/progress.ts +119 -0
- package/src/vm/arith.ts +236 -0
- package/src/vm/builtins.ts +1590 -0
- package/src/vm/emulate.ts +337 -0
- package/src/vm/expand.ts +721 -0
- package/src/vm/external.ts +265 -0
- package/src/vm/fifo.ts +226 -0
- package/src/vm/interp.ts +1623 -0
- package/src/vm/io.ts +570 -0
- package/src/vm/pattern.ts +477 -0
- package/src/vm/proc.ts +147 -0
- package/src/vm/state.ts +267 -0
- package/src/vm/test.ts +253 -0
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Word expansion, in bash order: brace expansion, tilde expansion, parameter
|
|
3
|
+
* / arithmetic / command substitution (left to right), field splitting,
|
|
4
|
+
* pathname expansion, quote removal.
|
|
5
|
+
*/
|
|
6
|
+
import * as os from 'node:os';
|
|
7
|
+
import { ArithError } from "./arith.js";
|
|
8
|
+
import { CommandError, DiscardError, FatalError } from "./proc.js";
|
|
9
|
+
import { globPaths, hasGlobChars, matchPattern, quotePattern, removePrefix, removeSuffix, replacePattern, unescapePattern } from "./pattern.js";
|
|
10
|
+
import { dashFlags } from "./state.js";
|
|
11
|
+
const BRK = { brk: true };
|
|
12
|
+
function isBrk(p) {
|
|
13
|
+
return 'brk' in p;
|
|
14
|
+
}
|
|
15
|
+
function toAtoms(parts) {
|
|
16
|
+
const atoms = [];
|
|
17
|
+
for (const p of parts) {
|
|
18
|
+
if (p.type === 'Lit')
|
|
19
|
+
for (const ch of p.value)
|
|
20
|
+
atoms.push({ ch });
|
|
21
|
+
else
|
|
22
|
+
atoms.push({ part: p });
|
|
23
|
+
}
|
|
24
|
+
return atoms;
|
|
25
|
+
}
|
|
26
|
+
function fromAtoms(atoms, like) {
|
|
27
|
+
const parts = [];
|
|
28
|
+
let lit = '';
|
|
29
|
+
const flush = () => {
|
|
30
|
+
if (lit !== '')
|
|
31
|
+
parts.push({ type: 'Lit', value: lit, id: like.id, span: like.span });
|
|
32
|
+
lit = '';
|
|
33
|
+
};
|
|
34
|
+
for (const a of atoms) {
|
|
35
|
+
if ('ch' in a)
|
|
36
|
+
lit += a.ch;
|
|
37
|
+
else {
|
|
38
|
+
flush();
|
|
39
|
+
parts.push(a.part);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
flush();
|
|
43
|
+
return parts;
|
|
44
|
+
}
|
|
45
|
+
function seqExpansion(body) {
|
|
46
|
+
let m = /^(-?\d+)\.\.(-?\d+)(?:\.\.(-?\d+))?$/.exec(body);
|
|
47
|
+
if (m) {
|
|
48
|
+
const a = Number(m[1]);
|
|
49
|
+
const b = Number(m[2]);
|
|
50
|
+
let step = m[3] ? Math.abs(Number(m[3])) : 1;
|
|
51
|
+
if (step === 0)
|
|
52
|
+
step = 1;
|
|
53
|
+
const width = (m[1].replace('-', '').startsWith('0') && m[1].length > 1) || (m[2].replace('-', '').startsWith('0') && m[2].length > 1)
|
|
54
|
+
? Math.max(m[1].length, m[2].length) : 0;
|
|
55
|
+
const out = [];
|
|
56
|
+
if (Math.abs(b - a) / step > 100000)
|
|
57
|
+
return null;
|
|
58
|
+
for (let i = a; a <= b ? i <= b : i >= b; i += a <= b ? step : -step) {
|
|
59
|
+
let s = String(Math.abs(i));
|
|
60
|
+
if (width)
|
|
61
|
+
s = s.padStart(width - (i < 0 ? 1 : 0), '0');
|
|
62
|
+
out.push((i < 0 ? '-' : '') + s);
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
m = /^([a-zA-Z])\.\.([a-zA-Z])(?:\.\.(-?\d+))?$/.exec(body);
|
|
67
|
+
if (m) {
|
|
68
|
+
const a = m[1].charCodeAt(0);
|
|
69
|
+
const b = m[2].charCodeAt(0);
|
|
70
|
+
const step = m[3] ? Math.abs(Number(m[3])) || 1 : 1;
|
|
71
|
+
const out = [];
|
|
72
|
+
for (let i = a; a <= b ? i <= b : i >= b; i += a <= b ? step : -step)
|
|
73
|
+
out.push(String.fromCharCode(i));
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function braceExpandAtoms(atoms) {
|
|
79
|
+
for (let i = 0; i < atoms.length; i++) {
|
|
80
|
+
const a = atoms[i];
|
|
81
|
+
if (!('ch' in a) || a.ch !== '{')
|
|
82
|
+
continue;
|
|
83
|
+
// find matching close and top-level commas
|
|
84
|
+
let depth = 0;
|
|
85
|
+
const commas = [];
|
|
86
|
+
let close = -1;
|
|
87
|
+
for (let j = i + 1; j < atoms.length; j++) {
|
|
88
|
+
const b = atoms[j];
|
|
89
|
+
if (!('ch' in b))
|
|
90
|
+
continue;
|
|
91
|
+
if (b.ch === '{')
|
|
92
|
+
depth++;
|
|
93
|
+
else if (b.ch === '}') {
|
|
94
|
+
if (depth === 0) {
|
|
95
|
+
close = j;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
depth--;
|
|
99
|
+
}
|
|
100
|
+
else if (b.ch === ',' && depth === 0)
|
|
101
|
+
commas.push(j);
|
|
102
|
+
}
|
|
103
|
+
if (close === -1)
|
|
104
|
+
continue;
|
|
105
|
+
const prefix = atoms.slice(0, i);
|
|
106
|
+
const suffixes = braceExpandAtoms(atoms.slice(close + 1));
|
|
107
|
+
let alternatives;
|
|
108
|
+
if (commas.length > 0) {
|
|
109
|
+
alternatives = [];
|
|
110
|
+
let start = i + 1;
|
|
111
|
+
for (const c of [...commas, close]) {
|
|
112
|
+
alternatives.push(...braceExpandAtoms(atoms.slice(start, c)));
|
|
113
|
+
start = c + 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const inner = atoms.slice(i + 1, close);
|
|
118
|
+
if (!inner.every((x) => 'ch' in x))
|
|
119
|
+
continue;
|
|
120
|
+
const seq = seqExpansion(inner.map((x) => x.ch).join(''));
|
|
121
|
+
if (!seq)
|
|
122
|
+
continue;
|
|
123
|
+
alternatives = seq.map((s) => [...s].map((ch) => ({ ch })));
|
|
124
|
+
}
|
|
125
|
+
const out = [];
|
|
126
|
+
for (const alt of alternatives)
|
|
127
|
+
for (const suf of suffixes)
|
|
128
|
+
out.push([...prefix, ...alt, ...suf]);
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
return [atoms];
|
|
132
|
+
}
|
|
133
|
+
export function braceExpand(word) {
|
|
134
|
+
if (!word.parts.some((p) => p.type === 'Lit' && p.value.includes('{')))
|
|
135
|
+
return [word];
|
|
136
|
+
const results = braceExpandAtoms(toAtoms(word.parts));
|
|
137
|
+
if (results.length === 1)
|
|
138
|
+
return [word];
|
|
139
|
+
return results.map((atoms) => ({ ...word, parts: fromAtoms(atoms, word) }));
|
|
140
|
+
}
|
|
141
|
+
export class Expander {
|
|
142
|
+
host;
|
|
143
|
+
constructor(host) {
|
|
144
|
+
this.host = host;
|
|
145
|
+
}
|
|
146
|
+
/** Expand words into fields (full expansion, for argv and `for` lists). */
|
|
147
|
+
async expandFields(words, proc, opts = {}) {
|
|
148
|
+
const out = [];
|
|
149
|
+
for (const w of words) {
|
|
150
|
+
for (const bw of braceExpand(w)) {
|
|
151
|
+
const pieces = await this.expandParts(bw.parts, proc, false, true);
|
|
152
|
+
for (const field of this.split(pieces, proc)) {
|
|
153
|
+
out.push(...this.glob(field, proc, opts));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
/** Expand without splitting or globbing (assignments, redirect targets, case word, [[ ]] operands). */
|
|
160
|
+
/**
|
|
161
|
+
* The replacement of `${x/pat/rep}`. With `patsub_replacement` (on by
|
|
162
|
+
* default, as in bash 5.2) every `&` in an unquoted part, including the
|
|
163
|
+
* value of an unquoted `$var`, stands for the matched text; quoted parts and
|
|
164
|
+
* `\&` are literal.
|
|
165
|
+
*/
|
|
166
|
+
async replacement(word, proc) {
|
|
167
|
+
if (!proc.state.shopts.has('patsub_replacement'))
|
|
168
|
+
return this.expandString(word, proc);
|
|
169
|
+
const pieces = [];
|
|
170
|
+
for (const [i, part] of word.parts.entries()) {
|
|
171
|
+
const text = await this.expandString({ ...word, parts: i === 0 ? word.parts.slice(0, 1) : [part] }, proc);
|
|
172
|
+
if (part.type === 'SglQuoted' || part.type === 'DblQuoted' || part.type === 'Escaped')
|
|
173
|
+
pieces.push(text);
|
|
174
|
+
else
|
|
175
|
+
text.split('&').forEach((t, j) => (j > 0 ? pieces.push(null, t) : pieces.push(t)));
|
|
176
|
+
}
|
|
177
|
+
if (!pieces.includes(null))
|
|
178
|
+
return pieces.join('');
|
|
179
|
+
return (match) => pieces.map((p) => (p === null ? match : p)).join('');
|
|
180
|
+
}
|
|
181
|
+
async expandString(word, proc, opts = {}) {
|
|
182
|
+
const pieces = await this.expandParts(word.parts, proc, false, true, opts.tildeAssign);
|
|
183
|
+
return this.joinPieces(pieces);
|
|
184
|
+
}
|
|
185
|
+
/** Expand to a pattern: quoted text is escaped, unquoted expansions stay pattern-active. */
|
|
186
|
+
async expandPattern(word, proc) {
|
|
187
|
+
const pieces = await this.expandParts(word.parts, proc, false, true);
|
|
188
|
+
let s = '';
|
|
189
|
+
for (const p of pieces) {
|
|
190
|
+
if (isBrk(p)) {
|
|
191
|
+
s += ' ';
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
s += p.quoted ? quotePattern(p.text) : p.text;
|
|
195
|
+
}
|
|
196
|
+
return s;
|
|
197
|
+
}
|
|
198
|
+
/** Expand a here-doc body (parameter/command/arith expansion only). */
|
|
199
|
+
async expandHeredoc(word, proc) {
|
|
200
|
+
const pieces = await this.expandParts(word.parts, proc, true, false);
|
|
201
|
+
return this.joinPieces(pieces);
|
|
202
|
+
}
|
|
203
|
+
joinPieces(pieces) {
|
|
204
|
+
let s = '';
|
|
205
|
+
for (const p of pieces)
|
|
206
|
+
s += isBrk(p) ? ' ' : p.text;
|
|
207
|
+
return s;
|
|
208
|
+
}
|
|
209
|
+
ifs(proc) {
|
|
210
|
+
const v = proc.state.getString('IFS');
|
|
211
|
+
return v === undefined ? ' \t\n' : v;
|
|
212
|
+
}
|
|
213
|
+
ifsFirst(proc) {
|
|
214
|
+
const v = proc.state.getString('IFS');
|
|
215
|
+
return v === undefined ? ' ' : v.slice(0, 1);
|
|
216
|
+
}
|
|
217
|
+
async expandParts(parts, proc, quoted, tilde, tildeAssign = false) {
|
|
218
|
+
const out = [];
|
|
219
|
+
for (let i = 0; i < parts.length; i++) {
|
|
220
|
+
const p = parts[i];
|
|
221
|
+
switch (p.type) {
|
|
222
|
+
case 'Lit': {
|
|
223
|
+
if (!quoted && tilde && (i === 0 || tildeAssign)) {
|
|
224
|
+
const t = this.tilde(p.value, proc, i === 0, tildeAssign);
|
|
225
|
+
if (t !== null) {
|
|
226
|
+
out.push(...t);
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
out.push({ text: p.value, quoted, split: false });
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case 'SglQuoted':
|
|
234
|
+
case 'Escaped':
|
|
235
|
+
out.push({ text: p.value, quoted: true, split: false });
|
|
236
|
+
break;
|
|
237
|
+
case 'DblQuoted': {
|
|
238
|
+
const inner = await this.expandParts(p.parts, proc, true, false);
|
|
239
|
+
const onlyAtExpansions = p.parts.length > 0 && p.parts.every((q) => q.type === 'ParamExp' && isAtExpansion(q));
|
|
240
|
+
if (inner.length === 0 && !onlyAtExpansions)
|
|
241
|
+
out.push({ text: '', quoted: true, split: false });
|
|
242
|
+
else
|
|
243
|
+
out.push(...inner);
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
case 'ParamExp':
|
|
247
|
+
out.push(...(await this.expandParam(p, proc, quoted)));
|
|
248
|
+
break;
|
|
249
|
+
case 'CmdSubst': {
|
|
250
|
+
const text = await this.host.commandSubst(p, proc);
|
|
251
|
+
out.push({ text, quoted, split: !quoted });
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
case 'ArithExp': {
|
|
255
|
+
let v;
|
|
256
|
+
try {
|
|
257
|
+
v = await this.host.arith(p.expr, proc);
|
|
258
|
+
}
|
|
259
|
+
catch (e) {
|
|
260
|
+
if (e instanceof ArithError)
|
|
261
|
+
throw new DiscardError(e.message);
|
|
262
|
+
throw e;
|
|
263
|
+
}
|
|
264
|
+
out.push({ text: String(v), quoted, split: !quoted });
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
case 'ProcSubst': {
|
|
268
|
+
const path = await this.host.processSubst(p, proc);
|
|
269
|
+
out.push({ text: path, quoted: true, split: false });
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
276
|
+
tilde(text, proc, atStart, assign) {
|
|
277
|
+
if (!text.includes('~'))
|
|
278
|
+
return null;
|
|
279
|
+
const home = proc.state.getString('HOME') ?? os.homedir();
|
|
280
|
+
const expandOne = (prefix) => {
|
|
281
|
+
if (prefix === '')
|
|
282
|
+
return home;
|
|
283
|
+
if (prefix === '+')
|
|
284
|
+
return proc.state.getString('PWD') ?? proc.state.cwd;
|
|
285
|
+
if (prefix === '-')
|
|
286
|
+
return proc.state.getString('OLDPWD') ?? null;
|
|
287
|
+
if (prefix === os.userInfo().username)
|
|
288
|
+
return os.homedir();
|
|
289
|
+
return null;
|
|
290
|
+
};
|
|
291
|
+
if (!assign) {
|
|
292
|
+
if (!atStart || !text.startsWith('~'))
|
|
293
|
+
return null;
|
|
294
|
+
const slash = text.indexOf('/');
|
|
295
|
+
const prefix = slash === -1 ? text.slice(1) : text.slice(1, slash);
|
|
296
|
+
const h = expandOne(prefix);
|
|
297
|
+
if (h === null)
|
|
298
|
+
return null;
|
|
299
|
+
return [{ text: h, quoted: true, split: false }, { text: slash === -1 ? '' : text.slice(slash), quoted: false, split: false }];
|
|
300
|
+
}
|
|
301
|
+
// assignment: after = (start of value) and after each unquoted ':'
|
|
302
|
+
const pieces = [];
|
|
303
|
+
const chunks = text.split(':');
|
|
304
|
+
chunks.forEach((chunk, idx) => {
|
|
305
|
+
if (idx > 0)
|
|
306
|
+
pieces.push({ text: ':', quoted: false, split: false });
|
|
307
|
+
if (chunk.startsWith('~') && (idx > 0 || atStart)) {
|
|
308
|
+
const slash = chunk.indexOf('/');
|
|
309
|
+
const prefix = slash === -1 ? chunk.slice(1) : chunk.slice(1, slash);
|
|
310
|
+
const h = expandOne(prefix);
|
|
311
|
+
if (h !== null) {
|
|
312
|
+
pieces.push({ text: h, quoted: true, split: false });
|
|
313
|
+
pieces.push({ text: slash === -1 ? '' : chunk.slice(slash), quoted: false, split: false });
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
pieces.push({ text: chunk, quoted: false, split: false });
|
|
318
|
+
});
|
|
319
|
+
return pieces;
|
|
320
|
+
}
|
|
321
|
+
// -------------------------------------------------------------------------
|
|
322
|
+
// Parameters
|
|
323
|
+
// -------------------------------------------------------------------------
|
|
324
|
+
/** Raw value: a string, an array of fields (`$@`, `${a[@]}`), or undefined (unset). */
|
|
325
|
+
async paramValue(p, proc) {
|
|
326
|
+
const st = proc.state;
|
|
327
|
+
let name = p.name;
|
|
328
|
+
if (p.indirect && !(p.index && isAllIndex(p.index))) {
|
|
329
|
+
const target = await this.simpleParamValue(name, null, proc);
|
|
330
|
+
if (target === undefined || target === '') {
|
|
331
|
+
throw new FatalError(`${name}: invalid indirect expansion`);
|
|
332
|
+
}
|
|
333
|
+
const m = /^([A-Za-z_][A-Za-z0-9_]*|[0-9]+|[@*#?$!-])(?:\[(.*)\])?$/s.exec(target);
|
|
334
|
+
if (!m)
|
|
335
|
+
throw new FatalError(`${target}: invalid variable name`);
|
|
336
|
+
name = m[1];
|
|
337
|
+
if (m[2] !== undefined) {
|
|
338
|
+
if (m[2] === '@' || m[2] === '*')
|
|
339
|
+
return { value: this.arrayValues(name, proc), star: m[2] === '*' };
|
|
340
|
+
return { value: this.elementValue(name, m[2], proc), star: false };
|
|
341
|
+
}
|
|
342
|
+
return this.paramValue({ ...p, name, indirect: false, index: p.index }, proc);
|
|
343
|
+
}
|
|
344
|
+
if (p.prefixNames) {
|
|
345
|
+
return { value: st.visibleNames().filter((n) => n.startsWith(name) && st.isSet(n)), star: p.prefixNames === '*' };
|
|
346
|
+
}
|
|
347
|
+
if (name === '@' || name === '*')
|
|
348
|
+
return { value: [...st.positional], star: name === '*' };
|
|
349
|
+
if (p.index) {
|
|
350
|
+
if (isAllIndex(p.index)) {
|
|
351
|
+
const star = literalIndex(p.index) === '*';
|
|
352
|
+
if (p.indirect)
|
|
353
|
+
return { value: this.arrayKeys(name, proc), star };
|
|
354
|
+
return { value: this.arrayValues(name, proc), star };
|
|
355
|
+
}
|
|
356
|
+
const v = st.lookup(name);
|
|
357
|
+
let key;
|
|
358
|
+
if (v?.value?.kind === 'assoc' || v?.declaredKind === 'assoc')
|
|
359
|
+
key = await this.expandString(p.index, proc);
|
|
360
|
+
else {
|
|
361
|
+
const idxText = await this.expandString(p.index, proc);
|
|
362
|
+
key = String(await this.host.arith(parseIndexExpr(idxText), proc));
|
|
363
|
+
}
|
|
364
|
+
return { value: this.elementValue(name, key, proc), star: false };
|
|
365
|
+
}
|
|
366
|
+
return { value: await this.simpleParamValue(name, null, proc), star: false };
|
|
367
|
+
}
|
|
368
|
+
async simpleParamValue(name, _index, proc) {
|
|
369
|
+
const st = proc.state;
|
|
370
|
+
switch (name) {
|
|
371
|
+
case '#': return String(st.positional.length);
|
|
372
|
+
case '?': return String(st.lastStatus);
|
|
373
|
+
case '-': return dashFlags(st);
|
|
374
|
+
case '$': return String(this.host.shellPid);
|
|
375
|
+
case '!': return st.lastBgPid === null ? undefined : String(st.lastBgPid);
|
|
376
|
+
case '0': return st.arg0;
|
|
377
|
+
case '@':
|
|
378
|
+
case '*': return st.positional.join(this.ifsFirst(proc));
|
|
379
|
+
}
|
|
380
|
+
if (/^[0-9]+$/.test(name))
|
|
381
|
+
return st.positional[Number(name) - 1];
|
|
382
|
+
const dyn = this.host.dynamic(proc, name);
|
|
383
|
+
if (dyn !== undefined)
|
|
384
|
+
return dyn;
|
|
385
|
+
return st.getString(name);
|
|
386
|
+
}
|
|
387
|
+
arrayValues(name, proc) {
|
|
388
|
+
if (name === 'FUNCNAME')
|
|
389
|
+
return proc.state.funcStack.length > 0 ? [...proc.state.funcStack, 'main'] : [];
|
|
390
|
+
const v = proc.state.lookup(name);
|
|
391
|
+
return varElements(v);
|
|
392
|
+
}
|
|
393
|
+
arrayKeys(name, proc) {
|
|
394
|
+
const v = proc.state.lookup(name);
|
|
395
|
+
if (!v || v.value === null)
|
|
396
|
+
return [];
|
|
397
|
+
if (v.value.kind === 'string')
|
|
398
|
+
return ['0'];
|
|
399
|
+
if (v.value.kind === 'indexed')
|
|
400
|
+
return [...v.value.values.keys()].sort((a, b) => a - b).map(String);
|
|
401
|
+
return [...v.value.values.keys()];
|
|
402
|
+
}
|
|
403
|
+
elementValue(name, key, proc) {
|
|
404
|
+
if (name === 'FUNCNAME')
|
|
405
|
+
return proc.state.funcStack.length > 0 ? [...proc.state.funcStack, 'main'][Number(key)] : undefined;
|
|
406
|
+
const v = proc.state.lookup(name);
|
|
407
|
+
if (!v || v.value === null)
|
|
408
|
+
return undefined;
|
|
409
|
+
if (v.value.kind === 'string')
|
|
410
|
+
return key === '0' ? v.value.value : undefined;
|
|
411
|
+
if (v.value.kind === 'indexed') {
|
|
412
|
+
let k = Number(key);
|
|
413
|
+
if (k < 0) {
|
|
414
|
+
const keys = [...v.value.values.keys()];
|
|
415
|
+
const max = keys.length === 0 ? -1 : Math.max(...keys);
|
|
416
|
+
k = max + 1 + k;
|
|
417
|
+
}
|
|
418
|
+
return v.value.values.get(k);
|
|
419
|
+
}
|
|
420
|
+
return v.value.values.get(key);
|
|
421
|
+
}
|
|
422
|
+
async expandParam(p, proc, quoted) {
|
|
423
|
+
const st = proc.state;
|
|
424
|
+
const { value: raw, star } = await this.paramValue(p, proc);
|
|
425
|
+
const isList = Array.isArray(raw);
|
|
426
|
+
const op = p.op;
|
|
427
|
+
// ${#x}
|
|
428
|
+
if (p.length) {
|
|
429
|
+
if (isList)
|
|
430
|
+
return [{ text: String(raw.length), quoted, split: !quoted }];
|
|
431
|
+
if (raw === undefined && st.options.nounset && !(p.name === '@' || p.name === '*'))
|
|
432
|
+
throw new FatalError(`${p.name}: unbound variable`, 127);
|
|
433
|
+
return [{ text: String([...(raw ?? '')].length), quoted, split: !quoted }];
|
|
434
|
+
}
|
|
435
|
+
const isUnset = raw === undefined || (isList && raw.length === 0 && (p.name === '@' || p.name === '*' || (p.index !== null)));
|
|
436
|
+
const isNull = isUnset || raw === '' || (isList && (raw.length === 0 || (raw.length === 1 && raw[0] === '')));
|
|
437
|
+
if (op && (op.kind === 'default' || op.kind === 'assign' || op.kind === 'error' || op.kind === 'alternative')) {
|
|
438
|
+
const missing = op.colon ? isNull : isUnset;
|
|
439
|
+
if (op.kind === 'alternative') {
|
|
440
|
+
if (missing)
|
|
441
|
+
return [];
|
|
442
|
+
return op.word ? this.expandOpWord(op.word, proc, quoted) : [];
|
|
443
|
+
}
|
|
444
|
+
if (missing) {
|
|
445
|
+
if (op.kind === 'default')
|
|
446
|
+
return op.word ? this.expandOpWord(op.word, proc, quoted) : [{ text: '', quoted, split: false }];
|
|
447
|
+
if (op.kind === 'assign') {
|
|
448
|
+
const v = op.word ? await this.expandString(op.word, proc) : '';
|
|
449
|
+
this.host.assign(proc, p.name, v);
|
|
450
|
+
return [{ text: v, quoted, split: !quoted }];
|
|
451
|
+
}
|
|
452
|
+
const msg = op.word ? await this.expandString(op.word, proc) : (op.colon ? 'parameter null or not set' : 'parameter not set');
|
|
453
|
+
throw new FatalError(`${p.name}: ${msg}`, 127);
|
|
454
|
+
}
|
|
455
|
+
// present: fall through to the plain value
|
|
456
|
+
}
|
|
457
|
+
else if (raw === undefined && st.options.nounset && !(p.name === '@' || p.name === '*') && !p.prefixNames && !(p.index && isAllIndex(p.index))) {
|
|
458
|
+
throw new FatalError(`${p.index ? `${p.name}[${literalIndex(p.index) ?? '…'}]` : p.name}: unbound variable`, 127);
|
|
459
|
+
}
|
|
460
|
+
let values = isList ? raw : raw === undefined ? [] : [raw];
|
|
461
|
+
const scalarUnset = !isList && raw === undefined;
|
|
462
|
+
if (op) {
|
|
463
|
+
switch (op.kind) {
|
|
464
|
+
case 'removePrefix':
|
|
465
|
+
case 'removeSuffix': {
|
|
466
|
+
const pat = await this.expandPattern(op.pattern, proc);
|
|
467
|
+
values = values.map((v) => (op.kind === 'removePrefix' ? removePrefix(v, pat, op.longest) : removeSuffix(v, pat, op.longest)));
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
case 'replace': {
|
|
471
|
+
const pat = await this.expandPattern(op.pattern, proc);
|
|
472
|
+
const rep = op.replacement ? await this.replacement(op.replacement, proc) : '';
|
|
473
|
+
values = values.map((v) => replacePattern(v, pat, rep, op.mode));
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
case 'case': {
|
|
477
|
+
const pat = op.pattern ? await this.expandPattern(op.pattern, proc) : null;
|
|
478
|
+
const conv = (ch) => (pat === null || matchPattern(ch, pat) ? (op.upper ? ch.toUpperCase() : ch.toLowerCase()) : ch);
|
|
479
|
+
values = values.map((v) => {
|
|
480
|
+
if (op.all)
|
|
481
|
+
return [...v].map(conv).join('');
|
|
482
|
+
return v === '' ? v : conv(v[0]) + v.slice(1);
|
|
483
|
+
});
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
case 'substring': {
|
|
487
|
+
const off = Number(await this.arithSafe(op.offset, proc));
|
|
488
|
+
const len = op.length === null ? null : Number(await this.arithSafe(op.length, proc));
|
|
489
|
+
if (isList) {
|
|
490
|
+
// positional slicing includes $0 at offset 0
|
|
491
|
+
let list = values;
|
|
492
|
+
if (p.name === '@' || p.name === '*')
|
|
493
|
+
list = [st.arg0, ...values];
|
|
494
|
+
let start = off < 0 ? list.length + off : off;
|
|
495
|
+
if (p.name !== '@' && p.name !== '*' && off >= 0)
|
|
496
|
+
start = off;
|
|
497
|
+
if (start < 0) {
|
|
498
|
+
values = [];
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
const end = len === null ? list.length : len < 0 ? list.length + len : start + len;
|
|
502
|
+
values = list.slice(start, Math.max(start, end));
|
|
503
|
+
if ((p.name === '@' || p.name === '*') && off === 0 && false)
|
|
504
|
+
values = list;
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
values = values.map((v) => {
|
|
508
|
+
const chars = [...v];
|
|
509
|
+
let start = off < 0 ? chars.length + off : off;
|
|
510
|
+
if (start < 0)
|
|
511
|
+
return '';
|
|
512
|
+
if (start > chars.length)
|
|
513
|
+
start = chars.length;
|
|
514
|
+
let end = len === null ? chars.length : len < 0 ? chars.length + len : start + len;
|
|
515
|
+
if (len !== null && len < 0 && end < start)
|
|
516
|
+
throw new FatalError(`${len}: substring expression < 0`);
|
|
517
|
+
end = Math.min(end, chars.length);
|
|
518
|
+
return chars.slice(start, end).join('');
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
case 'transform': {
|
|
524
|
+
values = values.map((v) => transform(v, op.operator, p.name, st.lookup(p.name)));
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
default:
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (scalarUnset && values.length === 0)
|
|
532
|
+
return [];
|
|
533
|
+
if (isList) {
|
|
534
|
+
if (quoted && !star) {
|
|
535
|
+
const out = [];
|
|
536
|
+
values.forEach((v, i) => {
|
|
537
|
+
if (i > 0)
|
|
538
|
+
out.push(BRK);
|
|
539
|
+
out.push({ text: v, quoted: true, split: false });
|
|
540
|
+
});
|
|
541
|
+
return out;
|
|
542
|
+
}
|
|
543
|
+
if (quoted && star)
|
|
544
|
+
return [{ text: values.join(this.ifsFirst(proc)), quoted: true, split: false }];
|
|
545
|
+
// unquoted: each element split separately
|
|
546
|
+
const out = [];
|
|
547
|
+
values.forEach((v, i) => {
|
|
548
|
+
if (i > 0)
|
|
549
|
+
out.push(BRK);
|
|
550
|
+
out.push({ text: v, quoted: false, split: true });
|
|
551
|
+
});
|
|
552
|
+
return out;
|
|
553
|
+
}
|
|
554
|
+
return [{ text: values[0] ?? '', quoted, split: !quoted }];
|
|
555
|
+
}
|
|
556
|
+
async arithSafe(e, proc) {
|
|
557
|
+
try {
|
|
558
|
+
return await this.host.arith(e, proc);
|
|
559
|
+
}
|
|
560
|
+
catch (err) {
|
|
561
|
+
if (err instanceof ArithError)
|
|
562
|
+
throw new DiscardError(err.message);
|
|
563
|
+
throw err;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
async expandOpWord(word, proc, quoted) {
|
|
567
|
+
const pieces = await this.expandParts(word.parts, proc, quoted, !quoted);
|
|
568
|
+
if (quoted)
|
|
569
|
+
return pieces;
|
|
570
|
+
// unquoted ${v:-a b}: the operand's unquoted text is subject to word splitting
|
|
571
|
+
return pieces.map((p) => (isBrk(p) || p.quoted ? p : { ...p, split: true }));
|
|
572
|
+
}
|
|
573
|
+
// -------------------------------------------------------------------------
|
|
574
|
+
// Field splitting and globbing
|
|
575
|
+
// -------------------------------------------------------------------------
|
|
576
|
+
/** Split pieces into fields; each field keeps its value and its glob pattern. */
|
|
577
|
+
split(pieces, proc) {
|
|
578
|
+
const ifs = this.ifs(proc);
|
|
579
|
+
const ws = (c) => (c === ' ' || c === '\t' || c === '\n') && ifs.includes(c);
|
|
580
|
+
const fields = [];
|
|
581
|
+
let value = '';
|
|
582
|
+
let pattern = '';
|
|
583
|
+
let globbable = false;
|
|
584
|
+
let started = false;
|
|
585
|
+
let afterWs = false;
|
|
586
|
+
const push = () => {
|
|
587
|
+
fields.push({ value, pattern, globbable });
|
|
588
|
+
value = '';
|
|
589
|
+
pattern = '';
|
|
590
|
+
globbable = false;
|
|
591
|
+
started = false;
|
|
592
|
+
};
|
|
593
|
+
for (const p of pieces) {
|
|
594
|
+
if (isBrk(p)) {
|
|
595
|
+
push();
|
|
596
|
+
afterWs = false;
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
if (!p.split || ifs === '') {
|
|
600
|
+
if (p.quoted) {
|
|
601
|
+
value += p.text;
|
|
602
|
+
pattern += quotePattern(p.text);
|
|
603
|
+
started = true;
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
value += p.text;
|
|
607
|
+
pattern += p.text;
|
|
608
|
+
if (hasGlobChars(p.text))
|
|
609
|
+
globbable = true;
|
|
610
|
+
if (p.text !== '')
|
|
611
|
+
started = true;
|
|
612
|
+
}
|
|
613
|
+
if (p.text !== '' || p.quoted)
|
|
614
|
+
afterWs = false;
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
for (const ch of p.text) {
|
|
618
|
+
if (!ifs.includes(ch)) {
|
|
619
|
+
value += ch;
|
|
620
|
+
pattern += ch === '\\' ? '\\\\' : ch;
|
|
621
|
+
if (ch === '*' || ch === '?' || ch === '[')
|
|
622
|
+
globbable = true;
|
|
623
|
+
started = true;
|
|
624
|
+
afterWs = false;
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (ws(ch)) {
|
|
628
|
+
if (started) {
|
|
629
|
+
push();
|
|
630
|
+
afterWs = true;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
else if (started) {
|
|
634
|
+
push();
|
|
635
|
+
afterWs = false;
|
|
636
|
+
}
|
|
637
|
+
else if (afterWs) {
|
|
638
|
+
afterWs = false;
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
push();
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (started)
|
|
646
|
+
push();
|
|
647
|
+
return fields;
|
|
648
|
+
}
|
|
649
|
+
glob(field, proc, opts) {
|
|
650
|
+
const st = proc.state;
|
|
651
|
+
if (!field.globbable || opts.noGlob || st.options.noglob || !hasGlobChars(field.pattern))
|
|
652
|
+
return [field.value];
|
|
653
|
+
const matches = globPaths(field.pattern, {
|
|
654
|
+
cwd: st.cwd,
|
|
655
|
+
dotglob: st.shopts.has('dotglob'),
|
|
656
|
+
globstar: st.shopts.has('globstar'),
|
|
657
|
+
nocase: st.shopts.has('nocaseglob'),
|
|
658
|
+
});
|
|
659
|
+
if (matches.length > 0)
|
|
660
|
+
return matches;
|
|
661
|
+
if (st.shopts.has('failglob'))
|
|
662
|
+
throw new CommandError(`no match: ${field.value}`);
|
|
663
|
+
if (st.shopts.has('nullglob'))
|
|
664
|
+
return [];
|
|
665
|
+
return [unescapePattern(field.pattern) === field.value ? field.value : field.value];
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function isAtExpansion(p) {
|
|
669
|
+
if (p.length)
|
|
670
|
+
return false;
|
|
671
|
+
if ((p.name === '@') && !p.index)
|
|
672
|
+
return p.op === null || p.op.kind !== 'default';
|
|
673
|
+
return p.index !== null && literalIndex(p.index) === '@';
|
|
674
|
+
}
|
|
675
|
+
function literalIndex(w) {
|
|
676
|
+
if (w.parts.length === 1 && w.parts[0].type === 'Lit')
|
|
677
|
+
return w.parts[0].value;
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
function isAllIndex(w) {
|
|
681
|
+
const l = literalIndex(w);
|
|
682
|
+
return l === '@' || l === '*';
|
|
683
|
+
}
|
|
684
|
+
/** Elements of a variable in index order. */
|
|
685
|
+
export function varElements(v) {
|
|
686
|
+
if (!v || v.value === null)
|
|
687
|
+
return [];
|
|
688
|
+
if (v.value.kind === 'string')
|
|
689
|
+
return [v.value.value];
|
|
690
|
+
if (v.value.kind === 'indexed')
|
|
691
|
+
return [...v.value.values.entries()].sort((a, b) => a[0] - b[0]).map((e) => e[1]);
|
|
692
|
+
return [...v.value.values.values()];
|
|
693
|
+
}
|
|
694
|
+
/** Subscript text → an arithmetic expression node (parsed lazily by the host). */
|
|
695
|
+
function parseIndexExpr(text) {
|
|
696
|
+
return { type: 'ArithWord', id: 0, span: { start: 0, end: 0 }, word: { type: 'Word', id: 0, span: { start: 0, end: 0 }, parts: [{ type: 'SglQuoted', value: text, ansi: false, id: 0, span: { start: 0, end: 0 } }] } };
|
|
697
|
+
}
|
|
698
|
+
export function shellQuote(s) {
|
|
699
|
+
if (s === '')
|
|
700
|
+
return "''";
|
|
701
|
+
if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(s))
|
|
702
|
+
return s;
|
|
703
|
+
if (/[\x00-\x1f\x7f]/.test(s)) {
|
|
704
|
+
return "$'" + s.replace(/[\\']/g, (c) => '\\' + c).replace(/[\x00-\x1f\x7f]/g, (c) => {
|
|
705
|
+
const map = { '\n': '\\n', '\t': '\\t', '\r': '\\r', '\x1b': '\\E' };
|
|
706
|
+
return map[c] ?? '\\' + c.charCodeAt(0).toString(8).padStart(3, '0');
|
|
707
|
+
}) + "'";
|
|
708
|
+
}
|
|
709
|
+
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
710
|
+
}
|
|
711
|
+
function transform(v, op, name, variable) {
|
|
712
|
+
switch (op) {
|
|
713
|
+
case 'Q': return shellQuote(v);
|
|
714
|
+
case 'U': return v.toUpperCase();
|
|
715
|
+
case 'u': return v === '' ? v : v[0].toUpperCase() + v.slice(1);
|
|
716
|
+
case 'L': return v.toLowerCase();
|
|
717
|
+
case 'E': return v.replace(/\\(n|t|r|e|E|\\|'|"|a|b|f|v)/g, (_, c) => ({ n: '\n', t: '\t', r: '\r', e: '\x1b', E: '\x1b', '\\': '\\', "'": "'", '"': '"', a: '\x07', b: '\b', f: '\f', v: '\v' }[c]));
|
|
718
|
+
case 'A': return `${name}=${shellQuote(v)}`;
|
|
719
|
+
case 'a': {
|
|
720
|
+
if (!variable)
|
|
721
|
+
return '';
|
|
722
|
+
let s = '';
|
|
723
|
+
if (variable.value?.kind === 'indexed' || variable.declaredKind === 'indexed')
|
|
724
|
+
s += 'a';
|
|
725
|
+
if (variable.value?.kind === 'assoc' || variable.declaredKind === 'assoc')
|
|
726
|
+
s += 'A';
|
|
727
|
+
if (variable.integer)
|
|
728
|
+
s += 'i';
|
|
729
|
+
if (variable.lower)
|
|
730
|
+
s += 'l';
|
|
731
|
+
if (variable.readonly)
|
|
732
|
+
s += 'r';
|
|
733
|
+
if (variable.upper)
|
|
734
|
+
s += 'u';
|
|
735
|
+
if (variable.exported)
|
|
736
|
+
s += 'x';
|
|
737
|
+
return s;
|
|
738
|
+
}
|
|
739
|
+
case 'P': return v;
|
|
740
|
+
case 'K': return shellQuote(v);
|
|
741
|
+
default: throw new FatalError(`${op}: bad substitution`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
//# sourceMappingURL=expand.js.map
|