Skip to content

Commit

Permalink
fix: handle very tall tables
Browse files Browse the repository at this point in the history
  • Loading branch information
mdonnalley committed Oct 9, 2024
1 parent 0627af9 commit b81be9e
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 7 deletions.
13 changes: 9 additions & 4 deletions examples/sf-specific/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -941,23 +941,28 @@ const deployResult = [
]

const deploy: TableOptions<(typeof deployResult)[number]> = {
borderStyle: 'vertical',
borderStyle: 'vertical-with-outline',
columns: [
'state',
'fullName',
'type',
{
key: 'filePath',
name: 'Path',
overflow: 'truncate',
overflow: 'wrap',
},
{
key: 'filePath',
name: 'Path',
overflow: 'wrap',
},
],
data: deployResult,
filter: (row) => row.state === 'Changed' && row.type.startsWith('A'),
headerOptions: {
color: 'white',
// color: 'white',
formatter: 'capitalCase',
inverse: true,
// inverse: true,
},
maxWidth: '100%',
overflow: 'truncate',
Expand Down
15 changes: 15 additions & 0 deletions examples/very-tall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {printTable} from '../src/index.js'

const height = 100_000
const data = Array.from({length: height}, (_, i) => ({age: i, name: `Foo ${i}`}))

printTable({
columns: ['name', 'age'],
data,
headerOptions: {
formatter: 'capitalCase',
},
horizontalAlignment: 'center',
title: 'Very Tall',
titleOptions: {bold: true},
})
122 changes: 119 additions & 3 deletions src/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export function formatTextWithMargins({
}
}

export function Table<T extends Record<string, unknown>>(props: TableOptions<T>) {
function setup<T extends Record<string, unknown>>(props: TableOptions<T>) {
const {
data,
filter,
Expand Down Expand Up @@ -264,6 +264,38 @@ export function Table<T extends Record<string, unknown>>(props: TableOptions<T>)
skeleton: BORDER_SKELETONS[config.borderStyle].separator,
})

return {
columns,
config,
dataComponent,
footerComponent,
headerComponent,
headerFooterComponent,
headingComponent,
headings,
processedData,
separatorComponent,
title,
titleOptions,
}
}

export function Table<T extends Record<string, unknown>>(props: TableOptions<T>) {
const {
columns,
config,
dataComponent,
footerComponent,
headerComponent,
headerFooterComponent,
headingComponent,
headings,
processedData,
separatorComponent,
title,
titleOptions,
} = setup(props)

return (
<Box flexDirection="column" width={determineWidthToUse(columns, config.maxWidth)}>
{title && <Text {...titleOptions}>{title}</Text>}
Expand Down Expand Up @@ -395,7 +427,12 @@ class Stream extends WriteStream {
private frames: string[] = []

public lastFrame(): string | undefined {
return this.frames.filter((f) => stripAnsi(f) !== '').at(-1)
return this.frames
.filter((f) => {
const stripped = stripAnsi(f)
return stripped !== '' && stripped !== '\n'
})
.at(-1)
}

write(data: string): boolean {
Expand All @@ -413,18 +450,93 @@ class Output {

public maybePrintLastFrame() {
if (this.stream instanceof Stream) {
process.stdout.write(`${this.stream.lastFrame()}\n`)
process.stdout.write(`${this.stream.lastFrame()}`)
} else {
process.stdout.write('\n')
}
}
}

function chunk<T>(array: T[], size: number): T[][] {
return array.reduce((acc, _, i) => {
if (i % size === 0) acc.push(array.slice(i, i + size))
return acc
}, [] as T[][])
}

function renderTableInChunks<T extends Record<string, unknown>>(props: TableOptions<T>): void {
const {
columns,
config,
dataComponent,
footerComponent,
headerComponent,
headerFooterComponent,
headingComponent,
headings,
processedData,
separatorComponent,
title,
titleOptions,
} = setup(props)

const headerOutput = new Output()
const headerInstance = render(
<Box flexDirection="column" width={determineWidthToUse(columns, config.maxWidth)}>
{title && <Text {...titleOptions}>{title}</Text>}
{headerComponent({columns, data: {}, key: 'header'})}
{headingComponent({columns, data: headings, key: 'heading'})}
{headerFooterComponent({columns, data: {}, key: 'footer'})}
</Box>,
{stdout: headerOutput.stream},
)
headerInstance.unmount()
headerOutput.maybePrintLastFrame()

const chunks = chunk(processedData, Math.max(1, process.stdout.rows / 2))
for (const chunk of chunks) {
const chunkOutput = new Output()
const instance = render(
<Box flexDirection="column" width={determineWidthToUse(columns, config.maxWidth)}>
{chunk.map((row, index) => {
// Calculate the hash of the row based on its value and position
const key = `row-${sha1(row)}-${index}`

// Construct a row.
return (
<Box key={key} flexDirection="column">
{separatorComponent({columns, data: {}, key: `separator-${key}`})}
{dataComponent({columns, data: row, key: `data-${key}`})}
</Box>
)
})}
</Box>,
{stdout: chunkOutput.stream},
)
instance.unmount()
chunkOutput.maybePrintLastFrame()
}

const footerOutput = new Output()
const footerInstance = render(
<Box flexDirection="column" width={determineWidthToUse(columns, config.maxWidth)}>
{footerComponent({columns, data: {}, key: 'footer'})}
</Box>,
{stdout: footerOutput.stream},
)
footerInstance.unmount()
footerOutput.maybePrintLastFrame()
}

/**
* Renders a table with the given data.
* @param options see {@link TableOptions}
*/
export function printTable<T extends Record<string, unknown>>(options: TableOptions<T>): void {
if (options.data.length > 50_000) {
renderTableInChunks(options)
}

const output = new Output()
const instance = render(<Table {...options} />, {stdout: output.stream})
instance.unmount()
Expand All @@ -443,6 +555,10 @@ export function printTables<T extends Record<string, unknown>[]>(
tables: {[P in keyof T]: TableOptions<T[P]>},
options?: Omit<ContainerProps, 'children'>,
): void {
if (tables.some((table) => table.data.length > 50_000)) {
throw new Error('The data is too large to print multiple tables. Please use `printTable` instead.')
}

const output = new Output()
const leftMargin = options?.marginLeft ?? options?.margin ?? 0
const rightMargin = options?.marginRight ?? options?.margin ?? 0
Expand Down

0 comments on commit b81be9e

Please sign in to comment.