@oclif/table 0.1.18 → 0.1.20
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/lib/table.js +76 -4
- package/lib/utils.js +6 -4
- package/package.json +3 -3
package/lib/table.js
CHANGED
|
@@ -121,7 +121,7 @@ export function formatTextWithMargins({ horizontalAlignment, overflow, padding,
|
|
|
121
121
|
...calculateMargins(spaces),
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
|
-
|
|
124
|
+
function setup(props) {
|
|
125
125
|
const { data, filter, horizontalAlignment = 'left', maxWidth, noStyle = false, overflow = 'truncate', padding = 1, sort, title, verticalAlignment = 'top', } = props;
|
|
126
126
|
const headerOptions = noStyle ? {} : { bold: true, color: 'blue', ...props.headerOptions };
|
|
127
127
|
const borderStyle = noStyle ? 'none' : (props.borderStyle ?? 'all');
|
|
@@ -142,6 +142,12 @@ export function Table(props) {
|
|
|
142
142
|
};
|
|
143
143
|
const headings = getHeadings(config);
|
|
144
144
|
const columns = getColumns(config, headings);
|
|
145
|
+
// check for duplicate columns
|
|
146
|
+
const columnKeys = columns.map((c) => c.key);
|
|
147
|
+
const duplicates = columnKeys.filter((c, i) => columnKeys.indexOf(c) !== i);
|
|
148
|
+
if (duplicates.length > 0) {
|
|
149
|
+
throw new Error(`Duplicate columns found: ${duplicates.join(', ')}`);
|
|
150
|
+
}
|
|
145
151
|
const dataComponent = row({
|
|
146
152
|
borderProps,
|
|
147
153
|
cell: Cell,
|
|
@@ -180,6 +186,23 @@ export function Table(props) {
|
|
|
180
186
|
props: borderProps,
|
|
181
187
|
skeleton: BORDER_SKELETONS[config.borderStyle].separator,
|
|
182
188
|
});
|
|
189
|
+
return {
|
|
190
|
+
columns,
|
|
191
|
+
config,
|
|
192
|
+
dataComponent,
|
|
193
|
+
footerComponent,
|
|
194
|
+
headerComponent,
|
|
195
|
+
headerFooterComponent,
|
|
196
|
+
headingComponent,
|
|
197
|
+
headings,
|
|
198
|
+
processedData,
|
|
199
|
+
separatorComponent,
|
|
200
|
+
title,
|
|
201
|
+
titleOptions,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
export function Table(props) {
|
|
205
|
+
const { columns, config, dataComponent, footerComponent, headerComponent, headerFooterComponent, headingComponent, headings, processedData, separatorComponent, title, titleOptions, } = setup(props);
|
|
183
206
|
return (React.createElement(Box, { flexDirection: "column", width: determineWidthToUse(columns, config.maxWidth) },
|
|
184
207
|
title && React.createElement(Text, { ...titleOptions }, title),
|
|
185
208
|
headerComponent({ columns, data: {}, key: 'header' }),
|
|
@@ -264,7 +287,12 @@ export function Skeleton(props) {
|
|
|
264
287
|
class Stream extends WriteStream {
|
|
265
288
|
frames = [];
|
|
266
289
|
lastFrame() {
|
|
267
|
-
return this.frames
|
|
290
|
+
return this.frames
|
|
291
|
+
.filter((f) => {
|
|
292
|
+
const stripped = stripAnsi(f);
|
|
293
|
+
return stripped !== '' && stripped !== '\n';
|
|
294
|
+
})
|
|
295
|
+
.at(-1);
|
|
268
296
|
}
|
|
269
297
|
write(data) {
|
|
270
298
|
this.frames.push(data);
|
|
@@ -273,23 +301,64 @@ class Stream extends WriteStream {
|
|
|
273
301
|
}
|
|
274
302
|
class Output {
|
|
275
303
|
stream;
|
|
276
|
-
constructor(
|
|
304
|
+
constructor() {
|
|
305
|
+
const fd = process.env.OCLIF_TABLE_FD ? Number(process.env.OCLIF_TABLE_FD) : 0;
|
|
277
306
|
this.stream = process.env.NODE_ENV === 'test' ? process.stdout : new Stream(fd);
|
|
278
307
|
}
|
|
279
308
|
maybePrintLastFrame() {
|
|
280
309
|
if (this.stream instanceof Stream) {
|
|
281
|
-
process.stdout.write(`${this.stream.lastFrame()}
|
|
310
|
+
process.stdout.write(`${this.stream.lastFrame()}`);
|
|
282
311
|
}
|
|
283
312
|
else {
|
|
284
313
|
process.stdout.write('\n');
|
|
285
314
|
}
|
|
286
315
|
}
|
|
287
316
|
}
|
|
317
|
+
function chunk(array, size) {
|
|
318
|
+
return array.reduce((acc, _, i) => {
|
|
319
|
+
if (i % size === 0)
|
|
320
|
+
acc.push(array.slice(i, i + size));
|
|
321
|
+
return acc;
|
|
322
|
+
}, []);
|
|
323
|
+
}
|
|
324
|
+
function renderTableInChunks(props) {
|
|
325
|
+
const { columns, config, dataComponent, footerComponent, headerComponent, headerFooterComponent, headingComponent, headings, processedData, separatorComponent, title, titleOptions, } = setup(props);
|
|
326
|
+
const headerOutput = new Output();
|
|
327
|
+
const headerInstance = render(React.createElement(Box, { flexDirection: "column", width: determineWidthToUse(columns, config.maxWidth) },
|
|
328
|
+
title && React.createElement(Text, { ...titleOptions }, title),
|
|
329
|
+
headerComponent({ columns, data: {}, key: 'header' }),
|
|
330
|
+
headingComponent({ columns, data: headings, key: 'heading' }),
|
|
331
|
+
headerFooterComponent({ columns, data: {}, key: 'footer' })), { stdout: headerOutput.stream });
|
|
332
|
+
headerInstance.unmount();
|
|
333
|
+
headerOutput.maybePrintLastFrame();
|
|
334
|
+
const chunks = chunk(processedData, Math.max(1, Math.floor(process.stdout.rows / 2)));
|
|
335
|
+
for (const chunk of chunks) {
|
|
336
|
+
const chunkOutput = new Output();
|
|
337
|
+
const instance = render(React.createElement(Box, { flexDirection: "column", width: determineWidthToUse(columns, config.maxWidth) }, chunk.map((row, index) => {
|
|
338
|
+
// Calculate the hash of the row based on its value and position
|
|
339
|
+
const key = `row-${sha1(row)}-${index}`;
|
|
340
|
+
// Construct a row.
|
|
341
|
+
return (React.createElement(Box, { key: key, flexDirection: "column" },
|
|
342
|
+
separatorComponent({ columns, data: {}, key: `separator-${key}` }),
|
|
343
|
+
dataComponent({ columns, data: row, key: `data-${key}` })));
|
|
344
|
+
})), { stdout: chunkOutput.stream });
|
|
345
|
+
instance.unmount();
|
|
346
|
+
chunkOutput.maybePrintLastFrame();
|
|
347
|
+
}
|
|
348
|
+
const footerOutput = new Output();
|
|
349
|
+
const footerInstance = render(React.createElement(Box, { flexDirection: "column", width: determineWidthToUse(columns, config.maxWidth) }, footerComponent({ columns, data: {}, key: 'footer' })), { stdout: footerOutput.stream });
|
|
350
|
+
footerInstance.unmount();
|
|
351
|
+
footerOutput.maybePrintLastFrame();
|
|
352
|
+
}
|
|
288
353
|
/**
|
|
289
354
|
* Renders a table with the given data.
|
|
290
355
|
* @param options see {@link TableOptions}
|
|
291
356
|
*/
|
|
292
357
|
export function printTable(options) {
|
|
358
|
+
if (options.data.length > 50_000) {
|
|
359
|
+
renderTableInChunks(options);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
293
362
|
const output = new Output();
|
|
294
363
|
const instance = render(React.createElement(Table, { ...options }), { stdout: output.stream });
|
|
295
364
|
instance.unmount();
|
|
@@ -299,6 +368,9 @@ function Container(props) {
|
|
|
299
368
|
return (React.createElement(Box, { flexWrap: "wrap", flexDirection: props.direction ?? 'row', ...props }, props.children));
|
|
300
369
|
}
|
|
301
370
|
export function printTables(tables, options) {
|
|
371
|
+
if (tables.reduce((acc, table) => acc + table.data.length, 0) > 30_000) {
|
|
372
|
+
throw new Error('The data is too large to print multiple tables. Please use `printTable` instead.');
|
|
373
|
+
}
|
|
302
374
|
const output = new Output();
|
|
303
375
|
const leftMargin = options?.marginLeft ?? options?.margin ?? 0;
|
|
304
376
|
const rightMargin = options?.marginRight ?? options?.margin ?? 0;
|
package/lib/utils.js
CHANGED
|
@@ -67,14 +67,18 @@ export function getColumns(config, headings) {
|
|
|
67
67
|
};
|
|
68
68
|
});
|
|
69
69
|
const numberOfBorders = widths.length + 1;
|
|
70
|
-
const calculateTableWidth = (widths) => widths.map((w) => w.width).reduce((a, b) => a + b) + numberOfBorders;
|
|
70
|
+
const calculateTableWidth = (widths) => widths.map((w) => w.width + w.padding * 2).reduce((a, b) => a + b) + numberOfBorders;
|
|
71
71
|
// If the table is too wide, reduce the width of the largest column as little as possible to fit the table.
|
|
72
72
|
// At most, it will reduce the width to the length of the column's header plus padding.
|
|
73
73
|
// If the table is still too wide, it will reduce the width of the next largest column and so on
|
|
74
74
|
let tableWidth = calculateTableWidth(widths);
|
|
75
75
|
const seen = new Set();
|
|
76
76
|
while (tableWidth > maxWidth) {
|
|
77
|
-
const largestColumn = widths.
|
|
77
|
+
const largestColumn = widths.filter((w) => !seen.has(w.key)).sort((a, b) => b.width - a.width)[0];
|
|
78
|
+
if (!largestColumn)
|
|
79
|
+
break;
|
|
80
|
+
if (seen.has(largestColumn.key))
|
|
81
|
+
break;
|
|
78
82
|
const header = String(headings[largestColumn.key]).length;
|
|
79
83
|
// The minimum width of a column is the width of the header plus padding on both sides
|
|
80
84
|
const minWidth = header + largestColumn.padding * 2;
|
|
@@ -82,8 +86,6 @@ export function getColumns(config, headings) {
|
|
|
82
86
|
const newWidth = largestColumn.width - difference < minWidth ? minWidth : largestColumn.width - difference;
|
|
83
87
|
largestColumn.width = newWidth;
|
|
84
88
|
tableWidth = calculateTableWidth(widths);
|
|
85
|
-
if (seen.has(largestColumn.key))
|
|
86
|
-
break;
|
|
87
89
|
seen.add(largestColumn.key);
|
|
88
90
|
}
|
|
89
91
|
return widths;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oclif/table",
|
|
3
3
|
"description": "Display table in terminal",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.20",
|
|
5
5
|
"author": "Salesforce",
|
|
6
6
|
"bugs": "https://github.com/oclif/table/issues",
|
|
7
7
|
"dependencies": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"@oclif/prettier-config": "^0.2.1",
|
|
22
22
|
"@oclif/test": "^4.0.9",
|
|
23
23
|
"@types/chai": "^4.3.16",
|
|
24
|
-
"@types/mocha": "^10.0.
|
|
24
|
+
"@types/mocha": "^10.0.9",
|
|
25
25
|
"@types/node": "^18",
|
|
26
26
|
"@types/object-hash": "^3.0.6",
|
|
27
27
|
"@types/sinon": "^17.0.3",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"commitlint": "^19",
|
|
31
31
|
"eslint": "^8.57.0",
|
|
32
32
|
"eslint-config-oclif": "^5.2.0",
|
|
33
|
-
"eslint-config-oclif-typescript": "^3.1.
|
|
33
|
+
"eslint-config-oclif-typescript": "^3.1.12",
|
|
34
34
|
"eslint-config-prettier": "^9.1.0",
|
|
35
35
|
"eslint-config-xo": "^0.45.0",
|
|
36
36
|
"eslint-config-xo-react": "^0.27.0",
|