Tackling Hugo Integration of Asciidoctor

Integrate Asciidoctor as everyone should expect

While Hugo comes with Asciidoctor support, they are in reality several issues to account for at this time:

While basic rendering of asciidoc files works, we soon encounter problems ; the very first

  • Source listings are not syntax-highlighted.

  • Include directives don’t work

  • There’s no icons with admonition blocks

  • Source listing callouts are way off

So reading a few blogs posts to tackle these issues, and learn :

The first useful hack

First we need to be able to override how hugo invoke asciidoctor. Hugo expects an executable name asciidoctor, we can trick it to run a shell script instead, by creating in the repository a file named ./bin/asciidoctor. Then run hugo with the following environment : env PATH=$PWD/bin:$PATH hugo serve.

Hugo uses : --no-header-footer --safe --trace - args, as seen in getAsciidocContent.

Table 1. from the man page

--safe

Set safe mode level to safe. Enables include directives, but prevents access to ancestor paths of source file. Provided for compatibility with the asciidoc command. If not set, the safe mode level defaults to unsafe when Asciidoctor is invoked using this script.

-s, --no-header-footer

Output an embeddable document, which excludes the header, the footer, and everything outside the body of the document. This option is useful for producing documents that can be inserted into an external template.

-

If FILE is - then the AsciiDoc source is read from standard input.

So using the trick learned in the above blog posts, and with quite a bit of trial and error I got up the following script:

./bin/asciidoctor
#!/bin/sh

ad="/usr/local/bin/asciidoctor"

$ad --trace --verbose \
  --base-dir ./content \                        #  (1)
  --no-header-footer \                          #  (2)
  --attribute nofooter \
  --attribute docinfo=shared \
  --attribute icons=font \                      #  (3)
  --attribute source-highlighter=highlightjs \  #  (4)
  --attribute sectlinks \                       #  (5)
  --attribute sectanchors \
  --attribute figure-caption! \
  --attribute toc-title! \
  -                                             #  (6)
1 The dot . is where hugo has been started, usually it’s the Hugo site root, and content is where website content is located by default, so the content folder is the base directory of asciidoc, this is important for include: directives where the path will be relative to the ./content folder. This option is useful because asciidoctor is invoked to use stdin/stdout via -
2 The most important settings, tell asciidoctor to not generate the header and footer as those are part of the Hugo theme, in addition, this command also tells to not generate content footer and trick asciidoctor to believe header and footer are part of docinfo by using the value shared. This helps in case of some external script that are being injected like FontAwesome, Highlight.JS, or MathJax for example.
3 Use Font Awesome, however Asciidoctor uses Font Awesome 4 which can be tricky to integrate in a theme that rely on Font Awesome 5.
4 This tells Asciidoctor to render pre code elements with highlight.js information for them. This require that the theme correctly set-up Highlight.JS.
5 sectlinks, sectanchors, figure-caption, or toc-title are attributes to tweak the rendering, usually they are part of the document, but since I prefer to have this setting global they are here in the command line.
6 Read the source from stdin

Then run hugo by overriding the PATH environment variable.

locally with ./bin/asciidoctor script
exec env PATH=$PWD/bin:$PATH hugo # the hugo options

By the way if you forgot the --no-header-footer option the whole HTML document produced by asciidoctor is inserted by Hugo (as expected in fact) in the generated page. Inline CSS and scripts import emitted by Asciidoctor are then competing with the one of the Hugo theme.

See the <div class="post">
<div class="post">

<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="generator" content="Asciidoctor 2.0.10">
    <title>Integrate Asciidoctor as everyone should expect</title>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700">
    <style>
    //... asciidoctor inline css
    </style>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">


    <body class="article">
        <div id="header">
        </div>
        <div id="content">

        ... the article content


        </div>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/styles/github.min.css">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/highlight.min.js"></script>
        <script>hljs.initHighlighting()</script>
        <script type="text/x-mathjax-config;executed=true">
        MathJax.Hub.Config({
          messageStyle: "none",
          tex2jax: {
            inlineMath: [["\\(", "\\)"]],
            displayMath: [["\\[", "\\]"]],
            ignoreClass: "nostem|nolatexmath"
          },
          asciimath2jax: {
            delimiters: [["\\$", "\\$"]],
            ignoreClass: "nostem|noasciimath"
          },
          TeX: { equationNumbers: { autoNumber: "none" } }
        })
        MathJax.Hub.Register.StartupHook("AsciiMath Jax Ready", function () {
          MathJax.InputJax.AsciiMath.postfilterHooks.Add(function (data, node) {
            if ((node = data.script.parentNode) && (node = node.parentNode) && node.classList.contains('stemblock')) {
              data.math.root.display = "block"
            }
            return data
          })
        })
        </script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_HTMLorMML"></script>
    </body>
    </html>
</div>

Porting the asciidoctor CSS

So now that we have the content HTML structure, the rendering of the article is still way off.

  • source listings are not syntax-highlighted.

  • Font Awesome icons are missing, incompatibility between FA4 and FA5

  • CSS is off or way off for almost all elements of Asciidoctor’s particular features,

The process was tedious, just copying the default asciidoctor css isn’t quite the right thing as stylesheet from the theme and the stylesheet from asciidoctor interfere, (corrupting other elements of the theme like the sidebar).

For example the theme alternate the background color table’s rows, and asciidoctor rely on lot on tables to layout some features, be it callout, admonition blocks, some lists, etc. in order to fix that the CSS has t be patched with rules like these:

tbody tr:nth-child(2n+1) td, tbody tr:nth-child(2n+1) th {
  background-color: inherit;
}
Using the web tools inspector to debug CSS issues

asciidoctor css debug

Actually this work took significantly more time that I was prepared for, fortunately the Asciidoctor default CSS helped, and the way the theme of my choosing was using Sass (many other thems use it as it’s part of Hugo).

I ported and tweaked necessary Asciidoctor CSS rules to my scss files using an online CSS to SASS/SCSS converter. I’m not a web developer so the result is probably incorrect, inefficient in some part but it works for the feature I’m using.

Like thme layout overrides, hugo let’s you override scss files in the respective directories like ./assets/scss.

./assets/scss/hyde-hyde.scss
@import "hyde-hyde/variables";
// poole
@import "poole/base";
@import "poole/layout";
@import "poole/posts";
// hyde-hyde
@import 'hyde-hyde/mixins';
@import 'hyde-hyde/base';
@import 'hyde-hyde/sidebar';
@import 'hyde-hyde/list';
@import 'hyde-hyde/post';
@import 'hyde-hyde/code';
@import 'hyde-hyde/gist';
@import 'hyde-hyde/navigation';
@import 'hyde-hyde/taxonomies';
@import 'hyde-hyde/project';
@import 'hyde-hyde/responsive';
@import 'hyde-hyde/misc';
@import 'hyde-hyde/theme';
@import 'hyde-hyde/asciidoctor';  (1)
1 Imports asciidoctor last.
./assets/scss/hyde-hyde/asciidoctor.scss
.post { (1)
  // asciidoctor style
}
1 Make asciidoctor rule applied within the first .post class this is necessary otherwise asciidoctor rules may interfere with theme elements that are name the same, such as .sidebar, or .title

The current state of this file covers most features of Asciidoctor but a few leftover like colors.

Resolving Font Awesome 4/5 issues

One of the neat thing about Asciidoctor is their use of Font Awesome is various parts like admonition icons. This is activated whith this attribute definition :fonts: icons.

But, Asciidoctor 2.0.10 uses Font Awesome 4.7, which is incompatible with my theme as it is using FOnt Awesome 5. This took me quite a while to figure out a solution, and it’s not perfect either.

To be more specific let’s dive into the admonition block TIP:, Asciidoctor will generate HTML with the icon-tip class.

<td class="icon">
    <i class="fa icon-tip" title="Tip"></i>
</td>

Asciidoctor uses the pseudo-element ::before in order to display the right icon. So only using unicode character code, and the right font family should do it.

td.icon {
  [class^="fa icon-"] {
    font-size: 2rem;
    font-family: 'Font Awesome 5 Free'; (1)
    cursor: default;
    text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
    text-rendering: auto;
    -webkit-font-smoothing: antialiased;
  }
  .icon-tip::before {
    content: "\f0eb"; (2)
    font-weight: 400; (3)
    text-shadow: 1px 1px 2px rgba(155, 155, 0, 0.8);
  }
}
1 Use the FA5 font family that has the icons
2 Equivalent to <i class="far fa-lightbulb"></i>, but use the right font-weight to access the free icons in the Regular set
3 400 allows access to the Regular set, however only icons present in the font family are present, in the Free family only a few Regular icons are available.
The fail

However, this didn’t work quite well. As I dicovered that the SVG framework that is used when the JS scripts are loaded at the end of the page don’t quite like the fa icon-tip class. The script always replace this element by the missing icon (a dotted circle with an exclamation mark alterning with a question mark). I tried may variations, adding script in the head, at the end, playing with defer, etc.

does not work
  <script defer src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/js/all.min.js" integrity="sha256-KzZiKy0DWYsnwMF+X1DvQngQ2/FxF7MF3Ff72XcpuPs=" crossorigin="anonymous"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/js/v4-shims.min.js" integrity="sha256-prFmieX9aRVhOV/ldXGklUUhS7NRBQUijQU4HcdnO8Q=" crossorigin="anonymous"></script>

It took me a while but I ultimately decided to remove the usage of the SVG framework and as such loading these scripts as I never got them to work. Leaving only the FA5 CSS imports.

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css" integrity="sha256-h20CPZ0QyXlBuAw7A+KluUYx/3pK+c7lYEpqLTlxjYQ=" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/v4-shims.min.css" integrity="sha256-wN7QJaqAwQ03kgUhyN4EU2phRdDkLrQYbFe0EvpQ60U=" crossorigin="anonymous" />

And now tada, even with the asciidoctor macro :

But be sure to use FA4 icons, otherwise the icon won’t be found :

Yet it is still possible to use asciidoctor’s passthrough macro with the HTML right class from FA5 <i class="fab fa-java fa-3x"></i>

or the pass:[<i class="fab fa-java fa-2x"></i>] inline macro

At some point it might be interesting to look for macro extensions, also see extension lab for more.

Github issues that helped me

Trying the semantic HTML5 backend

Also, one of the blog post is suggesting to switch to semantic HTML5, because the structure of the HTML5 asciidoctor’s backend is not modern enough, e.g.

<div class="paragraph">
<p>I’ve crafted my own simple shortcode for Amazon <code>{{&lt; amzn "B07XW76VHZ" &gt;}}</code> :</p>
</div>

Using the semantic HTML5 backend as suggested in this blog post require to gem install asciidoctor-html5s. And pass this option in the script --backend html5s.

In practice the Semantic HTML5 converter from https://github.com/jirutka/asciidoctor-html5s outputs semantic HTML, but doesn’t support all features of asciidoctor like icons for admonition blocks, also it uses the asciidoctor default CSS which is not suited for this semantic HTML5 tree: elements differ, CSS class names differ as well.

So while this slightly improves the HTML structure, this made CSS port too challenging for me, broken syntax highlighting, titles are not styled as expected, etc., so I didn’t use it in the end. At least this decision didn’t increase the complexity of the setup.

Using diagrams

2020-09-20: Hugo 0.74.0 and asciidoctor-diagram 2.0.3 finally allowed me to get rid of the setup below, which is nice in particular for diagrams. I’ll blog about the change at some point.

Asciidoctor has a useful extension asciidoctor-diagram that allows the generation of diagrams from simple text listing, this may not be necessary for everyone, but it was a mandatory thing for me.

It’s necessary to install the extension gem install asciidoctor-diagram and to adapt the ./bin/asciidoctor script to

./bin/asciidoctor
  $ad --trace --verbose \
    --base-dir ./content \
+   --require asciidoctor-diagram \
    --no-header-footer \
    --attribute nofooter \
    --attribute docinfo=shared \

This wasn’t enough as asciidoctor-diagram outputs the generated SVG in the wrong directory to be served by Hugo. I got a tip by reading this blog entry, which consists in modifying the URI of the diagrams in the rendered HTML, and moving the generated files.

./bin/asciidoctor
    --attribute sectanchors \
    --attribute figure-caption! \
    --attribute toc-title! \
-   -
+   - | sed -E -e "s/img src=\"([^/]+)\"/img src=\"\/diagram\/\1\"/"
+
+ mkdir -p static/diagram
+
+ if ls *.svg >/dev/null 2>&1; then
+ mv -f *.svg static/diagram
+ fi
+
+ if ls *.png >/dev/null 2>&1; then
+ mv -f *.png static/diagram
+ fi

In practice, I wasn’t satisfied by this setup for several reasons:

  1. The sed targets all img elements.

  2. The files are moved in static/diagram, this is the most annoying point, because

    1. This move operation is not performed atomically with the content rendering, thus when served locally it triggers another refresh, which also triggers the rendering of the original document and then regenerate the diagram images which are again moved to static/diagram, triggering another refresh cycle.

    2. The generated files are moved in the static folder but are not supposed to be put in git, this can create unnecessary noise.

To be faire the orignal blog post runs hugo serve with these options --disableFastRender --disableLiveReload --renderToDisk.

I couldn’t get a satisfying solution, so I decided to inline the generated SVG, by declaring the opts="inline" in the descriptor.

[plantuml,"sequence-diagram-example",format="svg",opts="inline"]
.Sequence diagram
----
// plantuml
----

This doesn’t require to pipe the output to sed and mv SVGs afterwards, but the asciidoctor-diagram outputs the images in the basedir.

While I’ve tried to set imagesoutdir or out_dir attributes in ./bin/asciidoctor as documented (her): Diagrams will be put in the mentioned folder like resources/_gen yet somehow there still stored on basedir as well.

Also, I have tried to make SVG inlining as default for diagrams using this change that appeared in Asciidoctor 2, but it didn’t quite work as expected, as explained in asciidoctor/asciidoctor-diagram#247 comment.

Also, in the future it may be interesting to look at using charts macro, like suggested by this blog post.

Tweaking .gitignore

For me this ws due to asciidoctor-diagram that generates files here in a .asciidoctor directory which is located in the configured base directory. Let’s ignore it.

.gitignore
+ ### Asciidoctor
+ **/.asciidoctor

Adapting Github Actions

As seen in the previous entry, the site is rendered by a Github Action job, this job needs to be updated to run asciidoctor the way I want it. Fortunately I read few blog entries on the topic.

I mostly got inspired by Gunnar Morling in this matter, and looked on his github repository to see on it was done. And went the same way.

Make a docker image with hugo and asciidoctor

His workflow is now based on running a docker image that has asciidoctor and hugo.

Since I wanted a full featured asciidoctor experience I needed something more that was in his image.

Trying to use docker-asciidoctor

I tried to use asciidoctor/docker-asciidoctor as a base image since it offers the whole experience. And it easy to just download and install a single executable.

However, I experienced issues to execute hugo, I suspect this is due to alpine base but I wasn’t able to pin point the issue:

bash-5.0# hugo version
bash: /usr/local/bin/hugo: No such file or directory
bash-5.0# ldd /usr/local/bin/hugo
        /lib64/ld-linux-x86-64.so.2 (0x7fcf85eb4000)
        libpthread.so.0 => /lib64/ld-linux-x86-64.so.2 (0x7fcf85eb4000)
        libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x7fcf85d1b000)
        libdl.so.2 => /lib64/ld-linux-x86-64.so.2 (0x7fcf85eb4000)
        libm.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7fcf85eb4000)
        libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x7fcf85d07000)
        libc.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7fcf85eb4000)

...

bash-5.0# ldd /usr/local/hugo/hugo
        /lib64/ld-linux-x86-64.so.2 (0x7f339a631000)
        libpthread.so.0 => /lib64/ld-linux-x86-64.so.2 (0x7f339a631000)
Error loading shared library libstdc++.so.6: No such file or directory (needed by /usr/local/hugo/hugo)
        libdl.so.2 => /lib64/ld-linux-x86-64.so.2 (0x7f339a631000)
        libm.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7f339a631000)
Error loading shared library libgcc_s.so.1: No such file or directory (needed by /usr/local/hugo/hugo)
        libc.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7f339a631000)

Create my own image

I ended up forking gunnarmorling/hugo-builder to adapt it to my needs, installing a more recent version of hugo, asciidoctor-diagrams, make hugo use of the asciidoctor script hack.

I found this blog instructions easy to follow in order to publish my own image on docker hub.

THe only issue I’ve encountered is that I forgot that a JDK is necessary to run asciidoctor-diagram.

ERROR 2020/04/20 22:00:39 posts/2020-04-20-tackling-hugo-integration-of-asciidoctor/index.adoc: asciidoctor: ERROR: <stdin>: line 284: Failed to generate image: Could not find Java executable

At this time here’s what my bric3/hugo-builder `Dockerfile looks like:

Dockerfile
FROM registry.fedoraproject.org/fedora-minimal

EXPOSE 1313
WORKDIR /src
VOLUME /src

RUN microdnf -y install curl ruby tar java-11-openjdk && microdnf clean all

ARG HUGO_VERSION=0.69.0
ARG ASCIIDOCTOR_VERSION=2.0.10
ARG ASCIIDOCTOR_DIAGRAM_VERSION=2.0.2

RUN gem install --no-document \
  "asciidoctor:${ASCIIDOCTOR_VERSION}" \
  "asciidoctor-diagram:${ASCIIDOCTOR_DIAGRAM_VERSION}"


# Downloading latest manually as packages are a bit dated
RUN mkdir -p /usr/local/hugo \
  && curl -LO https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz \
  && tar xzvf hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz -C /usr/local/hugo/ \
  && ln -s /usr/local/hugo/hugo /usr/local/bin/hugo \
  && rm hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz

ENV PATH="/src/bin:${PATH}"

Modifying the GA workflow

Then using this docker image I change the Github Action workflow to this.

# ...
jobs:
  build-deploy:
    runs-on: ubuntu-18.04
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true
          fetch-depth: 0

      - name: Build
        run: docker run --rm --volume $PWD:/src bric3/hugo-builder hugo

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
          publish_branch: master
          publish_dir: ./public
          cname: blog.arkey.fr

A note about speed

Using asciidoctor the rendering speed certainly took a hit, let’s compare some numbers. (It’s not reasonable benchmark though, but the magnitude of the hit certainly gives an indication).

dumb speed test numbers
❯ docker run --rm --volume $PWD:/src --publish "0.0.0.0:1313:1313" bric3/hugo-builder hugo serve --bind=0.0.0.0 --baseUrl=blog.local --buildDrafts
Building sites …
                   | EN-US
-------------------+--------
  Pages            |   378
  Paginator pages  |     1
  Non-page files   |    18
  Static files     |    75
  Processed images |     0
  Aliases          |     2
  Sitemaps         |     1
  Cleaned          |     0

Built in 27921 ms                        (1)
Watching for changes in /src/{archetypes,assets,content,data,layouts,static,themes}
Watching for config changes in /src/config.toml
Environment: "development"
Serving pages from memory
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at //blog.local:1313/ (bind address 0.0.0.0)
Press Ctrl+C to stop
^C%                                                                                                                                                                                                       ❯ docker run --rm --volume $PWD:/src --publish "0.0.0.0:1313:1313" bric3/hugo-builder hugo serve --bind=0.0.0.0 --baseUrl=blog.local --buildDrafts
❯ env PATH=$PWD/bin:$PATH hugo serve --baseUrl=blog.local --bind=0.0.0.0 --buildDrafts

                   | EN-US
-------------------+--------
  Pages            |   378
  Paginator pages  |     1
  Non-page files   |    18
  Static files     |    75
  Processed images |     0
  Aliases          |     2
  Sitemaps         |     1
  Cleaned          |     0

Built in 4767 ms                         (2)
Watching for changes in /Users/bric3/private/bric3.github.io/{archetypes,assets,content,data,layouts,static,themes}
Watching for config changes in /Users/bric3/private/bric3.github.io/config.toml
Environment: "development"
Serving pages from memory
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at //blog.local:1313/ (bind address 0.0.0.0)
Press Ctrl+C to stop
^C%
❯ env PATH=$PWD/bin:$PATH hugo serve --baseUrl=blog.local --bind=0.0.0.0

                   | EN-US
-------------------+--------
  Pages            |   307
  Paginator pages  |     1
  Non-page files   |     0
  Static files     |    75
  Processed images |     0
  Aliases          |     2
  Sitemaps         |     1
  Cleaned          |     0

Built in 145 ms                          (3)
Watching for changes in /Users/bric3/private/bric3.github.io/{archetypes,assets,content,data,layouts,static,themes}
Watching for config changes in /Users/bric3/private/bric3.github.io/config.toml
Environment: "development"
Serving pages from memory
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at //blog.local:1313/ (bind address 0.0.0.0)
Press Ctrl+C to stop
^C%
1 ~28s to build the whole website in Docker with two asciidoctor articles and around ten diagrams.
2 ~4.8s seconds to build the whole website with two asciidoctor articles and around ten diagrams.
3 145ms to build the whole website, but the two asciidoctor articles (those were drafts)

The docker hugo serve takes quite a long time, even when the site is rebuilt watching the changes, so docker based may have to be used with care. Fortunately the local hugo serve is much more fast to respond to change.

I’m using Docker Desktop for macOs 2.2.3.0 (edge channel), engine 19.03.8 with 4 CPU, 2GB of Memory, 1GB of SWAP.

Hardware : * 2,7 GHz Quad-Core Intel Core i7 * 16 GB 2133 MHz LPDDR3

Trying with Asciidoctorj

In order to see if speed improved, I tried to use asciidoctorj instead, it comes packed with asciidoctor-diagram by default, that’s useful.

  #!/bin/sh

- ad="/usr/local/bin/asciidoctor"
+ ad="/usr/local/bin/asciidoctorj"       (1)

  basedir=./content
  diagram_target=/assets/diagram

  $ad --trace --verbose \
  --base-dir ${basedir} \
  --require asciidoctor-diagram \        (2)
  --attribute icons=font \
  --attribute docinfo=shared \
  --no-header-footer \
  --attribute nofooter \
  --attribute sectlinks \
  --attribute sectanchors \
  --attribute figure-caption! \
  --attribute source-highlighter=highlightjs \
  --attribute toc-title! \
- - \
+ - 2> /dev/null                         (3)
1 Use asciidoctorj, that bath is usually the one from Homebrew, maybe Linuxbrew and others
2 Asciidoctorj 2.x is packed with asciidoctorj-diagram, and it’s not necessary to install the gem, but it’s necessary to add the --require option.
3 I’m not yet sure why but any message appearing there are treated as errors by Hugo, which terminates the process, so this simply tell to ignore them.

But in the end the speed is not quite that either, but this may be because I only have two Asciidoc articles at this time, so the JVM bootstrap doesn’t quite worth it. I’ll revisit this later if needed. Especially for the diagram part.

Wrap up

I went to Hugo because it supports Asciidoctor, however I didn’t expect so much work to benefit from a near complete Asciidoctor experience. And the current work isn’t even over.

I wasn’t looking for such asciidoctor or asciidoc tag when bootstrapping the site, but looking now I wish there was theme with asciidoctor support right out of the box, yet no theme have the asciidoctor tag at this time.

I really must thank all the people that blogged about this before, they really helped to make this happen.

Over the time I need to tackle some remaining topic

  1. [ ] Some color codes are missing, see #_text[this section] of the smoke test

  2. [ ] Table of content

  3. [ ] Extract colors in Sass variables

  4. [ ] Investigate custom macros, also see the extension lab

  5. [ ] Investigate -a data-uri or -a diagram-svg-type=inline

  6. [ ] Charts macro

  7. [ ] MathJax (stem)

The last two point could certainly be made optional by adding a toggle in the front matter that can activate something in the theme.


What really helped me was the help of a smoke test content to check what feature works or not, I adapted this gist from Dan Allen who is the project lead of asciidoctor:

Smoke test example

Not everything is rendered in a example block, like titles

= Asciidoctor Demo

Dan Allen <[email protected]>

  • emoji :plus: :spade:

  • html +

This is a demonstration of asciidoctor-ruby. And this is the preamble of this document.

Purpose

This document exercises many of the features of AsciiDoc to test the asciidoctor-ruby implementation.

If you want the output to look familiar, copy (or link) the AsciiDoc stylesheet, asciidoc.css, to the output directory.
Items marked with TODO are either not yet supported or a work in progress.

== First Steps with AsciiDoc

Inline markup
  • single quotes around a phrase place 'emphasis'

  • astericks around a phrase make the text bold

  • double astericks around one or more letters in a word make those letters bold

  • double underscore around a substring in a word emphasize that substring

  • use carrots around characters to make them superscript

  • use tildes around characters to make them subscript

  • to pass through HTML directly, surround the text with triple plus

  • characters can be escaped using a \

    • for instance, you can escape a quote inside emphasized text like 'Here's Johnny!'

  • you can safely use reserved XML characters like <, > and &, which are escaped when rendering

  • force a space between inline elements using the {sp} attribute

  • hold text together with an intrinsic non-breaking space attribute, {nbsp}

  • handle words with unicode characters like in the name Gregory Romé

  • claim your copyright ©, registered trademark ® or trademark ™

You can write text with inline links, optionally using an explicit link prefix. In either case, the link can have a query string.

If you want to break a line
just end it in a + sign
and continue typing on the next line.

=== Lists Upon Lists

Adjacent lists
  • this list

  • should join

  • to have

  • four items

Numbered lists
  1. These items

  2. will be auto-numbered

    1. and can be nested

  3. A numbered list can nest

    • unordered

    • list

    • items

Statement

I swear I left it in 'Guy's' car. Let's go look for it.

term

definition line two

another term

another definition, which can be literal (indented) or regular paragraph

This should be a standalone paragraph, not grabbed by the definition list.

  • first level written on two lines

  • first level

    with this literal text
    • second level

      • third level

        • fourth level

  • back to
    first level

Let’s make a horizontal rule…​


then take a break.

== We’re back!

Want to see a Tiger?

Do you feel safer with the tiger in a box?

hugo
Hugo in a box

Start of include::include.asciidoc.adoc[]

Optional Title
This is an example single-paragraph note.
Optional Title
This is an example single-paragraph note.
Tip.
Important.
Warning.
Caution.
Optional Title
*Listing* Block

Use: code or file listings
Optional Title

Sidebar Block

Use: sidebar notes :)

Example 1. Optional Title

Example Block

Use: examples :)

Default caption "Example:" can be changed using

[caption="Custom: "]

before example block.

Optional Title

NOTE Block

Use: multi-paragraph notes.

Optional Title
*Literal* Block

Use: workaround when literal
paragraph (indented) like
1. First.
2. Second.
incorrectly processed as list.
Optional Title

Quote Block

Use: cite somebody

— cite author
cite source

== Text

normal, italic, bold, mono.

``double quoted'', `single quoted'.

normal, super, sub.

Command: ls -al

mono *bold*

passthru bold

Path: '/some/filez.txt', '.b'

red text on yellow large all bold

colors

aqua aqua-background black black-background blue blue-background fuchsia fuchsia-background gray gray-background green green-background lime lime-background maroon maroon-background navy navy-background olive olive-background purple purple-background red red-background silver silver-background teal teal-background white white-background yellow yellow-background

Chars: nibmr

Escaped: _italic_, _italic_, t__e__st, t__e__st, bold, <b>normal</b> &#182; `not single quoted' \`\`not double quoted''

== Images

First home home , second home Alt text .

home
Block image
Alt text
Thumbnail linked to full image

My screenshot

== Lists

Bulleted
  • bullet

  • bullet

    • bullet

    • bullet

  • bullet

    • bullet

    • bullet

      • bullet

      • bullet

        • bullet

        • bullet

          • bullet

          • bullet

        • bullet

      • bullet

    • bullet

  • bullet

Bulleted 2
  • bullet

    • bullet

Ordered
  1. number

  2. number

    1. letter

    2. letter

  3. number

    1. loweralpha

    2. loweralpha

      1. lowerroman

      2. lowerroman

        1. upperalpha

        2. upperalpha

          1. upperroman

          2. upperroman

        3. upperalpha

      3. lowerroman

    3. loweralpha

  4. number

Ordered 2
  1. letter

  2. letter

    1. letter2

    2. letter2

      1. number

      2. number

        1. number2

        2. number2

        3. number2

        4. number2

      3. number

    3. letter2

  3. letter

Labeled
Term 1

Definition 1

Term 2

Definition 2

Term 2.1

Definition 2.1

Term 2.2

Definition 2.2

Term 3

Definition 3

Term 4

Definition 4

Term 4.1

Definition 4.1

Term 4.2

Definition 4.2

Term 4.2.1

Definition 4.2.1

Term 4.2.2

Definition 4.2.2

Term 4.3

Definition 4.3

Term 5

Definition 5

Labeled 2
Term 1

Definition 1

Term 1.1

Definition 1.1

Labeled horizontal
Term 1

Definition 1

Term 2

Definition 2

Term 2.1

Definition 2.1

Term 2.2

Definition 2.2

Term 3

Definition 3

Term 4

Definition 4

Term 4.1

Definition 4.1

Term 4.2

Definition 4.2

Term 4.2.1

Definition 4.2.1

Term 4.2.2

Definition 4.2.2

Term 4.3

Definition 4.3

Term 5

Definition 5

Q&A
  1. Question 1

    Answer 1

  2. Question 2

    Answer 2

Indent is optional
  • bullet

    • another bullet

      1. number

        1. again number

          1. letter

            1. again letter

            2. letter

        2. number

    • bullet

  • bullet

Break two lists
  1. number

  2. number

Independent paragraph break list.

  1. number

Header break list too
  1. number

  1. List block define list boundary too

  2. number

  3. number

  1. number

  2. number

Continuation
  • bullet continuation

    1. number continuation

      • bullet

        literal continuation
        1. letter

          Non-literal continuation.

          any block can be
          
          included in list

          Last continuation.

List block allow sublist inclusion
  • bullet

    • bullet

      • bullet

        • bullet

    • bullet

  • bullet

    1. number

      1. letter

        1. number

          1. letter

      2. letter

    2. number

Table 2. An example table
Col 1 Col 2 Col 3

1

Item 1

a

2

Item 2

b

3

Item 3

c

6

Three items

d

Table 3. CSV data, 15% each column

1

2

3

4

a

b

c

d

A

B

C

D

ID FName LName Address Phone

1

Vasya

Pupkin

London

+123

2

X

Y

A,B

45678

Table 4. Multiline cells, row/col span
Date Duration Avg HR Notes

22-Aug-08

10:24

157

Worked out MSHR (max sustainable heart rate) by going hard for this interval.

22-Aug-08

152

Back-to-back with previous interval.

24-Aug-08

none

End of include::include.asciidoc.adoc[]

Asciidoctor usage example, should contain 3 lines
doc = Asciidoctor::Document.new("*This* is it!", :header_footer => false)

puts doc.render

Here’s what it outputs (using the built-in templates):

<div class="paragraph">
  <p><strong>This</strong> is it!</p>
</div>

=== ``Quotes''

AsciiDoc is 'so' powerful!

This verse comes to mind.

La la la

Here’s another quote:

When you have eliminated all which is impossible, then whatever remains, however improbable, must be the truth.

— Sir Arthur Conan Doyle
The Adventures of Sherlock Holmes

=== Getting Literal

Want to get literal? Just prefix a line with a space (just one will do).
I'll join that party, too.

We forgot to mention in Numbered lists that you can change the numbering style.

  1. first item (yeah!)

  2. second item, looking so mono

  3. third item, mono it is!

That was literal

=== Passthrough block

Passthrough means pure HTML Ghost from this codepen

== Wrap-up

AsciiDoc is quite cool, you should try it!

Here’s a reference to the definition of another term, in case you forgot it.

One more thing. Happy documenting!

When all else fails, head over to http://google.com.

PlantUML smoke test example

Not everything is rendered in a example block, like titles

attribute diagram-svg-type : inline

== Sequence diagram

Foo1Foo1Foo2Foo2Foo3Foo3Foo4Foo4Foo5Foo5Foo6Foo6To boundaryTo controlTo entityTo databaseTo collections
Sequence diagram

== Activity diagrams (the new one)

yescondition AText 1aText 1byescondition BText 2yescondition CText 3yescondition DnothingText 4This note is on severallinesand cancontainHTMLCalling the methodfoo()is prohibitedText else
Activity diagram
step1step4step2step3step5A SectionB Section
Yet another activity diagram

== Timing diagram

Web BrowserInitializingidleProcessingWaiting{50 ms lag}Web UserAbsentWaitingok{150 ms}0200300500
Timing diagram

== Gantt diagram

1234567891011121314151617181920Prototype designCode prototypeWrite tests1234567891011121314151617181920
Gantt diagram

== Wireframe

FileEditSourceRefactorGeneralFullscreenBehaviorSavingOpen image in:Smart ModeSmooth images when zoomedConfirm image deletionShow hidden imagesCloseNewOpen FileCloseClose All
Wireframe example

== Ditaa

ditaaTextDocumentdiagram!magic!Lots of work

== Sprites diagram

AliceAliceBobBobTesting
Diagram with Sprite

EDIT 2021-01-22: The asciidoctor-diagram:2.1.0 saw some changes and now ships with few dependencies for some diagram component. PlantUML in particular saw a braking change in the way it handles GraphViz dot diagrams, it doesn’t support anymore jdot and instead require to use smetana.

  • asciidoctor-diagram:2.1.0

  • asciidoctor-diagram-ditaamini:0.13.1

  • asciidoctor-diagram-plantuml:1.2021.0

- !pragma graphviz_dot jdot
+ !pragma graphviz_dot smetana
Outdated

Following diagrams today require GraphViz’s dot binary, a possible alternative would be to use an abandoned port (3 years without update) of dot in Java, however not everything is supported, like arrows.

!pragma graphviz_dot jdot

== Component diagram

Some GroupOther GroupsMySqlThis is my folderFooHTTPFirst ComponentAnother ComponentFTPSecond ComponentExample 1Folder 3Frame 4
Component diagram

== State diagram

State1this is a stringthis is another stringState2
State diagram

== Object diagram

Object01Object02Object03Object04Object05Object06Object07Object084some labels
Object diagram

== Class diagram

«general»ObjectArrayListIn java, every classextends this one.This is a floating noteThis note is connectedto several objects.FooOn last defined class
Class diagram

== Use-case diagram

Main AdminUse the applicationUserStartThis is an example.A note can alsobe on several linesThis note is connectedto several objects.
Use case diagram

== Deployment diagram

cloud1cloud2cloud3cloud4cloud5
Deployment diagram
artifact1artifact2artifact3artifact4artifact5artifact6artifact7artifact8artifact9artifact10
Deployment artefact diagram

== Available archimate sprites

List Current SpritesCredit tohttp://www.archimatetool.comarchimate:accessactivityactoraggregationapplication-collaborationapplication-componentapplication-data-objectapplication-eventapplication-functionapplication-interactionapplication-interfaceapplication-processapplication-serviceassessment-filledassessmentassignmentassociation-unidirectassociationbusiness-activitybusiness-actorbusiness-collaborationbusiness-contractbusiness-eventbusiness-functionbusiness-interactionbusiness-interfacebusiness-locationbusiness-meaningbusiness-objectbusiness-processbusiness-productbusiness-representationbusiness-rolebusiness-servicebusiness-valuecollaborationcommunication-pathcomponentcompositionconstraint-filledconstraintcontractdeliverable-filleddeliverabledevicedriver-filleddrivereventflowfunctiongap-filledgapgoal-filledgoalimplementation-deliverableimplementation-eventimplementation-gapimplementation-plateauimplementation-workpackageinfluenceinteractioninterface-requiredinterface-symmetricinterfacejunction-andjunction-orjunctionlocationmeaningmotivation-assessmentmotivation-constraintmotivation-drivermotivation-goalmotivation-meaningmotivation-outcomemotivation-principlemotivation-requirementmotivation-stakeholdermotivation-valuenetworknodeobjectphysical-distribution-networkphysical-equipmentphysical-facilityphysical-materialplateauprinciple-filledprincipleprocessproductrealisationrepresentationrequirement-filledrequirementroleserviceservingspecialisationspecializationstakeholder-filledstrategy-capabilitystrategy-course-of-actionstrategy-resourcestrategy-value-streamsystem-softwaretechnology-artifacttechnology-collaborationtechnology-communication-networktechnology-communication-pathtechnology-devicetechnology-eventtechnology-functiontechnology-infra-interfacetechnology-infra-servicetechnology-interactiontechnology-interfacetechnology-networktechnology-nodetechnology-pathtechnology-processtechnology-servicetechnology-system-softwaretriggeringused-byvalueworkpackage-filled
Available sprites

== Archimate sprites

Handle claimCapture InformationNotifyAdditional StakeholdersValidateInvestigatePayScanningCustomer admnistrationClaims admnistrationPrintingPaymentDocumentManagementSystemGeneralCRMSystemHome & AwayPolicyAdministrationHome & AwayFinancialAdministrationExample from the "Archisurance case study" (OpenGroup).See=:business process=: application service=: application component
Archimate diagram
Macros

Ctrl+Shift+N
View  Zoom  Reset
OK
ruby, asciidoctor
ruby, asciidoctor

\$sqrt(4) = 2\$
\$H_2O\$
\$[[a,b],[c,d]]((n),(k))\$
\(C = \alpha + \beta Y^{\gamma} + \epsilon\)
[1]

Block switch
Maven
<dependency>
    <groupId>com.example</groupId>
    <artifactId>some-library</artifactId>
    <version>1.2.3</version>
</dependency>
Gradle
implementation 'com.example:some-library:1.2.3'
Kotlin
implementation("com.example:some-library:1.2.3")
Bazel
maven_jar(
    name = "jackson-annotations",
    artifact = "com.example:some-library:1.2.3",
    sha1 = "c626020ae55d19c690d25cb51c1532ba76e5890f",
)
Maven
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>2.11.0</version>
  <type>bundle</type>
</dependency>
Gradle
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.0'
Kotlin
implementation("com.fasterxml.jackson.core:jackson-annotations:2.11.0")
Bazel
maven_jar(
    name = "jackson-annotations",
    artifact = "com.fasterxml.jackson.core:jackson-annotations:2.11.0",
    sha1 = "c626020ae55d19c690d25cb51c1532ba76e5890f",
)
A
aaa
B
bbb
Json
{
  "a" : "b",
  "c" : [
    { "d" : "e" },
    { "d" : "f" },
    { "d" : "g" },
    { "d" : "h" }
  ]
}
Example 2. B

A

B

1

2

Hugo shortcodes

1. From the Macros example