[com.sagevisuals/readmoi "6"]
com.sagevisuals/readmoi {:mvn/version "6"}
(require '[readmoi.core :refer [-main print-form-then-eval]])
Software documentation should have lots of examples. But it's kinda a pain to write html or markdown containing
code examples. Write some code in the editor, evaluate it, copy and paste it into the ReadMe, back-and-forth. And if the software changes, the examples
may no longer be accurate. It sure would be nice if we could write (+ 1 2) directly into the ReadMe, and the document would automatically
insert 3 immediately afterwards. And if we ever decide to redefine +, re-generating the document would update all the
results.
Developing Clojure is a pleasure because we're writing the code while standing inside the code itself. Markdown and html don't provide that. Plus, my editor is already set up for lisp code structural editing, and I am hesitant to give it up.
Hiccup is a wonderful utility that consumes Clojure code and outputs html. All the benefits of Clojure transfer to authoring html. Code editors can sling around lisp forms with abandon. We have the whole Clojure universe at our disposal. And best of all, we can evaluate code examples, right there in the document itself. But, GitHub ReadMe documents are produced from markdown files, not hiccup.
The ReadMoi library generates html and markdown ReadMe files — with up-to-date, evaluated code examples — from hiccup source.
The resulting ReadMe document is structured exactly as you see here: a Clojars badge, navigation links, one or more html <section>s (Intro, Usage, Glossary, etc.) containing evaluated code examples, a
license statement, and a footer with copyright and compilation metadata.
We write our document, one .clj file per section. Each of those section files contains hiccup/html forms with text liberally sprinkled
with code examples. Then, we create an options file that tells ReadMoi which section files to load. The options file also contains
various…options (see below). Finally, we tell ReadMoi to generate the ReadMe files, one markdown file and one html file. Generating the ReadMe files involves processing the hiccup forms, during which the code examples are evaluated and the
returned values are inserted immediately next to the Clojure form.
The following steps assume a Leiningen project.clj file or a Maven pom.xml file in the project's root directory.
Complete the setup.
Write our ReadMe sections. The format of each section's file is…
[:section#❬:section-href❭
❬hiccup content❭]
:section-href is the value found in options map. ReadMoi automatically generates navigation links based on those hyperlink references
it finds in that map.
Also, we can show people how to use the software with the following pattern. In the section file, we merely write…
[:pre (print-form-then-eval "(+ 1 2)")]
…which Readmoi evaluates to…
[:pre [:code "(+ 1 2) ;; => 3"]]
…which Hiccup compiles to…
<pre><code>(+ 1 2) ;; => 3</code></pre>
…which our web browser renders as…
(+ 1 2) ;; => 3
Don't insert the return value. Every time we generate the document, the code examples are automatically re-evaluated. We can write and re-write our code examples and quickly see how they'll appear in the document. Also, the code examples stay synchronized as the codebase changes underneath.
Note: Any definitions (def, defn, etc.) will bind a value to a symbol in that namespace, which is useful and typically
what we'd want, but can on occasion be inconvenient.
The pretty-printing is delegated to zprint, which has a million and one options. print-form-then-eval provides about four
knobs to tweak the line-breaking, which is good enough for most examples in a ReadMe document. See the api documentation for details.
Copy readmoi_options.edn to
our project's resources/ directory.
Adjust the copied options file. The readmoi_options.edn file assigns all the required information and declares our
preferences for optional values. The map contains the following required keys:
:sections Vector containing one map for each section of the ReadMe. Each section map — having a one-to-one correspondence with one
.clj section files — has the following keys:
:section-name The section title (string). Required.
:section-href Hyperlink reference, internal or external (string). Required.
:section-skip-load? Indicates whether to load section contents from file (boolean). Set to true if external link.
For example, the API documentation is on another webpage, so there's no additional section file required to generate the
ReadMe document. It's merely a hyperlink to an external webpage. On the other hand, the Usage section is part of this
document, so we do need to load the source text to generate the document.
The following are optional keys:
:clojars-badge? Boolean that governs whether to display a Clojars badge. Information used to generate the badge is inferred from
project.clj file. Default nil.
:copyright-holder String that appears in copyright statement at page footer. Default nil.
:fn-map-additions Special :fn-map directives governing how zprint pretty-printer will format a function expression. Defaults to {}.
:license-hiccup Hiccup/html forms to replace the default license (MIT license) section.
:project-description Alternative project description (string) to use in preference to the project description supplied by
defproject in the project.clj file or the description entry of pom.xml.
:project-name-formatted Alternative project name (string) to use in preference to the project name supplied by
defproject in the project.clj file or name entry of pom.xml.
:preferred-project-metadata ReadMoi attempts to automatically detect the project's version number from either a
Leiningen project.clj file or a Maven pom.xml file. If both exist, a preference must be declared with this option
by associating to either :lein or :pom-xml. Defaults to nil.
:UUID Version 4 Universally Unique Identifier. Suggestion: eval-and-replace
(random-uuid). Default nil.
:readme-html-directory Alternative output html directory (string). Include trailing '/'.
Defaults to 'doc/'.
:readme-html-filename Alternative output html filename (string). Defaults to
'readme.html'.
:readme-markdown-directory Alternative output markdown directory (string). Include trailing `/`. Defaults to '' (i.e.,
project's root directory).
:readme-markdown-filename Alternative output markdown filename (string). Defaults to 'README.md'.
:sections-directory Alternative directory to find sections hiccup .clj files. Include trailing '/'. Default
resources/readme_sections/.
:separator String separating the s-expression and the evaluated result. Defaults to
' => '.
:wrap-at Column wrap base condition for print-form-then-eval and prettyfy. Defaults to 80.
:def-patterns The return values of a definition are not terribly informative. By default, ReadMoi suppresses the return
values of def, defn, and defmacro. Associating a set containing strings to :def-patterns
supercedes the default set. An empty set causes the return values of all definitions to be printed.
:tidy-html? Indent and wrap html and markdown files. Defaults to nil. Setting this
option to true may be desirable to minimize the version control 'diff' from one commit to the next. Note that the
tidy-ing procedure may insert line-breaks at an undesirable spot, e.g., within an in-text [:code ...] block. To keep the block on
one line, use a Unicode U+0A00 non-breaking space. An html non-breaking space entity,
, gets rendered literally.
Generate the html and markdown files. To get nicely formatted html
documents, we evaluate (-main). The most basic way to do
that is to hide it behind a #_ reader ignore form in one of our section .clj files. Then, while we're
writing in our repl-attached editor, we can evaluate the form as needed.
With only slightly more effort, we could make a generator script, similar to resources/readmoi_generator.clj. Making such a
script allows us require additional functions from other namespaces that ought not be visible in our text.
With that generator script in hand, we could further streamline this step by creating a Leiningen alias in our project.clj file.
:aliases {"readmoi" ["run" "-m" "readmoi-generator"]}
Then, generating the documents from the command line is merely this.
$ lein readmoi
ReadMoi produces two files. The first is a 'markdown' file that's actually plain old html, abusing
the fact that html passes through the markdown converter. By default, this markdown file is written to the
project's root directory where GitHub can find and display the ReadMe. We don't need a dedicated markdown converter to view this file;
copy it to a GitHub gist and it'll display similarly to when we view it on GitHub. The second file — by
default written to the resources/ directory — is a proper html document with a
<head>, etc., that is viewable in any browser. We may want to copy over the css file for some lightweight styling.
If a section's .clj file won't load, check the options map in readmoi_options.edn. The
:section-name must correspond to the section's filename.
If a navigation link doesn't work as expected, check that the html section element id in the section's
.clj file matches the :section-href in the options map in readmoi_options.edn.
If print-form-then-eval doesn't behave as
you'd like, try adjusting the width-fn and width-output parameters first. Then if that doesn't suit, try supplying
a function-specific formatting directive in the :fn-map-additions value of the options map in readmoi_options.edn.
The zprint pretty-printer has an astronomical amount of settings, but in the end, it just tries to do what its author thinks looks best.
Almost all the time it works great. My advice: don't chase perfection, just get it looking pretty good and spend the extra time on editing your
prose.
Here is some example hiccup/html that might live in a section file named super.clj in the
project's resources/readme_sections/ directory.
[:section#super
[:h3 "Super Awesome Stuff"]
[:p "Here's how to use " [:code "inc"] "."]
[:pre (print-form-then-eval "(inc 99)")]]
Notice that we didn't include the 100 yielded by evaluating (inc 99). During hiccup processing,
print-form-then-eval will do that for us, including inserting a separator.
Hiccup extracts id attributes from the thing following an html element's #. In this example, the
section element's id is #super.
We include this entry into the :sections map of the options file.
{:sections [{:section-name "Super Awesome Stuff"
:section-href "super"]}
Notice that the :section-href value in the options map matches the hiccup html element's id
attribute. That matching allows the navigation link at the top of the ReadMe to correctly anchor to the proper section somewhere later in the ReadMe.
After running -main, that combination of hiccup/html and options would be rendered in the final ReadMe
like this.
Super Awesome Stuff
Here's how to use
inc.(inc 99) ;; => 100
ReadMoi consulted the options file, learned that there was a section called 'super', loaded the contents of the super.clj file,
processed the hiccup/html contents of the file — which involved evaluating (inc 99) and then inserting
;; => 100 — and wrote the ReadMe files.
Speculoos: A data validation library. Lots of sections, tables, html lists, etc.
fn-in: A data structure handling library. Lots of example evaluations.
trlisp: Example of a project that is only Clojure-adjacent.
Term 1 definition...
Term 2 definition with internal link.
This program and the accompanying materials are made available under the terms of the MIT License.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |