How to pprint a clojure file?
Asked Answered
B

2

0

I want to be able to format an entire clojure file to look nice. The nicest thing I have found is clojures pprint. It does indentation and line breaks at correct locations. However it can only read clojure litterals. Clojure files are read in as strings. read-string will only take the first parenthesis of a string. Mapping read-string over the whole sequence has a host of issues I am running into. Does someone know of an automatic way to make a clojure file look pretty? Not just indent it correctly?

Banks answered 3/3, 2017 at 7:14 Comment(0)
J
4

You can use lein-zprint which will run the zprint library over your source files. If you are a boot user, you can use boot-fmt to process your files, which also uses zprint.

The zprint library will completely reformat your source from scratch to be as pretty as it knows how to make it. It actually tries a couple of things at each level to see which is "better", and has a number of heuristics built in to try to produce something that fits as much information as it can in the vertical space while still looking "pretty". It knows a lot about Clojure (and Clojurescript) source code, and knows which functions need different kinds of processing as well as handling the new clojure.spec (cljs.spec) files well too.

It is almost absurdly configurable, so with a little work you can tune it to output the code the way that you want to see it. But even without any configuration, it generally does a good job making your code look nice.

Using it with lein-zprint is pretty trivial. Place [lein-zprint "0.1.16"] into the :plugins vector of your project.clj:

:plugins [[lein-zprint "0.1.16"]]

Then, to format a source file, simply invoke lein zprint on that file:

$ lein zprint src/<project>/<file-name>.clj

Unless you tell it otherwise (which is trivial to do in your project.clj), it will rename the existing file to <file-name>.clj.old so that you have them to compare while you are trying it out.

Here is an example (obviously poorly formatted):

(defn apply-style-x
  "Given an existing-map and a new-map, if the new-map specifies a   
  style, apply it if it exists.  Otherwise do nothing. Return   
  [updated-map new-doc-map error-string]"
  [doc-string doc-map existing-map new-map] (let [style-name
  (get new-map :style :not-specified) ] (if
  (= style-name :not-specified) [existing-map doc-map nil]
  (let [style-map ( if (= style-name :default) 
  (get-default-options) (get-in existing-map [:style-map style-name]))]
  (cond (nil? style-name)
  [existing-map doc-map "Can't specify a style of nil!"] 
  style-map [(merge-deep existing-map style-map) (when doc-map 
  (diff-deep-doc (str doc-string " specified :style " style-name)
  doc-map existing-map style-map)) nil] :else
  [existing-map doc-map (str "Style '" style-name "' not found!")])))))

after formatting with $lein zprint 70 src/example/apply.clj which formats it for 70 columns, to make it fit better for this answer:

(defn apply-style-x
  "Given an existing-map and a new-map, if the new-map specifies a   
  style, apply it if it exists.  Otherwise do nothing. Return   
  [updated-map new-doc-map error-string]"
  [doc-string doc-map existing-map new-map]
  (let [style-name (get new-map :style :not-specified)]
    (if (= style-name :not-specified)
      [existing-map doc-map nil]
      (let [style-map (if (= style-name :default)
                        (get-default-options)
                        (get-in existing-map
                                [:style-map style-name]))]
        (cond
          (nil? style-name) [existing-map doc-map
                             "Can't specify a style of nil!"]
          style-map
            [(merge-deep existing-map style-map)
             (when doc-map
               (diff-deep-doc
                 (str doc-string " specified :style " style-name)
                 doc-map
                 existing-map
                 style-map)) nil]
          :else [existing-map doc-map
                 (str "Style '" style-name "' not found!")])))))
Jewish answered 3/3, 2017 at 15:40 Comment(0)
P
1

You can use weavejester/cljfmt:

$ boot -d cljfmt:0.5.6 repl

boot.user=> (require '[cljfmt.core :as cljfmt])
nil

boot.user=> (println (cljfmt/reformat-string
       #_=> "( let [x 3
       #_=>   y 4]
       #_=>   (+ (* x x
       #_=>   )(* y y)
       #_=>  ))"))

(let [x 3
      y 4]
  (+ (* x x) (* y y)))
nil

Check its README for the supported formatting options.

Patterson answered 3/3, 2017 at 7:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.