@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 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);
@@ -273,23 +301,64 @@ class Stream extends WriteStream {
273
301
  }
274
302
  class Output {
275
303
  stream;
276
- constructor(fd = 1) {
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()}\n`);
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.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.18",
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.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",