example

AuthorAlexander Artemenko
license:Unlicense

Example of Coo documentation for a Common Lisp library

This is a small library includes a few functions with docstrings and a documentation for the system and all included packages.

The purpose is to demonstrate core features of the coo library.

The repository can be used as a template for new libraries if you've choosen Coo for documenting your library.

Let's review features, provided by Coo.

Pros & Cons of Coo

Because of some bug in cl-docutils, I have to decorate this header above as a document's title :(

Pros

  • reStructured text format has flexible abstraction to extend its functionality. You can add new "roles" to support different types of documentation blocks.

  • reStructured text is widely used. It is like markdown, but is more suitable for writing large interlinked documents.

  • extensions can be written in Common Lisp.

  • cross-referencing works almost the same way like with cldomain.

  • it is very easy to install Coo and to build documentation for any Lisp system having the docstrings.

  • docstrings can be written in reStructured text format and reference other parts of the documentation.

Cons

  • reStructured implementation is incomplete and has bugs. For example, include tag duplicates preceding lines. Roles toctree and code-block aren't supported.

  • Syntax is more complex than Markdown and requires some time to get familiar with.

  • Syntax relies on indentation and sometimes it leads to the problems.

  • It is hard to debug error messages from cl-docutils. But the Coo's author is thinking about the alternatives. For example, cl-docutils' header level processing is very buggy which makes hard to write large docs like this one. It frequently signals the Title Level Inconsistent error.

  • You can fully control documentation layout. It is always file with system's description which includes one or more files for each system's packages.

  • It inconvenient to write large blocks of code as docstring and you have to read them from files at compile-time.

  • HTML themes aren't supported yet.

How to build the documentation

Comparing to cldomain, Coo usage is much more easier for a Common Lisper.

You just install it from Ultralisp.org and run from the REPL like that:

(coo:document-system "example"
                     :base-path #P"docs/build/")

Handwritten documentation

I think the ability to write a large pieces of documentation which aren't bound to a function, class or module is an important feature. This way you can tell the user about some toplevel abstractions and give a bird eye view on the library or system.

For example, handwritten parts of the documentation can provide some code snippets to demonstrate the ways, how to use the library:

(loop repeat 42
      collect (foo "bar" 100500))

And when you are talking about some function or class, you can reference it. For example, if I'm talking about foo function, I can reference it like this :function:`example/app:foo` and it will appear in the code as the link example/app:foo.

Extending Coo

Coo can be extended with new roles and directives using docutils.parser.rst:def-role and docutils.parser.rst:def-directive.

For example there here is a role to eval any lisp code and insert the result into the text:

(def-role eval(text)
  (handler-case
      (with-input-from-string(is text)
        (let ((*package* (find-package :common-lisp-user)))
          (let ((arg1 (read is t nil))
                (arg2 (read is nil nil)))
            (make-instance
             'docutils.nodes:inline-evaluation
             :expression (or arg2 arg1)
             :format (when arg2 arg1)))))
    (error(e)
      (make-node 'problematic
                 (write-to-string e :escape nil :readably nil )))))

To use it in the code, run:

  • :eval:`(package-name *package*)` ➞ "SCRIPT.BUILD-DOCS"

  • :eval:`(format nil " A  A" (lisp-implementation-type) (lisp-implementation-version))` ➞ "SBCL 2.0.11"

However, in reStructured format role is an inline entity. If you want to define a custom multiline block, then use docutils.parser.rst:def-directive.

cl-docutils includes a directive:

(def-directive evaluation
    (parent language
            &option
            (format symbol nil)
            (package symbol nil)
            &content content)
  (let ((language (intern (string-upcase language) :keyword)))
    (if content
        (let ((content
               (with-output-to-string(os)
                 (loop :for line :across content
                    :do (write-line line os)))))
          (add-child
           parent
           (make-instance
            'docutils.nodes:block-evaluation
            :format (or format (setting :default-evaluation-format parent))
            :expression (ecase language
                          (:lisp (let ((*package*
                                        (or (and package (find-package package))
                                            *package*)))
                                   (read-from-string content)))))))
      (report :error "Evaluation directive is empty; content required."))))

Here is an example of calling such block and interpreting the returned result as an HTML fragment:

.. evaluation:: lisp
   :format: HTML

   (format nil "<b> A</b>  A: % %<pre> A</pre>"
     (lisp-implementation-type)
     (lisp-implementation-version)
     (with-output-to-string (*standard-output*)
       (room nil)))

Result:

SBCL 2.0.11:
Dynamic space usage is:   111,269,232 bytes.
Immobile space usage is:   20,708,304 bytes (71,104 bytes overhead).
Read-only space usage is:           0 bytes.
Static space usage is:            736 bytes.
Control stack usage is:         8,720 bytes.
Binding stack usage is:         1,056 bytes.
Control and binding stack usage is for the current thread only.
Garbage collection is currently enabled.

This is a very cool feature, because this way you can create a custom blocks for your documentation.

For example, I always wanted to be able to show Weblocks widgets examples along with their rendered pictures. With Coo it is possible to create a directive, which will run the example's code, make a screeshot and save it into the static folder!

Autogenerated API

Coo provides a completely automated generation of the API reference.

At the end of the page with the system's documentation it includes links to all packages, provided by the system.