@oclif/table 0.1.17 → 0.1.19

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 CHANGED
@@ -121,7 +121,7 @@ export function formatTextWithMargins({ horizontalAlignment, overflow, padding,
121
121
  ...calculateMargins(spaces),
122
122
  };
123
123
  }
124
- export function Table(props) {
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.filter((f) => stripAnsi(f) !== '').at(-1);
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);
@@ -278,18 +306,58 @@ class Output {
278
306
  }
279
307
  maybePrintLastFrame() {
280
308
  if (this.stream instanceof Stream) {
281
- process.stdout.write(`${this.stream.lastFrame()}\n`);
309
+ process.stdout.write(`${this.stream.lastFrame()}`);
282
310
  }
283
311
  else {
284
312
  process.stdout.write('\n');
285
313
  }
286
314
  }
287
315
  }
316
+ function chunk(array, size) {
317
+ return array.reduce((acc, _, i) => {
318
+ if (i % size === 0)
319
+ acc.push(array.slice(i, i + size));
320
+ return acc;
321
+ }, []);
322
+ }
323
+ function renderTableInChunks(props) {
324
+ const { columns, config, dataComponent, footerComponent, headerComponent, headerFooterComponent, headingComponent, headings, processedData, separatorComponent, title, titleOptions, } = setup(props);
325
+ const headerOutput = new Output();
326
+ const headerInstance = render(React.createElement(Box, { flexDirection: "column", width: determineWidthToUse(columns, config.maxWidth) },
327
+ title && React.createElement(Text, { ...titleOptions }, title),
328
+ headerComponent({ columns, data: {}, key: 'header' }),
329
+ headingComponent({ columns, data: headings, key: 'heading' }),
330
+ headerFooterComponent({ columns, data: {}, key: 'footer' })), { stdout: headerOutput.stream });
331
+ headerInstance.unmount();
332
+ headerOutput.maybePrintLastFrame();
333
+ const chunks = chunk(processedData, Math.max(1, Math.floor(process.stdout.rows / 2)));
334
+ for (const chunk of chunks) {
335
+ const chunkOutput = new Output();
336
+ const instance = render(React.createElement(Box, { flexDirection: "column", width: determineWidthToUse(columns, config.maxWidth) }, chunk.map((row, index) => {
337
+ // Calculate the hash of the row based on its value and position
338
+ const key = `row-${sha1(row)}-${index}`;
339
+ // Construct a row.
340
+ return (React.createElement(Box, { key: key, flexDirection: "column" },
341
+ separatorComponent({ columns, data: {}, key: `separator-${key}` }),
342
+ dataComponent({ columns, data: row, key: `data-${key}` })));
343
+ })), { stdout: chunkOutput.stream });
344
+ instance.unmount();
345
+ chunkOutput.maybePrintLastFrame();
346
+ }
347
+ const footerOutput = new Output();
348
+ const footerInstance = render(React.createElement(Box, { flexDirection: "column", width: determineWidthToUse(columns, config.maxWidth) }, footerComponent({ columns, data: {}, key: 'footer' })), { stdout: footerOutput.stream });
349
+ footerInstance.unmount();
350
+ footerOutput.maybePrintLastFrame();
351
+ }
288
352
  /**
289
353
  * Renders a table with the given data.
290
354
  * @param options see {@link TableOptions}
291
355
  */
292
356
  export function printTable(options) {
357
+ if (options.data.length > 50_000) {
358
+ renderTableInChunks(options);
359
+ return;
360
+ }
293
361
  const output = new Output();
294
362
  const instance = render(React.createElement(Table, { ...options }), { stdout: output.stream });
295
363
  instance.unmount();
@@ -299,6 +367,9 @@ function Container(props) {
299
367
  return (React.createElement(Box, { flexWrap: "wrap", flexDirection: props.direction ?? 'row', ...props }, props.children));
300
368
  }
301
369
  export function printTables(tables, options) {
370
+ if (tables.reduce((acc, table) => acc + table.data.length, 0) > 30_000) {
371
+ throw new Error('The data is too large to print multiple tables. Please use `printTable` instead.');
372
+ }
302
373
  const output = new Output();
303
374
  const leftMargin = options?.marginLeft ?? options?.margin ?? 0;
304
375
  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.reduce((a, b) => (a.width > b.width ? a : b));
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.17",
4
+ "version": "0.1.19",
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.8",
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.11",
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",