Asciidoctor logo

I’ve been asked to design new courses for the next semester at my local university. Historically, I create course slides with Power Point and lab instructions with Word. There are other possible alternatives, for slides and documents:

I also recently wrote some lab documents with Asciidoctor, versioned with Git. The build process generates HTML pages and those are hosted on GitHub pages. The build is triggered at every push.

This solution has several advantages for me:

  • I’m in full control of the media. Asciidoctor files are kept on my hard drive, and any other Git repository I want, public or private, in the cloud or self-hosted.
  • The Asciidoctor format is really great for writing documentation.
  • With one source document, there can be multiple output formats (.e.g. HTML, PDF, etc.).
  • Hosted on Github/GitLab Pages, HTML generation can be automated for every push on the remote.

Starting point

Content should be released under the Creative Commons license. That means there’s no requirement for a private repository. In addition, a public repository will allows students to make Pull Requests. Both Github and GitLab provide free page hosting. Since developers are in general more familiar with Github than with GitLab, the former will be the platform of choice.

Content is made from courses and labs:

  • Courses should be displayed as slides
  • Labs as standard HTML documents

Setup

Setup differs slightly for courses and labs.

Courses

To display slides, let’s use Reveal.js. Fortunately, Asciidoctor Reveal.js allows to generate slides from Asciidoctor sources. With Ruby, the setup is pretty straightforward, thanks to the documentation:

  1. Install bundler:
    gem install bundler
  2. Create a Gemfile with the following content:
    source 'https://rubygems.org'
    
    gem 'asciidoctor-revealjs'
  3. Configure bundler:
    bundle config --local github.https true
    bundle --path=.bundle/gems --binstubs=.bundle/.bin
  4. Finally, generate HTML:
    bundle exec asciidoctor-revealjs source.adoc

Actually, the generation command is slightly different in my case:

bundle exec asciidoctor-revealjs -D output/cours\ (1)
                                 -a revealjs_history=true\ (2)
                                 -a revealjs_theme=white (3)
                                 -a revealjs_slideNumber=true\ (4)
                                 -a linkcss\ (5)
                                 -a customcss=../style.css\ (6)
                                 -a revealjsdir=https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.5.0 (7)
                                 cours/*.adoc (8)
1 Set the HTML output directory
2 Explicitly push the URL into the browser history at each navigation step
3 Use the white them (by default, it’s black)
4 Display the slide number - it’s just mandatory for reference purpose
5 Create an external CSS file instead of embedding the styles into each document - makes sense if there are more than one document
6 Override some styles
7 Reference the JavaScript framework to use - could be a local location
8 Generate all documents belonging to a folder - instead of just one specific document

Labs

To generate standard HTML documents from Asciidoctor is even easier:

bundle exec asciidoctor -D output/tp tp/*.adoc

Automated remote generation

As a software developer, it’s our sacred duty to automate as much as possible. In this context, it means generating HTML pages from Asciidoctor sources on each push to the remote repository.

While Github doesn’t provide any build tool, it integrates greatly with Travis CI. I’ve already written about the process to publish HTML on Github Pages. The only differences in the build file come from:

  1. The project structure
  2. The above setup

Automated local generation

So far, so good. Everything works fine, every push to the remote generates the site. The only thing drawback, is that in order to preview the site, one has either to push…​ or to type the whole command-line every time. The bash history helps somewhat, until some other command is required.

As a user, I want to preview the HTML automatically in order to keep me from writing the same command-line over and over.

— Me

This one a bit is harder, because it’s not related to Asciidoctor. A full-blown solution with LiveReload and everything would be probably be overkill (read that I’m too lazy). But I’ll be happy enough with a watch over the local file system, and the trigger of a command when changes are detected. As my laptop runs on OSX, here’s a solution that "works on my machine". This is based on the launchd process and the plist format.

This format is XML-based, but "weakly-typed" and based on the order of elements. For example, key value pairs are defined as such:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>key1</key>
  <string>value1</string>
  <key>key2</key>
  <string>value2</string>
</dict>

A couple of things I had to find out on my own - I had no prior experience with launchd:

  1. A .plist file should be named as per the key named Label.
  2. It should be located in the ~/Library/LaunchAgents/ folder.
  3. In this specific case, the most important part is to understand how to watch the filesystem. It’s achieved with the WatchPaths key associated with the array of paths to watch.
  4. The second most important part is the command to execute, ProgramArguments. The syntax is quite convoluted: every argument on the command line (as separated by spaces) should be an element in an array.
    It seems the $PATH is not initialized with environment variables of my own user, so the full path to the executable should be used.
  5. As debugging is mandatory - at least with the first few runs, feel free to use StandardErrorPath and StandardOutPath to respectively write standard err and out in files.

The final .plist looks something like that:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>ch.frankel.generatejaveecours.plist</string>
  <key>WorkingDirectory</key>
  <string>/Users/frankel/projects/course/javaee</string>
  <key>Program</key>
  <string>/usr/local/bin/bundle</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/bundle</string>
    <string>exec</string>
    <string>asciidoctor-revealjs</string>
    <string>-a</string>
    <string>revealjs_history=true</string>
    <string>-a</string>
    <string>revealjs_theme=white</string>
    <string>-a</string>
    <string>revealjs_slideNumber=true</string>
    <string>-a</string>
    <string>linkcss</string>
    <string>-a</string>
    <string>customcss=../style.css</string>
    <string>-a</string>
    <string>revealjsdir=https://cdnjs.cloudflare.com/ajax/libs/reveal.js/3.5.0</string>
    <string>cours/*.adoc</string>
  </array>
  <key>WatchPaths</key>
  <array>
    <string>/Users/frankel/projects/course/javaee/cours</string>
  </array>
  <key>StandardErrorPath</key>
  <string>/tmp/asciidocgenerate.err</string>
  <key>StandardOutPath</key>
  <string>/tmp/asciidocgenerate.out</string>
</dict>
</plist>

To finally launch the daemon, use the launchctl command:

launchctl load ~/Library/LaunchAgents/ch.frankel.generatejaveecours.plist

Conclusion

While automating HTML page generation on every push is quite straightforward, previewing HTML before the push requires manual action. However, with a bit of research, it’s possible to scan for changes on the file system and automate generation on the laptop as well. Be proud to be lazy!

The complete source code for this post can be found on Github.