Make markdownToAdf(adfToMarkdown(doc)) deep-equal doc: textBreak, empty keys, -0, typed carry fences, code blocks of several text nodes
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { AdfAttributes, AdfMark, AdfNode } from '../../adf/document.ts'
|
||||
import type { AdfAttributes, AdfMark, AdfNode, EmptyKey } from '../../adf/document.ts'
|
||||
import type { BlockNodeModel } from '../../adf/block-nodes.ts'
|
||||
import type { ConvertFault } from '../../result.ts'
|
||||
import type { DirectiveAttributes, DirectiveValue } from '../directive-syntax.ts'
|
||||
@@ -7,15 +7,18 @@ import { attributeNestingMessage, nodeAttrs, nodeContent, nodeMarks } from '../.
|
||||
import { attributeValue, directivePrefix, spellAttributeValue, unknownDirectiveFault } from '../directive-syntax.ts'
|
||||
import { blockArgument, blockDirectiveForm, marksAttribute, readMarkValues } from '../block-directive.ts'
|
||||
import { blockNodeModel } from '../../adf/block-nodes.ts'
|
||||
import { carryName } from '../opaque-carry.ts'
|
||||
import { carryFencePrefix, carryName } from '../opaque-carry.ts'
|
||||
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { inlineMarkSpellingFault } from './directive-marks.ts'
|
||||
import { inlineNodeModel } from '../../adf/inline-nodes.ts'
|
||||
import { readEmptyKeys, spellsEmpty } from '../empty-keys.ts'
|
||||
import { readVocabulary } from './directive-attributes.ts'
|
||||
import { slotLineEndingFault } from '../directive-syntax.ts'
|
||||
import { textBreakName } from '../text-break.ts'
|
||||
import { textDirectiveName } from '../text-directive.ts'
|
||||
|
||||
export type BlockDirectiveNode = { contentModel: BlockNodeModel['contentModel']; node: AdfNode }
|
||||
// `emptyContent` is whether the directive spells content=empty, which holds no body.
|
||||
export type BlockDirectiveNode = { contentModel: BlockNodeModel['contentModel']; emptyContent: boolean; node: AdfNode }
|
||||
|
||||
export function readBlockDirectiveNode(
|
||||
name: string,
|
||||
@@ -24,12 +27,15 @@ export function readBlockDirectiveNode(
|
||||
path: ConvertErrorPath,
|
||||
): Result<BlockDirectiveNode> {
|
||||
if (name === carryName) {
|
||||
return failure('malformed-directive', `the name ${carryName} is reserved for the opaque carry, whose block form is the ${carryName} fence`, path)
|
||||
return failure('malformed-directive', `the name ${carryName} is reserved for the opaque carry, whose block form is the ${carryFencePrefix} fence`, path)
|
||||
}
|
||||
const model = blockNodeModel(name)
|
||||
if (model === undefined) return faulted(inlineSpellingFault(name) ?? unknownDirectiveFault(name), path)
|
||||
const argumentKey = blockArgument(name)
|
||||
const rest = new Map(attributes)
|
||||
const spelled = attributes.get(marksAttribute)
|
||||
const empty = readEmptyKeys(attributes, spellsEmpty(spelled) ? ['attrs', 'content', 'marks'] : ['attrs', 'content'])
|
||||
if (empty.fault !== undefined) return faulted(empty.fault, path)
|
||||
const { rest } = empty.value
|
||||
rest.delete(marksAttribute)
|
||||
const elsewhere: Elsewhere | undefined = argumentKey === undefined ? undefined : { key: argumentKey, slot: 'argument' }
|
||||
const attrs = readVocabulary(name, rest, model.attributes, elsewhere, path)
|
||||
@@ -38,10 +44,11 @@ export function readBlockDirectiveNode(
|
||||
if (argumentKey === undefined) return failure('unsupported-node-shape', `${name} takes no argument: this one spells one`, path)
|
||||
attrs.value[argumentKey] = argument
|
||||
}
|
||||
const spelled = attributes.get(marksAttribute)
|
||||
const marks: Result<AdfMark[] | undefined> = spelled === undefined ? success(undefined) : readMarks(name, spelled, path)
|
||||
const marks: Result<AdfMark[] | undefined> = spelled === undefined || spellsEmpty(spelled) ? success(undefined) : readMarks(name, spelled, path)
|
||||
if (!marks.ok) return marks
|
||||
return success({ contentModel: model.contentModel, node: namedNode(name, attrs.value, marks.value) })
|
||||
const node = namedNode(name, attrs.value, marks.value, empty.value.empty, path)
|
||||
if (!node.ok) return node
|
||||
return success({ contentModel: model.contentModel, emptyContent: empty.value.empty.has('content'), node: node.value })
|
||||
}
|
||||
|
||||
export function readInlineDirectiveNode(
|
||||
@@ -55,7 +62,9 @@ export function readInlineDirectiveNode(
|
||||
const slot = model.textAttribute
|
||||
if (slot === undefined && content !== undefined) return failure('unsupported-node-shape', `${name} takes no content: this one holds some`, path)
|
||||
const elsewhere: Elsewhere | undefined = slot === undefined ? undefined : { key: slot, slot: 'content' }
|
||||
const attrs = readVocabulary(name, attributes, model.attributes, elsewhere, path)
|
||||
const empty = readEmptyKeys(attributes, ['attrs', 'content', 'marks'])
|
||||
if (empty.fault !== undefined) return faulted(empty.fault, path)
|
||||
const attrs = readVocabulary(name, empty.value.rest, model.attributes, elsewhere, path)
|
||||
if (!attrs.ok) return attrs
|
||||
if (slot !== undefined && content !== undefined) {
|
||||
const text = slotText(content)
|
||||
@@ -66,14 +75,14 @@ export function readInlineDirectiveNode(
|
||||
if (spans !== undefined) return faulted(spans, path)
|
||||
attrs.value[slot] = text
|
||||
}
|
||||
return success(namedNode(name, attrs.value, undefined))
|
||||
return namedNode(name, attrs.value, undefined, empty.value.empty, path)
|
||||
}
|
||||
|
||||
// A name the other position spells names that spelling, never the code a later MINOR may fill (docs/decisions.md §Which code a cause takes).
|
||||
function inlineSpellingFault(name: string): ConvertFault | undefined {
|
||||
const mark = inlineMarkSpellingFault(name)
|
||||
if (mark !== undefined) return mark
|
||||
if (inlineNodeModel(name) === undefined && name !== textDirectiveName) return undefined
|
||||
if (inlineNodeModel(name) === undefined && name !== textDirectiveName && name !== textBreakName) return undefined
|
||||
return { code: 'unsupported-node-shape', message: `${name} takes the inline form, ${directivePrefix}${name}{…}, never the block form` }
|
||||
}
|
||||
|
||||
@@ -100,7 +109,11 @@ function readMarks(type: string, spelled: DirectiveValue, path: ConvertErrorPath
|
||||
return success(marks)
|
||||
}
|
||||
|
||||
function namedNode(type: string, attrs: AdfAttributes, marks: readonly AdfMark[] | undefined): AdfNode {
|
||||
const named = Object.keys(attrs).length === 0 ? { type } : { attrs, type }
|
||||
return marks === undefined ? named : { ...named, marks: [...marks] }
|
||||
function namedNode(type: string, attrs: AdfAttributes, marks: readonly AdfMark[] | undefined, empty: ReadonlySet<EmptyKey>, path: ConvertErrorPath): Result<AdfNode> {
|
||||
const held = Object.keys(attrs).length > 0
|
||||
if (held && empty.has('attrs')) return failure('unsupported-node-shape', `${type} spells attrs=empty beside an attribute it holds`, path)
|
||||
const node: AdfNode = held || empty.has('attrs') ? { attrs, type } : { type }
|
||||
if (empty.has('content')) node.content = []
|
||||
if (marks !== undefined || empty.has('marks')) node.marks = [...(marks ?? [])]
|
||||
return success(node)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user