Multiline Clojure docstrings

16,096

Solution 1

If you're using Emacs, grab clojure-mode.el from technomancy's Github, which differs from the one in ELPA (I don't know why, both claim to be version 1.11.5, maybe someone can comment on that?) but includes clojure-fill-docstring which will format docstrings with nice indentation and linewrapping, bound by default to C-c M-q.

It will take this:

(defn flatten
  "Takes any nested combination of sequential things (lists, vectors, etc.) and returns their contents as a single, flat sequence. (flatten nil) returns an empty sequence."
  {:added "1.2"
   :static true}
  [x]
  (filter (complement sequential?)
          (rest (tree-seq sequential? seq x))))

and turn it into this:

(defn flatten
  "Takes any nested combination of sequential things (lists, vectors,
  etc.) and returns their contents as a single, flat sequence.
  (flatten nil) returns an empty sequence."
  {:added "1.2"
   :static true}
  [x]
  (filter (complement sequential?)
          (rest (tree-seq sequential? seq x))))

after you do C-c M-q with your point inside the docstring.

Solution 2

Is there a better way to format multiline docstrings?

My suggestion is to use Markdown formatting in your docstrings. Here are some reasons why:

  • it's what's used at github in README's and project wikis (and many Clojure users use and are familiar with github).

  • judging by the number of .md files you find present in various Clojure projects, it appears to be a preferred markup format among Clojure users.

  • the popular Marginalia doc tool renders markdown-formatted docstrings and comments (and my understanding is that Autodoc (the tool used to generate the docs at clojure.org) will eventually render markdown in docstrings as well).

  • It looks good as plain text, is easy to type, doesn't require any special editor support, and the markup is minimal and easy to remember.

Also, you're probably already familiar with it, since Stackoverflow uses it for questions/answers/comments (and sites like reddit and various blog commenting systems use Markdown as well).

Solution 3

I agree with @uvtc that markdown is a nice choice. As an addendum, I'd like to note that it is trivial to generate your own markdown doc viewing function for use at the REPL. The following code assumes you have the markdown-clj package on your classpath (e.g. via dev dependencies), and are using a REPL in OSX:

(ns docs
  (:require [clojure.java.shell :as s]
            [markdown.core :as md]))

(defmacro opendoc [name]
   `(do
        (md/md-to-html (java.io.StringReader. (:doc (meta (var ~name)))) "/tmp/doc.html")
        (s/sh "open" "/tmp/doc.html")
    )
  )

You might want to look at the source for clojure.repl/doc to handle special cases (e.g. this one assumes you'll be passing in a proper symbol for a var). It might also be nice to have the filename reflect the namespace/function name for "caching", instead of just reusing the same filename for every request...but I'm keeping it simple for illustration purposes.

The OSX open command simply asks the OS to open a file by detecting its type. Thus:

REPL=> (docs/opendoc my.ns/f)

will cause your default browser to open the HTMLified version of your function's docstring.

One other caveat: If you indent your multiline string (which editors commonly do), then your MD may end up with weirdness (e.g. bullet lists might nest in a way you do not intend). One way to solve this is to trim that back out. For example:

(defn boo
  "
  # Title
  My thing

  * Item one
  * Item two
  "
  [args] ...)

and then modify the opendoc function to first apply a left trim:

(defn ltrim [str] (clojure.string/replace str #"(?m)^ {0,3}" ""))

(defmacro opendoc [name]
  `(do
    (md/md-to-html (java.io.StringReader. (ltrim (:doc (meta (var ~name))))) "/tmp/doc.html")
    (s/sh "open" "/tmp/doc.html")
   )
  )
Share:
16,096

Related videos on Youtube

mikera
Author by

mikera

Founder of Convex. Clojure hacker, game development, crypto and machine learning enthusiast.

Updated on June 01, 2022

Comments

  • mikera
    mikera about 2 years

    I've noticed that Clojure multiline docstrings seem to be manually formatted in most cases, including the ones in clojure.core. Example from https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj :

    (defn flatten
      "Takes any nested combination of sequential things (lists, vectors,
      etc.) and returns their contents as a single, flat sequence.
      (flatten nil) returns an empty sequence."
      {:added "1.2"
       :static true}
      [x]
      (filter (complement sequential?)
              (rest (tree-seq sequential? seq x))))
    

    This seems odd, as it means that different docstrings will have different line wrap lengths etc. which need to be manually maintained.

    Is there a better way to format multiline docstrings?

    • Jeremy
      Jeremy about 12 years
      I think the solution to this largely depends on being able to configure (or enhance) your editor to format the doc strings for you, either as you type or on demand.
    • sw1nn
      sw1nn about 12 years
      There are other docstring conventions that could/should be formalized too IMHO e.g. from let -> (let bindings & body) bindings => binding-form init-expr
  • mikera
    mikera about 12 years
    Interesting idea, I certainly do use markdown and like it for other things (e.g. README.mds on Github). Though I'm not sure markdown is generally supported by tools that read docstrings - in particular typing (doc xxx) at the Clojure REPL just returns the docstring as plain text....
  • uvtc
    uvtc about 12 years
    It doesn't need to be supported in any way --- it looks good as plain text. Right now, (doc xxx) will continue to spit it out unmodified like it already does. If the docstring is markdown-formatted, it will simply look better and be a bit more consistently-formatted.
  • uvtc
    uvtc about 12 years
    As for line-wrapping, as you point out, doc doesn't currently seem to do that. However, note that most markdown processors will do a straight markdown->markdown "conversion" for you, which is includes line-wrapping. I could imagine doc eventually doing something like that.
  • David J.
    David J. over 11 years
    I just updated clojure-mode from ELPA. Like you mentioned, it seemed to be out of date (I could not find the clojure-fill-docstring function). To solve this problem, I ran package-list-packages and found an old (out of date) clojure-mode and deleted it (mark with d, execute with x.) Problem solved.
  • Tony K.
    Tony K. about 9 years
    On OSX, you could also install lynx and markdown at the command line, then use clojure.java.shell to send the docstring through (markdown | lynx -stdin -dump). Then you'd have a library function that would work nicely from the repl.
  • Didier A.
    Didier A. over 8 years
    I think it'd be nice if Clojure standardized on this across the board. And if it added to Markdown a way to reference other functions from within a function.