Skip to content

Rapidly converts AsciiDoc to Markdown.

License

Notifications You must be signed in to change notification settings

opendevise/downdoc

Repository files navigation

downdoc: Down-convert AsciiDoc to Markdown

What is downdoc?

The downdoc package provides both a CLI (command: downdoc) and a JavaScript function (downdoc) to down-convert AsciiDoc to Markdown. “But why,” you may ask?

The reality is, the AsciiDoc format isn’t supported everywhere. For instance, npm package repositories such as https://npmjs.com and https://yarnpkg.com assume the README for a package is in Markdown. But you’re a sensible developer who wants to maintain your README in AsciiDoc. Enter downdoc.

When the situation calls for it, you can draft content in AsciiDoc and convert it to Markdown where only Markdown is accepted. Typical examples include an issue tracker or chat application. To post in Markdown, run the AsciiDoc content through downdoc and paste the result it produces. downdoc handles the context switch to Markdown so your brain doesn’t have to.

This project initially started out with a goal to transpile an AsciiDoc README to the Markdown format for npm packaging. In that regard, it was designed to be just good enough to get the job done. Over time, downdoc has proven to have broader application and has thus been gradually improved. It now does a reasonably good job of mapping constructs in AsciiDoc to Markdown for all situations. It even offers dedicated configuration to target some Markdown flavors and environments.

How does it work?

downdoc employs a minimal approach to map the AsciiDoc syntax to Markdown that does not make use of Asciidoctor. While this gives downdoc the advantage of having no dependencies and being exceptionally fast, it does not support the full AsciiDoc syntax. Rather, it’s intended to be used on AsciiDoc documents that are written with conversion to Markdown in mind.

Supported syntax

In order for downdoc to convert an AsciiDoc document, it must have lines that end with LF (\n), not CRLF (\r\n).

Here’s a rough list of the AsciiDoc syntax that downdoc current handles:

  • document title (atx-style only; Markdown-style headings not supported)

  • implicit author and revision lines in document header

  • common intrinsic attributes (empty, idprefix, idseparator, nbsp, sp, vbar, zwsp)

  • document attribute entries (single-line value only; no support for pass macro)

  • document attribute references

  • part/chapter/section titles and discrete headings (atx-style only; Markdown-style headings not supported)

  • block titles

  • formatted text (restricted to boundaries of a single line)

    • strong (bold), emphasis (italic), code (monospace), literal code, mark, span w/ line-through role, double and single curly quotes, curly apostrophe

    • metadata (ID and roles) attached to strong, emphasis and code spans are processed and dropped

    • literal code span only escapes attribute references

    • use quotes attribute to specify characters (space-separated) for double curly quotes (e.g., “ ”)

    • use markdown-strikethrough attribute to specify characters to use to enclose strikethrough text

  • ordered, unordered (including checklists), and callout lists (1-19 or .)

  • nested ordered and unordered lists (use markdown-list-indent attribute to control indent size of output)

  • description lists with optional qanda style

  • list continuation (for current list item only; no support for ancestor list continuation)

  • link and URL macros (honors hide-uri-scheme attribute to hide URL scheme in visible URL); can be escaped

  • escaped link and URL macros and autolinks (bare URLs)

  • internal xrefs (honors value of idprefix and idseparator document attributes)

  • block IDs for document, sections, paragraphs

  • block IDs for other blocks (e.g., listing, literal, lists, example, sidebar, etc.) that have a title

    • anchor is placed at start of title

  • auto-generated text for internal xrefs (section and verbatim blocks only) (honors reftext attribute on section or on block with ID and title)

  • inline anchor (shorthand syntax only, no reftext)

  • block and inline image macros

  • literal blocks, listing blocks, and source blocks with optional source language

  • diagram blocks (literal block with diagram dialect)

  • literal paragraphs (auto-detects command prompt and promotes to command code block; first line must be least indented line)

  • callout numbers (i.e., conums) (1-19 or .) in verbatim blocks

  • attribute references in literal paragraphs and verbatim blocks when subs=attributes+ or subs=+attributes is specified

  • indent=0 attribute on verbatim blocks

  • admonition paragraphs and blocks

  • quote blocks with optional attribution

  • Markdown-style blockquotes (nested blocks are passed through as written; only inline markup is down-converted)

  • thematic breaks (including Markdown-style, e.g., ---)

  • hard line breaks

    • supports explicit character or hardbreaks option on paragraph

    • uses backslash as line break character by default (use markdown-line-break document attribute to override)

  • ifdef and ifndef preprocessor conditionals (can be nested)

  • escaped ifdef, ifndef, and include preprocessor directives

  • tables

    • cols attirbute with optional horizontal alignments

    • explicit header row or implicit header row unless noheader option is set

    • no support for cell style (ignores cell style in colspec and cellspec)

  • collapsible blocks (set the markdown-collapsible-variant attribute to spoiler to generate Zulip-compatible spoiler block)

  • comment lines and blocks

  • passthrough blocks

  • stem blocks (i.e., display math) (assumes latexmath notation)

  • inline stem macro (no passthrough semantics)

  • delimited example, open, and sidebar blocks (delimiter lines removed)

Here’s the list of document attributes unique to this converter that control its behavior:

  • markdown-collapsible-variant (default: disclosure; accepts: disclosure or spoiler)

  • markdown-line-break (default: \; accepts any characters)

  • markdown-list-indent (default: not set; accepts a positive integer)

  • markdown-strikethrough (default: ~~; accepts a mark sequence or a space-separated pair of HTML tags)

  • markdown-unwrap-prose (when set, will remove newlines between lines in paragraphs; reverses ventilated prose)

  • quotes (default: <q> </q>; accepts a space-separated pair of HTML tags or marks)

To use a backtick in a code span in Markdown, it must be enclosed in backticks then enclosed in non-backtick characters, such as spaces. To achieve this, we recommend setting the backtick attribute as follows:

:backtick: {sp}```{sp}

Then you can reference it anywhere in a code span using the {backtick} attribute reference. If you need to use a backtick outside of a code span, you may want to split it into two separate attributes. You may need to play around a bit to get the output you want.

Currently, include directives are dropped. However, you can first run the document through Asciidoctor Reducer to incorporate the content from any included files. Add the --preserve-conditionals option when running Asciidoctor Reducer to preserve preprocessor conditional directives in the output of this step. If you then run downdoc on the output produced by Asciidoctor Reducer, it will convert the entire document, includes and all.

Support for additional syntax may be added in the future.

Prerequisites

In order to use this extension, you must have Node.js 16.17.0 or higher installed on your machine.

Install

Use the following command to install the downdoc package into your project:

$ npm i downdoc

By default, npm i will install the latest stable release. The version number for stable downdoc releases ends with -stable because downdoc is a reclaimed package.

Alternately, you can defer installation and invoke the CLI using the npx command.

Usage

CLI

$ npx downdoc README.adoc

The downdoc command automatically generates a Markdown file. By default, the Markdown file has the same name as the AsciiDoc file with the file extension changed to .md (e.g., README.md).

You can instruct the command to write to a different file using the -o (or --output) option.

$ npx downdoc -o out.md README.adoc

If the value of the -o option is -, the command will write the output to the console (i.e., stdout).

$ npx downdoc -o - README.adoc

You can pipe from input and output by using - as the input path.

$ cat README.adoc | npx downdoc -

You can pass additional runtime AsciiDoc attributes using the -a (or --attribute) option.

$ npx downdoc -a hide-uri-scheme -a markdown-list-indent=4 README.adoc

To print a usage statement that includes a complete list of available options, pass the -h option.

API

const downdoc = require('downdoc')
const fsp = require('node:fs/promises')

;(async () => {
  await fsp
    .readFile('README.adoc', 'utf8')
    .then((asciidoc) => fsp.writeFile('README.md', downdoc(asciidoc) + '\n', 'utf8'))
})()

The downdoc function accepts an object (i.e., map) of options as the second argument.

downdoc(asciidoc, { attributes: { 'markdown-list-indent': 4 } })

Currently the only supported option in the API is attributes, which is an object (i.e., map) of runtime AsciiDoc attributes.

npm publish

The prime focus of this tool is to convert an AsciiDoc README to Markdown for npm packaging. This switch is done by leveraging the pre and post lifecycle hooks of the publish task. In the pre hook, you convert the README to Markdown and hide the AsciiDoc README. The npm publish task will then discover the Markdown README and include it in the package. In the post hook, you remove the Markdown README and restore the AsciiDoc README.

Using this technique, the published npm package ends up with a Markdown README, but the README in your repository remains in AsciiDoc. We refer to this process as the README dance.

If that sounds complicated, no need to worry. downdoc has you covered. The downdoc CLI provides the helpers you need to call during these lifecycle hooks. To use them, add the following entries to the scripts property in the package.json at the root of your project.

"postpublish": "downdoc --postpublish",
"prepublishOnly": "downdoc --prepublish",

Let’s have a look at where these entries go when we step back and look at a complete file:

{
  "name": "my-package",
  "version": "1.0.0",
  "scripts": {
    "postpublish": "downdoc --postpublish",
    "prepublishOnly": "downdoc --prepublish",
    "test": "mocha"
  }
}

If you don’t want to declare a dependency on the downdoc package in your project, prepend the call to downdoc with npx -y:

"postpublish": "npx -y downdoc --postpublish",
"prepublishOnly": "npx -y downdoc --prepublish",

When an AsciiDoc file is converted using the --prepublish CLI option, both the env=npm and env-npm document attributes are set. This allows you to show or hide content in the README that is displayed in the npm package registry.

You can find an example of downdoc used for this purpose in the downdoc project itself.

Create executables

Thus far, we’ve assumed that you’re running downdoc using Node.js installed on your system. However, downdoc is one of those tools you might want to use in any environment. In that case, what you want is an executable that doesn’t require Node.js to be installed. That’s where pkg comes in.

Using pkg, you can bundle Node.js and downdoc into a single executable (i.e., a precompiled binary) per system (OS and architecture). To do so, clone this project and run the following command:

$ npx pkg -t node18-linux,node18-macos,node18-win .

This command will produce downdoc-linux, downdoc-macos, and downdoc-win.exe. You can transfer any one of these executables to a suitable system and run it without having to install Node.js. For example:

$ ./downdoc-linux README.adoc

The binary includes the package metadata and source code of this project in raw form. Run npx pkg -h or read the pkg README to learn more about how it works.

Copyright © 2022-present Dan Allen (OpenDevise Inc.) and the individual contributors to this project.

Use of this software is granted under the terms of the MIT License.