zprint

Introduction

Writing code should be about expression your intent more than painstakingly aligning and wrapping a stream of characters. Some languages get this right out of the box, and some have extensive tooling maintained by generous entities.

In the case of Clojure there are four options that I am aware of:

  1. Do it yourself by bashing tirelessly away on your keyboard;
  2. cljfmt;
  3. cljstyle;
  4. zprint.

For reasons I’m not going to go into I’m using zprint and provide the configuration necessary to format things just the way I like.

Configuration

The reference documentation contains a lot of information. Of particular interest to me is the :fn-map and its names.

  • :arg1->
  • :arg1-body
  • :arg1-extend
  • :arg1-force-nl
  • :arg1-mixin
  • :arg1-pair-body
  • :arg1-pair
  • :arg1
  • :arg2-fn
  • :arg2-pair
  • :arg2
  • :binding
  • :extend
  • :flow-body
  • :flow
  • :fn
  • :force-nl-body
  • :force-nl
  • :gt2-force-nl
  • :gt3-force-nl
  • :hang
  • :noarg1-body
  • :none-body
  • :none
  • :pair-fn

$HOME/.zprint.edn

{:search-config? true :width 80}

$PWD/.zprint.edn

{:extend {:flow? true :indent 0 :nl-separator? true}
 :fn-map {"are"     [:none {:list       {:indent-only? true}
                            :next-inner {:list {:indent-only? false}}}]
          "s/cat"   [:binding {:binding {:force-nl? false}}]
          "s/fdef"  :arg1-force-nl
          "testing" :arg1-force-nl
          "try"     :flow}
 :map    {:comma? false :sort-in-code? true :sort? true}
 :set    {:sort-in-code? true :sort? true}
 :style  [:binding-nl :extend-nl :hiccup :how-to-ns :justified :map-nl :pair-nl]
 :width  80}

Test

Explain

zprint --help

Some Clojure

(ns fmt
  (:require
   [com.stuartsierra.component :as component]
   [clojure.string :as str]
   [clojure.spec.alpha :as s]
   [clojure.test :refer [are deftest is testing]]))

(defprotocol IPretty
  (pretty [this]))

(defn- init-state [_app] {:zprint/cool? true})

(defrecord App [db]
  component/Lifecycle
  (start [this]
    (if (some? db)
      this
      (let [state (init-state this)]
        ;; Some side effects would typically take place now.
        (assoc this :db (atom (assoc state ::fmt "please"))))))
  (stop [this] (dissoc this :db))

  IPretty
  (pretty [this] true))

(s/fdef string->string
  :args (s/cat :string string?)
  :ret  string?)

(defn string->string
  [string]
  (cond-> string
    (str/ends-with? string "bar") (subs 0 (rand-int (dec (count string))))))

(deftest string->string-works
  (is (= #{"a" "b" "c"} #{"a" "b" "c"}))
  (testing "who knows what"
    (is (= {} (string->string "okay"))))
  (are [in out] (= out (string->string in))
    "foo" "foo"
    "foobar" "tests don't like rand-int"))