diff --git a/par/md.test.ts b/par/md.test.ts index 57af2b9..0325f83 100644 --- a/par/md.test.ts +++ b/par/md.test.ts @@ -5,7 +5,7 @@ import { MarkdownParser } from "./md.ts"; const ren = new HtmlStrRenderer(); Deno.test({ - name: "should parse header", + name: "should parse empty ATX header", fn: () => { const par = new MarkdownParser(); const res = par.parse("#"); @@ -14,16 +14,16 @@ Deno.test({ }); Deno.test({ - name: "should parse header with text", + name: "should parse ATX header with text", fn: () => { const par = new MarkdownParser(); - const res = par.parse("# hello"); - assertEquals(ren.render(res), "<h1>hello</h1>"); + assertEquals(ren.render(par.parse("# hello")), "<h1>hello</h1>"); + assertEquals(ren.render(par.parse("# hello#")), "<h1>hello#</h1>"); }, }); Deno.test({ - name: "should parse header with specific level", + name: "should parse ATX header with specific level", fn: () => { const par = new MarkdownParser(); assertEquals(ren.render(par.parse("# hello")), "<h1>hello</h1>"); @@ -36,7 +36,7 @@ Deno.test({ }); Deno.test({ - name: "should parse header if line contains additional spaces", + name: "should parse ATX header if line contains additional spaces", fn: () => { const par = new MarkdownParser(); assertEquals(ren.render(par.parse(" # hello")), "<h1>hello</h1>"); @@ -44,3 +44,15 @@ Deno.test({ assertEquals(ren.render(par.parse(" # hello")), "<h1>hello</h1>"); }, }); + +Deno.test({ + name: "should parse ATX header with closing sequence", + fn: () => { + const par = new MarkdownParser(); + assertEquals(ren.render(par.parse("# #")), "<h1></h1>"); + assertEquals(ren.render(par.parse("# hello #")), "<h1>hello</h1>"); + assertEquals(ren.render(par.parse("# hello #########")), "<h1>hello</h1>"); + assertEquals(ren.render(par.parse("# hello # ")), "<h1>hello</h1>"); + assertEquals(ren.render(par.parse("###### hello #")), "<h6>hello</h6>"); + }, +}); diff --git a/par/md.ts b/par/md.ts index e6a275a..b41bcd3 100644 --- a/par/md.ts +++ b/par/md.ts @@ -2,7 +2,8 @@ import { AnyNode, Elem, Fragment, TextNode } from "../core/node.ts"; import { isNil } from "../core/utils.ts"; import { Parser } from "./types.ts"; -const RE_OPEN_HEADING = /^\s{0,3}(#{1,6})(\s|$)/; +const RE_OPEN_ATX_HEADING = /^\s{0,3}(#{1,6})(\s|$)/; +const RE_CLOSE_ATX_HEADING = /(^|\s+)#*\s*$/; export class MarkdownParser implements Parser { parse(input: string): AnyNode { @@ -10,29 +11,32 @@ export class MarkdownParser implements Parser { let readStr = input; - const match = RE_OPEN_HEADING.exec(readStr); + const match = RE_OPEN_ATX_HEADING.exec(readStr); if (!isNil(match)) { readStr = readStr.slice(match[0].length); - console.log({ match }); - - const heading: AstHeading = { - kind: AstKind.Heading, + const atxHeading: AstAtxHeading = { + kind: AstKind.AtxHeading, level: match[1].length as HeadingLevel, content: [], }; - ast.content.push(heading); + ast.content.push(atxHeading); if (match[2].length > 0) { - const textContent = readStr.split("\n", 1)[0]; - readStr = readStr.slice(textContent.length); + const endMatch = RE_CLOSE_ATX_HEADING.exec(readStr); - const text: AstText = { - kind: AstKind.Text, - content: textContent, - }; + const headingContent = isNil(endMatch) + ? readStr.split("\n", 1)[0] + : readStr.slice(0, endMatch.index); + readStr = readStr.slice(headingContent.length); - heading.content.push(text); + if (headingContent.trim().length) { + const text: AstText = { + kind: AstKind.Text, + content: headingContent.trim(), + }; + atxHeading.content.push(text); + } } } @@ -40,7 +44,7 @@ export class MarkdownParser implements Parser { } } -function Heading(ast: AstHeading): Elem { +function Heading(ast: AstAtxHeading): Elem { return new Elem(`h${ast.level}`, {}, ast.content.map(Text)); } @@ -51,9 +55,9 @@ function Text(ast: AstText): TextNode { // AST type AstDocument = BaseAstItem<AstKind.Document, AstDocumentChild[]>; -type AstDocumentChild = AstHeading; +type AstDocumentChild = AstAtxHeading; -interface AstHeading extends BaseAstItem<AstKind.Heading, AstText[]> { +interface AstAtxHeading extends BaseAstItem<AstKind.AtxHeading, AstText[]> { level: HeadingLevel; } @@ -68,6 +72,6 @@ interface BaseAstItem<K extends AstKind, Cont> { enum AstKind { Document, - Heading, + AtxHeading, Text, }