refactor: Split out AsciiDoctor rendering
This commit is contained in:
78
scripts/sydnix/src/asciidoc/render.clj
Normal file
78
scripts/sydnix/src/asciidoc/render.clj
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
(ns asciidoc.render
|
||||||
|
(:require
|
||||||
|
[clojure.pprint :refer [pprint]]
|
||||||
|
[asciidoc.types :as types]
|
||||||
|
[clojure.spec.alpha :as s]
|
||||||
|
[clojure.zip :as zip]))
|
||||||
|
|
||||||
|
(defn- block-zip [root-block]
|
||||||
|
(zip/zipper #(s/conform ::block %)
|
||||||
|
:content
|
||||||
|
(fn [block children] (assoc block :content children))
|
||||||
|
root-block))
|
||||||
|
|
||||||
|
(defn- escape-line [line]
|
||||||
|
;; TODO
|
||||||
|
line)
|
||||||
|
|
||||||
|
(declare render*)
|
||||||
|
|
||||||
|
(defn- render-attributes [attributes]
|
||||||
|
(let [sorted-attributes (->> attributes
|
||||||
|
seq
|
||||||
|
(sort-by first))]
|
||||||
|
(doseq [[k v] sorted-attributes]
|
||||||
|
(printf ":%s: %s\n" (name k) v))))
|
||||||
|
|
||||||
|
(defn- render-document-header [depth {:keys [arguments] :as block}]
|
||||||
|
(println "=" (:title arguments))
|
||||||
|
(when-let [author (:author arguments)]
|
||||||
|
(println author)
|
||||||
|
(when-let [version (:version arguments)]
|
||||||
|
(println version)))
|
||||||
|
(when-let [attributes (:a arguments)]
|
||||||
|
(render-attributes attributes))
|
||||||
|
(println)
|
||||||
|
(run! #(render* (inc depth) %) (:content block)))
|
||||||
|
|
||||||
|
(defn- render-p [block]
|
||||||
|
(apply println (:content block)))
|
||||||
|
|
||||||
|
(defn- render-section [depth {:keys [arguments] :as block}]
|
||||||
|
(print (apply str (repeat (inc depth) \=))
|
||||||
|
(:title arguments)
|
||||||
|
"\n\n")
|
||||||
|
(run! render-p (:content block))
|
||||||
|
(println))
|
||||||
|
|
||||||
|
(defn- render* [depth block]
|
||||||
|
(case (:context block)
|
||||||
|
:document (do (assert (zero? depth)
|
||||||
|
"Document block should only occur as root node.")
|
||||||
|
(render-document-header depth block))
|
||||||
|
:section (render-section depth block)
|
||||||
|
:p (render-p block)
|
||||||
|
(throw (ex-info "no case" {:for (:context block)
|
||||||
|
:block block}))))
|
||||||
|
|
||||||
|
(def my-manpage
|
||||||
|
[:document {:title "sydnix(1)"
|
||||||
|
:author "Madeleine Sydney Ślaga"
|
||||||
|
:a {:doctype "manpage"
|
||||||
|
:manmanual "SYDNIX"
|
||||||
|
:mansource "SYDNIX"}}
|
||||||
|
[:section {:title "Name"}
|
||||||
|
[:p "sydnix - Inspect and operate upon the system."]]
|
||||||
|
[:section {:title "Synopsis"}
|
||||||
|
[:p "*sydnix* [_COMMAND_]... [_OPTION_]... [_FILE_]..."]]])
|
||||||
|
|
||||||
|
(defn conform! [spec x]
|
||||||
|
(let [x' (s/conform spec x)]
|
||||||
|
(if (= x' :clojure.spec.alpha/invalid)
|
||||||
|
(throw (ex-info "invalid" (s/explain-data spec x)))
|
||||||
|
x')))
|
||||||
|
|
||||||
|
(defn render
|
||||||
|
"Render an AsciiDoc block to `*out*`."
|
||||||
|
[block]
|
||||||
|
(render* 0 (conform! :asciidoc.types/document block)))
|
||||||
47
scripts/sydnix/src/asciidoc/types.clj
Normal file
47
scripts/sydnix/src/asciidoc/types.clj
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
(ns asciidoc.types
|
||||||
|
(:require
|
||||||
|
[clojure.spec.alpha :as s]
|
||||||
|
[spec-dict :refer [->opt dict]]))
|
||||||
|
|
||||||
|
(defn- make-block-arguments [req opt]
|
||||||
|
(let [args (dict req
|
||||||
|
(->opt opt)
|
||||||
|
^:opt {:a map?})]
|
||||||
|
(if (empty? req)
|
||||||
|
(s/? (s/and map? args))
|
||||||
|
(s/& (s/? map?) args))))
|
||||||
|
|
||||||
|
(defn- make-block
|
||||||
|
[& {:keys [name required-args optional-args content-spec]
|
||||||
|
:or {required-args {}
|
||||||
|
optional-args {}
|
||||||
|
content-spec (s/* ::block)}}]
|
||||||
|
(s/and vector?
|
||||||
|
(s/cat :context (s/and keyword? #{name})
|
||||||
|
:arguments (make-block-arguments required-args optional-args)
|
||||||
|
:content content-spec)
|
||||||
|
;; Set arguments to {} if none were provided.
|
||||||
|
(s/conformer #(if (contains? % :arguments)
|
||||||
|
%
|
||||||
|
(assoc % :arguments {})))))
|
||||||
|
|
||||||
|
(s/def ::document
|
||||||
|
(make-block :name :document
|
||||||
|
:required-args {:title string?}
|
||||||
|
:optional-args {:author string?
|
||||||
|
:version string?}))
|
||||||
|
|
||||||
|
(s/def ::p
|
||||||
|
(make-block :name :p
|
||||||
|
:content-spec (s/* string?)))
|
||||||
|
|
||||||
|
(s/def ::section
|
||||||
|
(make-block :name :section
|
||||||
|
:required-args {:title string?}))
|
||||||
|
|
||||||
|
(s/def ::block
|
||||||
|
(s/and (s/or :document ::document
|
||||||
|
:section ::section
|
||||||
|
:p ::p)
|
||||||
|
;; `s/or` provides tagging that `make-block` already does.
|
||||||
|
(s/conformer #(nth % 1))))
|
||||||
@@ -1,135 +1,52 @@
|
|||||||
(ns sydnix.commands.help
|
(ns sydnix.commands.help
|
||||||
(:require
|
(:require
|
||||||
[clojure.pprint :refer [pprint]]
|
[clojure.pprint :refer [pprint]]
|
||||||
|
[asciidoc.render :as ar]
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.zip :as zip]
|
[clojure.zip :as zip]
|
||||||
[spec-dict :refer [dict]]
|
[spec-dict :refer [dict]]
|
||||||
[sydnix.cli-table :refer [*cli-table*]]))
|
[sydnix.cli-table :refer [*cli-table*]]))
|
||||||
|
|
||||||
(s/def ::block-context
|
(defn- find-satisfying [p xs]
|
||||||
#{:document :section :paragraph})
|
(->> xs (filter p) first))
|
||||||
|
|
||||||
(s/def :block/arguments
|
(defn- find-dispatched [dispatch]
|
||||||
(s/and map?
|
(find-satisfying #(= dispatch (:cmds %)) *cli-table*))
|
||||||
(dict ^:opt {:a map?})))
|
|
||||||
|
|
||||||
(s/def ::block
|
(defn- docs-for-command [command-spec]
|
||||||
(s/and vector?
|
(let [command (cons "sydnix" (:cmds command-spec))]
|
||||||
(s/and (s/cat :context keyword?
|
[:document {:title (str (str/join "-" command)
|
||||||
:arguments (s/? :block/arguments)
|
"(1)")
|
||||||
:content (s/* (constantly true)))
|
|
||||||
;; Set default fields.
|
|
||||||
(s/conformer #(if (contains? % :arguments)
|
|
||||||
%
|
|
||||||
(assoc % :arguments {}))))))
|
|
||||||
|
|
||||||
(s/def :document/doctype #{:manpage})
|
|
||||||
(s/def :document/manmanual string?)
|
|
||||||
(s/def :document/mansource string?)
|
|
||||||
(s/def :document/man-linkstyle string?)
|
|
||||||
|
|
||||||
(s/def :block/attributes map?)
|
|
||||||
|
|
||||||
#_
|
|
||||||
(s/def :document/attributes
|
|
||||||
(s/keys :opt-un [:document/doctype
|
|
||||||
:document/manmanual
|
|
||||||
:document/mansource
|
|
||||||
:document/man-linkstyle]))
|
|
||||||
|
|
||||||
(s/def :document/arguments
|
|
||||||
(s/and :block/arguments
|
|
||||||
(dict {:title string?}
|
|
||||||
^:opt {:author string?}
|
|
||||||
^:opt {:version string?})))
|
|
||||||
|
|
||||||
(s/def :document/content (s/coll-of ::block))
|
|
||||||
|
|
||||||
(s/def ::document
|
|
||||||
(s/and ::block
|
|
||||||
(dict {:context #{:document}
|
|
||||||
:content (s/coll-of ::block)})))
|
|
||||||
|
|
||||||
(defn- block-zip [root-block]
|
|
||||||
(zip/zipper #(s/conform ::block %)
|
|
||||||
:content
|
|
||||||
(fn [block children] (assoc block :content children))
|
|
||||||
root-block))
|
|
||||||
|
|
||||||
(defn- escape-line [line]
|
|
||||||
;; TODO
|
|
||||||
line)
|
|
||||||
|
|
||||||
(defn- render-attributes [attributes]
|
|
||||||
(let [sorted-attributes (->> attributes
|
|
||||||
seq
|
|
||||||
(sort-by first))]
|
|
||||||
(doseq [[k v] sorted-attributes]
|
|
||||||
(printf ":%s: %s\n" (name k) v))))
|
|
||||||
|
|
||||||
(defn- render-document-header [{:keys [arguments]}]
|
|
||||||
(println "=" (:title arguments))
|
|
||||||
(when-let [author (:author arguments)]
|
|
||||||
(println author)
|
|
||||||
(when-let [version (:version arguments)]
|
|
||||||
(println version)))
|
|
||||||
(when-let [attributes (:a arguments)]
|
|
||||||
(render-attributes attributes)))
|
|
||||||
|
|
||||||
(defn- render-section [depth {:keys [arguments] :as block}]
|
|
||||||
(println (apply str (repeat (inc depth) \=))
|
|
||||||
(:title arguments))
|
|
||||||
(apply println (:content block)))
|
|
||||||
|
|
||||||
(defn- render* [depth block-zipper]
|
|
||||||
#_
|
|
||||||
(pprint block-zipper)
|
|
||||||
(let [block (zip/node block-zipper)]
|
|
||||||
(case (:context block)
|
|
||||||
:document (do (assert (zero? depth)
|
|
||||||
"Document block should only occur as root node.")
|
|
||||||
(render-document-header block)
|
|
||||||
(println)
|
|
||||||
(when-let [x (zip/left block-zipper)]
|
|
||||||
(render* (inc depth) x))
|
|
||||||
(when-let [x (zip/down block-zipper)]
|
|
||||||
(recur (inc depth) x)))
|
|
||||||
:section (do (render-section depth block)
|
|
||||||
(println)
|
|
||||||
(when-let [l (zip/left block-zipper)]
|
|
||||||
(recur (inc depth) l)))
|
|
||||||
(throw (ex-info "no case" {:for (:context block)
|
|
||||||
:block block})))))
|
|
||||||
|
|
||||||
(defn- render
|
|
||||||
"Render an AsciiDoc block to `*out*`."
|
|
||||||
[block]
|
|
||||||
(render* 0 (block-zip (s/conform ::document block))))
|
|
||||||
|
|
||||||
(def my-manpage
|
|
||||||
[:document {:title "sydnix(1)"
|
|
||||||
:author "Madeleine Sydney Ślaga"
|
:author "Madeleine Sydney Ślaga"
|
||||||
:a {:doctype "manpage"
|
:a {:doctype "manpage"
|
||||||
:manmanual "SYDNIX"
|
:manmanual "SYDNIX"
|
||||||
:mansource "SYDNIX"}}
|
:mansource "SYDNIX"}}
|
||||||
[:section {:title "Name"}
|
[:section {:title "Name"}
|
||||||
"sydnix - Inspect and operate upon the system."]
|
[:p (format "%s - %s"
|
||||||
|
(str/join " " command)
|
||||||
|
(:desc command-spec))]]
|
||||||
[:section {:title "Synopsis"}
|
[:section {:title "Synopsis"}
|
||||||
"*sydnix* [_COMMAND_]... [_OPTION_]... [_FILE_]..."]])
|
[:p (format "%s [_option_…]"
|
||||||
|
(str/join " " command))]]
|
||||||
|
[:section {:title "Options"}
|
||||||
|
[:p "synopsis"]]]))
|
||||||
|
|
||||||
|
(defn- render-docs-for-command [command-spec]
|
||||||
|
(try (ar/render (docs-for-command command-spec))
|
||||||
|
(catch Exception e
|
||||||
|
(prn e)
|
||||||
|
(throw e))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn- docs-for-command [command])
|
|
||||||
|
|
||||||
(defn- command-fn [opts]
|
(defn- command-fn [opts]
|
||||||
(prn "help" opts))
|
(prn "help" opts))
|
||||||
|
|
||||||
(defn adorn-with-help-option [command-fn]
|
(defn adorn-with-help-option [command-fn]
|
||||||
(fn [{:keys [opts dispatch]}]
|
(fn [{:keys [opts dispatch] :as w}]
|
||||||
(if (:help opts)
|
(if (:help opts)
|
||||||
(do (printf "<help for %s>\n" (str/join " " dispatch))
|
(render-docs-for-command (find-dispatched dispatch))
|
||||||
(pprint *cli-table*))
|
|
||||||
(command-fn opts))))
|
(command-fn opts))))
|
||||||
|
|
||||||
(def command
|
(def command
|
||||||
|
|||||||
@@ -3,6 +3,13 @@
|
|||||||
[clojure.java.shell :refer [sh]]
|
[clojure.java.shell :refer [sh]]
|
||||||
[sydnix.commands.help :refer [adorn-with-help-option]]))
|
[sydnix.commands.help :refer [adorn-with-help-option]]))
|
||||||
|
|
||||||
|
(def command-options-spec
|
||||||
|
{:flake {:coerce :string
|
||||||
|
:ref "«URI»"
|
||||||
|
:desc "«URI» is a path to a Flake, which is passed to `nixos-rebuild
|
||||||
|
--flake «URI»` as is."
|
||||||
|
:default "path:///persist/dots"}})
|
||||||
|
|
||||||
(defn- command-fn [{:keys [args opts]}]
|
(defn- command-fn [{:keys [args opts]}]
|
||||||
(let [rebuild-cmd
|
(let [rebuild-cmd
|
||||||
(concat ["sudo" "nixos-rebuild"]
|
(concat ["sudo" "nixos-rebuild"]
|
||||||
@@ -15,5 +22,4 @@
|
|||||||
{:cmds ["rebuild"]
|
{:cmds ["rebuild"]
|
||||||
:desc "Rebuild the system NixOS and Home-manager configuration"
|
:desc "Rebuild the system NixOS and Home-manager configuration"
|
||||||
:fn (adorn-with-help-option command-fn)
|
:fn (adorn-with-help-option command-fn)
|
||||||
:spec {:flake {:coerce :string
|
:spec command-options-spec})
|
||||||
:default "path:///persist/dots"}}})
|
|
||||||
|
|||||||
Reference in New Issue
Block a user