Use AsciiDoc for Technical Documentation

Use AsciiDoc for Technical Documentation
Asciidoc editor/preview screenshot from the official site

Overview

There are multiple document formats for documentation out there. Markdown, ReStructured text, AsciiDoc and many others. And while it seems Markdown has won, I prefer to use AsciiDoc and in this post I enumerate 6 reasons why.

1) Support for multiple backends

  • A single project can be transformed into a html5 document, a pdf or an ebook.

  • Interactive slides with code interpretation and highlighting with asciidoctor-reveal.js

  • This blog is written in AsciiDoc

2) File includes

AsciiDoc offers a powerful file include mechanism, can include a whole file, or a specific section or even multiple sections. It can include other asciidoc files, or source files. This feature can be leveraged to ensure the documentation is not out of sync with the application/service code.

The following AsciiDoc snippet includes a whole file as a source block:

AsciiDoctor include
[source, typescript]
-----
.AsciiDoctor include
include::PlaywrightTestCase.ts[]
-----
Produces the following output (click to reveal)
AsciiDoctor include
import { test, expect } from '@playwright/test';

test('has title', async ({ page }) => {
await page.goto('https://google.com/');

    // Expect a title "to contain" a substring.
    await expect(page).toHaveTitle(/Playwright/);
});

test('get started link', async ({ page }) => {
await page.goto('https://playwright.dev/');

    // Click the get started link.
    await page.getByRole('link', { name: 'Get started' }).click();

    // Expects page to have a heading with the name of Installation.
    await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});

We can also use include with the lines attribute to just include a section

AsciiDoctor include with lines attribute
[source, typescript]
.AsciiDoctor include with lines attribute
------
include::PlaywrightTestCase.ts[lines=1..8]
------
Produces the following output (click to reveal)
AsciiDoctor include with lines attribute
import { test, expect } from '@playwright/test';

test('has title', async ({ page }) => {
    await page.goto('https://google.com/');

    // Expect a title "to contain" a substring.
    await expect(page).toHaveTitle(/Playwright/);
});

There is also the possibility to include remote files or to use tags to delimiter what is included on the source block.

3) Diagram as code support

AsciiDoc supports a variety of diagrams

The asciidoctor-diagram gem needs to be installed.
Different diagrams have different requirements, staruml requires java, mermaid requires nodejs

For example here it is the AsciiDoc code to define a plantuml sequence diagram

Plant Uml diagram
[plantuml,"cycle", "svg", role=sequence, title='Plant Uml diagram']
------
!pragma graphviz_dot jdot

@startuml
hide footbox

Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response

Alice -> Bob: Another authentication Request
Alice <-- Bob: Another authentication Response

@enduml
------
Produces the following diagram (click to reveal)
cycle
Figure 1. Plant Uml diagram

4) Powerful tables

The following table with source block codes and nested tables is difficult to build with markdown.

Header 1 Header 2 Header 3
  • Unordered list

  • Another bullet

To be able to insert rich content on the table, make sure to use AsciiDoc style (a) on the column definition or in front of the cell separator

Use !=== to start a nested table and use ! as the column separator, for this to work the column must have a style operator

Header1 Header2

C11

C12

This row has a cell with colspan=3

Hello world source block
public static void main(String [] args){
    System.out.println("Hallo source block from within AsciiDoc!!");
}
Logo
cycle 2
Figure 2. Another Diagram
AsciiDoc code for the table above
AsciiDoc table
    [cols="1a,1a,1a"]
    |===
    |Header 1 |Header 2 |Header 3

    |
    - Unordered list
    - Another bullet

    |
    TIP: To be able to insert rich content on the table, make sure to use `AsciiDoc` style (`a`) on the https://docs.asciidoctor.org/asciidoc/latest/tables/format-column-content/#cols-style[column definition] or in front of the https://docs.asciidoctor.org/asciidoc/latest/tables/format-cell-content/#a-operator[cell separator]
    |

    [cols="2,1"]
    Use `!===` to start a nested table and use `!` as the column separator, for this to work the column must have `a` https://docs.asciidoctor.org/asciidoc/latest/tables/format-column-content/[style operator]
    !===
    ! Header1 ! Header2

    ! C11
    ! C12


    3+| This row has a cell with colspan=3

    .Source
    [source, java]
    .Hello world source block
    ----
    public static void main(String [] args){
    sout("Hallo source block from within AsciiDoc!!");
    }
    ----


    ^| image::/2024/10/28/why-i-prefer-asciidoc-for-technical-documentation/asciidoc-logo.png[alt="Logo"]

    2+^|
    [plantuml,"cycle-2", "svg", role=sequence,title="Another Diagram"]
    ----
    !pragma graphviz_dot jdot
    @startuml
    hide footbox

    Alice -> Bob: Authentication Request
    Bob --> Alice: Authentication Response

    Alice -> Bob: Another authentication Request
    Alice <-- Bob: Another authentication Response

    @enduml
    ----


    |===

5) Code callouts

Code callouts allow to explain an example source code block step by step, I found them very useful as a reader and when trying to explain API’s or SDK’s.

Example java code
@ApplicationScoped
@Identifier("RedisHostProvider") (1)
public class RedisConfig implements RedisHostsProvider {
    @Override
    public Set<URI> getHosts() {
        // do stuff to get the host
        String host = System.getenv("REDIS_CONNECTION_STRING");  (2)
        if (host == null){ (3)
            throw new RuntimeException("Unable to configure redis server, please set secret/env variable REDIS_CONNECTION_STRING");
        }
        return Collections.singleton(URI.create(host));
    }
}
1 Use in application.properties as follows :%prod.quarkus.redis.hosts-provider-name=RedisHostProvider
2 Locate the configuration using an environment variable
3 If the variable is not defined throw error.

6) Tooling Ecosystem

You can find several tools,plugins and extensions for AsciiDoc.

As an avid JetBrains user, I think the best by far is the excellent AsciiDoc plugin for JetBrains IDE’s like IntelliJ, PyCharm and WebStorm.

For Visual Studio Code you’ll find several extensions, with AsciiDoc being the official option.

The project also offers an official browser extension that previews documents and works with Chrome, Firefox, Edge and other browsers.

Additional Resources

The first time I learned about AsciiDoc and AsciiDoctor was at Devoxx 2017 conference. It was on the talk Asciidoctor: New, Noteworthy, and Beyond by Alex Soto

And here are some links/blogs resources

Comments

To add comments, reply in my Bluesky post

asciidoc documentation