Skip to content

Instantly share code, notes, and snippets.

@borkdude
Created January 24, 2022 19:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save borkdude/d7a863e047d83642d097129f1fd4c6c7 to your computer and use it in GitHub Desktop.
Save borkdude/d7a863e047d83642d097129f1fd4c6c7 to your computer and use it in GitHub Desktop.
Babashka-compatible notebook solution from https://www.loop-code-recur.io/live-clojure-cookbooks/
(ns dev.docgen
(:require [clojure.pprint :refer [pprint]]
[rewrite-clj.zip :as z]
[clojure.string :as str]
[clojure.java.io :as io])
(:import (java.io File)))
(defn -form->markdown
"Clojure forms are evaluated and the result is automatically inserted in the generated markdown.
There is a special handling for 'def, we show their var binding instead"
[zloc]
(str "```clj" \newline
(z/string zloc)
\newline "; =>" \newline
(if (= 'def (z/sexpr (z/down zloc)))
;; evaluate the def and prints its binding instead of the var
(do (load-string (z/string zloc))
(with-out-str (pprint (load-string (z/string (as-> (z/down zloc) def-zloc
;; go to the var name
(z/find-next def-zloc #(symbol? (z/sexpr %)))))))))
;; not a def, evaluate the form as is
(with-out-str (pprint (load-string (z/string zloc)))))
"```" \newline))
(defn -comment->markdown
"comments are in the shape:
;; Some optional **markdown** formatting
So simply remove the starting '; chars and whitespaces to convert to markdown"
[zloc]
(str/replace (z/string zloc) #"^;+[ \t]+" ""))
(defn file->markdown [file]
(let [zloc (z/of-file file {:track-position? true})
;; get the top namespace to bind the current ns
file-ns (when (= 'ns (z/sexpr (z/down zloc)))
(some-> (z/find-next (z/down zloc) #(symbol? (z/sexpr %)))
z/sexpr))]
(assert file-ns "the file should start with a ns form")
(binding [*ns* (create-ns file-ns)]
(->> zloc
(iterate z/right*)
(take-while (complement z/end?))
(map (fn [zloc]
(try
(cond
(z/sexpr-able? zloc) (-form->markdown zloc)
(z/whitespace-or-comment? zloc) (-comment->markdown zloc)
:else (z/string zloc))
(catch Exception ex
(println "error at position" (z/position zloc)
(.getMessage ex)
(some-> (.getCause ex) .getMessage)
(z/string zloc))))))
(str/join)))))
(defn compile-cookbooks [input-path output-path]
(assert (.exists (io/file input-path)) (format "the folder %s does not exist" input-path))
(doseq [f (file-seq (io/file input-path))
:when (.isFile f)
:let [markdown (file->markdown (.getPath f))
output-name (str/replace (.getName ^File f) #".clj[cs]?$" ".md")]]
(spit (str output-path "/" output-name) markdown)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment