std/markdown
Markdown reads CommonMark with GitHub's tables and front matter into a document tree that is a value, with the lines of every block and link; HTML and Markdown are written from the tree.
The generated reference of std/markdown has every public construct with its signature, its doc comment and its examples.
std/markdown is Markdown as a document format, written in TorbScript: CommonMark 0.31.2 with the tables of GitHub
Flavored Markdown and front matter. Markdown().parse(text) reads a text into a MarkdownDocument, a tree of blocks
and inlines that is a value - every text is a Markdown document, so reading never fails. HTML is a separate step over
that tree, and so is Markdown: what the writer writes reads back as the same document. All 652 examples of the
CommonMark specification and the 8 examples of GitHub's table extension are its tests, and every one of them passes -
read, rendered, and written back. The design is TEXT-FORMATS.md section 3 and slice 1 of
RELEASE.md.
Import#
use Markdown, MarkdownDocument, FrontMatter, MarkdownBlock, BlockContent, MarkdownList, ListItem from "std/markdown"
use MarkdownTable, ColumnAlignment, MarkdownInline, List.plainText from "std/markdown"use Markdown, List.plainText from "std/markdown"
const document = Markdown().parse("# Values\n\nA *binding* holds [a value](values.md).\n")
for block in document.blocks {
if const .Heading(level, inlines) = block.content {
print "heading {level} on line {block.line}: {inlines.plainText()}"
}
}
print document.html()Declarations#
Markdown#
type Markdown {
frontMatter: Bool = true
tables: Bool = true
fn parse(text: String): MarkdownDocument
fn html(text: String): String
}The reader with its options. frontMatter reads a first line ---, up to the next line --- or ..., as front
matter instead of a thematic break; tables reads a paragraph's last line followed by a delimiter row
(| --- | :-: |) as the header of a table. With both off the reader is CommonMark as the specification has it; with
them on, a text without front matter and without a table reads exactly the same.
MarkdownDocument and FrontMatter#
type MarkdownDocument {
blocks: List<MarkdownBlock>
frontMatter: FrontMatter? = None
}
extend MarkdownDocument {
fn html(): String
fn safeHtml(): String
}
extend MarkdownDocument {
fn markdown(): String
}
type FrontMatter {
text: String
line: Int
fn document(): Result<YamlDocument, YamlError>
}A document: its blocks, and its front matter. FrontMatter.document() reads the front matter through
std/yaml, with the lines of the Markdown file, so a problem in it names the line of the page. html() is
the HTML of the blocks - the front matter is not part of it - safeHtml() the HTML for text that is not trusted, and
markdown() the document written back as Markdown.
MarkdownBlock and BlockContent#
type MarkdownBlock {
content: BlockContent
line: Int = 0
endLine: Int = 0
}
type BlockContent {
case Paragraph(inlines: List<MarkdownInline>)
case Heading(level: Int, inlines: List<MarkdownInline>)
case ThematicBreak
case CodeBlock(info: String, text: String, fenced: Bool)
case HtmlBlock(html: String)
case BlockQuote(blocks: List<MarkdownBlock>)
case ItemList(list: MarkdownList)
case Table(table: MarkdownTable)
}
type MarkdownList {
ordered: Bool
marker: String
start: Int
tight: Bool
items: List<ListItem>
}
type ListItem {
blocks: List<MarkdownBlock>
line: Int = 0
}
type MarkdownTable {
alignments: List<ColumnAlignment>
header: List<List<MarkdownInline>>
rows: List<List<List<MarkdownInline>>>
}
type ColumnAlignment {
case Unspecified
case Left
case Center
case Right
}A block, and the lines of the file it spans, counted from 1 - front matter included, so a tool that checks a page can
name the line of what it found. A code block keeps its info string (trb check) and its text as it was written,
without the fence; an indented one has an empty info string. A list's marker is its bullet or the delimiter after
its numbers, and a tight list is one whose paragraphs HTML writes without <p>. A table row has as many cells as its
header: a missing cell is empty, and an extra one is dropped, as GitHub does.
MarkdownInline#
type MarkdownInline {
case Text(text: String)
case Code(text: String)
case Emphasis(children: List<Self>)
case Strong(children: List<Self>)
case Link(destination: String, title: String, children: List<Self>, line: Int)
case Image(destination: String, title: String, children: List<Self>, line: Int)
case Html(html: String)
case SoftBreak
case HardBreak
}
extend List<MarkdownInline> {
fn plainText(): String
}What a paragraph, a heading or a cell holds, with escapes and entity references resolved: a text is the text a reader
sees. A link's destination is as it was written, resolved through a reference definition where the link is a
reference; it is percent-encoded only when HTML is written. line is the line a link or an image starts on.
inlines.plainText() is the inlines without their markup - the alt of an image, the text of a heading an anchor
is made of.
html(), safeHtml() and markdown()#
The writers are methods of MarkdownDocument, listed with it above. document.html() writes the HTML the
specification's examples expect, which passes raw HTML through. document.safeHtml() is for text that is not trusted,
such as the doc comments of a package someone else wrote: a raw HTML block or span is written as escaped text, so it is
visible in the page and does nothing, and a link or image destination that starts with javascript:, vbscript: or
data: (in any case, and ignoring spaces and control characters before and inside the scheme) is written as an empty
href or src. Everything else is what html() writes. document.markdown() writes one form for every construct -
* and **, fenced code, - and 1. lists, ATX headings where a heading fits on one line - and escapes every
character of a text that could be read as markup, so reading what it writes gives the same document.
Related#
- std/yaml - what front matter is read by.
- The standard library - the other packages.