@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.
Files changed (203) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +275 -2
  3. package/bin/mash.ts +253 -0
  4. package/dist/bin/mash.d.ts +39 -0
  5. package/dist/bin/mash.d.ts.map +1 -0
  6. package/dist/bin/mash.js +281 -0
  7. package/dist/bin/mash.js.map +1 -0
  8. package/dist/src/analysis/analyze.d.ts +97 -0
  9. package/dist/src/analysis/analyze.d.ts.map +1 -0
  10. package/dist/src/analysis/analyze.js +1406 -0
  11. package/dist/src/analysis/analyze.js.map +1 -0
  12. package/dist/src/analysis/fs.d.ts +39 -0
  13. package/dist/src/analysis/fs.d.ts.map +1 -0
  14. package/dist/src/analysis/fs.js +117 -0
  15. package/dist/src/analysis/fs.js.map +1 -0
  16. package/dist/src/analysis/outputs.d.ts +16 -0
  17. package/dist/src/analysis/outputs.d.ts.map +1 -0
  18. package/dist/src/analysis/outputs.js +277 -0
  19. package/dist/src/analysis/outputs.js.map +1 -0
  20. package/dist/src/analysis/value.d.ts +76 -0
  21. package/dist/src/analysis/value.d.ts.map +1 -0
  22. package/dist/src/analysis/value.js +248 -0
  23. package/dist/src/analysis/value.js.map +1 -0
  24. package/dist/src/behaviors/defs/core.d.ts +15 -0
  25. package/dist/src/behaviors/defs/core.d.ts.map +1 -0
  26. package/dist/src/behaviors/defs/core.js +1084 -0
  27. package/dist/src/behaviors/defs/core.js.map +1 -0
  28. package/dist/src/behaviors/defs/devtools.d.ts +9 -0
  29. package/dist/src/behaviors/defs/devtools.d.ts.map +1 -0
  30. package/dist/src/behaviors/defs/devtools.js +432 -0
  31. package/dist/src/behaviors/defs/devtools.js.map +1 -0
  32. package/dist/src/behaviors/defs/macos.d.ts +12 -0
  33. package/dist/src/behaviors/defs/macos.d.ts.map +1 -0
  34. package/dist/src/behaviors/defs/macos.js +454 -0
  35. package/dist/src/behaviors/defs/macos.js.map +1 -0
  36. package/dist/src/behaviors/defs/tools.d.ts +9 -0
  37. package/dist/src/behaviors/defs/tools.d.ts.map +1 -0
  38. package/dist/src/behaviors/defs/tools.js +639 -0
  39. package/dist/src/behaviors/defs/tools.js.map +1 -0
  40. package/dist/src/behaviors/dsl.d.ts +95 -0
  41. package/dist/src/behaviors/dsl.d.ts.map +1 -0
  42. package/dist/src/behaviors/dsl.js +267 -0
  43. package/dist/src/behaviors/dsl.js.map +1 -0
  44. package/dist/src/behaviors/index.d.ts +12 -0
  45. package/dist/src/behaviors/index.d.ts.map +1 -0
  46. package/dist/src/behaviors/index.js +18 -0
  47. package/dist/src/behaviors/index.js.map +1 -0
  48. package/dist/src/behaviors/registry.d.ts +45 -0
  49. package/dist/src/behaviors/registry.d.ts.map +1 -0
  50. package/dist/src/behaviors/registry.js +266 -0
  51. package/dist/src/behaviors/registry.js.map +1 -0
  52. package/dist/src/behaviors/roles.d.ts +29 -0
  53. package/dist/src/behaviors/roles.d.ts.map +1 -0
  54. package/dist/src/behaviors/roles.js +198 -0
  55. package/dist/src/behaviors/roles.js.map +1 -0
  56. package/dist/src/behaviors/types.d.ts +120 -0
  57. package/dist/src/behaviors/types.d.ts.map +1 -0
  58. package/dist/src/behaviors/types.js +12 -0
  59. package/dist/src/behaviors/types.js.map +1 -0
  60. package/dist/src/index.d.ts +27 -0
  61. package/dist/src/index.d.ts.map +1 -0
  62. package/dist/src/index.js +24 -0
  63. package/dist/src/index.js.map +1 -0
  64. package/dist/src/integrations/deepsea.d.ts +121 -0
  65. package/dist/src/integrations/deepsea.d.ts.map +1 -0
  66. package/dist/src/integrations/deepsea.js +199 -0
  67. package/dist/src/integrations/deepsea.js.map +1 -0
  68. package/dist/src/machine.d.ts +139 -0
  69. package/dist/src/machine.d.ts.map +1 -0
  70. package/dist/src/machine.js +400 -0
  71. package/dist/src/machine.js.map +1 -0
  72. package/dist/src/parser/ast.d.ts +369 -0
  73. package/dist/src/parser/ast.d.ts.map +1 -0
  74. package/dist/src/parser/ast.js +17 -0
  75. package/dist/src/parser/ast.js.map +1 -0
  76. package/dist/src/parser/parser.d.ts +48 -0
  77. package/dist/src/parser/parser.d.ts.map +1 -0
  78. package/dist/src/parser/parser.js +1950 -0
  79. package/dist/src/parser/parser.js.map +1 -0
  80. package/dist/src/policy/effects.d.ts +86 -0
  81. package/dist/src/policy/effects.d.ts.map +1 -0
  82. package/dist/src/policy/effects.js +53 -0
  83. package/dist/src/policy/effects.js.map +1 -0
  84. package/dist/src/policy/policy.d.ts +130 -0
  85. package/dist/src/policy/policy.d.ts.map +1 -0
  86. package/dist/src/policy/policy.js +532 -0
  87. package/dist/src/policy/policy.js.map +1 -0
  88. package/dist/src/printer/doc.d.ts +151 -0
  89. package/dist/src/printer/doc.d.ts.map +1 -0
  90. package/dist/src/printer/doc.js +409 -0
  91. package/dist/src/printer/doc.js.map +1 -0
  92. package/dist/src/printer/highlight.d.ts +53 -0
  93. package/dist/src/printer/highlight.d.ts.map +1 -0
  94. package/dist/src/printer/highlight.js +339 -0
  95. package/dist/src/printer/highlight.js.map +1 -0
  96. package/dist/src/printer/html.d.ts +72 -0
  97. package/dist/src/printer/html.d.ts.map +1 -0
  98. package/dist/src/printer/html.js +423 -0
  99. package/dist/src/printer/html.js.map +1 -0
  100. package/dist/src/printer/print.d.ts +63 -0
  101. package/dist/src/printer/print.d.ts.map +1 -0
  102. package/dist/src/printer/print.js +484 -0
  103. package/dist/src/printer/print.js.map +1 -0
  104. package/dist/src/printer/summary.d.ts +50 -0
  105. package/dist/src/printer/summary.d.ts.map +1 -0
  106. package/dist/src/printer/summary.js +621 -0
  107. package/dist/src/printer/summary.js.map +1 -0
  108. package/dist/src/trace/events.d.ts +183 -0
  109. package/dist/src/trace/events.d.ts.map +1 -0
  110. package/dist/src/trace/events.js +9 -0
  111. package/dist/src/trace/events.js.map +1 -0
  112. package/dist/src/trace/progress.d.ts +58 -0
  113. package/dist/src/trace/progress.d.ts.map +1 -0
  114. package/dist/src/trace/progress.js +83 -0
  115. package/dist/src/trace/progress.js.map +1 -0
  116. package/dist/src/vm/arith.d.ts +29 -0
  117. package/dist/src/vm/arith.d.ts.map +1 -0
  118. package/dist/src/vm/arith.js +258 -0
  119. package/dist/src/vm/arith.js.map +1 -0
  120. package/dist/src/vm/builtins.d.ts +31 -0
  121. package/dist/src/vm/builtins.d.ts.map +1 -0
  122. package/dist/src/vm/builtins.js +1773 -0
  123. package/dist/src/vm/builtins.js.map +1 -0
  124. package/dist/src/vm/emulate.d.ts +75 -0
  125. package/dist/src/vm/emulate.d.ts.map +1 -0
  126. package/dist/src/vm/emulate.js +380 -0
  127. package/dist/src/vm/emulate.js.map +1 -0
  128. package/dist/src/vm/expand.d.ts +68 -0
  129. package/dist/src/vm/expand.d.ts.map +1 -0
  130. package/dist/src/vm/expand.js +744 -0
  131. package/dist/src/vm/expand.js.map +1 -0
  132. package/dist/src/vm/external.d.ts +34 -0
  133. package/dist/src/vm/external.d.ts.map +1 -0
  134. package/dist/src/vm/external.js +228 -0
  135. package/dist/src/vm/external.js.map +1 -0
  136. package/dist/src/vm/fifo.d.ts +66 -0
  137. package/dist/src/vm/fifo.d.ts.map +1 -0
  138. package/dist/src/vm/fifo.js +232 -0
  139. package/dist/src/vm/fifo.js.map +1 -0
  140. package/dist/src/vm/interp.d.ts +209 -0
  141. package/dist/src/vm/interp.d.ts.map +1 -0
  142. package/dist/src/vm/interp.js +1641 -0
  143. package/dist/src/vm/interp.js.map +1 -0
  144. package/dist/src/vm/io.d.ts +180 -0
  145. package/dist/src/vm/io.d.ts.map +1 -0
  146. package/dist/src/vm/io.js +502 -0
  147. package/dist/src/vm/io.js.map +1 -0
  148. package/dist/src/vm/pattern.d.ts +46 -0
  149. package/dist/src/vm/pattern.d.ts.map +1 -0
  150. package/dist/src/vm/pattern.js +498 -0
  151. package/dist/src/vm/pattern.js.map +1 -0
  152. package/dist/src/vm/proc.d.ts +94 -0
  153. package/dist/src/vm/proc.d.ts.map +1 -0
  154. package/dist/src/vm/proc.js +130 -0
  155. package/dist/src/vm/proc.js.map +1 -0
  156. package/dist/src/vm/state.d.ts +104 -0
  157. package/dist/src/vm/state.d.ts.map +1 -0
  158. package/dist/src/vm/state.js +233 -0
  159. package/dist/src/vm/state.js.map +1 -0
  160. package/dist/src/vm/test.d.ts +11 -0
  161. package/dist/src/vm/test.d.ts.map +1 -0
  162. package/dist/src/vm/test.js +277 -0
  163. package/dist/src/vm/test.js.map +1 -0
  164. package/package.json +63 -4
  165. package/src/analysis/analyze.ts +1395 -0
  166. package/src/analysis/fs.ts +121 -0
  167. package/src/analysis/outputs.ts +242 -0
  168. package/src/analysis/value.ts +264 -0
  169. package/src/behaviors/defs/core.ts +970 -0
  170. package/src/behaviors/defs/devtools.ts +400 -0
  171. package/src/behaviors/defs/macos.ts +426 -0
  172. package/src/behaviors/defs/tools.ts +596 -0
  173. package/src/behaviors/dsl.ts +293 -0
  174. package/src/behaviors/index.ts +19 -0
  175. package/src/behaviors/registry.ts +261 -0
  176. package/src/behaviors/roles.ts +187 -0
  177. package/src/behaviors/types.ts +127 -0
  178. package/src/index.ts +27 -0
  179. package/src/integrations/deepsea.ts +259 -0
  180. package/src/machine.ts +483 -0
  181. package/src/parser/ast.ts +472 -0
  182. package/src/parser/parser.ts +1826 -0
  183. package/src/policy/effects.ts +102 -0
  184. package/src/policy/policy.ts +578 -0
  185. package/src/printer/doc.ts +454 -0
  186. package/src/printer/highlight.ts +283 -0
  187. package/src/printer/html.ts +441 -0
  188. package/src/printer/print.ts +528 -0
  189. package/src/printer/summary.ts +601 -0
  190. package/src/trace/events.ts +102 -0
  191. package/src/trace/progress.ts +119 -0
  192. package/src/vm/arith.ts +236 -0
  193. package/src/vm/builtins.ts +1590 -0
  194. package/src/vm/emulate.ts +337 -0
  195. package/src/vm/expand.ts +721 -0
  196. package/src/vm/external.ts +265 -0
  197. package/src/vm/fifo.ts +226 -0
  198. package/src/vm/interp.ts +1623 -0
  199. package/src/vm/io.ts +570 -0
  200. package/src/vm/pattern.ts +477 -0
  201. package/src/vm/proc.ts +147 -0
  202. package/src/vm/state.ts +267 -0
  203. package/src/vm/test.ts +253 -0
@@ -0,0 +1,621 @@
1
+ /**
2
+ * One-line summaries: a snippet squeezed into a target width while keeping its
3
+ * *shape* (which commands run, how they are combined), e.g.
4
+ *
5
+ * cd …/harness-port && pnpm run build … > …/build.log 2>&1 && echo done
6
+ *
7
+ * The summary is a tree of structure tokens (command names, operators,
8
+ * keywords) and reducible parts. Starting from everything shown (with
9
+ * multi-line strings cut to their first line, here-doc bodies and comments
10
+ * omitted), it repeatedly applies the reduction with the lowest cost per
11
+ * column it saves (counting at most the columns still needed) until the line
12
+ * fits. Costs encode the priorities, cheapest first:
13
+ *
14
+ * shorten long strings, substitutions, words (head and closing quote kept) 1
15
+ * paths and redirection targets → their tail (`…/file.txt`) 1.2
16
+ * collapse assignment values (`FOO=…`; short ones cost double) 4
17
+ * hide plain arguments (strings, words) ┐ hidden runs merge into 3
18
+ * hide long flags (`--frozen-lockfile`) │ one `…` whose tooltip 3.5
19
+ * shorten path tails further │ lists them 4
20
+ * hide paths │ 6
21
+ * hide short flags (`-rn`) ┘ 8
22
+ * hide subcommands (`git status`) 80
23
+ * collapse compound bodies (`for …; do …; done`) 60
24
+ * drop trailing statements of a list (`a; b; …`) 100
25
+ * command names that are paths → their tail 150
26
+ * (and finally a hard cut at the width)
27
+ *
28
+ * Hiding an item of 6 columns or fewer costs double. Command names,
29
+ * operators, keywords and `]`/`]]` are never reduced.
30
+ *
31
+ * Every `…` keeps what it hides: truncated atoms carry the full text as a
32
+ * tooltip, hidden arguments and collapsed bodies carry their source.
33
+ */
34
+ import { parse } from "../parser/parser.js";
35
+ import { Highlight, simpleCommandRoles } from "./highlight.js";
36
+ const ELLIPSIS = '…';
37
+ const tok = (text, cls) => ({ k: 'tok', text, cls });
38
+ const SP = tok(' ');
39
+ /** Parse and summarize. Unparsable input falls back to a whitespace-collapsed, cut source. */
40
+ export function summarize(source, options = {}) {
41
+ let script;
42
+ try {
43
+ script = parse(source);
44
+ }
45
+ catch {
46
+ const width = options.width ?? 80;
47
+ const flat = source.replace(/\s+/g, ' ').trim();
48
+ const text = flat.length <= width ? flat : flat.slice(0, Math.max(0, width - 1)) + ELLIPSIS;
49
+ const html = flat.length <= width
50
+ ? `<span class="mash sum">${esc(flat)}</span>`
51
+ : `<span class="mash sum">${esc(flat.slice(0, Math.max(0, width - 1)))}<span class="el" title="${esc(source)}">${ELLIPSIS}</span></span>`;
52
+ return { text, html, reductions: 0 };
53
+ }
54
+ return summarizeScript(script, source, options);
55
+ }
56
+ export function summarizeScript(script, source, options = {}) {
57
+ const width = Math.max(4, options.width ?? 80);
58
+ const b = new Builder(source);
59
+ const root = b.list(script.body);
60
+ const atomWidth = (a) => atomPieces(a, source).reduce((n, p) => n + p.text.length, 0);
61
+ const lineWidth = () => render(root, source).reduce((n, p) => n + p.text.length, 0);
62
+ const undos = [];
63
+ for (let iter = 0; iter < 5000; iter++) {
64
+ const overflow = lineWidth() - width;
65
+ if (overflow <= 0)
66
+ break;
67
+ const cands = [];
68
+ collect(root, cands, { atomWidth, src: source, width: (n) => render(n, source).reduce((m, p) => m + p.text.length, 0) });
69
+ let best = null;
70
+ let bestScore = Infinity;
71
+ for (const c of cands) {
72
+ if (c.saving <= 0)
73
+ continue;
74
+ const score = c.cost / Math.min(c.saving, overflow + 1);
75
+ if (score < bestScore || (score === bestScore && best && c.key > best.key)) {
76
+ best = c;
77
+ bestScore = score;
78
+ }
79
+ }
80
+ if (!best)
81
+ break;
82
+ undos.push(best.apply());
83
+ }
84
+ // Relax: greedy steps can overshoot. Newest first, undo each reduction the
85
+ // line no longer needs (restoring the exact state when it is still needed:
86
+ // reductions of one atom do not commute).
87
+ let reductions = undos.length;
88
+ if (lineWidth() <= width) {
89
+ for (let i = undos.length - 1; i >= 0; i--) {
90
+ const snap = snapshot(root);
91
+ undos[i]();
92
+ if (lineWidth() > width)
93
+ restore(root, snap);
94
+ else
95
+ reductions--;
96
+ }
97
+ }
98
+ let pieces = render(root, source);
99
+ if (pieces.reduce((n, p) => n + p.text.length, 0) > width) {
100
+ pieces = hardCut(pieces, width, source);
101
+ reductions++;
102
+ }
103
+ const hl = new Highlight(script, source);
104
+ return { text: pieces.map((p) => p.text).join(''), html: toHtml(pieces, hl), reductions };
105
+ }
106
+ // ---------------------------------------------------------------------------
107
+ // AST → summary tree
108
+ // ---------------------------------------------------------------------------
109
+ class Builder {
110
+ src;
111
+ pos = 0;
112
+ constructor(src) {
113
+ this.src = src;
114
+ }
115
+ atom(span, kind) {
116
+ return { k: 'atom', start: span.start, end: span.end, kind, pos: this.pos++ };
117
+ }
118
+ classify(w) {
119
+ const text = this.src.slice(w.span.start, w.span.end);
120
+ if (/^--./.test(text))
121
+ return 'lflag';
122
+ if (/^-[^-\s]/.test(text))
123
+ return 'flag';
124
+ const bare = text.replace(/^["']|["']$/g, '');
125
+ // paths: start like one (`/`, `~`, `./`, `$HOME/`, `dir/`), no blanks, no regex/substitution syntax
126
+ if (/^(~|\.{0,2}\/|\$\{?\w+\}?\/|[\w.@+-]+\/)/.test(bare) && !/[\s^\\|()`]/.test(bare) && !/^\w\/.*\/.*\/\w*$/.test(bare))
127
+ return 'path';
128
+ if (w.parts.every((p) => p.type === 'Lit' || p.type === 'Escaped'))
129
+ return 'word';
130
+ return 'str';
131
+ }
132
+ list(stmts) {
133
+ const items = stmts.map((s) => this.stmt(s));
134
+ const seps = stmts.slice(0, -1).map((s) => (s.background ? ' ' : '; '));
135
+ return { k: 'list', items, seps, spans: stmts.map((s) => [s.span.start, s.span.end]), keep: stmts.length, pos: this.pos++ };
136
+ }
137
+ stmt(s) {
138
+ return s.background ? [this.command(s.command), SP, tok('&', 'op')] : this.command(s.command);
139
+ }
140
+ body(stmts) {
141
+ if (stmts.length === 0)
142
+ return [];
143
+ const start = stmts[0].span.start;
144
+ const end = stmts[stmts.length - 1].span.end;
145
+ return { k: 'elide', inner: this.list(stmts), start, end, collapsed: false, size: end - start };
146
+ }
147
+ redirs(rs) {
148
+ return rs.map((r) => {
149
+ if (r.heredoc)
150
+ return [SP, tok(this.src.slice(r.span.start, r.span.end), 'rd'), { k: 'fold', start: r.heredoc.body.span.start, end: r.heredoc.body.span.end }];
151
+ const opText = this.src.slice(r.span.start, r.target.span.start);
152
+ if (/^\d*[<>]&$/.test(opText.trim()) || r.target.span.end <= r.target.span.start)
153
+ return [SP, tok(this.src.slice(r.span.start, r.span.end), 'rd')];
154
+ return [SP, tok(opText, 'rd'), this.atom(r.target.span, this.classify(r.target) === 'path' ? 'target' : 'val')];
155
+ });
156
+ }
157
+ assign(a) {
158
+ const valueStart = a.value ? a.value.span.start : a.span.end;
159
+ const name = tok(this.src.slice(a.span.start, valueStart), 'asg');
160
+ if (!a.value || a.value.span.end <= a.value.span.start)
161
+ return name;
162
+ return [name, this.atom(a.value.span, 'val')];
163
+ }
164
+ command(c) {
165
+ switch (c.type) {
166
+ case 'SimpleCommand': {
167
+ const out = [];
168
+ for (const a of c.assigns)
169
+ out.push(this.assign(a), SP);
170
+ const items = [];
171
+ let name = null;
172
+ let roles = [];
173
+ try {
174
+ roles = simpleCommandRoles(c);
175
+ }
176
+ catch {
177
+ // display only
178
+ }
179
+ c.words.forEach((w, i) => {
180
+ if (w.type === 'Assign') {
181
+ items.push({ node: { ...this.atom(w.span, 'str') }, hidden: false });
182
+ return;
183
+ }
184
+ if (!name) {
185
+ name = this.atom(w.span, 'cmd');
186
+ return;
187
+ }
188
+ const text = this.src.slice(w.span.start, w.span.end);
189
+ // known roles refine the guess: files and directories shorten to their tail, patterns and code to their head
190
+ const role = roles[i];
191
+ let kind = this.classify(w);
192
+ if ((role === 'dir' || role === 'file') && kind !== 'flag' && kind !== 'lflag')
193
+ kind = 'path';
194
+ else if ((role === 'pattern' || role === 'code') && kind === 'path')
195
+ kind = 'str';
196
+ else if (role === 'subcommand')
197
+ kind = 'sc';
198
+ items.push({ node: this.atom(w.span, kind), hidden: false, fixed: text === ']' || text === ']]' });
199
+ });
200
+ if (name)
201
+ out.push(name);
202
+ else if (out.length)
203
+ out.pop();
204
+ if (items.length)
205
+ out.push(SP, { k: 'args', items });
206
+ out.push(this.redirs(c.redirs));
207
+ return out;
208
+ }
209
+ case 'Pipeline': {
210
+ const prefix = this.src.slice(c.span.start, c.stages[0].span.start).trim().replace(/\s+/g, ' ');
211
+ const out = prefix ? [tok(prefix, 'kw'), SP] : [];
212
+ c.stages.forEach((st, i) => {
213
+ if (i > 0)
214
+ out.push(SP, tok(c.pipeStderr[i - 1] ? '|&' : '|', 'op'), SP);
215
+ out.push(this.command(st));
216
+ });
217
+ return out;
218
+ }
219
+ case 'AndOr': {
220
+ const out = [];
221
+ c.items.forEach((it, i) => {
222
+ if (i > 0)
223
+ out.push(SP, tok(c.ops[i - 1], 'op'), SP);
224
+ out.push(this.command(it));
225
+ });
226
+ return out;
227
+ }
228
+ case 'FunctionDef':
229
+ return [tok(`${c.name}()`, 'fn'), SP, this.command(c.body)];
230
+ case 'Group':
231
+ return [tok('{', 'kw'), SP, this.body(c.body), tok(';', 'op'), SP, tok('}', 'kw'), this.redirs(c.redirs)];
232
+ case 'Subshell':
233
+ return [tok('(', 'kw'), SP, this.body(c.body), SP, tok(')', 'kw'), this.redirs(c.redirs)];
234
+ case 'If': {
235
+ const out = [];
236
+ c.clauses.forEach((cl, i) => {
237
+ out.push(i === 0 ? tok('if', 'kw') : [tok(';', 'op'), SP, tok('elif', 'kw')], SP, this.list(cl.cond), tok(';', 'op'), SP, tok('then', 'kw'), SP, this.body(cl.body));
238
+ });
239
+ if (c.elseBody)
240
+ out.push(tok(';', 'op'), SP, tok('else', 'kw'), SP, this.body(c.elseBody));
241
+ out.push(tok(';', 'op'), SP, tok('fi', 'kw'), this.redirs(c.redirs));
242
+ return out;
243
+ }
244
+ case 'Loop':
245
+ return [tok(c.kind, 'kw'), SP, this.list(c.cond), tok(';', 'op'), SP, tok('do', 'kw'), SP, this.body(c.body), tok(';', 'op'), SP, tok('done', 'kw'), this.redirs(c.redirs)];
246
+ case 'For':
247
+ case 'Select': {
248
+ const head = [tok(c.type === 'For' ? 'for' : 'select', 'kw'), SP, tok(c.variable, 'var')];
249
+ if (c.items !== null) {
250
+ head.push(SP, tok('in', 'kw'));
251
+ if (c.items.length)
252
+ head.push(SP, { k: 'args', items: c.items.map((w) => ({ node: this.atom(w.span, this.classify(w)), hidden: false })) });
253
+ }
254
+ return [head, tok(';', 'op'), SP, tok('do', 'kw'), SP, this.body(c.body), tok(';', 'op'), SP, tok('done', 'kw'), this.redirs(c.redirs)];
255
+ }
256
+ case 'ArithFor': {
257
+ const exprs = [c.init, c.cond, c.step].filter((e) => e !== null);
258
+ const from = exprs.length ? exprs[exprs.length - 1].span.end : c.span.start;
259
+ const close = this.src.indexOf('))', from);
260
+ return [this.atom({ start: c.span.start, end: close + 2 }, 'str'), tok(';', 'op'), SP, tok('do', 'kw'), SP, this.body(c.body), tok(';', 'op'), SP, tok('done', 'kw'), this.redirs(c.redirs)];
261
+ }
262
+ case 'Case': {
263
+ const items = [];
264
+ c.items.forEach((it, i) => {
265
+ if (i > 0)
266
+ items.push(SP);
267
+ it.patterns.forEach((p, j) => {
268
+ if (j > 0)
269
+ items.push(tok('|', 'op'));
270
+ items.push(this.atom(p.span, 'word'));
271
+ });
272
+ items.push(tok(')', 'op'));
273
+ if (it.body.length)
274
+ items.push(SP, this.list(it.body));
275
+ items.push(SP, tok(it.terminator, 'op'));
276
+ });
277
+ const start = c.items[0]?.span.start ?? c.span.start;
278
+ const end = c.items[c.items.length - 1]?.span.end ?? c.span.start;
279
+ const region = c.items.length ? { k: 'elide', inner: items, start, end, collapsed: false, size: end - start } : [];
280
+ return [tok('case', 'kw'), SP, this.atom(c.word.span, 'word'), SP, tok('in', 'kw'), SP, region, SP, tok('esac', 'kw'), this.redirs(c.redirs)];
281
+ }
282
+ case 'ArithCommand':
283
+ case 'CondCommand': {
284
+ let end = c.redirs.length > 0 ? c.redirs[0].span.start : c.span.end;
285
+ while (end > c.span.start && /\s/.test(this.src[end - 1]))
286
+ end--;
287
+ return [this.atom({ start: c.span.start, end }, 'str'), this.redirs(c.redirs)];
288
+ }
289
+ }
290
+ }
291
+ }
292
+ // ---------------------------------------------------------------------------
293
+ // Rendering
294
+ // ---------------------------------------------------------------------------
295
+ function closeLen(src, a) {
296
+ if (a.end - a.start <= 2 || a.kind === 'path' || a.kind === 'target')
297
+ return 0;
298
+ return `'")}\``.includes(src[a.end - 1]) ? 1 : 0;
299
+ }
300
+ /** The pieces showing an atom under its current limit. */
301
+ function atomPieces(a, src) {
302
+ const { start: s, end: e } = a;
303
+ const full = src.slice(s, e);
304
+ const nlAt = full.indexOf('\n');
305
+ const lineEnd = nlAt >= 0 ? s + nlAt : e;
306
+ const forced = nlAt >= 0;
307
+ const wrap = { title: full };
308
+ // paths keep their tail whenever they are shortened
309
+ const tailMode = a.tail || (a.limit !== undefined && (a.kind === 'path' || a.kind === 'target'));
310
+ if (tailMode && !forced) {
311
+ // path tail: `…/file.txt`, or the last limit-1 characters
312
+ let slash = -1;
313
+ for (let i = e - 2; i > s; i--) {
314
+ if (src[i] === '/') {
315
+ slash = i;
316
+ break;
317
+ }
318
+ }
319
+ let from = slash > s ? slash : s;
320
+ if (a.limit !== undefined && e - from + 1 > a.limit)
321
+ from = Math.max(s, e - Math.max(1, a.limit - 1));
322
+ if (from <= s)
323
+ return [{ text: full, src: [s, e] }];
324
+ return [{ text: ELLIPSIS, el: true, wrap }, { text: src.slice(from, e), src: [from, e], wrap }];
325
+ }
326
+ if (!forced && (a.limit === undefined || a.limit >= e - s))
327
+ return [{ text: full, src: [s, e] }];
328
+ const close = closeLen(src, a);
329
+ const visibleEnd = forced ? lineEnd : e;
330
+ let headEnd = visibleEnd;
331
+ if (a.limit !== undefined)
332
+ headEnd = Math.min(visibleEnd, s + Math.max(0, a.limit - 1 - close));
333
+ // never end the head inside a line with trailing blanks
334
+ while (headEnd > s && /\s/.test(src[headEnd - 1]))
335
+ headEnd--;
336
+ const out = [];
337
+ if (headEnd > s)
338
+ out.push({ text: src.slice(s, headEnd), src: [s, headEnd], wrap });
339
+ out.push({ text: ELLIPSIS, el: true, wrap });
340
+ if (close && (a.limit === undefined || a.limit > 1))
341
+ out.push({ text: src.slice(e - close, e), src: [e - close, e], wrap });
342
+ return out;
343
+ }
344
+ function render(node, src, out = []) {
345
+ if (Array.isArray(node)) {
346
+ for (const n of node)
347
+ render(n, src, out);
348
+ return out;
349
+ }
350
+ switch (node.k) {
351
+ case 'tok':
352
+ out.push({ text: node.text, cls: node.cls });
353
+ break;
354
+ case 'atom':
355
+ out.push(...atomPieces(node, src));
356
+ break;
357
+ case 'fold':
358
+ out.push({ text: ELLIPSIS, el: true, title: src.slice(node.start, node.end) });
359
+ break;
360
+ case 'elide':
361
+ if (node.collapsed)
362
+ out.push({ text: ELLIPSIS, el: true, title: src.slice(node.start, node.end) });
363
+ else
364
+ render(node.inner, src, out);
365
+ break;
366
+ case 'list': {
367
+ const n = node.items.length;
368
+ for (let i = 0; i < Math.min(node.keep, n); i++) {
369
+ if (i > 0)
370
+ out.push({ text: node.seps[i - 1], cls: node.seps[i - 1].trim() ? 'op' : undefined });
371
+ render(node.items[i], src, out);
372
+ }
373
+ if (node.keep < n) {
374
+ if (node.keep > 0)
375
+ out.push({ text: node.seps[node.keep - 1], cls: 'op' });
376
+ out.push({ text: ELLIPSIS, el: true, title: src.slice(node.spans[node.keep][0], node.spans[n - 1][1]) });
377
+ }
378
+ break;
379
+ }
380
+ case 'args': {
381
+ let i = 0;
382
+ let first = true;
383
+ while (i < node.items.length) {
384
+ if (!first)
385
+ out.push({ text: ' ' });
386
+ first = false;
387
+ const it = node.items[i];
388
+ if (!it.hidden) {
389
+ render(it.node, src, out);
390
+ i++;
391
+ continue;
392
+ }
393
+ let j = i;
394
+ while (j < node.items.length && node.items[j].hidden)
395
+ j++;
396
+ out.push({ text: ELLIPSIS, el: true, title: src.slice(node.items[i].node.start, node.items[j - 1].node.end) });
397
+ i = j;
398
+ }
399
+ break;
400
+ }
401
+ }
402
+ return out;
403
+ }
404
+ function collect(node, cands, ctx, inArgs = false) {
405
+ if (Array.isArray(node)) {
406
+ for (const n of node)
407
+ collect(n, cands, ctx);
408
+ return;
409
+ }
410
+ switch (node.k) {
411
+ case 'atom':
412
+ atomCands(node, cands, ctx, inArgs);
413
+ return;
414
+ case 'elide': {
415
+ if (node.collapsed)
416
+ return;
417
+ collect(node.inner, cands, ctx);
418
+ cands.push({ cost: 60, saving: ctx.width(node.inner) - 1, key: node.size, apply: () => { node.collapsed = true; return () => { node.collapsed = false; }; } });
419
+ return;
420
+ }
421
+ case 'list': {
422
+ const shown = Math.min(node.keep, node.items.length);
423
+ for (let i = 0; i < shown; i++)
424
+ collect(node.items[i], cands, ctx);
425
+ if (node.keep > 1) {
426
+ const last = node.keep - 1;
427
+ // dropping the last shown statement: when others are already dropped, it merges into their `…`
428
+ const saving = ctx.width(node.items[last]) + (node.keep < node.items.length ? node.seps[last - 1].length : -1);
429
+ cands.push({ cost: 100, saving, key: node.pos, apply: () => { node.keep--; return () => { node.keep++; }; } });
430
+ }
431
+ return;
432
+ }
433
+ case 'args':
434
+ node.items.forEach((it, i) => {
435
+ if (it.hidden)
436
+ return;
437
+ collect(it.node, cands, ctx, true);
438
+ if (it.fixed)
439
+ return;
440
+ const w = ctx.atomWidth(it.node);
441
+ // short items say a lot per column: hiding them costs double
442
+ // subcommands are nearly structure (`git status`): hidden only after compound bodies collapse
443
+ const cost = { str: 3, word: 3, val: 3, cmd: 3, sc: 80, lflag: 3.5, target: 6, path: 6, flag: 8 }[it.node.kind] * (w <= 6 ? 2 : 1);
444
+ const merges = (node.items[i - 1]?.hidden ? 1 : 0) + (node.items[i + 1]?.hidden ? 1 : 0);
445
+ // alone it becomes `…`; next to a hidden run it disappears with its space
446
+ const saving = merges === 0 ? w - 1 : merges === 1 ? w + 1 : w + 3;
447
+ cands.push({ cost, saving, key: it.node.pos, apply: () => { it.hidden = true; return () => { it.hidden = false; }; } });
448
+ });
449
+ return;
450
+ default:
451
+ return;
452
+ }
453
+ }
454
+ function atomCands(a, cands, ctx, inArgs) {
455
+ const w = ctx.atomWidth(a);
456
+ const shorten = (cost, minKeep) => {
457
+ // shorten by half at a time (at least to minKeep); re-scored every round
458
+ const limit = Math.max(minKeep, Math.ceil(w / 2));
459
+ if (limit < w)
460
+ cands.push({ cost, saving: w - limit, key: a.pos, apply: () => { const old = a.limit; a.limit = limit; return () => { a.limit = old; }; } });
461
+ };
462
+ const toTail = (cost) => {
463
+ a.tail = true;
464
+ const tw = ctx.atomWidth(a);
465
+ a.tail = false;
466
+ if (tw < w)
467
+ cands.push({ cost, saving: w - tw, key: a.pos, apply: () => { a.tail = true; return () => { a.tail = false; }; } });
468
+ };
469
+ switch (a.kind) {
470
+ case 'str':
471
+ case 'word':
472
+ if (w > 12)
473
+ shorten(1, 12);
474
+ return;
475
+ case 'path':
476
+ case 'target':
477
+ if (w <= 12)
478
+ return; // short paths (`/dev/null`) are clearer whole; they can still be hidden as arguments
479
+ if (!a.tail)
480
+ toTail(1.2);
481
+ else
482
+ shorten(4, a.kind === 'target' ? 6 : 8);
483
+ return;
484
+ case 'val':
485
+ if (w > 12)
486
+ shorten(1, 12);
487
+ else if (w > 1)
488
+ cands.push({ cost: w <= 6 ? 8 : 4, saving: w - 1, key: a.pos, apply: () => { const old = a.limit; a.limit = 1; return () => { a.limit = old; }; } });
489
+ return;
490
+ case 'cmd':
491
+ if (inArgs)
492
+ return;
493
+ // only command names that are paths (`/usr/local/bin/x`, `./scripts/build.sh`) shrink
494
+ if (!ctx.src.slice(a.start, a.end).includes('/'))
495
+ return;
496
+ if (!a.tail)
497
+ toTail(150);
498
+ else
499
+ shorten(150, 6);
500
+ return;
501
+ default:
502
+ return;
503
+ }
504
+ }
505
+ /** The reduction state of a tree (limits, tails, hidden args, collapsed bodies, list lengths). */
506
+ function snapshot(node, out = []) {
507
+ if (Array.isArray(node)) {
508
+ for (const n of node)
509
+ snapshot(n, out);
510
+ return out;
511
+ }
512
+ switch (node.k) {
513
+ case 'atom':
514
+ out.push(node.limit, node.tail);
515
+ break;
516
+ case 'elide':
517
+ out.push(node.collapsed);
518
+ snapshot(node.inner, out);
519
+ break;
520
+ case 'list':
521
+ out.push(node.keep);
522
+ for (const it of node.items)
523
+ snapshot(it, out);
524
+ break;
525
+ case 'args':
526
+ for (const it of node.items) {
527
+ out.push(it.hidden);
528
+ snapshot(it.node, out);
529
+ }
530
+ break;
531
+ default: break;
532
+ }
533
+ return out;
534
+ }
535
+ function restore(node, snap, at = { i: 0 }) {
536
+ if (Array.isArray(node)) {
537
+ for (const n of node)
538
+ restore(n, snap, at);
539
+ return;
540
+ }
541
+ switch (node.k) {
542
+ case 'atom':
543
+ node.limit = snap[at.i++];
544
+ node.tail = snap[at.i++];
545
+ break;
546
+ case 'elide':
547
+ node.collapsed = snap[at.i++];
548
+ restore(node.inner, snap, at);
549
+ break;
550
+ case 'list':
551
+ node.keep = snap[at.i++];
552
+ for (const it of node.items)
553
+ restore(it, snap, at);
554
+ break;
555
+ case 'args':
556
+ for (const it of node.items) {
557
+ it.hidden = snap[at.i++];
558
+ restore(it.node, snap, at);
559
+ }
560
+ break;
561
+ default: break;
562
+ }
563
+ }
564
+ /** Cut the rendered line at `width` columns, ending in an ellipsis that holds the rest. */
565
+ function hardCut(pieces, width, src) {
566
+ const out = [];
567
+ let used = 0;
568
+ const budget = width - 1;
569
+ let restStart = null;
570
+ for (const p of pieces) {
571
+ if (used + p.text.length <= budget) {
572
+ out.push(p);
573
+ used += p.text.length;
574
+ continue;
575
+ }
576
+ const room = budget - used;
577
+ if (room > 0) {
578
+ const text = p.text.slice(0, room);
579
+ out.push({ ...p, text, src: p.src ? [p.src[0], p.src[0] + room] : undefined, el: false });
580
+ used += room;
581
+ }
582
+ if (p.src && restStart === null)
583
+ restStart = p.src[0] + Math.max(0, room);
584
+ break;
585
+ }
586
+ out.push({ text: ELLIPSIS, el: true, title: restStart !== null ? src.slice(restStart) : src });
587
+ return out;
588
+ }
589
+ // ---------------------------------------------------------------------------
590
+ // HTML
591
+ // ---------------------------------------------------------------------------
592
+ const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]);
593
+ function pieceHtml(p, hl) {
594
+ if (p.el)
595
+ return `<span class="el"${p.title !== undefined ? ` title="${esc(p.title)}"` : ''}>${esc(p.text)}</span>`;
596
+ if (p.src) {
597
+ return hl.runs(p.src[0], p.src[1]).map((r) => {
598
+ const cls = r.cls || p.cls;
599
+ return cls ? `<span class="${cls}">${esc(r.text)}</span>` : esc(r.text);
600
+ }).join('');
601
+ }
602
+ return p.cls ? `<span class="${p.cls}">${esc(p.text)}</span>` : esc(p.text);
603
+ }
604
+ function toHtml(pieces, hl) {
605
+ let html = '';
606
+ for (let i = 0; i < pieces.length; i++) {
607
+ const p = pieces[i];
608
+ if (p.wrap) {
609
+ let inner = '';
610
+ let j = i;
611
+ while (j < pieces.length && pieces[j].wrap === p.wrap)
612
+ inner += pieceHtml(pieces[j++], hl);
613
+ html += `<span class="tr" title="${esc(p.wrap.title)}">${inner}</span>`;
614
+ i = j - 1;
615
+ continue;
616
+ }
617
+ html += pieceHtml(p, hl);
618
+ }
619
+ return `<span class="mash sum">${html}</span>`;
620
+ }
621
+ //# sourceMappingURL=summary.js.map