flagstaff 0.0.1 → 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.
@@ -0,0 +1,785 @@
1
+ import chalk from 'roundel/chalk';
2
+ import { measure } from './width.js';
3
+ const ESC = '\u001B';
4
+ const SGR = /\u001B\[(?:\d*;){0,5}\d*m/g;
5
+ const SGR_CAPTURE = /\u001B\[((?:\d*;){0,5}\d*)m/g;
6
+ const HYPERLINK_TAG = `${ESC}]8;;\u0007`;
7
+ const HALF = 2;
8
+ export function strlen(str) {
9
+ const stripped = String(str).replaceAll(SGR, '');
10
+ return stripped.split('\n').reduce((memo, s) => Math.max(memo, measure(s)), 0);
11
+ }
12
+ const repeat = (str, times) => (times > 0 ? str.repeat(times) : '');
13
+ export function pad(str, len, padChar, dir) {
14
+ const length = strlen(str);
15
+ if (len + 1 < length)
16
+ return str;
17
+ const padlen = len - length;
18
+ if (dir === 'right')
19
+ return repeat(padChar, padlen) + str;
20
+ if (dir === 'center') {
21
+ const right = Math.ceil(padlen / HALF);
22
+ return repeat(padChar, padlen - right) + str + repeat(padChar, right);
23
+ }
24
+ return str + repeat(padChar, padlen);
25
+ }
26
+ const CODE_CACHE = {};
27
+ const CODE_PAIRS = {};
28
+ for (const [name, on, off] of [
29
+ ['bold', 1, 22],
30
+ ['italics', 3, 23],
31
+ ['underline', 4, 24],
32
+ ['inverse', 7, 27],
33
+ ['strikethrough', 9, 29],
34
+ ]) {
35
+ const onCode = `${ESC}[${on}m`;
36
+ const offCode = `${ESC}[${off}m`;
37
+ CODE_CACHE[onCode] = { set: name, to: true };
38
+ CODE_CACHE[offCode] = { set: name, to: false };
39
+ CODE_PAIRS[name] = { on: onCode, off: offCode };
40
+ }
41
+ const FG_LOW = 30;
42
+ const FG_HIGH = 39;
43
+ const BG_LOW = 40;
44
+ const BG_HIGH = 49;
45
+ const FG_BRIGHT_LOW = 90;
46
+ const FG_BRIGHT_HIGH = 97;
47
+ const BG_BRIGHT_LOW = 100;
48
+ const BG_BRIGHT_HIGH = 107;
49
+ const DECIMAL = 10;
50
+ function updateState(state, controlChars) {
51
+ const code = controlChars[1] ? Number.parseInt(controlChars[1].split(';')[0] ?? '0', DECIMAL) : 0;
52
+ if ((code >= FG_LOW && code <= FG_HIGH) || (code >= FG_BRIGHT_LOW && code <= FG_BRIGHT_HIGH)) {
53
+ state['lastForegroundAdded'] = controlChars[0];
54
+ return;
55
+ }
56
+ if ((code >= BG_LOW && code <= BG_HIGH) || (code >= BG_BRIGHT_LOW && code <= BG_BRIGHT_HIGH)) {
57
+ state['lastBackgroundAdded'] = controlChars[0];
58
+ return;
59
+ }
60
+ if (code === 0) {
61
+ for (const key of Object.keys(state))
62
+ delete state[key];
63
+ return;
64
+ }
65
+ const info = CODE_CACHE[controlChars[0]];
66
+ if (info)
67
+ state[info.set] = info.to;
68
+ }
69
+ function readState(line) {
70
+ const code = new RegExp(SGR_CAPTURE.source, 'g');
71
+ const state = {};
72
+ let controlChars = code.exec(line);
73
+ while (controlChars !== null) {
74
+ updateState(state, controlChars);
75
+ controlChars = code.exec(line);
76
+ }
77
+ return state;
78
+ }
79
+ function unwindState(state, ret) {
80
+ const lastBackground = state['lastBackgroundAdded'];
81
+ const lastForeground = state['lastForegroundAdded'];
82
+ delete state['lastBackgroundAdded'];
83
+ delete state['lastForegroundAdded'];
84
+ let out = ret;
85
+ for (const key of Object.keys(state)) {
86
+ if (state[key])
87
+ out += CODE_PAIRS[key]?.off ?? '';
88
+ }
89
+ if (lastBackground !== undefined && lastBackground !== `${ESC}[49m`)
90
+ out += `${ESC}[49m`;
91
+ if (lastForeground !== undefined && lastForeground !== `${ESC}[39m`)
92
+ out += `${ESC}[39m`;
93
+ return out;
94
+ }
95
+ function rewindState(state, ret) {
96
+ const lastBackground = state['lastBackgroundAdded'];
97
+ const lastForeground = state['lastForegroundAdded'];
98
+ delete state['lastBackgroundAdded'];
99
+ delete state['lastForegroundAdded'];
100
+ let out = ret;
101
+ for (const key of Object.keys(state)) {
102
+ if (state[key])
103
+ out = (CODE_PAIRS[key]?.on ?? '') + out;
104
+ }
105
+ if (typeof lastBackground === 'string' && lastBackground !== `${ESC}[49m`)
106
+ out = lastBackground + out;
107
+ if (typeof lastForeground === 'string' && lastForeground !== `${ESC}[39m`)
108
+ out = lastForeground + out;
109
+ return out;
110
+ }
111
+ function truncateWidth(str, desiredLength) {
112
+ if (str.length === strlen(str))
113
+ return str.slice(0, Math.max(0, desiredLength));
114
+ let out = str;
115
+ while (strlen(out) > desiredLength)
116
+ out = out.slice(0, -1);
117
+ return out;
118
+ }
119
+ function truncateWidthWithAnsi(str, desiredLength) {
120
+ const code = new RegExp(SGR_CAPTURE.source, 'g');
121
+ const split = str.split(SGR);
122
+ let splitIndex = 0;
123
+ let retLen = 0;
124
+ let ret = '';
125
+ const state = {};
126
+ while (retLen < desiredLength) {
127
+ const found = code.exec(str);
128
+ let toAdd = split[splitIndex] ?? '';
129
+ splitIndex += 1;
130
+ if (retLen + strlen(toAdd) > desiredLength)
131
+ toAdd = truncateWidth(toAdd, desiredLength - retLen);
132
+ ret += toAdd;
133
+ retLen += strlen(toAdd);
134
+ if (retLen < desiredLength) {
135
+ if (!found)
136
+ break;
137
+ ret += found[0];
138
+ updateState(state, found);
139
+ }
140
+ }
141
+ return unwindState(state, ret);
142
+ }
143
+ export function truncate(str, desiredLength, truncateChar = '…') {
144
+ if (strlen(str) <= desiredLength)
145
+ return str;
146
+ let ret = truncateWidthWithAnsi(str, desiredLength - strlen(truncateChar));
147
+ ret += truncateChar;
148
+ if (str.includes(HYPERLINK_TAG) && !ret.includes(HYPERLINK_TAG))
149
+ ret += HYPERLINK_TAG;
150
+ return ret;
151
+ }
152
+ const WHITESPACE = /(\s+)/g;
153
+ function wordWrapLine(maxLength, input) {
154
+ const lines = [];
155
+ const split = input.split(WHITESPACE);
156
+ let line = [];
157
+ let lineLength = 0;
158
+ let whitespace;
159
+ for (let i = 0; i < split.length; i += HALF) {
160
+ const word = split[i] ?? '';
161
+ let newLength = lineLength + strlen(word);
162
+ if (lineLength > 0 && whitespace !== undefined)
163
+ newLength += whitespace.length;
164
+ if (newLength > maxLength) {
165
+ if (lineLength !== 0)
166
+ lines.push(line.join(''));
167
+ line = [word];
168
+ lineLength = strlen(word);
169
+ }
170
+ else {
171
+ line.push(whitespace ?? '', word);
172
+ lineLength = newLength;
173
+ }
174
+ whitespace = split[i + 1];
175
+ }
176
+ if (lineLength)
177
+ lines.push(line.join(''));
178
+ return lines;
179
+ }
180
+ function textWrapLine(maxLength, input) {
181
+ const lines = [];
182
+ let line = '';
183
+ const pushLine = (str, ws) => {
184
+ if (line.length && ws !== undefined && ws !== '')
185
+ line += ws;
186
+ line += str;
187
+ while (line.length > maxLength) {
188
+ lines.push(line.slice(0, maxLength));
189
+ line = line.slice(maxLength);
190
+ }
191
+ };
192
+ const split = input.split(WHITESPACE);
193
+ for (let i = 0; i < split.length; i += HALF)
194
+ pushLine(split[i] ?? '', i ? split[i - 1] : undefined);
195
+ if (line.length)
196
+ lines.push(line);
197
+ return lines;
198
+ }
199
+ export function wordWrap(maxLength, input, wrapOnWordBoundary = true) {
200
+ const handler = wrapOnWordBoundary ? wordWrapLine : textWrapLine;
201
+ return input.split('\n').flatMap((line) => handler(maxLength, line));
202
+ }
203
+ function colorizeLines(input) {
204
+ let state = {};
205
+ return input.map((raw) => {
206
+ const line = rewindState(state, raw);
207
+ state = readState(line);
208
+ return unwindState({ ...state }, line);
209
+ });
210
+ }
211
+ export function hyperlink(url, text) {
212
+ const OSC = `${ESC}]`;
213
+ const BEL = '\u0007';
214
+ return [OSC, '8', ';', ';', url || text, BEL, text, OSC, '8', ';', ';', BEL].join('');
215
+ }
216
+ const DEBUG_LEVEL = { WARN: 1, INFO: 2, DEBUG: 3 };
217
+ let debugMessages = [];
218
+ let debugLevel = 0;
219
+ const record = (msg, min) => {
220
+ if (debugLevel >= min)
221
+ debugMessages.push(msg);
222
+ };
223
+ const warn = (msg) => record(msg, DEBUG_LEVEL.WARN);
224
+ const info = (msg) => record(msg, DEBUG_LEVEL.INFO);
225
+ const debugLog = (msg) => record(msg, DEBUG_LEVEL.DEBUG);
226
+ function defaultOptions() {
227
+ return {
228
+ chars: {
229
+ 'top': '─',
230
+ 'top-mid': '┬',
231
+ 'top-left': '┌',
232
+ 'top-right': '┐',
233
+ 'bottom': '─',
234
+ 'bottom-mid': '┴',
235
+ 'bottom-left': '└',
236
+ 'bottom-right': '┘',
237
+ 'left': '│',
238
+ 'left-mid': '├',
239
+ 'mid': '─',
240
+ 'mid-mid': '┼',
241
+ 'right': '│',
242
+ 'right-mid': '┤',
243
+ 'middle': '│',
244
+ },
245
+ truncate: '…',
246
+ colWidths: [],
247
+ rowHeights: [],
248
+ colAligns: [],
249
+ rowAligns: [],
250
+ style: { 'padding-left': 1, 'padding-right': 1, 'head': ['red'], 'border': ['grey'], 'compact': false },
251
+ head: [],
252
+ };
253
+ }
254
+ export function mergeOptions(options, defaults) {
255
+ const base = defaults ?? defaultOptions();
256
+ const given = options ?? {};
257
+ return { ...base, ...given, chars: { ...base.chars, ...given.chars }, style: { ...base.style, ...given.style } };
258
+ }
259
+ const CHAR_NAMES = ['top', 'top-mid', 'top-left', 'top-right', 'bottom', 'bottom-mid', 'bottom-left', 'bottom-right', 'left', 'left-mid', 'mid', 'mid-mid', 'right', 'right-mid', 'middle'];
260
+ const firstDefined = (...args) => args.find((v) => v !== undefined && v !== null);
261
+ function setOption(objA, objB, nameB, target) {
262
+ const parts = nameB.split('-');
263
+ if (parts.length > 1) {
264
+ const nameA = parts[0] + parts.slice(1).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join('');
265
+ target[nameA] = firstDefined(objA[nameA], objA[nameB], objB[nameA], objB[nameB]);
266
+ return;
267
+ }
268
+ target[nameB] = firstDefined(objA[nameB], objB[nameB]);
269
+ }
270
+ const sumPlusOne = (a, b) => a + b + 1;
271
+ function findDimension(table, startingIndex, span) {
272
+ let ret = table[startingIndex] ?? 0;
273
+ for (let i = 1; i < span; i += 1)
274
+ ret += 1 + (table[startingIndex + i] ?? 0);
275
+ return ret;
276
+ }
277
+ function styled(names, content) {
278
+ if (!names || names.length === 0)
279
+ return content;
280
+ let fn = chalk;
281
+ for (let i = names.length - 1; i >= 0; i -= 1) {
282
+ fn = typeof fn === 'function' || typeof fn === 'object' ? Reflect.get(fn, names[i] ?? '') : undefined;
283
+ if (fn === undefined)
284
+ return content;
285
+ }
286
+ return typeof fn === 'function' ? fn(content) : content;
287
+ }
288
+ export class Cell {
289
+ x = 0;
290
+ y = 0;
291
+ colSpan;
292
+ rowSpan;
293
+ content;
294
+ options;
295
+ chars = {};
296
+ truncate = '…';
297
+ paddingLeft = 0;
298
+ paddingRight = 0;
299
+ head;
300
+ border;
301
+ fixedWidth;
302
+ lines = [];
303
+ desiredWidth = 0;
304
+ desiredHeight = 0;
305
+ widths = [];
306
+ heights = [];
307
+ width = 0;
308
+ height = 0;
309
+ hAlign;
310
+ vAlign;
311
+ drawRight = false;
312
+ cells;
313
+ href;
314
+ constructor(options) {
315
+ const opts = typeof options === 'boolean' || typeof options === 'number' || typeof options === 'bigint' || typeof options === 'string' ? { content: String(options) } : (options ?? {});
316
+ this.options = opts;
317
+ const content = opts['content'];
318
+ if (typeof content === 'boolean' || typeof content === 'number' || typeof content === 'bigint' || typeof content === 'string')
319
+ this.content = String(content);
320
+ else if (!content)
321
+ this.content = opts['href'] ?? '';
322
+ else
323
+ throw new Error(`Content needs to be a primitive, got: ${typeof content}`);
324
+ this.colSpan = opts['colSpan'] ?? 1;
325
+ this.rowSpan = opts['rowSpan'] ?? 1;
326
+ if (opts['href'] !== undefined)
327
+ this.href = opts['href'];
328
+ }
329
+ mergeTableOptions(tableOptions, cells) {
330
+ this.cells = cells;
331
+ const optionsChars = (this.options['chars'] ?? {});
332
+ const chars = {};
333
+ for (const name of CHAR_NAMES)
334
+ setOption(optionsChars, tableOptions.chars, name, chars);
335
+ this.chars = chars;
336
+ this.truncate = this.options['truncate'] ?? tableOptions.truncate ?? '…';
337
+ const style = (this.options['style'] ?? {});
338
+ this.options['style'] = style;
339
+ const tableStyle = tableOptions.style;
340
+ setOption(style, tableStyle, 'padding-left', this);
341
+ setOption(style, tableStyle, 'padding-right', this);
342
+ this.head = (style['head'] ?? tableStyle['head']);
343
+ this.border = (style['border'] ?? tableStyle['border']);
344
+ this.fixedWidth = tableOptions.colWidths[this.x];
345
+ this.lines = this.computeLines(tableOptions);
346
+ this.desiredWidth = strlen(this.content) + this.paddingLeft + this.paddingRight;
347
+ this.desiredHeight = this.lines.length;
348
+ }
349
+ computeLines(tableOptions) {
350
+ const tableWordWrap = tableOptions.wordWrap ?? tableOptions.textWrap;
351
+ const wrap = this.options['wordWrap'] ?? tableWordWrap;
352
+ if (this.fixedWidth && wrap) {
353
+ this.fixedWidth -= this.paddingLeft + this.paddingRight;
354
+ if (this.colSpan) {
355
+ for (let i = 1; i < this.colSpan; i += 1)
356
+ this.fixedWidth += tableOptions.colWidths[this.x + i] ?? 0;
357
+ }
358
+ const tableBoundary = tableOptions.wrapOnWordBoundary ?? true;
359
+ const onBoundary = this.options['wrapOnWordBoundary'] ?? tableBoundary;
360
+ return this.wrapLines(wordWrap(this.fixedWidth, this.content, onBoundary));
361
+ }
362
+ return this.wrapLines(this.content.split('\n'));
363
+ }
364
+ wrapLines(computedLines) {
365
+ return colorizeLines(computedLines);
366
+ }
367
+ init(tableOptions) {
368
+ const { x, y } = this;
369
+ this.widths = tableOptions.colWidths.slice(x, x + this.colSpan);
370
+ this.heights = tableOptions.rowHeights.slice(y, y + this.rowSpan);
371
+ this.width = this.widths.reduce(sumPlusOne, -1);
372
+ this.height = this.heights.reduce(sumPlusOne, -1);
373
+ this.hAlign = this.options['hAlign'] ?? tableOptions.colAligns?.[x];
374
+ this.vAlign = this.options['vAlign'] ?? tableOptions.rowAligns?.[y];
375
+ this.drawRight = x + this.colSpan === tableOptions.colWidths.length;
376
+ }
377
+ draw(lineNum, spanningCell) {
378
+ if (lineNum === 'top')
379
+ return this.drawTop(this.drawRight);
380
+ if (lineNum === 'bottom')
381
+ return this.drawBottom(this.drawRight);
382
+ const n0 = lineNum;
383
+ if (!n0)
384
+ info(`${this.y}-${this.x}: ${this.rowSpan - n0}x${this.colSpan} Cell ${truncate(this.content, 10, this.truncate)}`);
385
+ const padLen = Math.max(this.height - this.lines.length, 0);
386
+ let padTop = 0;
387
+ if (this.vAlign === 'center')
388
+ padTop = Math.ceil(padLen / HALF);
389
+ else if (this.vAlign === 'bottom')
390
+ padTop = padLen;
391
+ const n = lineNum;
392
+ if (n < padTop || n >= padTop + this.lines.length)
393
+ return this.drawEmpty(this.drawRight, spanningCell);
394
+ const forceTruncation = this.lines.length > this.height && n + 1 >= this.height;
395
+ return this.drawLine(n - padTop, this.drawRight, forceTruncation, spanningCell);
396
+ }
397
+ drawTop(drawRight) {
398
+ const content = [];
399
+ if (this.cells) {
400
+ this.widths.forEach((width, index) => {
401
+ content.push(this.topLeftChar(index), repeat(this.chars[this.y === 0 ? 'top' : 'mid'] ?? '', width));
402
+ });
403
+ }
404
+ else {
405
+ content.push(this.topLeftChar(0), repeat(this.chars[this.y === 0 ? 'top' : 'mid'] ?? '', this.width));
406
+ }
407
+ if (drawRight)
408
+ content.push(this.chars[this.y === 0 ? 'topRight' : 'rightMid'] ?? '');
409
+ return styled(this.border, content.join(''));
410
+ }
411
+ topLeftChar(offset) {
412
+ const x = this.x + offset;
413
+ let leftChar;
414
+ if (this.y === 0) {
415
+ leftChar = x === 0 ? 'topLeft' : offset === 0 ? 'topMid' : 'top';
416
+ }
417
+ else if (x === 0) {
418
+ leftChar = 'leftMid';
419
+ }
420
+ else {
421
+ leftChar = offset === 0 ? 'midMid' : 'bottomMid';
422
+ if (this.cells) {
423
+ if (this.cells[this.y - 1]?.[x] instanceof ColSpanCell)
424
+ leftChar = offset === 0 ? 'topMid' : 'mid';
425
+ if (offset === 0) {
426
+ let i = 1;
427
+ while (this.cells[this.y]?.[x - i] instanceof ColSpanCell)
428
+ i += 1;
429
+ if (this.cells[this.y]?.[x - i] instanceof RowSpanCell)
430
+ leftChar = 'leftMid';
431
+ }
432
+ }
433
+ }
434
+ return this.chars[leftChar] ?? '';
435
+ }
436
+ leftEdge(spanningCell) {
437
+ let left = this.chars[this.x === 0 ? 'left' : 'middle'] ?? '';
438
+ if (this.x && spanningCell !== undefined && this.cells) {
439
+ let cellLeft = this.cells[this.y + spanningCell]?.[this.x - 1];
440
+ while (cellLeft instanceof ColSpanCell)
441
+ cellLeft = this.cells[cellLeft.y]?.[cellLeft.x - 1];
442
+ if (!(cellLeft instanceof RowSpanCell))
443
+ left = this.chars['rightMid'] ?? '';
444
+ }
445
+ return left;
446
+ }
447
+ drawLine(lineNum, drawRight, forceTruncationSymbol, spanningCell) {
448
+ const left = this.leftEdge(spanningCell);
449
+ const right = drawRight ? (this.chars['right'] ?? '') : '';
450
+ let line = this.lines[lineNum] ?? '';
451
+ const len = this.width - (this.paddingLeft + this.paddingRight);
452
+ if (forceTruncationSymbol)
453
+ line += this.truncate;
454
+ let content = truncate(line, len, this.truncate);
455
+ if (this.href !== undefined)
456
+ content = hyperlink(this.href, content);
457
+ content = pad(content, len, ' ', this.hAlign);
458
+ content = repeat(' ', this.paddingLeft) + content + repeat(' ', this.paddingRight);
459
+ return this.stylizeLine(left, content, right);
460
+ }
461
+ stylizeLine(left, content, right) {
462
+ const l = styled(this.border, left);
463
+ const r = styled(this.border, right);
464
+ return l + (this.y === 0 ? styled(this.head, content) : content) + r;
465
+ }
466
+ drawBottom(drawRight) {
467
+ const left = this.chars[this.x === 0 ? 'bottomLeft' : 'bottomMid'] ?? '';
468
+ const content = repeat(this.chars['bottom'] ?? '', this.width);
469
+ const right = drawRight ? (this.chars['bottomRight'] ?? '') : '';
470
+ return styled(this.border, left + content + right);
471
+ }
472
+ drawEmpty(drawRight, spanningCell) {
473
+ const left = this.leftEdge(spanningCell);
474
+ const right = drawRight ? (this.chars['right'] ?? '') : '';
475
+ return this.stylizeLine(left, repeat(' ', this.width), right);
476
+ }
477
+ }
478
+ export class ColSpanCell {
479
+ x = 0;
480
+ y = 0;
481
+ colSpan = 1;
482
+ rowSpan = 1;
483
+ draw(lineNum) {
484
+ if (typeof lineNum === 'number')
485
+ debugLog(`${this.y}-${this.x}: 1x1 ColSpanCell`);
486
+ return '';
487
+ }
488
+ init() {
489
+ }
490
+ mergeTableOptions() {
491
+ }
492
+ }
493
+ export class RowSpanCell {
494
+ originalCell;
495
+ x = 0;
496
+ y = 0;
497
+ colSpan = 1;
498
+ rowSpan = 1;
499
+ cellOffset = 0;
500
+ offset = 0;
501
+ constructor(originalCell) {
502
+ this.originalCell = originalCell;
503
+ }
504
+ init(tableOptions) {
505
+ this.cellOffset = this.y - this.originalCell.y;
506
+ this.offset = findDimension(tableOptions.rowHeights, this.originalCell.y, this.cellOffset);
507
+ }
508
+ draw(lineNum) {
509
+ if (lineNum === 'top')
510
+ return this.originalCell.draw(this.offset, this.cellOffset);
511
+ if (lineNum === 'bottom')
512
+ return this.originalCell.draw('bottom');
513
+ debugLog(`${this.y}-${this.x}: 1x${this.colSpan} RowSpanCell for ${this.originalCell.content}`);
514
+ return this.originalCell.draw(this.offset + 1 + lineNum);
515
+ }
516
+ mergeTableOptions() {
517
+ }
518
+ }
519
+ function nextFree(alloc, col) {
520
+ let c = col;
521
+ while ((alloc[c] ?? 0) > 0)
522
+ c += 1;
523
+ return c;
524
+ }
525
+ function layoutTable(table) {
526
+ const alloc = {};
527
+ table.forEach((row, rowIndex) => {
528
+ let col = 0;
529
+ for (const cell of row) {
530
+ cell.y = rowIndex;
531
+ cell.x = rowIndex ? nextFree(alloc, col) : col;
532
+ const rowSpan = cell.rowSpan || 1;
533
+ const colSpan = cell.colSpan || 1;
534
+ if (rowSpan > 1) {
535
+ for (let cs = 0; cs < colSpan; cs += 1)
536
+ alloc[cell.x + cs] = rowSpan;
537
+ }
538
+ col = cell.x + colSpan;
539
+ }
540
+ for (const idx of Object.keys(alloc)) {
541
+ const key = Number(idx);
542
+ alloc[key] = (alloc[key] ?? 0) - 1;
543
+ if ((alloc[key] ?? 0) < 1)
544
+ delete alloc[key];
545
+ }
546
+ });
547
+ }
548
+ const maxWidth = (table) => table.reduce((mw, row) => row.reduce((m, cell) => Math.max(m, cell.x + (cell.colSpan || 1)), mw), 0);
549
+ function cellsConflict(a, b) {
550
+ const yMax1 = a.y - 1 + (a.rowSpan ?? 1);
551
+ const yMax2 = b.y - 1 + (b.rowSpan || 1);
552
+ const yConflict = !(a.y > yMax2 || b.y > yMax1);
553
+ const xMax1 = a.x - 1 + (a.colSpan ?? 1);
554
+ const xMax2 = b.x - 1 + (b.colSpan || 1);
555
+ const xConflict = !(a.x > xMax2 || b.x > xMax1);
556
+ return yConflict && xConflict;
557
+ }
558
+ function conflictExists(rows, x, y) {
559
+ const iMax = Math.min(rows.length - 1, y);
560
+ for (let i = 0; i <= iMax; i += 1) {
561
+ for (const other of rows[i] ?? []) {
562
+ if (cellsConflict({ x, y }, other))
563
+ return true;
564
+ }
565
+ }
566
+ return false;
567
+ }
568
+ const allBlank = (rows, y, xMin, xMax) => {
569
+ for (let x = xMin; x < xMax; x += 1) {
570
+ if (conflictExists(rows, x, y))
571
+ return false;
572
+ }
573
+ return true;
574
+ };
575
+ function insertCell(cell, row) {
576
+ let x = 0;
577
+ while (x < row.length && (row[x]?.x ?? 0) < cell.x)
578
+ x += 1;
579
+ row.splice(x, 0, cell);
580
+ }
581
+ function fillInTable(table) {
582
+ const hMax = table.length;
583
+ const wMax = maxWidth(table);
584
+ debugLog(`Max rows: ${hMax}; Max cols: ${wMax}`);
585
+ for (let y = 0; y < hMax; y += 1) {
586
+ for (let x = 0; x < wMax; x += 1) {
587
+ if (conflictExists(table, x, y))
588
+ continue;
589
+ const opts = { x, y, colSpan: 1, rowSpan: 1 };
590
+ let scan = x + 1;
591
+ while (scan < wMax && !conflictExists(table, scan, y)) {
592
+ opts.colSpan += 1;
593
+ scan += 1;
594
+ }
595
+ let y2 = y + 1;
596
+ while (y2 < hMax && allBlank(table, y2, opts.x, opts.x + opts.colSpan)) {
597
+ opts.rowSpan += 1;
598
+ y2 += 1;
599
+ }
600
+ const cell = new Cell(opts);
601
+ cell.x = opts.x;
602
+ cell.y = opts.y;
603
+ warn(`Missing cell at ${cell.y}-${cell.x}.`);
604
+ insertCell(cell, table[y] ?? []);
605
+ x = scan - 1;
606
+ }
607
+ }
608
+ }
609
+ function addRowSpanCells(table) {
610
+ table.forEach((row, rowIndex) => {
611
+ for (const cell of row) {
612
+ for (let i = 1; i < cell.rowSpan; i += 1) {
613
+ const rowSpanCell = new RowSpanCell(cell);
614
+ rowSpanCell.x = cell.x;
615
+ rowSpanCell.y = cell.y + i;
616
+ rowSpanCell.colSpan = cell.colSpan;
617
+ insertCell(rowSpanCell, table[rowIndex + i] ?? []);
618
+ }
619
+ }
620
+ });
621
+ }
622
+ function addColSpanCells(cellRows) {
623
+ for (let rowIndex = cellRows.length - 1; rowIndex >= 0; rowIndex -= 1) {
624
+ const columns = cellRows[rowIndex] ?? [];
625
+ for (let columnIndex = 0; columnIndex < columns.length; columnIndex += 1) {
626
+ const cell = columns[columnIndex];
627
+ if (!cell)
628
+ continue;
629
+ for (let k = 1; k < cell.colSpan; k += 1) {
630
+ const colSpanCell = new ColSpanCell();
631
+ colSpanCell.x = cell.x + k;
632
+ colSpanCell.y = cell.y;
633
+ columns.splice(columnIndex + 1, 0, colSpanCell);
634
+ }
635
+ }
636
+ }
637
+ }
638
+ function generateCells(rows) {
639
+ return rows.map((raw) => {
640
+ let row = raw;
641
+ if (!Array.isArray(row)) {
642
+ const record = row;
643
+ const key = Object.keys(record)[0] ?? '';
644
+ const value = record[key];
645
+ row = Array.isArray(value) ? [key, ...value] : [key, value];
646
+ }
647
+ return row.map((cell) => new Cell(cell));
648
+ });
649
+ }
650
+ export function makeTableLayout(rows) {
651
+ const cellRows = generateCells(rows);
652
+ layoutTable(cellRows);
653
+ fillInTable(cellRows);
654
+ addRowSpanCells(cellRows);
655
+ addColSpanCells(cellRows);
656
+ return cellRows;
657
+ }
658
+ function makeComputeDimensions(spanKey, desiredKey, axis, forcedMin) {
659
+ return (vals, table) => {
660
+ const result = [];
661
+ const spanners = [];
662
+ const auto = {};
663
+ for (const row of table) {
664
+ for (const cell of row) {
665
+ if ((cell[spanKey] || 1) > 1)
666
+ spanners.push(cell);
667
+ else
668
+ result[cell[axis]] = Math.max(result[cell[axis]] ?? 0, cell[desiredKey] ?? 0, forcedMin);
669
+ }
670
+ }
671
+ vals.forEach((val, index) => {
672
+ if (typeof val === 'number')
673
+ result[index] = val;
674
+ });
675
+ for (let k = spanners.length - 1; k >= 0; k -= 1) {
676
+ const cell = spanners[k];
677
+ if (!cell)
678
+ continue;
679
+ const span = cell[spanKey];
680
+ const col = cell[axis];
681
+ let existingWidth = result[col];
682
+ let editableCols = typeof vals[col] === 'number' ? 0 : 1;
683
+ if (typeof existingWidth === 'number') {
684
+ for (let i = 1; i < span; i += 1) {
685
+ existingWidth += 1 + (result[col + i] ?? 0);
686
+ if (typeof vals[col + i] !== 'number')
687
+ editableCols += 1;
688
+ }
689
+ }
690
+ else {
691
+ const own = desiredKey === 'desiredWidth' ? cell.desiredWidth : 1;
692
+ if (own === undefined)
693
+ continue;
694
+ existingWidth = own - 1;
695
+ if (!auto[col] || (auto[col] ?? 0) < existingWidth)
696
+ auto[col] = existingWidth;
697
+ }
698
+ const desired = cell[desiredKey] ?? 0;
699
+ if (desired > existingWidth) {
700
+ let i = 0;
701
+ while (editableCols > 0 && desired > existingWidth) {
702
+ if (typeof vals[col + i] !== 'number') {
703
+ const dif = Math.round((desired - existingWidth) / editableCols);
704
+ existingWidth += dif;
705
+ result[col + i] = (result[col + i] ?? 0) + dif;
706
+ editableCols -= 1;
707
+ }
708
+ i += 1;
709
+ }
710
+ }
711
+ }
712
+ Object.assign(vals, result, auto);
713
+ for (let j = 0; j < vals.length; j += 1)
714
+ vals[j] = Math.max(forcedMin, vals[j] || 0);
715
+ };
716
+ }
717
+ export const computeWidths = makeComputeDimensions('colSpan', 'desiredWidth', 'x', 1);
718
+ export const computeHeights = makeComputeDimensions('rowSpan', 'desiredHeight', 'y', 1);
719
+ function debugLevelFor(debug) {
720
+ if (typeof debug === 'boolean')
721
+ return DEBUG_LEVEL.WARN;
722
+ if (typeof debug === 'number')
723
+ return debug;
724
+ if (typeof debug === 'string')
725
+ return Number.parseInt(debug, DECIMAL);
726
+ warn(`Debug option is expected to be boolean, number, or string. Received a ${typeof debug}`);
727
+ return DEBUG_LEVEL.WARN;
728
+ }
729
+ function doDraw(row, lineNum, result) {
730
+ const str = row.map((cell) => cell.draw(lineNum)).join('');
731
+ if (str.length)
732
+ result.push(str);
733
+ }
734
+ export class Table extends Array {
735
+ constructor(opts) {
736
+ super();
737
+ const options = mergeOptions(opts);
738
+ Object.defineProperty(this, 'options', { value: options, enumerable: Boolean(options.debug) });
739
+ if (!options.debug)
740
+ return;
741
+ debugLevel = debugLevelFor(options.debug);
742
+ Object.defineProperty(this, 'messages', {
743
+ get: () => debugMessages,
744
+ });
745
+ }
746
+ static reset() {
747
+ debugMessages = [];
748
+ }
749
+ toString() {
750
+ let array = [...this];
751
+ const head = this.options.head ?? [];
752
+ const headersPresent = head.length > 0;
753
+ if (headersPresent)
754
+ array = [head, ...array];
755
+ else
756
+ this.options.style.head = [];
757
+ const cells = makeTableLayout(array);
758
+ for (const row of cells) {
759
+ for (const cell of row)
760
+ cell.mergeTableOptions(this.options, cells);
761
+ }
762
+ computeWidths(this.options.colWidths, cells);
763
+ computeHeights(this.options.rowHeights, cells);
764
+ for (const row of cells) {
765
+ for (const cell of row)
766
+ cell.init(this.options);
767
+ }
768
+ const result = [];
769
+ for (let rowIndex = 0; rowIndex < cells.length; rowIndex += 1) {
770
+ const row = cells[rowIndex] ?? [];
771
+ const heightOfRow = this.options.rowHeights[rowIndex] ?? 0;
772
+ if (rowIndex === 0 || !this.options.style.compact || (rowIndex === 1 && headersPresent))
773
+ doDraw(row, 'top', result);
774
+ for (let lineNum = 0; lineNum < heightOfRow; lineNum += 1)
775
+ doDraw(row, lineNum, result);
776
+ if (rowIndex + 1 === cells.length)
777
+ doDraw(row, 'bottom', result);
778
+ }
779
+ return result.join('\n');
780
+ }
781
+ get width() {
782
+ return (this.toString().split('\n')[0] ?? '').length;
783
+ }
784
+ }
785
+ export default Table;