Writing an Ada programmer's guide with Dynamo, Pandoc and Read the Docs

By Stephane Carrez

Writing and maintaining documentation is always a pain for a developer. Most of the time, the documentation is not updated after some changes are made in the code and the documentation becomes out of date. What is described here is not a miracle but it helps in promoting to have an accurate documentation together with the implementation.

user-guide-generation.png

Writing user's guide in Ada specification

Since I often forget to update some external documentation, I've found convenient to have it close to the implementation within the code. I'm not speaking about a reference documentation that explains every type, function or procedure provided by an Ada package. I'm talking about a programmer's guide.

The solution I've used is to write a small programmer's guide within some Ada package specification. The programmer's guide is written within comments before the package declaration. It is then extracted and merged with other package documentation to create the final programmer's guide. One benefit in having such small programmer's guide in the package specification is that it also brings some piece of documentation to developers: the user's guide is close to the specification.

The documentation is written using Markdown syntax and put before the package declaration. The extraction tool recognizes a number of formatting patterns and commands that help in merging the different pieces in one or several files.

Section headers

First the small programmer's guide must start with a section header introduced by at least one = (equal) sign. The programmer's guide documentation ends with the start of the Ada package declaration. Unlike AdaBrowse and AdaDoc, the package specification is not parsed and not used.

--  = Streams =
--  The `Util.Streams` package provides several types and operations to allow the
--  composition of input and output streams.  Input streams can be chained together so that
--  ...
...
package Util.Streams is ...

When an Ada package specification includes such comment, a documentation file is created. The generated file name is derived from the package name found after the package keyword. The . (dot) are replaced by _ (underscore) and the .md extension is added. In this example, the generated file is Util_Streams.md.

Merging with @include <file>

The @include command indicates that the small programmer's guide from the given file must be included. For example, the Streams support of Ada Utility Library is provided by several packages, each being a child of the Util.Streams package. The different pieces of the programmer's guide are merged together by using the following comments:

--  @include util-streams-buffered.ads
--  @include util-streams-texts.ads
--  @include util-streams-files.ads
--  @include util-streams-pipes.ads
--  @include util-streams-sockets.ads
--  @include util-streams-raw.ads
--  @include util-streams-buffered-encoders.ads

Autolink

To avoid having links in the Ada comments, an auto-link feature is used so that some words or short sentences can be turned into links automatically. The auto-link feature works by using a simple text file that indicates words or sequence or words that should be changed to a link. The text file contains one link definition per line, composed of a set of words that must match and the associated link.

Java Bean                  https://en.wikipedia.org/wiki/JavaBean
Java Log4j                 https://logging.apache.org/log4j/2.x/
Log4cxx                    https://logging.apache.org/log4cxx/latest_stable/index.html
RFC7231                    https://tools.ietf.org/html/rfc7231

The auto-link feature is very basic. To match a link, a sequence of several words must be present on the same comment line. For example, the following documentation extract:

--  = Logging =
--  The `Util.Log` package and children provide a simple logging framework inspired
--  from the Java Log4j library.  It is intended to provide a subset of logging features

will generate the following Markdown extract with a link for the "Java Log4j" word sequence:

# Logging
The `Util.Log` package and children provide a simple logging framework inspired
from the [Java Log4j](https://logging.apache.org/log4j/2.x/) library...

Code extract

Having code examples is important for a programmer's guide and I've made the choice to have them as part of the comment. The extraction tool recognizes them by assuming that they are introduced by an empty line and indented by at least 4 spaces. The code extractor will use the Markdown fenced code block (```) to enclose them.

--  is free but using the full package name is helpful to control precisely the logs.
--
--    with Util.Log.Loggers;
--    package body X.Y is
--      Log : constant Util.Log.Loggers := Util.Log.Loggers.Create ("X.Y");
--    end X.Y;
--
--  == Logger Messages ==

Extracting documentation from Ada specification

Once the documentation is written, the Dynamo command is used to extract, merge and generate the documentation. The build-doc sub-command scans the project files, reading the Ada specification, some project XML files and generates the documentation in Markdown format. The -pandoc option tells the documentation generator to write the documentation for a book oriented organization formatted with Pandoc. It will generate them in the docs directory.

dynamo build-doc -pandoc docs

Putting it all together

Pandoc being a versatile document converter, it allows to format all the generated documentation with some other files and produce a final PDF document. Several files are not generated by Dynamo and they are written either as LaTeX (pagebreak.tex) or Markdown, for example the cover page, the introduction and installation chapters.

By using a custom LaTeX template (eisvogel.tex) and using several configuration option some nice PDF is generated.

pandoc -f markdown -o util-book.pdf --listings --number-sections \
  --toc --template=./docs/eisvogel.tex docs/title.md docs/pagebreak.tex \
  docs/intro.md docs/pagebreak.tex docs/Installation.md docs/pagebreak.tex \
  docs/Util_Log.md docs/pagebreak.tex docs/Util_Properties.md docs/pagebreak.tex \
  docs/Util_Dates.md doc/pagebreak.tex docs/Util_Beans.md docs/pagebreak.tex \
  docs/Util_Http.md docs/pagebreak.tex docs/Util_Streams.md docs/pagebreak.tex \
  docs/Util_Encoders.md docs/pagebreak.tex docs/Util_Events_Timers.md \
  docs/pagebreak.tex docs/Util_Measures.md

Here is the final PDF file: Ada Utility Library Programmer's Guide

Publishing the programmer's guide

Read the Docs offers a free documentation hosting with a complete build and publication environment. They are able to connect to a GitHub repository and be notified each time some changes are pushed to build automatically the documentation.

The documentation is produced by MkDocs and the mkdocs.yml configuration file allows to configure how the documentation is built, organized and presented:

site_name: Ada Utility Library
docs_dir: doc
pages:
  - Introduction: index.md
  - Installation: Installation.md
  - Log: Util_Log.md
  - Properties: Util_Properties.md
  - Dates: Util_Dates.md
  - Ada Beans: Util_Beans.md
  - HTTP: Util_Http.md
  - Streams: Util_Streams.md
  - Encoders: Util_Encoders.md
  - Measures: Util_Measures.md
  - Timers: Util_Events_Timers.md
theme: readthedocs

Here is the final programmer's guide: Ada Utility Library Users' Guide.

Conclusion

I've found quite useful to write the programmer's guide within the Ada specification. Doing so also helps during the design of a new package because it forces to think a little bit on how the package is going to be used. There are some drawbacks in using this model:

  • Each time the documentation must be fixed, the Ada specification file is modified,
  • The layout of a programmer's guide does not always follow a package organization,
  • Merging the documentation from different parts could bring some complexity when you have to find out where some documentation actually comes from.

Add a comment

To add a comment, you must be connected. Login