@@ -10,17 +10,9 @@ import jsyaml from 'js-yaml';
10
10
import { orderBy } from 'natural-orderby' ;
11
11
import sliceAnsi from 'slice-ansi' ;
12
12
import sw from 'string-width' ;
13
- import { Interfaces , Flags as F } from '@oclif/core' ;
14
13
15
14
import write from './write.js' ;
16
15
17
- /* eslint-disable @typescript-eslint/no-explicit-any */
18
- /* eslint-disable @typescript-eslint/explicit-function-return-type */
19
- /* eslint-disable @typescript-eslint/no-unsafe-return */
20
- /* eslint-disable @typescript-eslint/no-unsafe-member-access */
21
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
22
- /* eslint-disable @typescript-eslint/no-unsafe-argument */
23
-
24
16
function sumBy < T > ( arr : T [ ] , fn : ( i : T ) => number ) : number {
25
17
return arr . reduce ( ( sum , i ) => sum + fn ( i ) , 0 ) ;
26
18
}
@@ -49,17 +41,19 @@ function termwidth(stream: NodeJS.WriteStream): number {
49
41
const stdtermwidth = Number . parseInt ( process . env . OCLIF_COLUMNS ! , 10 ) || termwidth ( process . stdout ) ;
50
42
51
43
class Table < T extends Record < string , unknown > > {
52
- private columns : Array < table . Column < T > & { key : string ; maxWidth ?: number ; width ?: number } > ;
44
+ private columns : Array < Column < T > & { key : string ; maxWidth ?: number ; width ?: number } > ;
45
+
46
+ private options : Options & { printLine : ( s : unknown ) => void } ;
53
47
54
- private options : table . Options & { printLine ( s : any ) : any } ;
48
+ private data : Array < Record < string , unknown > > ;
55
49
56
- public constructor ( private data : T [ ] , columns : table . Columns < T > , options : table . Options = { } ) {
50
+ public constructor ( data : T [ ] , columns : Columns < T > , options : Options = { } ) {
57
51
// assign columns
58
52
this . columns = Object . keys ( columns ) . map ( ( key : string ) => {
59
53
const col = columns [ key ] ;
60
54
const extended = col . extended ?? false ;
61
55
// turn null and undefined into empty strings by default
62
- const get = col . get ?? ( ( row : any ) => row [ key ] ?? '' ) ;
56
+ const get = col . get ?? ( ( row : Record < string , unknown > ) : unknown => row [ key ] ?? '' ) ;
63
57
const header = typeof col . header === 'string' ? col . header : capitalize ( key . replaceAll ( '_' , ' ' ) ) ;
64
58
const minWidth = Math . max ( col . minWidth ?? 0 , sw ( header ) + 1 ) ;
65
59
@@ -87,20 +81,17 @@ class Table<T extends Record<string, unknown>> {
87
81
sort,
88
82
title,
89
83
} ;
90
- }
91
84
92
- public display ( ) {
93
85
// build table rows from input array data
94
- let rows = this . data . map ( ( d ) => {
95
- const row : any = { } ;
96
- for ( const col of this . columns ) {
97
- let val = col . get ( d ) ;
98
- if ( typeof val !== 'string' ) val = inspect ( val , { breakLength : Number . POSITIVE_INFINITY } ) ;
99
- row [ col . key ] = val ;
100
- }
101
-
102
- return row ;
103
- } ) ;
86
+ let rows = data . map ( ( d ) =>
87
+ Object . fromEntries (
88
+ this . columns . map ( ( col ) => {
89
+ let val = col . get ( d ) ;
90
+ if ( typeof val !== 'string' ) val = inspect ( val , { breakLength : Number . POSITIVE_INFINITY } ) ;
91
+ return [ col . key , val ] ;
92
+ } )
93
+ )
94
+ ) ;
104
95
105
96
// filter rows
106
97
if ( this . options . filter ) {
@@ -110,7 +101,7 @@ class Table<T extends Record<string, unknown>> {
110
101
if ( isNot ) header = header . slice ( 1 ) ;
111
102
const col = this . findColumnFromHeader ( header ) ;
112
103
if ( ! col || ! regex ) throw new Error ( 'Filter flag has an invalid value' ) ;
113
- rows = rows . filter ( ( d : any ) => {
104
+ rows = rows . filter ( ( d ) => {
114
105
const re = new RegExp ( regex ) ;
115
106
const val = d [ col . key ] as string ;
116
107
const match = val . match ( re ) ;
@@ -122,7 +113,11 @@ class Table<T extends Record<string, unknown>> {
122
113
if ( this . options . sort ) {
123
114
const sorters = this . options . sort . split ( ',' ) ;
124
115
const sortHeaders = sorters . map ( ( k ) => ( k . startsWith ( '-' ) ? k . slice ( 1 ) : k ) ) ;
125
- const sortKeys = this . filterColumnsFromHeaders ( sortHeaders ) . map ( ( c ) => ( v : any ) => v [ c . key ] ) ;
116
+ const sortKeys = this . filterColumnsFromHeaders ( sortHeaders ) . map (
117
+ ( c ) =>
118
+ ( v : Record < string , unknown > ) : unknown =>
119
+ v [ c . key ]
120
+ ) ;
126
121
const sortKeysOrder = sorters . map ( ( k ) => ( k . startsWith ( '-' ) ? 'desc' : 'asc' ) ) ;
127
122
rows = orderBy ( rows , sortKeys , sortKeysOrder ) ;
128
123
}
@@ -137,7 +132,9 @@ class Table<T extends Record<string, unknown>> {
137
132
}
138
133
139
134
this . data = rows ;
135
+ }
140
136
137
+ public display ( ) : void {
141
138
switch ( this . options . output ) {
142
139
case 'csv' : {
143
140
this . outputCSV ( ) ;
@@ -162,11 +159,9 @@ class Table<T extends Record<string, unknown>> {
162
159
163
160
private filterColumnsFromHeaders (
164
161
filters : string [ ]
165
- ) : Array < table . Column < T > & { key : string ; maxWidth ?: number ; width ?: number } > {
166
- // unique
167
- filters = [ ...new Set ( filters ) ] ;
168
- const cols : Array < table . Column < T > & { key : string ; maxWidth ?: number ; width ?: number } > = [ ] ;
169
- for ( const f of filters ) {
162
+ ) : Array < Column < T > & { key : string ; maxWidth ?: number ; width ?: number } > {
163
+ const cols : Array < Column < T > & { key : string ; maxWidth ?: number ; width ?: number } > = [ ] ;
164
+ for ( const f of [ ...new Set ( filters ) ] ) {
170
165
const c = this . columns . find ( ( i ) => i . header . toLowerCase ( ) === f . toLowerCase ( ) ) ;
171
166
if ( c ) cols . push ( c ) ;
172
167
}
@@ -176,19 +171,19 @@ class Table<T extends Record<string, unknown>> {
176
171
177
172
private findColumnFromHeader (
178
173
header : string
179
- ) : ( table . Column < T > & { key : string ; maxWidth ?: number ; width ?: number } ) | undefined {
174
+ ) : ( Column < T > & { key : string ; maxWidth ?: number ; width ?: number } ) | undefined {
180
175
return this . columns . find ( ( c ) => c . header . toLowerCase ( ) === header . toLowerCase ( ) ) ;
181
176
}
182
177
183
- private getCSVRow ( d : any ) : string [ ] {
178
+ private getCSVRow ( d : Record < string , unknown > ) : string [ ] {
184
179
const values = this . columns . map ( ( col ) => d [ col . key ] || '' ) as string [ ] ;
185
180
const lineToBeEscaped = values . find (
186
181
( e : string ) => e . includes ( '"' ) || e . includes ( '\n' ) || e . includes ( '\r\n' ) || e . includes ( '\r' ) || e . includes ( ',' )
187
182
) ;
188
183
return values . map ( ( e ) => ( lineToBeEscaped ? `"${ e . replaceAll ( '"' , '""' ) } "` : e ) ) ;
189
184
}
190
185
191
- private outputCSV ( ) {
186
+ private outputCSV ( ) : void {
192
187
const { columns, data, options } = this ;
193
188
194
189
if ( ! options [ 'no-header' ] ) {
@@ -201,11 +196,11 @@ class Table<T extends Record<string, unknown>> {
201
196
}
202
197
}
203
198
204
- private outputJSON ( ) {
199
+ private outputJSON ( ) : void {
205
200
this . options . printLine ( JSON . stringify ( this . resolveColumnsToObjectArray ( ) , undefined , 2 ) ) ;
206
201
}
207
202
208
- private outputTable ( ) {
203
+ private outputTable ( ) : void {
209
204
const { data, options } = this ;
210
205
// column truncation
211
206
//
@@ -222,7 +217,7 @@ class Table<T extends Record<string, unknown>> {
222
217
// terminal width
223
218
const maxWidth = stdtermwidth - 2 ;
224
219
// truncation logic
225
- const shouldShorten = ( ) => {
220
+ const shouldShorten = ( ) : void => {
226
221
// don't shorten if full mode
227
222
if ( options [ 'no-truncate' ] ?? ( ! process . stdout . isTTY && ! process . env . CLI_UX_SKIP_TTY_CHECK ) ) return ;
228
223
@@ -304,7 +299,7 @@ class Table<T extends Record<string, unknown>> {
304
299
// with multi-line strings
305
300
let numOfLines = 1 ;
306
301
for ( const col of columns ) {
307
- const d = ( row as any ) [ col . key ] as string ;
302
+ const d = row [ col . key ] as string ;
308
303
const lines = d . split ( '\n' ) . length ;
309
304
if ( lines > numOfLines ) numOfLines = lines ;
310
305
}
@@ -318,7 +313,7 @@ class Table<T extends Record<string, unknown>> {
318
313
let l = options . rowStart ;
319
314
for ( const col of columns ) {
320
315
const width = col . width ;
321
- let d = ( row as any ) [ col . key ] as string ;
316
+ let d = row [ col . key ] as string ;
322
317
d = d . split ( '\n' ) [ i ] || '' ;
323
318
const visualWidth = sw ( d ) ;
324
319
const colorWidth = d . length - visualWidth ;
@@ -344,100 +339,45 @@ class Table<T extends Record<string, unknown>> {
344
339
this . options . printLine ( jsyaml . dump ( this . resolveColumnsToObjectArray ( ) ) ) ;
345
340
}
346
341
347
- private resolveColumnsToObjectArray ( ) : Array < { [ k : string ] : any } > {
342
+ private resolveColumnsToObjectArray ( ) : Array < Record < string , unknown > > {
348
343
const { columns, data } = this ;
349
- return data . map ( ( d : any ) => Object . fromEntries ( columns . map ( ( col ) => [ col . key , d [ col . key ] ?? '' ] ) ) ) ;
344
+ return data . map ( ( d ) => Object . fromEntries ( columns . map ( ( col ) => [ col . key , d [ col . key ] ?? '' ] ) ) ) ;
350
345
}
351
346
}
352
347
353
- export function table < T extends Record < string , unknown > > (
354
- data : T [ ] ,
355
- columns : table . Columns < T > ,
356
- options : table . Options = { }
357
- ) : void {
348
+ export function table < T extends Record < string , unknown > > ( data : T [ ] , columns : Columns < T > , options : Options = { } ) : void {
358
349
new Table ( data , columns , options ) . display ( ) ;
359
350
}
360
351
361
- export namespace table {
362
- export const Flags : {
363
- columns : Interfaces . OptionFlag < string | undefined > ;
364
- csv : Interfaces . BooleanFlag < boolean > ;
365
- extended : Interfaces . BooleanFlag < boolean > ;
366
- filter : Interfaces . OptionFlag < string | undefined > ;
367
- 'no-header' : Interfaces . BooleanFlag < boolean > ;
368
- 'no-truncate' : Interfaces . BooleanFlag < boolean > ;
369
- output : Interfaces . OptionFlag < string | undefined > ;
370
- sort : Interfaces . OptionFlag < string | undefined > ;
371
- } = {
372
- columns : F . string ( { description : 'only show provided columns (comma-separated)' , exclusive : [ 'extended' ] } ) ,
373
- csv : F . boolean ( { description : 'output is csv format [alias: --output=csv]' , exclusive : [ 'no-truncate' ] } ) ,
374
- extended : F . boolean ( { char : 'x' , description : 'show extra columns' , exclusive : [ 'columns' ] } ) ,
375
- filter : F . string ( { description : 'filter property by partial string matching, ex: name=foo' } ) ,
376
- 'no-header' : F . boolean ( { description : 'hide table header from output' , exclusive : [ 'csv' ] } ) ,
377
- 'no-truncate' : F . boolean ( { description : 'do not truncate output to fit screen' , exclusive : [ 'csv' ] } ) ,
378
- output : F . string ( {
379
- description : 'output in a more machine friendly format' ,
380
- exclusive : [ 'no-truncate' , 'csv' ] ,
381
- options : [ 'csv' , 'json' , 'yaml' ] ,
382
- } ) ,
383
- sort : F . string ( { description : "property to sort by (prepend '-' for descending)" } ) ,
384
- } ;
385
-
386
- type IFlags = typeof Flags ;
387
- type ExcludeFlags < T , Z > = Pick < T , Exclude < keyof T , Z > > ;
388
- type IncludeFlags < T , K extends keyof T > = Pick < T , K > ;
389
-
390
- export function flags ( ) : IFlags ;
391
- export function flags < Z extends keyof IFlags = keyof IFlags > ( opts : { except : Z | Z [ ] } ) : ExcludeFlags < IFlags , Z > ;
392
- export function flags < K extends keyof IFlags = keyof IFlags > ( opts : { only : K | K [ ] } ) : IncludeFlags < IFlags , K > ;
393
-
394
- export function flags ( opts ?: any ) : any {
395
- if ( opts ) {
396
- const f = { } ;
397
- const o = ( opts . only && typeof opts . only === 'string' ? [ opts . only ] : opts . only ) || Object . keys ( Flags ) ;
398
- const e = ( opts . except && typeof opts . except === 'string' ? [ opts . except ] : opts . except ) || [ ] ;
399
- for ( const key of o ) {
400
- if ( ! ( e as any [ ] ) . includes ( key ) ) {
401
- ( f as any ) [ key ] = ( Flags as any ) [ key ] ;
402
- }
403
- }
404
-
405
- return f ;
406
- }
407
-
408
- return Flags ;
409
- }
410
-
411
- export type Column < T extends Record < string , unknown > > = {
412
- extended : boolean ;
413
- header : string ;
414
- minWidth : number ;
415
- get ( row : T ) : unknown ;
416
- } ;
417
-
418
- export type Columns < T extends Record < string , unknown > > = { [ key : string ] : Partial < Column < T > > } ;
419
-
420
- // export type OutputType = 'csv' | 'json' | 'yaml'
421
-
422
- export type Options = {
423
- [ key : string ] : any ;
424
- columns ?: string ;
425
- extended ?: boolean ;
426
- filter ?: string ;
427
- 'no-header' ?: boolean ;
428
- 'no-truncate' ?: boolean ;
429
- output ?: string ;
430
- sort ?: string ;
431
- title ?: string ;
432
- printLine ?( s : any ) : any ;
433
- } ;
434
- }
435
-
436
- const getWidestColumnWith = ( data : any [ ] , columnKey : string ) : number =>
352
+ export type Column < T extends Record < string , unknown > > = {
353
+ extended : boolean ;
354
+ header : string ;
355
+ minWidth : number ;
356
+ get ( row : T ) : unknown ;
357
+ } ;
358
+
359
+ export type Columns < T extends Record < string , unknown > > = { [ key : string ] : Partial < Column < T > > } ;
360
+
361
+ export type Options = {
362
+ [ key : string ] : unknown ;
363
+ columns ?: string ;
364
+ extended ?: boolean ;
365
+ filter ?: string ;
366
+ 'no-header' ?: boolean ;
367
+ 'no-truncate' ?: boolean ;
368
+ output ?: string ;
369
+ rowStart ?: string ;
370
+ sort ?: string ;
371
+ title ?: string ;
372
+ printLine ?( s : unknown ) : void ;
373
+ } ;
374
+
375
+ const getWidestColumnWith = ( data : Array < Record < string , unknown > > , columnKey : string ) : number =>
437
376
data . reduce ( ( previous , current ) => {
438
377
const d = current [ columnKey ] ;
378
+ if ( typeof d !== 'string' ) return previous ;
439
379
// convert multi-line cell to single longest line
440
380
// for width calculations
441
- const manyLines = ( d as string ) . split ( '\n' ) ;
381
+ const manyLines = d . split ( '\n' ) ;
442
382
return Math . max ( previous , manyLines . length > 1 ? Math . max ( ...manyLines . map ( ( r : string ) => sw ( r ) ) ) : sw ( d ) ) ;
443
383
} , 0 ) ;
0 commit comments