Lookup own namespace?

Hello!

I’m looking to be able to create Clojure files looking like this:

(ns th.scratch.doc.doc1
  (:require [th.scratch.doc.lib :refer [document h1 p]]))

(document "My document")

(p "An attempt to write things that naturally sequence")

(h1 "What is natural sequencing?")

(p "Natural sequencing is when the order of operations matter."
   "When you write a word document, this is typically the case.")

Currently, th.scratch.doc.lib just contains stubs. I’m looking to implement those.

I want to be able to organize the generated documents from the namespace in which they are created. In this case th.scratch.doc.doc1. I’m thinking I might need macros and *ns*, but I’m on thin ground, so I’m asking.

How would you enable document and its likes to be organizable from the namespace in which they are used?

Looking forward to hear your opinions!

Teodor

My stub at it:

(ns doclib)

(defonce ^:private *state (atom {}))

(defn- add! [key str]
  (swap! *state update *ns* (fnil conj []) [key str])
  nil)

(defmacro ^:private deftag [sym]
  (let [key (keyword sym)]
    `(defn ~sym [& strs#]
       (doseq [str# strs#]
         (add! ~key str#)))))

(deftag document)

(deftag p)

(deftag h1)

(defn get-doc []
  (get @*state *ns*))

(ns doclib-user
  (:require [doclib :refer [document p h1]]))

(document "My document")

(p "An attempt to write things that naturally sequence")

(h1 "What is natural sequencing?")

(p "Natural sequencing is when the order of operations matter."
   "When you write a word document, this is typically the case.")

(doclib/get-doc)
=> [[:document "My document"] 
    [:p "An attempt to write things that naturally sequence"] 
    [:h1 "What is natural sequencing?"] 
    [:p "Natural sequencing is when the order of operations matter."] 
    [:p "When you write a word document, this is typically the case."]]

One problem I see with it is that re-evaluating forms in doclib-user will add new entries into it’s doc.

1 Like

That’s beautiful!

I’m sorta converging on the same solution myself. I’m thinking of giving the document a special meaning like the ns macro, to reset “current document progression”.

With some reworking, I ended up here, utilizing metasorous/oz for live-reload:

;; library

(ns th.doc.api
  (:require [oz.core :as oz]
            [clojure.pprint :refer [pprint]]))

(def ^:dynamic *current-doc* (atom {}))

(defn el [el-name & args]
  (swap! *current-doc*
         update :doc/body
         conj (into [el-name] args)))

(defmacro document [title]
  (reset! *current-doc* {:doc/title title
                         :doc/ns *ns*
                         :doc/body []}))

(defmacro p [& args]
  (apply el :p args))

(defmacro h1 [& args]
  (apply el :h1 args))

(defmacro h2 [& args]
  (apply el :h2 args))

(defn load-doc [doc-ns]
  (let [container (atom [])]
    (binding [*current-doc* container]
      (require doc-ns :reload))
    @container))

(defn view-current-doc!
  []
  (let [doc @*current-doc*]
    (def last-doc doc)
    (let [hiccup-markup (into [:div] (:doc/body doc))]
      (def last-hiccup-markup hiccup-markup)
      (oz/view! hiccup-markup))))

(comment
  ;; REPL stuff

  last-hiccup-markup

  (->> (get last-doc :doc/body)
       (into [:div]))
  )
;; document

(ns th.doc.doc1
  (:require [th.doc.api :as doc :refer :all]))

(document "My other document")

(p "An attempt to write things that naturally sequence")

(h1 "What is natural sequencing???")

(p "Natural sequencing is when the order of operations matter."
   "When you write a word document, this is typically the case.")

(p "Can you see this?")

(def numbers (into [] (range 20)))

(h2 "Does this seem nice?")

(p "The numbers from 0 to 19 are: " (str numbers))

;; Get doc down here.

(doc/view-current-doc!)

Yet I seem to have messed up the macro, as numbers don’t get evaluated as it should:

Your p/h1/h2 should be ordinary functions, not macros

1 Like

You’re right. I’ve already captured the namespace. Thanks!

Wait, that might not still apply if I want to be able to capture things like

(ol (li "First item")
    (li "Second item"))

Because then, I would need to know that (li) is being called inside of (ul).

Do you agree?

Agree, you probably want to make (li ,,,) inside top-level (ol ,,,) just return hiccup markup.

1 Like

That was a fun exercise:

(ns doclib)

(def *docs (atom {}))

(def ^:dynamic *in-progress* false)

(defn document [title]
  (swap! *docs assoc *ns* {:doc/body [] :doc/title title}))

(defn ensure-realized [x]
  (cond-> x (seqable? x) doall))

(defn- emit-tag-macro [kw body]
  (let [hiccup-expr `[~kw [email protected](map #(list `ensure-realized %) body)]]
    `(if *in-progress*
       ~hiccup-expr
       (binding [*in-progress* true]
         (swap! *docs update-in [*ns* :doc/body] conj ~hiccup-expr)))))

(defmacro deftag [sym]
  (let [kw (keyword sym)
        body-sym (gensym "body")]
    `(defmacro ~sym [& ~body-sym]
       (emit-tag-macro ~kw ~body-sym))))

(deftag h1)
(deftag p)
(deftag li)
(deftag ul)
(deftag ol)

(defn get-doc []
  (get @*docs *ns*))

(ns doclib-user
  (:require [doclib :refer :all]))

(document "My document")

(h1 "Hi")

(p "An attempt to write things that naturally sequence")

(ol (for [i (range 3)]
      (li (str "boop " i))))

(doclib/get-doc)
=>
{:doc/title "My document", 
 :doc/body [[:h1 "Hi"] 
            [:p "An attempt to write things that naturally sequence"]
            [:ol ([:li "boop 0"] 
                  [:li "boop 1"] 
                  [:li "boop 2"])]]}
1 Like

Thanks, @vlaaad! I’m glad I got your input.

Teodor

1 Like