Coelho.Markdown (coelho v0.18.0)

Copy Markdown View Source

Documents to Markdown, and back.

Coelho.Markdown.to_markdown(document)
#=> "## Release notes\n\nFixed **three** bugs."

For a README, an export, a commit message, a language model's prompt — anywhere Markdown is the format and HTML is not. The output is CommonMark with two GitHub extensions, ~~strikethrough~~ and pipe tables, and every character a writer typed comes back as that character: what would be syntax is escaped, so a paragraph that starts with # stays a paragraph.

A mark is written with its Markdown delimiters — **, *, ~~ — where CommonMark is certain to read them as such, and as the equivalent HTML element where it would not: a bold *b right after a letter, a span that begins with a space. Both are Markdown, and both come back. Underline, highlight, subscript and superscript have no delimiters at all, so they are always their element: <u>, <mark>, <sub>, <sup>.

What Markdown cannot say

It is dropped, or written another way, rather than approximated:

  • an empty paragraph or heading, and a line break at the edge of one
  • a paragraph's alignment, and a table cell's span; a table's first row is its header, whatever it held. A cell is one line: a paragraph in it writes a line break as <br>, a | as &#124; and code as <code>, since GitHub splits the row on any other |, and anything else in it — two paragraphs, a list, a code block — is written as Coelho's HTML
  • an ordered list numbered past nine digits, which no Markdown marker holds, is written as Coelho's HTML
  • a heading holding a line break is written as Coelho's own HTML for it
  • an attachment is a link to it — resolved through :context, as Coelho.Render resolves it — or its name when it has no URL, and its caption a paragraph after it
  • an image without alt text comes back with an empty one, and a URL comes back percent-encoded where CommonMark encodes it ([, ], a space)
  • a code block's language is written, and lost again on import, as it is by Coelho.HTML.from_html/3

from_markdown/3 goes the other way, through Coelho.HTML.from_html/3, so the schema decides what is kept exactly as it does for imported HTML. It needs the optional :mdex (a precompiled binding to comrak, a CommonMark parser) and :floki dependencies; to_markdown/3 needs neither.

Your own nodes and marks

A node of the application's own renders as its children — block content as blocks, inline content as text — or, for a void node, as the text its :to_text gives. :nodes and :marks say otherwise, by name:

Coelho.Markdown.to_markdown(document, schema,
  nodes: %{mention: fn node, _children -> "@" <> node["attrs"]["handle"] end},
  marks: %{spoiler: {"||", "||"}}
)

A node override returns the Markdown itself, escaped as it needs; a mark override is the pair of strings it is wrapped in. It applies to a shipped mark as well — highlight: {"==", "=="} for a renderer that reads it.

Summary

Functions

Converts Markdown into a validated document, and says what it left behind.

Renders a validated document as Markdown. nil is "".

Types

opts()

@type opts() :: [
  nodes: %{optional(atom()) => (map(), String.t() -> iodata())},
  marks: %{optional(atom()) => {String.t(), String.t()}},
  context: Coelho.Attachments.context()
]

Functions

from_markdown(markdown, schema \\ Schema.default(), opts \\ [])

@spec from_markdown(String.t(), Coelho.Schema.t(), keyword()) ::
  {:ok, map(), [Coelho.HTML.warning()]} | {:error, term()}

Converts Markdown into a validated document, and says what it left behind.

Through HTML: MDEx renders the Markdown — CommonMark, with GitHub's tables and strikethrough — and Coelho.HTML.from_html/3 imports that under the schema, so HTML written inside the Markdown is held to the same rules as any other: see its documentation for the warnings.

to_markdown(document, schema \\ Schema.default(), opts \\ [])

@spec to_markdown(map() | nil, Coelho.Schema.t(), opts()) :: String.t()

Renders a validated document as Markdown. nil is "".