kkinnear / zprint

Executables, uberjar, and library to beautifully format Clojure and Clojurescript source code and s-expressions.
MIT License
551 stars 47 forks source link

Zprints escapes already escaped newlines #326

Open didibus opened 2 months ago

didibus commented 2 months ago

I am calling the following zprint function:

(zprint
 '(defn testivius "This is a\n        multiline docstring." {:inline (fn ([a] ` (com.xadecimal.expose-api.impl-test/testivius ~a)) ([b c] ` (com.xadecimal.expose-api.impl-test/testivius ~b ~c)))} ([a] (com.xadecimal.expose-api.impl-test/testivius a)) ([b c] (com.xadecimal.expose-api.impl-test/testivius b c))))

;;; It prints

(defn testivius
  "This is a\n        multiline docstring."
  {:inline (fn
             ([a]
              (clojure.core/seq
                (clojure.core/concat
                  (clojure.core/list
                    (quote com.xadecimal.expose-api.impl-test/testivius))
                  (clojure.core/list a))))
             ([b c]
              (clojure.core/seq
                (clojure.core/concat
                  (clojure.core/list
                    (quote com.xadecimal.expose-api.impl-test/testivius))
                  (clojure.core/list b)
                  (clojure.core/list c)))))}
  ([a] (com.xadecimal.expose-api.impl-test/testivius a))
  ([b c] (com.xadecimal.expose-api.impl-test/testivius b c)))

The issue is the docstring is escaped again by zprint, so it ends up printing in one line, instead of multiline as you see we got:

  "This is a\n        multiline docstring."

If I wrap the call in with-out-str I can see it double escaping:

"(defn testivius\n  \"This is a\\n        multiline docstring.\"\n  {:inline (fn\n             ([a]\n              (clojure.core/seq\n                (clojure.core/concat\n                  (clojure.core/list\n                    (quote com.xadecimal.expose-api.impl-test/testivius))\n                  (clojure.core/list a))))\n             ([b c]\n              (clojure.core/seq\n                (clojure.core/concat\n                  (clojure.core/list\n                    (quote com.xadecimal.expose-api.impl-test/testivius))\n                  (clojure.core/list b)\n                  (clojure.core/list c)))))}\n  ([a] (com.xadecimal.expose-api.impl-test/testivius a))\n  ([b c] (com.xadecimal.expose-api.impl-test/testivius b c)))\n"

Is there a way to fix this?

kkinnear commented 1 month ago

This is a very interesting question indeed. You might think it had a simple answer, and it might, but I sure haven't found it yet despite a considerable time spent working on it. Until I know for sure exactly what is going on, I can't say for sure, but it seems like the output you want should be at least optionally available. More to come...

kkinnear commented 1 month ago

This will be possible, but unfortunately it will have to wait until the next release. This was really quite the challenge, but in retrospect it seems straightforward.

When parsing Clojure source, rewrite-clj parses a string containing an actual newline differently from a string containing the characters \n. In an actual Clojure string there is only one way to represent a newline, and it looks like \n. So there is no way for you to "tell" zprint that you really want this string to format with actual newlines when you have a Clojure structure for a defn form.

In order to do what you want, I have created a new style called :docstring-nl, which will, if the third thing in an actual Clojure list with defn as its first element is a string, format only that string in such a way that any \n characters turn into actual newlines.

It required a small change to the spec for option maps, or I could give you an option-fn that does it in the current release. I'm sorry you will have to wait a bit, but at least it will work soon.

didibus commented 1 month ago

Awesome! I can wait no worries. I've got a rudimentary workaround now with a string/replace I run as a post-process step. But I'll be happy to switch to the first class support.

Thanks for looking into it! And love zprint by the way, amazing library and great addition to Clojure.