-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbordered.ts
More file actions
105 lines (93 loc) · 3.37 KB
/
bordered.ts
File metadata and controls
105 lines (93 loc) · 3.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/**
* @fileoverview Bordered table renderer using Unicode box-drawing
* characters (`┌`, `─`, `│`, `┴`, …). The right choice when the
* output context renders box-drawing reliably (modern terminals,
* GitHub markdown, most CI runners).
*
* Shares column-width calculation, alignment, and padding with the
* simple renderer via `./padding`.
*/
import colors from '../external/yoctocolors-cjs'
import { ArrayPrototypePush } from '../primordials/array'
import { MathMax } from '../primordials/math'
import { displayWidth, padText } from './padding'
import type { TableColumn } from './types'
/**
* Format data as an ASCII table with borders.
*
* @param data - Array of data objects
* @param columns - Column configuration
* @returns Formatted table string
*
* @example
* import { formatTable } from '@socketsecurity/lib/tables/bordered'
* import colors from 'yoctocolors-cjs'
*
* const data = [
* { name: 'lodash', version: '4.17.21', issues: 0 },
* { name: 'react', version: '18.2.0', issues: 2 },
* ]
* const columns = [
* { key: 'name', header: 'Package' },
* { key: 'version', header: 'Version', align: 'center' },
* { key: 'issues', header: 'Issues', align: 'right', color: (v) => v === '0' ? colors.green(v) : colors.red(v) },
* ]
* console.log(formatTable(data, columns))
* // Output:
* // ┌─────────┬─────────┬────────┐
* // │ Package │ Version │ Issues │
* // ├─────────┼─────────┼────────┤
* // │ lodash │ 4.17.21 │ 0 │
* // │ react │ 18.2.0 │ 2 │
* // └─────────┴─────────┴────────┘
*/
export function formatTable(
data: Array<Record<string, unknown>>,
columns: TableColumn[],
): string {
if (data.length === 0) {
return '(no data)'
}
// Calculate column widths
const widths = columns.map(col => {
const headerWidth = displayWidth(col.header)
const maxDataWidth = MathMax(
...data.map(row => displayWidth(String(row[col.key] ?? ''))),
)
return col.width ?? MathMax(headerWidth, maxDataWidth)
})
const lines: string[] = []
// Top border
const topBorder = `┌─${widths.map(w => '─'.repeat(w)).join('─┬─')}─┐`
ArrayPrototypePush(lines, colors.dim(topBorder))
// Header row
const headerCells = columns.map((col, i) => {
const text = colors.bold(col.header)
return padText(text, widths[i] as number, col.align)
})
ArrayPrototypePush(
lines,
colors.dim('│ ') + headerCells.join(colors.dim(' │ ')) + colors.dim(' │'),
)
// Header separator
const headerSep = `├─${widths.map(w => '─'.repeat(w)).join('─┼─')}─┤`
ArrayPrototypePush(lines, colors.dim(headerSep))
// Data rows
for (const row of data) {
const cells = columns.map((col, i) => {
let value = String(row[col.key] ?? '')
if (col.color) {
value = col.color(value)
}
return padText(value, widths[i] as number, col.align)
})
ArrayPrototypePush(
lines,
colors.dim('│ ') + cells.join(colors.dim(' │ ')) + colors.dim(' │'),
)
}
// Bottom border
const bottomBorder = `└─${widths.map(w => '─'.repeat(w)).join('─┴─')}─┘`
ArrayPrototypePush(lines, colors.dim(bottomBorder))
return lines.join('\n')
}