Contents
  1. Downloads
  2. Preface
  3. Part I Writing
  4. 1 Quickstart
  5. 2 Writing in Markdown
  6. 3 Authoring in Hiccup
  7. Part II Configuration and Theming
  8. 4 Configuring the Book
  9. 5 Theming and Editions
  10. 6 Commands
  11. Part III Producing and Publishing
  12. 7 Producing a Book
  13. Structure lives in book.edn
  14. 8 Editions
  15. 9 Distributing Your Book
  16. 10 How It Works
  17. A Error Catalog
  18. B Clojure for Authors
  19. C A Chapter Written in Code
  20. A table the book writes for itself
  21. Documentation that cannot drift
  22. Down to a single formatting object
  23. When to reach for code
  24. Glossary
  25. List of Figures
  26. List of Tables
  27. List of Listings
  28. Bibliography
  29. Index
  30. Search

Appendix C

A Chapter Written in Code

Every other chapter in this manual is Markdown. This one is a .clj file: the build runs it and uses the value of its last expression as the chapter. The result is the same author Hiccup described in the authoring chapter, so everything downstream is identical. What changes is that the content can be computed.

You do not need prior Clojure to follow along, and you can treat this file as a template to copy. Every form it uses is introduced in the Clojure appendix: the four data shapes, plus def to name a value, for to build rows, and into to pour them into an element. Each section below shows the lines of this file that produce it, included from the file itself, so the source you read is the source that ran.

A table the book writes for itself

The authoring chapter lists the author vocabulary by hand. This appendix asks the build instead: the tag set lives in a var the expansion engine itself uses, and the chapter turns it into rows.

(def tag-names
  (sort (map name vocabulary/sugar-tags)))

(def vocabulary-table
  (into [:table {:id :tbl-vocab
                 :caption "The author vocabulary, read from the running build"}]
        (for [row (partition-all 3 tag-names)]
          (into [:tr] (for [tag row] [:td [:code tag]])))))
Listing 6. Generating a table from the build's own tag registry

Line by line: tag-names takes the tag set, turns each tag into its name, and sorts them. partition-all slices the sorted names into rows of three, the for wraps each name in a table cell, and into pours the rows into a captioned table. The result renders as Table 4:

Table 4. The author vocabulary, read from the running build
aadmonitionblockquote
brbuttoncite
codedddetails
diagramdldt
emepigraphexample
figurefootnoteh1
h2h3h4
h5h6hr
imgindexkbd
keep-togetherlimark
mathmenuol
openoverviewp
p-firstpage-breakpre
sidebarspanstrong
subsuptable
tbodytdth
theadtrul
xref

When a tag joins the vocabulary, the next build updates the table. There is no copy to forget.

Documentation that cannot drift

The theming chapter explains that PDF margins default to the classical 2:3:4:6 construction. Prose can describe the rule; a chapter written in code can run it. The table below calls the same function the PDF compiler calls, once per named trim:

(def canon-table
  (into [:table {:id :tbl-canon
                 :caption "Margins per trim, from the canon function"}]
        (cons [:tr [:th "Trim"]
               [:th "Inner"] [:th "Top"] [:th "Outer"] [:th "Bottom"]]
              (for [[trim {:keys [width]}] (sort-by first theme/page-sizes)]
                (let [margins (theme/canon-margins width 2/3)]
                  [:tr
                   [:td [:code (str trim)]]
                   [:td (:margin-inside margins)]
                   [:td (:margin-top margins)]
                   [:td (:margin-outside margins)]
                   [:td (:margin-bottom margins)]])))))
Listing 7. Margin values computed by calling the implementation
Table 5. Margins per trim, from the canon function
TrimInnerTopOuterBottom
:a423.3mm35.0mm46.7mm70.0mm
:digest15.6mm23.3mm31.1mm46.7mm
:letter24.0mm36.0mm48.0mm72.0mm

The table lists 3 trims because that is how many the build knows about; the count in this sentence is computed too. If the canon derivation ever changes, Table 5 follows it on the next build, and a stale number here would be a bug in Smia, not in the manual.

Down to a single formatting object

The same file can reach below the sugar. The end of this appendix carries a small letterspaced colophon line in the PDF editions, written directly as a formatting object and wrapped in a condition so the web editions skip it:

(def pdf-editions
  {:any-of [{:equals [:edition :screen]}
            {:equals [:edition :print]}
            {:equals [:edition :print-x]}]})

(def colophon
  [:when pdf-editions
   [:fo/block {:text-align "center" :letter-spacing "0.15em"
               :font-size "9pt" :color "#666666" :space-before "24pt"}
    "SET IN SMIA"]])
Listing 8. A print-only flourish, one element deep

This is the escape-hatch matrix at its most granular: one element, in one place, in some editions, with no stylesheet layer involved. The condition map is the same :when vocabulary Markdown uses for conditional content.

When to reach for code

  • A reference table whose rows already live in your project: a configuration registry, an error catalog, a benchmark output file.
  • Values the text must state and the implementation already knows: defaults, limits, version-dependent behavior.
  • Structure Markdown cannot express, such as merged table cells.
  • Bulk content that follows a pattern: fifty near-identical sections want a for, not fifty files.
  • Production at scale: a publisher whose pipeline already holds the content as data can emit chapters from it directly, and the build stays deterministic, scriptable, and headless.

Prose-heavy chapters stay nicer in Markdown; this manual keeps every other chapter there. The two front ends share one vocabulary, so a book can mix them freely, chapter by chapter.

In the PDF editions a small letterspaced colophon closes this page, set directly in FO. This paragraph is its web-edition replacement, chosen by the same condition.