mirror of
https://github.com/larvit/adf-codec.git
synced 2026-11-13 20:28:28 +01:00
Answer architecture round 1: 0 🔴, 5 🟡; comments and docs name the narrowing, item 112 files GFM's ragged rows, a paragraph's lines carry their positions
This commit is contained in:
@@ -495,7 +495,9 @@ Serves [Goals](#goals) 1, 4 and 5.
|
||||
- The pipe table that `losslessMarkdownToAdf` reads narrows GFM's twice: every row opens with a
|
||||
pipe, so GFM's bare form is an error result, and an alignment colon in the delimiter row is an
|
||||
error too — ADF holds no column alignment. `portableMarkdownToAdf` reads both and drops the
|
||||
alignment. The trailing pipe is canonical output, optional in input.
|
||||
alignment, but narrows GFM's table too: a row whose cell count differs from the header's is an
|
||||
error result where GFM pads or cuts it, and a line holding no `|` ends the table where GFM reads
|
||||
it as a row. The trailing pipe is canonical output, optional in input.
|
||||
- Past that and `~~`, `losslessMarkdownToAdf` reads no GFM: an autolink literal and a `- [ ]`
|
||||
marker stay text, and a checklist is the `taskList` directive — `portableMarkdownToAdf` turns the
|
||||
marker into a `taskList`.
|
||||
|
||||
+7
-6
@@ -347,16 +347,17 @@ claimed and must parse as part of a pipe table, else it is a named error — esc
|
||||
(`\|`) to keep it literal text. The shape a missing leading pipe leaves is claimed too: a line
|
||||
holding an unescaped `|`, followed inside one paragraph by a line whose cells are all `-` runs,
|
||||
an alignment colon among them, and match it in count — GFM's table without the outer pipes — is
|
||||
a named error rather than the prose it reads as; `portableMarkdownToAdf` reads it, and an
|
||||
alignment colon, as GFM does, dropping the alignment. A pipe table is a header row, a delimiter row whose cells are
|
||||
runs of one or more `-` (canonical `---`), and body rows; rows follow code-fence indentation.
|
||||
a named error rather than the prose it reads as. `portableMarkdownToAdf` reads that shape as a
|
||||
table, its body rows with or without the leading `|` until a line holding none, and drops an
|
||||
alignment colon's alignment. A pipe table is a header row, a delimiter row whose cells are runs of
|
||||
one or more `-` (canonical `---`), and body rows; rows follow code-fence indentation.
|
||||
Cells split on unescaped `|` before inline parsing — `\|` stays in the cell text, and the
|
||||
inline layer's ordinary CommonMark escaping yields the pipe; each cell is the inline content of
|
||||
one paragraph, trimmed; canonical form pads cells with single spaces and ends rows with `|`
|
||||
(optional in input). Named errors: a delimiter or body row whose cell count differs from the
|
||||
header's, and an alignment colon in the delimiter row — ADF holds no column alignment. In a
|
||||
pipe cell a hard break is `!adf:hardBreak{}` and a literal `|` is `\|`; a `|` inside a quoted
|
||||
attribute value is already `\u007c`, so the split never reaches it.
|
||||
header's, and, outside `portableMarkdownToAdf`, an alignment colon in the delimiter row — ADF holds
|
||||
no column alignment. In a pipe cell a hard break is `!adf:hardBreak{}` and a literal `|` is `\|`;
|
||||
a `|` inside a quoted attribute value is already `\u007c`, so the split never reaches it.
|
||||
|
||||
The directive form nests cells as containers of block content inside `tableRow` containers:
|
||||
|
||||
|
||||
@@ -54,12 +54,14 @@ type OpenLeaf = { position: SourcePosition } & (
|
||||
| { closer: RegExp | undefined; construct: string; kind: 'html' }
|
||||
| { held: string[]; kind: 'indented-code'; lines: string[] }
|
||||
| { indentation: number; info: string; kind: 'fenced-code'; lines: string[]; marker: string }
|
||||
| { kind: 'paragraph'; lines: string[]; positions: SourcePosition[] }
|
||||
| { kind: 'paragraph'; lines: [ParagraphLine, ...ParagraphLine[]] }
|
||||
| { kind: 'pipe-table'; rows: [string[], ...string[][]] }
|
||||
)
|
||||
|
||||
type ContainerStart = { kind: 'blockquote'; rest: Line } | { fresh: boolean; indentation: number; kind: 'item'; list: ListBlock; rest: Line }
|
||||
|
||||
type ParagraphLine = { position: SourcePosition; text: string }
|
||||
|
||||
// The line from an absolute column on: a tab a cut splits keeps the stop it is measured against.
|
||||
type Line = { column: number; text: string }
|
||||
|
||||
@@ -469,11 +471,10 @@ function appendParagraph(walk: Walk, line: string): void {
|
||||
const leaf = walk.leaf
|
||||
const text = line.replace(/^[ \t]+/, '')
|
||||
if (leaf?.kind !== 'paragraph') {
|
||||
walk.leaf = { kind: 'paragraph', lines: [text], position: walk.position, positions: [walk.position] }
|
||||
walk.leaf = { kind: 'paragraph', lines: [{ position: walk.position, text }], position: walk.position }
|
||||
return
|
||||
}
|
||||
leaf.lines.push(text)
|
||||
leaf.positions.push(walk.position)
|
||||
leaf.lines.push({ position: walk.position, text })
|
||||
}
|
||||
|
||||
function closeLeaf(walk: Walk): void {
|
||||
@@ -497,17 +498,17 @@ function openGfmTable(walk: Walk, opener: string): boolean {
|
||||
const paragraph = walk.leaf
|
||||
if (walk.claims.pipeTables !== 'gfm' || paragraph?.kind !== 'paragraph') return false
|
||||
const delimiter = pipeCells(opener) ?? barePipeCells(opener)
|
||||
const header = barePipeCells(paragraph.lines.at(-1) ?? '')
|
||||
if (delimiter === undefined || header?.length !== delimiter.length || !isDelimiterRow(delimiter)) return false
|
||||
paragraph.lines.pop()
|
||||
const position = paragraph.positions.pop() ?? walk.position
|
||||
if (paragraph.lines.length === 0) walk.leaf = undefined
|
||||
else closeLeaf(walk)
|
||||
walk.leaf = { kind: 'pipe-table', position, rows: [header, delimiter] }
|
||||
const last = paragraph.lines.at(-1)
|
||||
const header = last === undefined ? undefined : barePipeCells(last.text)
|
||||
if (last === undefined || delimiter === undefined || header?.length !== delimiter.length || !isDelimiterRow(delimiter)) return false
|
||||
const [first, ...earlier] = paragraph.lines.slice(0, -1)
|
||||
walk.leaf = first === undefined ? undefined : { ...paragraph, lines: [first, ...earlier] }
|
||||
closeLeaf(walk)
|
||||
walk.leaf = { kind: 'pipe-table', position: last.position, rows: [header, delimiter] }
|
||||
return true
|
||||
}
|
||||
|
||||
// GFM, Tables: a row without its leading pipe goes on until a line opens another block.
|
||||
// GFM, Tables: a row without its leading `|` continues the table while it holds a `|` and opens no other block.
|
||||
function pipeTableRow(claims: Claims, line: string): string[] | undefined {
|
||||
const cells = pipeCells(line)
|
||||
if (cells !== undefined || claims.pipeTables !== 'gfm' || opensBlock(claims, line)) return cells
|
||||
@@ -516,7 +517,7 @@ function pipeTableRow(claims: Claims, line: string): string[] | undefined {
|
||||
|
||||
function pipeTableBlock(rows: readonly [string[], ...string[][]], position: SourcePosition, claims: Claims): Block {
|
||||
const [header, delimiter, ...body] = rows
|
||||
if (claims.pipeTables !== 'gfm' && delimiter !== undefined && delimiter.some(isPipeAlignment)) {
|
||||
if (claims.pipeTables === 'flavour' && delimiter !== undefined && delimiter.some(isPipeAlignment)) {
|
||||
return faultedBlock('a pipe table carries no column alignment ADF could hold: this delimiter row holds an alignment colon', position)
|
||||
}
|
||||
if (delimiter === undefined || !isDelimiterRow(delimiter)) {
|
||||
@@ -554,10 +555,10 @@ function takeParagraph(walk: Walk): Extract<Block, { kind: 'paragraph' }> | unde
|
||||
const leaf = walk.leaf
|
||||
if (leaf?.kind !== 'paragraph') return undefined
|
||||
walk.leaf = undefined
|
||||
const text = readLinkDefinitions(walk.definitions, leaf.lines.join('\n'))
|
||||
const text = readLinkDefinitions(walk.definitions, leaf.lines.map((line) => line.text).join('\n'))
|
||||
if (text === '') return undefined
|
||||
const kept = leaf.positions[leaf.lines.length - text.split('\n').length]
|
||||
return { kind: 'paragraph', position: kept ?? leaf.position, text }
|
||||
const kept = leaf.lines[leaf.lines.length - text.split('\n').length]
|
||||
return { kind: 'paragraph', position: kept?.position ?? leaf.position, text }
|
||||
}
|
||||
|
||||
function currentBlocks(walk: Walk): Block[] {
|
||||
|
||||
@@ -293,6 +293,8 @@ test('reads GFM\'s pipe table without its leading pipes or with alignment colons
|
||||
assert.deepEqual(read('a | b\n--- | --- | ---\n'), [said('a | b --- | --- | ---')])
|
||||
assert.deepEqual(read('- a | b\n--- | ---\n'), [bare('bulletList', bare('listItem', said('a | b --- | ---')))])
|
||||
assert.deepEqual(read('a | b\n--- | ---\nc\n'), [bare('table', table.content?.[0] ?? said('')), said('c')])
|
||||
const ragged = portableMarkdownToAdf('Intro.\na | b\n--- | ---\nc | d | e\n')
|
||||
assert.deepEqual(ragged.ok ? undefined : [ragged.error.code, ragged.error.position], ['malformed-pipe-table', { line: 2, offset: 7 }])
|
||||
})
|
||||
|
||||
test('reads what CommonMark reads where no row reads, a directive and a carry fence among it, an image sharing its paragraph with a marker among it', () => {
|
||||
|
||||
@@ -10,7 +10,7 @@ export type Claims = {
|
||||
carryFence: boolean
|
||||
directives: boolean
|
||||
highlights: boolean
|
||||
// `gfm` reads GFM's whole pipe table, its alignment dropped; `flavour` reads rows opening with `|` over a `-` delimiter row.
|
||||
// `gfm` reads rows with or without the leading `|`, alignment colons dropped; `flavour` only rows opening with `|` over `-` runs.
|
||||
pipeTables: 'flavour' | 'gfm' | false
|
||||
strikethrough: boolean
|
||||
taskMarkers: boolean
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
`Bar = 9`
|
||||
|
||||
`Next ID = 112`
|
||||
`Next ID = 113`
|
||||
|
||||
| Goal | W |
|
||||
|---|---|
|
||||
@@ -50,6 +50,7 @@
|
||||
| 68 | 0.3.0 | | **Carry a non-text inline node holding `content` or `text`, a block holding `text`, and a leaf block holding `content`.** | 3 | 3 | 4 | 4 | 7 | 1 | 18.2 |
|
||||
| 75 | 0.3.0 | | **Make the generated-markdown property write block carries as `adf:` fences, canonical and hostile.** | 2 | 2 | 4 | 3 | 7 | 1 | 17.5 |
|
||||
| 81 | 0.3.0 | | **Fail the gate unless the README's error tables list exactly the codes `ConvertErrorCode` holds.** | 1 | 2 | 2 | 5 | 5 | 3 | 16.3 |
|
||||
| 112 | 0.3.0 | question | **Read a GFM pipe table's ragged and pipe-less body rows, and a one-column table without leading pipes, in `portableMarkdownToAdf` as GFM does, keeping every cell.** | 3 | 3 | 5 | 6 | 6 | 4, 7 | 16.3 |
|
||||
| 46 | 0.3.0 | | **Publish the bundle size in the README, failing the release pipeline when it drifts.** | 2 | 4 | 3 | 5 | 5 | 3, 8 | 14.8 |
|
||||
| 72 | 0.3.0 | decision | **Re-create `gitea.larvit.se/larvit/adf-codec` as a read-only pull mirror of GitHub, its issues and Actions off and its description pointing at GitHub.** | 2 | 1 | 1 | 3 | 6 | 3 | 13.1 |
|
||||
| 107 | 0.3.0 | decision | **Spell the block carry's JSON so its length stays linear in the node's JSON.** | 4 | 5 | 7 | 3 | 5 | 1 | 10.8 |
|
||||
@@ -272,6 +273,16 @@ and hostile. Found by the technical-principles audit, 2026-10-04.
|
||||
leaves the README's tables a false contract under a green gate. Assert that the tables list exactly
|
||||
the union. Found by the technical-principles audit of item 78, 2026-10-05.
|
||||
|
||||
### 112. Read a GFM pipe table's ragged and pipe-less body rows, and a one-column table without leading pipes, in `portableMarkdownToAdf` as GFM does, keeping every cell.
|
||||
|
||||
GFM pads a short row with empty cells, cuts a long one to the header's count, reads a line holding
|
||||
no `|` after a table as a row, and reads `a |` over `--- |` as a one-column table.
|
||||
`portableMarkdownToAdf` refuses a ragged row as `malformed-pipe-table`, ends the table at a line
|
||||
holding no `|`, and reads the one-column shape as a paragraph; the README's pipe-table guarantee
|
||||
names the first two. Cutting a long row drops content (Goal 7) where Goal 4 asks for GFM's reading:
|
||||
which wins is the maintainer's. The one-column shape needs `isBareDelimiterRow` in the writer's
|
||||
escaping to cover it too.
|
||||
|
||||
### 46. Publish the bundle size in the README, failing the release pipeline when it drifts.
|
||||
|
||||
Lands after item 7, which changes it. The quantity is what a consumer downloads and loads: the
|
||||
|
||||
Reference in New Issue
Block a user