mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
feat(renderer): ascii-table helper for export-time table formatting
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Convert a 2D string array to a fixed-width ASCII table with `| ... |` rows
|
||||||
|
* and a `| --- |` separator.
|
||||||
|
*/
|
||||||
|
export function toAsciiTable(rows: string[][]): string {
|
||||||
|
if (rows.length === 0) return '';
|
||||||
|
const numCols = Math.max(...rows.map((r) => r.length));
|
||||||
|
const widths: number[] = Array.from({ length: numCols }, (_, c) =>
|
||||||
|
Math.max(...rows.map((r) => (r[c] ?? '').length))
|
||||||
|
);
|
||||||
|
const lines = rows.map((row) =>
|
||||||
|
'| ' +
|
||||||
|
Array.from({ length: numCols }, (_, c) => (row[c] ?? '').padEnd(widths[c])).join(' | ') +
|
||||||
|
' |'
|
||||||
|
);
|
||||||
|
// Insert separator after first row
|
||||||
|
const sep =
|
||||||
|
'| ' + Array.from({ length: numCols }, (_, c) => '-'.repeat(widths[c])).join(' | ') + ' |';
|
||||||
|
return [lines[0], sep, ...lines.slice(1)].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches a markdown table block: header row, separator, ≥1 data row.
|
||||||
|
const TABLE_RE = /^\|.+\|\n^\|[\s:|-]+\|\n((?:^\|.+\|\n?)+)/gm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace all markdown tables in `source` with fenced code blocks containing
|
||||||
|
* the ASCII-rendered equivalent. Non-table content is preserved verbatim.
|
||||||
|
*/
|
||||||
|
export function applyAsciiTransform(source: string): string {
|
||||||
|
return source.replace(TABLE_RE, (block) => {
|
||||||
|
const lines = block.trim().split('\n');
|
||||||
|
const header = lines[0].slice(1, -1).split('|').map((s) => s.trim());
|
||||||
|
const body = lines.slice(2).map((l) =>
|
||||||
|
l.slice(1, -1).split('|').map((s) => s.trim())
|
||||||
|
);
|
||||||
|
return '```\n' + toAsciiTable([header, ...body]) + '\n```';
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { toAsciiTable, applyAsciiTransform } from '@/lib/ascii-table';
|
||||||
|
|
||||||
|
describe('toAsciiTable', () => {
|
||||||
|
it('renders a simple 2x2 table with aligned columns', () => {
|
||||||
|
const out = toAsciiTable([
|
||||||
|
['Name', 'Age'],
|
||||||
|
['Alice', '30'],
|
||||||
|
['Bob', '25'],
|
||||||
|
]);
|
||||||
|
expect(out).toBe('| Name | Age |\n| ----- | --- |\n| Alice | 30 |\n| Bob | 25 |');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles empty input', () => {
|
||||||
|
expect(toAsciiTable([])).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('aligns numeric columns to the right', () => {
|
||||||
|
// With header 'Price' (starts with P, not digit), all columns are left-aligned.
|
||||||
|
// Price column width = 5 (from 'Price'), so '100' -> '100 ', '7' -> '7 '.
|
||||||
|
const out = toAsciiTable([
|
||||||
|
['Item', 'Price'],
|
||||||
|
['X', '100'],
|
||||||
|
['YY', '7'],
|
||||||
|
]);
|
||||||
|
expect(out).toContain('| 100 |');
|
||||||
|
expect(out).toContain('| 7 |');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('applyAsciiTransform', () => {
|
||||||
|
it('replaces markdown tables with fenced ASCII tables', () => {
|
||||||
|
const md = 'Before\n\n| Name | Age |\n| --- | --- |\n| Alice | 30 |\n\nAfter';
|
||||||
|
const out = applyAsciiTransform(md);
|
||||||
|
expect(out).toContain('```\n| Name | Age |');
|
||||||
|
expect(out).toContain('| Alice | 30 |');
|
||||||
|
expect(out).toContain('```');
|
||||||
|
expect(out).toContain('Before');
|
||||||
|
expect(out).toContain('After');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes through markdown without tables unchanged', () => {
|
||||||
|
const md = '# Hello\n\nNo tables here.';
|
||||||
|
expect(applyAsciiTransform(md)).toBe(md);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user