Version: 5.3

Using Scribble Programatically

Danny Yoo <dyoo@hashcollision.org>

There are times when generating Scribble documentation through the command line option doesn’t provide fine-enough control over the output. This quick guide shows how to generate Scribble documents programmatically through pure Racket.

Let’s say that we have a small document, like the following

"hello.scrbl"

#lang scribble/base
Hello @emph{beautiful} world!

When we invoke the "hello.scrbl" module, it’ll provide a doc binding that represents a decoded document, a part. Let’s use dynamic-require to look at this doc.
> (define my-doc (dynamic-require "hello.scrbl" 'doc))
> my-doc

(part

 #f

 (list (list 'part (generated-tag)))

 #f

 (style #f '())

 '()

 (list

  (paragraph

   (style #f '())

   (list "Hello " (element 'italic '("beautiful")) " world!")))

 '())

We can walk through this part structure, using selectors such as part-blocks:
> (require scribble/core)
> (require scribble/base)
> (part-blocks my-doc)

(list

 (paragraph

  (style #f '())

  (list "Hello " (element 'italic '("beautiful")) " world!")))

Subtle: note that, for this particular example, we also rip out the existing tags associated to the document. Scribble uses tags to represent data such as cross-referencing between documents. If we try to generate documentation simultaneously for my-doc and my-updated-doc, Scribble will get rightfully confused. As an exercise, preserve the existing tags and try to render both documents at once: you should see warnings.

Since the document is a structured value, it’s amendable to functions that we can write to automatically reprocess them if we choose to. For example, let’s add a new paragraph to the end of the document.
> (define my-updated-doc
    (struct-copy part my-doc
      [tags (list)]
      [blocks (append (part-blocks my-doc)
                      (list (para "Here's something we added")))]))

The most common operation we can perform on a part is to render it. Let’s render the original document as well as our updated version of the document:

> (require scribble/render)
> (render (list my-doc my-updated-doc)
          (list "hello.scrbl" "updated-hello.scrbl"))

Ok, that was quiet. What just happened?

By default, the renderer generates HTML text, using filenames based on the second list of paths we pass to render. In this case, it wrote out to "hello.html" and "updated-hello.html" in the current directory. render also writes out auxiliary files, such as "scribble.css", in the current directory too.

Don’t try to control current-directory directly, as Scribble itself can muck with it throughout the rendering process.

If we want to direct the output elsewhere, we’ll want to use the #:dest-dir option to render. Let’s do that next.
> (render #:dest-dir "dest"
          (list my-doc my-updated-doc)
          (list "hello.scrbl" "updated-hello.scrbl"))
> (directory-list "dest")

'(#<path:hello.html>

  #<path:scribble-common.js>

  #<path:scribble-style.css>

  #<path:scribble.css>

  #<path:updated-hello.html>)

There are the files that render generated for us.