Liking cljdoc? Tell your friends :D

Setup
API
Changelog
Introduction
Usage
Examples
Glossary
Contact

ReadMoi

A Clojure library for generating a project ReadMe from hiccup/html

Setup

Leiningen/Boot

[com.sagevisuals/readmoi "5"]

Clojure CLI/deps.edn

com.sagevisuals/readmoi {:mvn/version "5"}

Require

(require '[readmoi.core :refer [-main print-form-then-eval]])

Introduction

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.

Usage

Overview

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.

Detailed usage

The following steps assume a Leiningen project.clj file or a  Maven pom.xml file in the project's root directory.

  1. Complete the setup.

  2. 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…

    &lt;pre&gt;&lt;code&gt;(+ 1 2) ;; => 3&lt;/code&gt;&lt;/pre&gt;

    …which our web browser renders as…

    (+ 1 2) ;; => 3

    Don't bother inserting the return value. Every time we generate the document, the code is 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.

    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.

  3. Copy readmoi_options.edn to our project's resources/ directory.

  4. 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.

    • :project-name-formatted Alternative project name (string) to use in preference to the project name supplied by defproject in the project.clj file.

    • :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.

    • :tidy-html? Indent and wrap html and markdown files. Defaults to nil. Setting this option to true may be desirable 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, &amp;nbsp;, gets rendered literally.

  5. Generate the html and markdown files. We must 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.

Troubleshooting

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.

Example ReadMoi documents

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.

ReadMoi examples from other projects

Speculoos: A data validation library. Lengthy and complex.

fn-in: A data structure handling library. Lots of example evaluations.

trlisp: Example of a project that is only Clojure-adjacent.

Glossary

term 1

Term 1 definition...

term 2

Term 2 definition with internal link.


License

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

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close