Hi!
I’m printing some code and this is the best I could come up with:
(defn pprint-str [x]
(-> (pprint/pprint x)
(->> (pprint/with-pprint-dispatch pprint/code-dispatch))
(with-out-str)
(str/replace "\\n" "\n")))
I pass in (quoted) forms like:
(defn f "hi\nthere" [x] (inc x))
As you can see, the docstring has a newline in it, which pprint
escapes (as edn) but I don’t want that because it is code. This code gets written out as a namespace and it looks wrong to have the docstrings contain escaped newlines. So as a workaround I then replace all escaped newlines with normal newlines. This is not perfect though, because functions could include strings in their body, and those should remain escaped.
It seems like a bit of a challenge to differentiate docstrings from other strings and treat them differently… I looked into cljfmt and rewrite-clj which handle this problem just fine, but I couldn’t figure out how they actually do it .
Does anyone have any suggestions on how to solve the issue with printing docstrings, or can point me to how cljfmt solves it?
I don’t see any escaping?..
user=> (pprint-str '(defn f "hi\nthere" [x] (inc x)))
"(defn f \"hi\nthere\" [x] (inc x))\n"
user=> (println *1)
(defn f "hi
there" [x] (inc x))
nil
1 Like
@p-himik I think the issue was that the (str/replace "\\n" "\n")
was not wanted in general—it was the desired behavior for docstrings, but not all the other strings.
Short answer: it appears that clojure.pprint/code-dispatch
is built in a way that is not easily configurable.
Longer answer: clojure.pprint/code-dispatch
uses clojure.pprint/*code-table*
, a private dynamic var. *code-table*
is a map from symbol to function printing that symbol. Its entry for 'defn
is clojure.pprint/pprint-defn
, also private.
Even longer, derailing answer: It appears that we can monkey-patch clojure.pprint/*code-table*
with our own defn
printer:
(ns scratch
(:require
[clojure.pprint :as pprint]
[clojure.pprint.dispatch]))
(def my-function-code
'(defn f "hi\nthere" [x] (inc x)))
(pprint/with-pprint-dispatch pprint/code-dispatch
(pprint/pprint my-function-code))
;; Prints like Tim showed.
(def original-code-table (deref #'clojure.pprint/*code-table*))
(defn teodor-defn-writer [_]
(println "(defn teodor-function [])"))
(binding [clojure.pprint/*code-table* (assoc original-code-table 'defn teodor-defn-writer)]
(pprint/with-pprint-dispatch pprint/code-dispatch
(pprint/pprint my-function-code)))
;; Prints "(defn teodor-function [])"
I tried reading the original implementation for printing code, but I couldn’t understand what was going on. Here it is:
I think it might be easier to print to a string first, then use rewrite-clj
to traverse over the resulting string, changing only the docstring in defn
s to what you want.
Which is more in line with both what you imply and what @p-himik suggests
Ah, duh. Copied the code blindly and completely missed that str/replace
is already in there.
@timothypratley cljfmt
, as far as I can tell, also doesn’t work with that form. It preserves that \n
:
user=> (require '[cljfmt.core :as fmt] '[rewrite-clj.node :as n])
nil
user=> (println (n/string (fmt/reformat-form '(defn f "hi\nthere" [x] (inc x)))))
(defn f "hi\nthere" [x] (inc x))
user=> (println (fmt/reformat-string "(defn f \"hi\\nthere\" [x] (inc x))"))
(defn f "hi\nthere" [x] (inc x))
Of course it also doesn’t work if you use a form and use a literal newline, since it’s not different from \n
:
user=> (println (n/string (fmt/reformat-form '(defn f "hi
there" [x] (inc x)))))
(defn f "hi\nthere" [x] (inc x))
nil
But of course it does work if that \n
is not escaped (well, in a way):
user=> (println (fmt/reformat-string "(defn f \"hi\nthere\" [x] (inc x))"))
(defn f "hi
there" [x] (inc x))
So that makes me think that formatting is done not on forms but on strings.
And yeah, here it is in the sources: cljfmt/cljfmt/src/cljfmt/tool.clj at master · weavejester/cljfmt · GitHub
1 Like
Oh I see now, thank you @p-himik and @teodorlu that explains it perfectly.
Much appreciated!
1 Like