Setup
API
Changelog
Introduction
Usage
Examples
Glossary
Contact
[com.sagevisuals/readmoi "1"]
com.sagevisuals/readmoi {:mvn/version "1"}
(require '[readmoi.core :refer [generate-all]])
Software documentation should have lots of examples. But it's kinda a pain to write html or markdown with code, copying and pasting, 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)
, 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 generated 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 section file 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, 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 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.
[:pre [:code (print-form-then-eval "(+ 1 2)")]]
…which gets rendered as…
(+ 1 2) ;; => 3
Don't bother inserting the return value. Every time we generate the document, the code is re-evaluated. We can 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.) 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.
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.
: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. Defaults to 80
.
Generate the html and markdown files. We could evaluate…
(generate-all (read-string (slurp "project.clj"))
(load-file "resources/readmoi_options.edn"))
…in whatever namespace we loaded generate-all
. Or, we could copy resources/readmoi_generator.clj
and evaluate all forms in the namespace (cider command C-c C-k
). Some day, I'll make this a command line tool or a Leiningen plugin.
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 minimal 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 [:code (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 link to the proper section somewhere later in the ReadMe.
After running generate-all
, 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.
fn-in
: A data structure handling library.
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 is a website building & hosting documentation for Clojure/Script libraries
× close