Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

markdown - render a Markdown subset to HTML and ANSI

Import with import "markdown.j" as markdown;. Renders a small CommonMark subset to HTML (through the htmlwriter module, so escaping is handled for you) and to styled terminal text (through the ansi module). Pure Jennifer: line-oriented block parsing with a small inline scanner. Runs on either binary.

use io;
import "markdown.j" as markdown;

io.printf("%s\n", markdown.toHtml("# Hi\n\nA **bold** word."));
# <h1>Hi</h1><p>A <strong>bold</strong> word.</p>

io.printf("%s\n", markdown.toAnsi("- one\n- two"));   # styled on a TTY

Runnable: examples/modules/markdown_demo.j.

Surface

Rendering (Markdown in, HTML / terminal text out):

CallReturnsNotes
markdown.toHtml(md)stringRender to HTML: block elements concatenated, no indentation.
markdown.toAnsi(md)stringRender to terminal text with ansi styling (self-suppressing).

Authoring (build Markdown text - the inverse):

CallReturnsNotes
markdown.header(level, s)stringATX heading; level is "h1".."h6" (throws otherwise).
markdown.style(kind, s)stringInline emphasis; kind is "bold" / "italic" / "code".
markdown.link(text, url)string[text](url).
markdown.bullets(items)stringUnordered list, one - item per line.
markdown.numbered(items)stringOrdered list, 1. item upward.
markdown.codeBlock(text)stringFenced code block around verbatim text.
markdown.table(headings, aligns, rows)stringGFM table from column headings, per-column alignment, and rows.
markdown.tablePretty(md)stringReformat every table’s source columns to line up; other lines untouched.

Supported Markdown

A deliberately small CommonMark subset:

BlockSyntaxHTML
Heading (levels 1-6)# H###### H<h1><h6>
Paragraphconsecutive text lines<p> (lines joined by )
Unordered list- x / * x / + x<ul><li>
Ordered list1. x<ol><li>
Fenced code block``````<pre><code>
Table (GFM)| a | b | + | --- | --- | row<table> (aligned terminal columns in ANSI)
InlineSyntaxHTMLANSI
Bold**text**<strong>bold
Italic*text*<em>italic
Code`text`<code>cyan
Link[text](url)<a href="url">underline + (url)

HTML output

toHtml builds an htmlwriter node tree and renders it, so all text and every link target are correctly escaped - &, <, > in text and code, and &/" in an href - and you cannot produce malformed markup:

markdown.toHtml("[t](http://x/?a=1&b=2) and <b> & `x<y`");
# <p><a href="http://x/?a=1&amp;b=2">t</a> and &lt;b&gt; &amp; <code>x&lt;y</code></p>

Output is compact (no newlines between block elements), which diffs and round-trips predictably; wrap it in your own formatter if you need indented source. A code block’s content is escaped but never treated as Markdown.

ANSI output

toAnsi renders for a terminal: headings and **bold** in bold, *italic* in italic, inline code in cyan, links underlined with their URL in parentheses, list items with - / N. markers, and fenced code indented and dimmed. Styling comes from the ansi module, which suppresses itself when stdout is not a terminal (or NO_COLOR is set) and is forced on by FORCE_COLOR - so piping the output gives clean plain text, and ansi.strip(markdown.toAnsi(md)) gives it unconditionally.

Authoring Markdown

The authoring helpers are the inverse of the renderer: they build Markdown text, so a program can assemble a document (and, since it is Markdown, round-trip it through toHtml / toAnsi):

use io;
import "markdown.j" as markdown;

def items as list of string init ["fast", "small", "strict"];
def doc as string init markdown.header("h1", "Jennifer") + "\n\n";
$doc = $doc + "It is " + markdown.style("bold", "great") + ". Features:\n\n";
$doc = $doc + markdown.bullets($items) + "\n\n";
$doc = $doc + "See " + markdown.link("the docs", "https://example/docs") + ".";

io.printf("%s\n", $doc);          # Markdown source
io.printf("%s\n", markdown.toHtml($doc));   # ... or rendered

The text is inserted literally: a caller passing Markdown metacharacters (a * or ` inside a heading, say) is responsible for escaping them. header throws a catchable value error on a level outside "h1".."h6", and style on a kind other than "bold" / "italic" / "code".

Tables

table turns tabular data into a GFM table in one call: column headings, per-column aligns ("left" / "right" / "center" / "none", or [] for all-default), and rows (each a list of string):

def rows as list of list of string init [];
$rows[] = ["Ada", "95"];
$rows[] = ["Bo", "88"];
io.printf("%s\n", markdown.table(["Name", "Score"], ["left", "right"], $rows));
# | Name | Score |
# | :--- | ---: |
# | Ada | 95 |
# | Bo | 88 |

Columns follow headings: a short row is padded with empty cells and extra cells are dropped, so every row is the same width. A | in a cell is escaped to \| and a newline becomes a space, so cell content can’t break the table. An align value outside the four names throws a catchable value error.

The reader understands GFM tables too, so an authored table round-trips: toHtml(markdown.table(...)) renders a <table> (with per-column align), and toAnsi renders aligned terminal columns. A parsed table needs a header row, a delimiter row (| --- | :--: |), and its data rows; cell content is inline-parsed (emphasis / code / links work in cells), and a table interrupts an open paragraph.

tablePretty reformats the source of every table in a document so its columns line up - the handcraft-then-prettify workflow, in one call - and leaves every non-table line exactly as written:

def messy as string init "| Name | Score |\n|:-|-:|\n| Ada | 95 |";
io.printf("%s\n", markdown.tablePretty($messy));
# | Name | Score |
# | :--- | ----: |
# | Ada  |    95 |

Each column is padded to its widest cell (minimum three, so the delimiter keeps its dashes), data cells follow the column’s alignment, and an escaped \| is preserved. It is idempotent: prettifying an already-pretty table is a no-op.

Not supported

This is a subset, chosen to stay small and TinyGo-clean:

  • Inline spans do not nest. The content of **...**, `...`, and a link’s text is taken as plain text, so **a b** does not render the inner code span.
  • No blockquotes, thematic breaks (---), images, reference links, autolinks, HTML passthrough, or setext (underlined) headings.
  • No nested / indented lists; a list is a flat run of same-kind items.

For anything beyond this subset, render with an external tool. The module is sized for READMEs, help text, and comment / docblock bodies, not general-purpose CommonMark conformance.

See also