Liking cljdoc? Tell your friends :D

zprint Reference

zprint is a library and command line tool providing a variety of pretty printing capabilities for both Clojure code and Clojure/EDN structures. It can meet almost anyone's needs. As such, it supports the a variety of major source code formattng approaches.

Table of Contents

What does zprint do?

One of the things I like the most about Clojure (and any Lisp) is that the logical structure of a function has a visual representation -- if the function is pretty printed in a known way. Zprint exists in part to take any Clojure code, and pretty print it so that you can visually grasp its underlying structure.

You can see the features available in zprint below, but the major goals for zprint are:

  • Reformat (pretty print) Clojure and Clojurescript code, completely ignoring any existing white space within functions. Fit the result as strictly as possible within a specified margin, while using the vertical space most efficiently.

    For example, here is a before and after:

(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)
  [exist ing-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!")])))))

(zprint-fn apply-style-x 90)

(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!")])))))
  • Do a great job, "out of the box" on formatting code at the repl, code in files, and EDN data structures. Also be highly configurable, so you can adapt zprint to print things the way you like them without having to dive into the internals of zprint.

  • Handle comments while printing Clojure source. Some folks eschew comments, which is fine (unless I have to maintain their code, of course). For these folks, zprint's comment handling will provide no value. For everyone else, it is nice that comments don't disappear when pretty printing source code.

  • Do all of this with excellent (and competitive) performance.

See zprint formatting:

  • classic zprint -- ignores whitespace in function definitions and formats code with a variety of heuristics to look as good as hand-formatted code (see examples)
  • respect blank lines -- similar to classic zprint, but blank lines inside of function defintions are retained, while code is otherwise formatted to look beautiful (see examples)
  • indent only -- very different from classic zprint -- no code ever changes lines, it is only correctly indented on whatever line it was already on (see examples)

In addition, zprint is very handy to use at the REPL.

Features

In addition to meeting the goals listed above, zprint has the following specific features:

  • Runs fast enough to be used as a filter while in an editor
  • Prints function definitions at the Clojure repl (including clojure.core functions - czprint-fn
  • Prints s-expressions (EDN structures) at the repl
  • Processes Clojure source files through lein-zprint
  • Supports Clojure and Clojurescript
  • Competitive performance
  • Highly configurable, with an intuitive function classification scheme
  • Respects the right margin specification
  • Handles comments, will word wrap long ones
  • Optionally will indent right hand element of a pair (see below)
  • Maximize screen utilization when formatting code
  • Sort map keys alphabetically by default
  • Order map keys as desired
  • Color specified map keys as desired or based on depth (depth is EXPERIMENTAL)
  • Constant pairing (for keyword argument functions)
  • Does a great job printing spec files
  • Justify paired output (maps, binding forms, cond clauses, etc.) if desired
  • Syntax coloring at the terminal
  • Optionally preserve all incoming newlines in source
  • Optionally properly indent each line, and don't add or remove any newlines

All of this is just so many words, of course. Give zprint a try on your code or data structures, and see what you think!

API

The API for zprint is small. A simple example:

(require '[zprint.core :as zp])

(zp/zprint {:a "this is a pretty long value"
 :b "a shorter value" :c '(a pretty long list of symbols)})

{:a "this is a pretty long value",
 :b "a shorter value",
 :c (a pretty long list of symbols)}

The basic API (except for the -fn variants) is supported in both Clojure and Clojurescript:

;; The basic call uses defaults, prints to stdout
(zprint x)

;; All zprint- functions also allow the following arguments:

(zprint x <width>)
(zprint x <width> <options>)
(zprint x <options>)

;; Format a function to stdout (accepts arguments as above)
(zprint-fn <fn-name>)        ; Clojure only

;; Output to a string instead of stdout
(zprint-str x)
(zprint-fn-str <fn-name>)    ; Clojure only

;; Colorize output for an ANSI terminal
;;
;;   None of the syntax coloring in this readme is from zprint, it is
;;   all due to the github flavored markdown.
;;
(czprint x)
(czprint-fn <fn-name>)       ; Clojure only
(czprint-str x)
(czprint-fn-str <fn-name>)   ; Clojure only

If <width> is an integer, it is assumed to be a the width. If it is a map, it is assumed to be an options map. You can have both, either, or neither in any zprint or czprint call.

In addition to the above API, you can access zprint's file processing capabilities (as does lein-zprint), by calling:

(zprint-file infile file-name outfile options)

or format strings containing multiple "top level" forms by calling:

(zprint-file-str file-str zprint-specifier new-options doc-str)

Both of these functions support the ;!zprint file comment API, which supports changes to the formatting to be stored in a source file as specially formatted comments. See here for full documentation on this capability.

NOTE: The only supported API is what is documented in this readme!

If you need to refresh your memory for the API while at the repl, try:

(zprint nil :help)

Note that classic zprint completely ignores all whitespace and line breaks in the function definition -- the formatting above is entirely independent of the source of the function. When using the zprint binaries or lein-zprint to format source files, whitespace in the file between function definitions is always preserved.

Zprint has two fundemental regimes -- formatting s-expressions, or parsing a string and formatting the results of the parsing. When the -fn versions of the API are used, zprint acquires the source of the function, parses it, and formats the result at the repl.

Configuration

Quick Start

The basic API is:

(zprint x <width> <options>)
;or
(zprint x <options>)

If the third parameter is a number, it is used as the width of the output. Default is 80. Zprint works hard to fit things into the width, though strings will cause it to fail, as will very small widths.

<options> is a map of options.

Zprint prints out code and s-expressions the way that I think it looks "best", which is very much like how most people write Clojure code. It does many specific things not covered by the "community" coding standards. In addition, it does some things slightly differently than the community standards. If what you see as the default formatting doesn't please you, you could try specifying the :style as :community, for example:

; the default way:

(czprint-fn defn)

; the community way:

(czprint-fn defn {:style :community})

If this is more pleasing, you might read below as to how you could configure this as your personal default. You can, of course, also configure loads specific parameters to tune the formatting to your particular taste. You can also direct zprint to format any function (including functions you have defined) in a wide variety of ways using several different paths to get zprint to understand your desired configuration.

Introduction to Configuration

Part of the reason that zprint exists is because I find that the visual structure of pretty printed Clojure code tells me a lot about the semantics of the code. I also have a few areas where my preferred formatting differs from the the current community standards. By default zprint will format Clojure code the way I think it makes the most sense, but it is very easy for you to configure it to output code and data in a way more to your liking. You don't need to be an expert in pretty printer engines to figure out how to alter its configuration.

Since I created zprint to be easily configured, there are lots of configuration options as well as several different ways to configure zprint.

You mostly don't have to care about any of this unless you want to change the way that zprint outputs your code or data. If you do, read on...

Configuration uses an Options map

The formatting done by zprint is driven off of an options map. Zprint is built with an internal, default, options map. This internal options map is updated at the time that zprint is first called by examining the .zprintrc file. You can update this internal options map at any time by calling set-options! with a map containing the parts of the options map you with to alter. You can specify an options map on any individual zprint or czprint call, and it will only be used for the duration of that call.

When altering zprint's configuration by using a .zprintrc file, calling set-options!, or specifying an options map on an individual call, the things that you specify in the options map replace the current values in the internal options map. Only the specific values you specify are changed -- you don't have to specify an entire sub-map when configuring zprint.

You can always see the current internal configuration of zprint by typing (zprint nil :explain) or (czprint nil :explain). In addition, the :explain output will also show you how each element in the internal options map received its current value. Given the number of ways that zprint can be configured, (zprint nil :explain) can be very useful to sort out how a particular configuration element was configured with its current value.

The options map has a few top level configuration options, but much of the configuration is grouped into sub-maps. The top level of the options map looks like this:


{:agent {...},
 :array {...},
 :atom {...},
 :binding {...},
 :color-map {...},
 :color false,
 :comment {...},
 :cwd-zprintrc? false,
 :delay {...},
 :extend {...},
 :fn-map {...},
 :fn-obj {...},
 :future {...},
 :list {...},
 :map {...},
 :max-depth 1000,
 :max-length 1000,
 :object {...},
 :pair {...},
 :pair-fn {...},
 :parse-string? false,
 :promise {...},
 :reader-cond {...},
 :record {...},
 :search-config? false,
 :set {...},
 :style nil,
 :style-map {...},
 :tab {...},
 :uneval {...},
 :vector {...},
 :width 80,
 :zipper? false}

Where to put an options map

When zprint (or one of the zprint-filters) is called for the first time it will configure itself from all of the information that it has available at that time. It will examine the following information in order to configure itself:

  • The file $HOME/.zprintrc or if that file does not exist, the file $HOME/.zprint.edn for an options map in EDN format.
  • If the file found above or the options map on the command line has :search-config? true, it will look in the current directory for a file .zprintrc and if it doesn't find one, it will look for .zprint.edn.l If it doesn't find either of them, it will look in the parent of the current directory for the same two files, in the same order. This process will stop when it finds a file or it reaches the root of the file system. If the file it finds is in the home directory (that is, it is the same file found in the first step in this process, above), it will read the file but it will not use the results (because it has already done so).
  • If the file found in the home directory or the options map on the command line has :cwd-zprintrc? set to true, and did not have :search-config? set to true, then it will search the current directory for .zprintrc and .zprint.edn in that order, and use the information from the first file it finds.

The {:search-config? true} capability is designed to allow projects to have zprint configuration files in various places in the project structure. There might be a zprint configuration file at the root of a project, which would be used for every source file in the project.

You can invoke the function (configure-all!) at any time to cause zprint to re-examine the above information. It will delete any current configuration and rebuild it from the information available at that time.

If you do not want to have zprint configured with the above external information, your first use of the zprint library should be the call:

(set-options! {:configured? true})

This will cause zprint to use the default options map regardless of what appears in any of the external configuration areas. This would be of value to anyone using the zprint library to format something a particular way which they didn't want to be affected by an individual's personal configuration.

You can add configuration information by:

  • Calling set-options! with an options map, which is saved in the internal options map across calls
  • Specifing an options map on any call to zprint, which only affects that call to zprint or czprint

Options map format

.zprintrc or .zprint.edn

The .zprintrc file contain a sparse options map in EDN format (see below). That is to say, you only specify the elements that you wish to alter in a .zprintrc file. Thus, to change the indent for a map to be 0, you would have a .zprintrc file as follows:

{:map {:indent 0}}

zprint will configure itself from at most two EDN files containing an options map:

  • $HOME/.zprintrc or $HOME/.zprint.edn which is always read
  • .zprintrc or .zprint.edn in the current working directory or its parents, up to the root of the file system if the configuration file in $HOME has :search-config? set to true.
  • .zprintrc or .zprint.edn in the current working directory, which is only read if the configuration file in $HOME has {:cwd-zprintrc? true} and does not have {:search-config? true} in its options map

Note that these files are only read and converted when zprint initially configures itself, which is at the first use of a zprint or czprint function. You can invoke configure-all! at any later time which will cause all of the external forms of configuration (e.g. .zprintrc, environment variables, and Java system properties) to be read and converted again.

set-options!

You call set-options! with an EDN map of the specific key-value pairs that you want changed from the current values. This is useful both when using zprint at the REPL, as well as when you are using to output information from a program that wants to configure zprint in some particular way. For example:

(require '[zprint.core :as zp])

(zp/set-options! {:map {:indent 0}})

Options map on an individual call

You simply specify the options map on the call itself:

(require '[zprint.core :as zp])

(def my-map {:stuff "a fairly long value"
   :bother
   "A much longer value, which makes this certainly not fit in 80 columns"})

(zp/zprint my-map {:map {:indent 0}})

{:bother
 "A much longer value, which makes this certainly not fit in 80 columns",
 :stuff "a fairly long value"}

Configuring functions in an options map

There are several keys whose values must be functions, in order to allow complex analysis of the structure or code to be formatted. Function definitions for these keys may only be specified in the $HOME/.zprintrc or $HOME/.zprint.edn files, in calls to set-options!, or in options maps in individual calls.
Function defintions are explicitly disallowed in other .zprintrc and .zprint.edn files for security reasons, since code must be executed in order to define functions.

When configuring function in files, use the (fn [x y] ...) form of definition as opposed to the #(...) reader-macro form.

Option Validation

All changes to the options map are validated to some degree for correctness. When the change to the internal options map is itself a map, when using the .zprintrc file, calling (set-options! ...), or specifying an options map on an individual call, every key in the options map is validated, and some level of at least type validation on the values is also performed. Thus:

(czprint nil {:map {:hang false}})
Exception Option errors in this call: In the key-sequence [:map :hang] the key :hang was not recognized as valid!  zprint.core/determine-options (core.cljc:415)

This call will fail validation because there is no :hang key in :map. The "?" is missing from :hang?.

All option validation errors must be fixed, or zprint will not operate.

What is Configurable

The following categores of information are configurable:

  • generalized capabilities
  • syntax coloring
  • function classification for pretty printing
  • specific option values for maps, lists, vectors, pairs, bindings, arrays, etc.

Generalized capabilites

:width

An integer width into which the formatted output should fit. zprint will work very hard to fit the formatted output into this width, though there are limits to its effort. For instance, it will not reduce the minimum indents in order to satisfy a particular width requirement. This will be most obvious when widths are small, in the 15 to 30 range. Normally you might never notice this with the default 80 column width.

Long strings will also cause zprint to exceed the requested width. Comments will be wrapped by default so as not to exceed the width, though you can disable comment wrapping. See the :comment section.

The :width specification in the options map is most useful for specifying the default width, as you can also give a width specification as the second argument of any of the zprint functions.

:parse-string?

By default, zprint expects an s-expression and will format it. If you specify :parse-string? true in an options map, then the first argument must be a string, and zprint will parse the string and format the output. It expects a single expression in the string, and will trim spaces from before and after that single expression.

:parse-string-all?

By default, zprint expects an s-expression and will format it. If you specify :parse-string-all? true in an options map, then the first argument must be a string, and zprint will parse the string and format the output. It will accept multiple expressions in the string, and will parse and format each expression independently. It will drop all whitespace between the expressions (and before the first expression), and will by default separate each expression with a new-line, since the expressions are formatted beginning in column 1.

(czprint "(def a :b) (def c :d)" 40 {:parse-string-all? true})
(def a :b)
(def c :d)

You can separate the expressions with addtional newlines (or pretty much anything that ends with a new-line) by including an options map with :parse {:interpose string} in it. The string must end with a new-line, or the resulting formatting will not be correct.

(czprint "(def a :b) (def c :d)" 40 {:parse-string-all? true :parse {:interpose "\n\n"}})
(def a :b)

(def c :d)

Note that zprint-file-str, the routine used for all of the standalone zprint binaries, uses :parse-string-all.

:parallel?

As of 0.3.0, on Clojure zprint will use mutiple threads in several ways, including pmap. By default, if used as a library in a program, it will not use any parallel features because if it does, your program will not exit unless you call (shutdown-agents). When zprint is running at the REPL, it will enable parallel features as this doesn't turn into a problem when exiting the REPL.

In the event that you have configured {:parallel? false} in any of the various .zprintrc files, it will not be enabled when running at the REPL.

If you want it to run more quickly when embedded in a program, certainly you should set :parallel? to true -- but don't forget to call (shutdown-agents) at the end of your program or your program won't exit!

:coerce-to-false

Experimental

This experimental capability exists in order to allow specification of option maps for remote invocations of zprint, where boolean true and false in the options maps turn into something other than true and false when zprint is invoked remotely. For instance, if true becomes 1 in the remove invocation, and false becomes 0, it would be impossible to invoke zprint with a reasaonable options map, since both 1and 0 would fail to validate as they are not boolean. Relaxing the validation rules would not help, as 0 is never going to be false for Clojure.

In this (very rare) case you could set :coerce-to-false to the value that you want to be false. If you do this the options map you specify will be searched for all values which must be boolean. If they are already boolean (i.e., already true or false), they are not changed. If they are not boolean, then if they equal the value of :coerce-to-false, they will be set to false, and otherwise they will be set to true.

In the example above, {:coerce-to-false 0} would correctly set the various boolean values.

You may rely on this capability not going away as long as you let me know that you are using it by opening an issue.

:cache

In cases where zprint needs to cache some value, the following keys indicate a directory where all cached data will reside:

:location "HOME"

If this does not appear, the location is the home directory ".". If this does appear, it must be a string and it will either be considered an environment variable or a Java system property. If the string contains a ".", it will be considered a Java system property and will be looked resolved in that fashion, and if it does not contain a ".", it will be considered an environment variable and resolved in that fashion.

:directory ".zprint"

This is the directory in which the various aspects of the cache will reside. This directory is used or created in the :location (see immediately above). Typically this directory would start with a "." so that it would not normally be visible. The default is ".zprint".

:url

The only things currently cached are the results of URL lookups of option maps used for configuration. These lookups are triggered by the --url or --url-only switches on the uberjar and graalvm binaries. There are two values associated with cacheing of URL lookups.

:cache-secs 300

This the time that for which the result of a URL lookup is cached.

:cache-dir "urlcache"

This the name of the directory in which the cached URL results are held. This directory is itself located in the [:cache :directory] value described immediately above.

:color?

As of zprint 0.5.1, the :color? option key will control whether or not the output is produced with ANSI escape sequences based on the :color-map option key. The functions czprint and its variants czprint-* essentially simply set {:color? true}.

The :color? key also controls zprint-file-str, which is used inside of the uberjar and the graalVM binaries that are distributed, and so you can specify {:color? true} in an options map included on the command line of these utilities to get colorized output. This output will only be useful when displayed on a "terminal" which interprets ANSI escapse sequences.

You will want to avoid setting {:color? true} in a $HOME/.zprintrc file, as then all of the files produced by the uberjar and graalVM binaries would always contain ANSI escapse sequences!

Syntax coloring

Zprint will colorize the output when the czprint and czprint-fn calls are used. It is limited to the colors available on an ANSI terminal. You can get the same output by adding the {:color? true} option to any call to zprint or zprint-fn.

Note that {:color? true} will also affect any uberjar or zprint-filter invocations as well, so you probably want to avoid placing {:color? true} in your $HOME/.zprintrc file, as it will cause the files produced to contain ANSI escape sequences.

The key :color-map contains by default:

:color-map {:brace :red,
            :bracket :purple,
            :char :black,      ; Note Clojurescript difference below!
            :comma :none,
            :comment :green,
            :deref :red,
            :false :black,
            :fn :blue,
            :hash-brace :red,
            :hash-paren :green,
            :keyword :magenta,
            :nil :yellow,
            :none :black,
            :number :purple,
            :paren :green,
            :quote :red,
            :regex :black,
            :string :red,
            :symbol :black,
            :syntax-quote-paren :red
            :true :black,
            :uneval :magenta,
            :user-fn :black},

Note that in Clojurescript, you cannot set a unique :char color value, as things that return true from(char? ...) also return true from (string? ...), since in Clojurescript chars are simply single character strings. Due to this difference, the color value for :string takes precedence.

You can change any of these to any other available value.

For example:

(czprint-fn defn {:color-map {:paren :black}})

You can include multiple ANSI sequences together:

(czprint-fn defn {:color-map {:none [:bright-black :italic]}})

The available values are (including their ANSI codes, for reference):

  {:off 0,
   :reset 0,
   :bold 1,
   :faint 2,
   :italic 3,
   :underline 4,
   :blink 5,
   :reverse 7,
   :hidden 8,
   :strike 9,
   :normal 22,
   :italic-off 23,
   :underline-off 24,
   :blink-off 25,
   :reverse-off 27,
   :hidden-off 28,
   :strike-off 29,
   :black 30,
   :none 30,
   :red 31,
   :green 32,  
   :yellow 33, 
   :blue 34,   
   :magenta 35,
   :purple 35,
   :cyan 36, 
   :white 37,
   :xsf 38,
   :back-black 40,  
   :back-red 41,    
   :back-green 42,  
   :back-yellow 43, 
   :back-blue 44,    
   :back-magenta 45, 
   :back-purple 45,
   :back-cyan 46,
   :back-white 47,
   :bright-black 90,
   :bright-red 91,
   :bright-green 92,
   :bright-yellow 93,
   :bright-blue 94,
   :bright-magenta 95,
   :bright-purple 95,
   :bright-cyan 96,
   :bright-white 97,
   :back-bright-black 100,
   :back-bright-red 101,
   :back-bright-green 102,
   :back-bright-yellow 103,
   :back-bright-blue 104,
   :back-bright-magenta 105,
   :back-bright-purple 105,
   :back-bright-cyan 106,
   :back-bright-white 107}

There is also a different color map for unevaluated items, i.e. those prefaced with #_ and ignored by the Clojure reader. This is the default :uneval color map:

:uneval {:color-map {:brace :yellow,
                     :bracket :yellow,
                     :char :magenta,  ; not available in Clojurescript, see above
                     :comma :none,
                     :comment :green,
                     :deref :yellow,
                     :false :yellow,
                     :fn :cyan,
                     :hash-brace :yellow,
                     :hash-paren :yellow,
                     :keyword :yellow,
                     :nil :yellow,
                     :none :yellow,
                     :number :yellow,
                     :paren :yellow,
                     :quote :yellow,
                     :regex :yellow,
                     :string :yellow,
                     :symbol :cyan,
                     :syntax-quote-paren :yellow,
                     :true :yellow,
                     :uneval :magenta,
                     :user-fn :cyan}},

You can also change these to any of the colors specified above.

Note that in this documentation, the syntax coloring of Clojure code is that provided by the GitHub flavored markdown, and not zprint!

Function Classification for Pretty Printing

While most functions will pretty print without special processing, some functions are more clearly comprehended when processed specially for pretty printing. Generally, if a function call fits on the current line, none of these classifications matter. These only come into play when the function call doesn't fit on the current line. The following examples are shown with an implied width of well less than 80 columns in order to demonstrate the function style in a concise manner.

Note that the community style guide specifies different indentation amounts for functions (forms) that have "body" parameters, and functions that just have arguments. Personally, I've never really distinguished between these different types of functions (which is why the default indent for both is 2). But I've created classifications so that you can class some functions as having body arguments instead of just plain arguments, so that if you specify a different indent for arg-type functions than body-type functions, the right things will happen.

A function that is not classified explicitly by appearing in the :fn-map is considered an "arg" function as opposed to "body" function, and the indent for its arguments is controlled by :list {:indent-arg n} if it appears, and :list {:indent n} if it does not.

How does zprint classify functions that are called with a namespace on the front? First, it looks up the string in the fn-map, and if it finds it, then it uses that. If it doesn't find it, and the function string has a "/" in it, it then looks up string to the right of the "/".

The available classifications are:

:arg1

Print the first argument on the same line as the function, if possible. Later arguments are indented the amount specified by :list {:indent-arg n}, or :list {:indent n} if :indent-arg is not specified.

 (apply str
   "prepend this one"
   (generate-strings from arguments))

:arg1-body

Print the first argument on the same line as the function, if possible. Later arguments are indented the amount specified by :list {:indent n}.

 (if (= a 1)
   (map inc coll)
   (map dec coll))

:arg1-pair

The function has an important first argument, then the rest of the arguments are paired up. Leftmost part of the pair is indented by :list {:indent-arg n} if it is specified, and :list {:indent n} if it is not.

 (assoc my-map
   :key1 :val1
   :key2 :val2)

:arg1-pair-body

The function has an important first argument, then the rest of the arguments are paired up. The leftmost part of the pair is indented by the amount specified by :list {:indent n}.

 (case fn-style
   :arg1 nil
   :arg1-pair :pair
   :arg1-extend :extend
   :arg2 :arg1
   :arg2-pair :arg1-pair
   fn-style)

:arg1-force-nl

This is like :arg1, but since it appears in :fn-force-nl, it will never print on one line even if it would otherwise fit.

:arg1-mixin

Print Rum defc, defcc, and defcs macros in a standard way. Puts the mixins under the first line, and above the argument vector. Does not require <, will operate properly with any element in that position. Allows but does not require a docstring.

(rum/defcs component
 "This is a docstring for the component."
 < rum/static
   rum/reactive
   (rum/local 0 ::count)
   (rum/local "" ::text)
 [state label]
 (let [count-atom (::count state)
       text-atom (::text state)]
   [:div]))

:arg2

Print the first argument on the same line as the function name if it will fit on the same line. If it does, print the second argument on the same line as the first argument if it fits. Indentation of later arguments is controlled by :list {:indent n}

  (as-> initial-value tag
    (process stuff tag bother)
    (more-process tag foo bar))

:arg2-pair

Just like :arg2, but prints the third through last arguments as pairs. Indentation of the leftmost elements of the pairs is controlled by :list {:indent n}. If any of the rightmost elements end up not fitting or not hanging well, the flow indent is controlled by :pair {:indent n}.

  (condp = stuff
    :bother "bother"
    :foo "foo"
    :bar "bar"
    "baz")

:arg2-fn

Just like :arg2, but prints the third through last arguments as functions.

  (proxy [Classname] []
    (stuff [] bother)
    (foo [bar] baz))

:binding (function type)

The function has a binding clause as its first argument. Print the binding clause two-up (as pairs) The indent for any wrapped binding element is :binding {:indent n}, the indent for the functions executed after the binding is :list {:indent n}.

 (let [first val1
       second
         (calculate second using a lot of arguments)
       c d]
   (+ a c))

:pair-fn (function type)

The function has a series of clauses which are paired. Whether or not the paired clauses use hang or flow with respect to the function name is controlled by :pair-fn {:hang? boolean} and the indent of the leftmost element is controlled by :pair-fn {:indent n}.

The actual formatting of the pairs themselves is controlled by :pair. The controls for :pair-fn govern how to handle the block of pairs -- whether or not they should be in a hang with respect to the function name. The controls for how the elements within the pairs are printed are governed by :pair. For instance, the indent of any of the rightmost elements of the pair if they don't fit on the same line or don't hang well is :pair {:indent n}.

 (cond
   (and (= a 1) (> b 3)) (vector c d e)
   (= d 4) (inc a))

:hang

The function has a series of arguments where it would be nice to put the first on the same line as the function and then indent the rest to that level. This would usually always be nice, but zprint tries extra hard for these. The indent when the arguments don't hang well is :list {:indent n}.

 (and (= i 1)
      (> (inc j) (stuff k)))

:extend (function type)

The s-expression has a series of symbols with one or more forms following each. The level of indent is configurable by :extend {:indent n}.

  (reify
    stuff
      (bother [] (println))
    morestuff
      (really [] (print x))
      (sure [] (print y))
      (more-even [] (print z)))

:arg1-extend

For the several functions which have an single argument prior to the :extend syntax. They must have one argument, and if the second argument is a vector, it is also handled separately from the :extend syntax. The level of indent is controlled by :extend {:indent n}

  (extend-protocol ZprintProtocol
    ZprintType
      (more-stuff [x] (str x))
      (more-bother [y] (list y))
      (more-foo [z] (nil? z))))

  (deftype ZprintType
    [a b c]
    ZprintProtocol
      (stuff [this x y] a)
      (bother [this] b)
      (bother [this x] (list x c))
      (bother [this x y] (list x y a b)))

:arg1->

Print the first argument on the same line as the function, if possible. Later arguments go indented and :arg1 and :arg-1-pair top level fns are become :none and :pair, respectively.

Currently -> is :narg1-body, however, and there are no :arg1-> functions.

  (-> opts
    (assoc
      :stuff (list "and" "bother"))
      (dissoc :things))

:noarg1-body

Print the function in whatever way is possible without special handling. However, top level fns become different based on the lack of their first argument. Thus, :arg1 becomes :none, :arg1-pair becomes :pair, etc.

  (-> opts
      (assoc
        :stuff (list "and" "bother"))
      (dissoc :things))

:force-nl and :force-nl-body

Tag a function which should not format with all of its arguments on the same line even if they fit. Note that this function type has to show up in the set that is the value of :fn-force-nl to have any effect.

  (->> opts
       foo
       bar
       baz)

:fn

Print the first argument on the same line as the (fn ...) if it will fit on the same line. If it does, and the second argument is a vector, print it on the same line as the first argument if it fits. Indentation is controlled by :list {:indent n}.

  (fn [a b c]
    (let [d c]
      (inc d)))

  (fn myfunc [a b c]
    (let [d c]
      (inc d)))

:flow and :flow-body

Don't hang under any circumstances. :flow assumes that the function has arguments, :flow-body assumes that the arguments are body elements. The only difference is when there are different indents for arguments and body elements. Note that both :flow and :flow-body appear in the set :fn-force-nl, so that they will also never print one one line.

  (foo
    (bar a b c)
    (baz d e f))

:gt2-force-nl and :gt3-force-nl

These two function styles exist to be assigned to functions that should be printed on one line if they fit on one line -- unless they have more than 2 or 3 arguments. These exist for functions that would otherwise not fit into any function style. These function styles appear by default in the two sets :fn-gt2-force-nl and :fn-gt3-force-nl respectively.

If function foo has a function style of :gt2-force-nl, then

  (foo (bar a b c) (baz d e f))

  (foo (bar a b c)
       (baz d e f)
       (stuff x y z))

:none

This is for things like special forms that need to be in this map to show up as functions for syntax coloring, but don't actually trigger the function recognition logic to represent them as such. Also, :none is used to remove the default classification for functions by specifying it in an option map. The indent for arguments that don't hang or fit on the same line is :list {:indent-arg n} if it is specified, and :list {:indent n} if it is not.

:none-body

Like none, but the indent for arguments that don't hang or fit on the same is always :list {:indent n}.

Changing or Adding Function Classifications

You can change the classification of an existing function or add a new one by changing the map at key :fn-map. A fragment of the existing map is shown below:

:fn-map {"!=" :hang,
          "->" :noarg1-body,
          "->>" :force-nl-body,
          "=" :hang,
          "and" :hang,
          "apply" :arg1,
          "assoc" :arg1-pair,
          "binding" :binding,
          "case" :arg1-pair,
          "catch" :none,
          "cond" :pair-fn,
	  ...}

Note that the function names are strings. You can add any function you wish to the :fn-map, and it will be interpreted as described above.

You can also add a key-value pair to the :fn-map where the key is :default, and the value will be used for any function which does not appear in the :fn-map, or which does appear in the :fn-map but whose value is :none.

Altering the formatting inside of certain functions

You can associate an options map with a function classification, and that options map will be used when formatting inside of that function. This association is made by using a vector for the function classification, with the classification first and the options map second. For example:

:fn-map {"!=" :hang,
          "->" :noarg1-body,
          "->>" :force-nl-body,
          "=" :hang,
          "and" :hang,
          "apply" :arg1,
          "assoc" :arg1-pair,
          "binding" :binding,
          "case" :arg1-pair,
          "catch" :none,
          "cond" :pair-fn,
	  ...
	  "defproject" [:arg2-pair {:vector {:wrap? false}}]
	  "defprotocol" :arg1-force-nl
	  ...}

This will cause vectors inside of defproject to not wrap the elements in the vector, instead of this (which is what you would get with just :arg2-pair):

(defproject name version
  :test :this
  :stuff [:aaaaa :bbbbbbb :ccccccccc :ddddddd
          :eeeeeee])

you will get this by default:

(defproject name version
  :test :this
  :stuff [:aaaaa
          :bbbbbbb
          :ccccccccc
          :ddddddd
          :eeeeeee])

You can alter the formatting of just the top level of a function by resetting some of the configuration when zprint decends one level from the function in the function map.

For example, say that you wanted to enable {:list {:respect-nl? true}} for the comment function, but didn't want that to be in force while the expressions inside of the comment function were formatted.

Here is the input:

(def rnl2x
"(comment
(defn x
  [y]
  (println y))

(this
  is
  a
         thing that is interesting)

(def z


[:this-is-a-test :with-3-blanks-above?])

(def a :more stuff)



(def b :3-blanks-above))")

Here is the output when you do nothing special:

(zprint rnl2x {:parse-string? true})
(comment (defn x [y] (println y))
         (this is a thing that is interesting)
         (def z [:this-is-a-test :with-3-blanks-above?])
         (def a :more stuff)
         (def b :3-blanks-above))

Here is the output when you enable :list {:respect-nl? true} for comment:

(zprint rnl2x {:parse-string? true :fn-map {"comment" [:none {:list {:respect-nl? true}}]}})
(comment
  (defn x
    [y]
    (println y))

  (this
    is
    a
    thing
    that
    is
    interesting)

  (def z


    [:this-is-a-test :with-3-blanks-above?])

  (def a :more stuff)



  (def b :3-blanks-above))

Here is the output when you reset the :respect-nl? for processing at the next inner level:

(zprint rnl2x {:parse-string? true :fn-map {"comment" [:none {:list {:respect-nl? true} :next-inner {:list {:respect-nl? false}}}]}})
(comment
  (defn x [y] (println y))

  (this is a thing that is interesting)

  (def z [:this-is-a-test :with-3-blanks-above?])

  (def a :more stuff)



  (def b :3-blanks-above))

NOTE: While you can use :style <whatever> in the options map in a :fn-map vector: [<fn-type> <options-map>], if you want to remove that style you have to either set a new style or change the various individual configuration parameters of that style to be what you think they were before. It is not possible to simply restore the options map to what it was prior to the changes made by the <options-map>.

Replacing functions with reader-macros

The :fn-map is can be used to allow backtranslation of arbitrary functions into reader-macros. For instance, (quote a) can be backtranslated into 'a by using the following :fn-map entry:

{:fn-map {"quote" [:replace-w-string {} {:list {:replacement-string "'"}}]}}

If there is a function which has a fn-type of :replace-w-string and the options map has a {:list {:replacement-string "'"}} value, then that function will be replaced by the string. The leading and trailing "()" will be removed, as will the function name. If there is only one options map in the vector which is the value of the key-value pair, then it is used for both structures and source formatting. However, if there are two maps as the second and third elements in the vector, the first map (which is the second element of the vector) is used as the options map for source formatting, and the second map (third element of the vector) is used as the options map for structure formatting. Thus, the example above only replaces (quote a) with 'a when formatting structures, and not when formatting source. If there was just one options map, it would perform this replacement when formatting both structures and source.

Configuring the :fn-map

Often the :fn-map is configured by changing the .zprintrc file so that functions are formattted the way you prefer. You can change the default formatting of functions as well as configure formatting for your own functions. To remove formating for a function which has previously been configured, set the formatting to :none.

Controlling Single and Multi-line Output

By default, zprint will print any function call (or any structure) on one line if it will fit on one line. However, some functions are generally printed on multiple lines even if they would fit on one line, and zprint will do this for some functions by default.

There are three sets which control which function styles will never print on one line even if they would otherwise fit:

:fn-force-nl #{:force-nl :force-nl-body :noarg1 :noarg1-body :arg1-force-nl :flow :flow-body}

This is a set that specifies which function types will always format with a hang or a flow, and never be printed on the same line even if they fit.

:fn-gt2-force-nl #{:gt2-force-nl :binding}

This is a set that specifies which function types will always format with a hang or a flow, and never be printed on the same line if they have more than 2 arguments.

:fn-gt3-force-nl #{:gt3-force-nl :arg1-pair :arg1-pair-body}

This is a set that specifies which function types will always format with a hang or a flow, and never be printed on the same line if they have more than 3 arguments.

Altering the configuration of sets in the options map

You can add one or more function styles to a set by simply placing a set containing only the additional function styles as the value of the appropriate key. Thus:

  (set-options! {:fn-gt2-force-nl #{:arg1-pair}})

yields a value for the key :fn-gt2-force-nl of #{:gt2-force-nl :binding :arg1-pair}. It does not replace the set at that key with the new set, but includes its elements into the set. Thus you don't have to specify the entire set to alter its value by adding something to it.

How, then, do you remove elements from one of the sets in the options map? You specify a set of elements to remove, rooted at the :remove key. Thus:

  (set-options! {:remove {:fn-gt3-force-nl #{:arg1-pair}}})

will yield a value for :fn-gt3-force-nl of #{:gt3-force-nl :arg1-pair-body}.

If both additions and removals are specified in the same options map, the removals are performed first and the additions second.

Detailed Configuration for maps, lists, vectors, etc.

Internally, there are several formatting capabilities that are used in slightly different ways to format a wide variety of syntactic elements. These basic capabilities are parameterized, and the parameters are varied based on the syntactic element. Before going into detail about the individual elements, let's look at the overview of the capabilities:

A note about two-up printing

Part of the reason for zprint's existence revolves around the current approach to indenting used for cond clauses, binding vectors, and maps and other things with pairs (extend and reader conditionals).

Back in the day some of the key functions that include pairs, e.g. cond and let, had their pairs nested in parentheses. Clojure doesn't follow this convention, which does create cleaner looking code in the usual case, when the second part of the pair is short and fits on the same line or when the second part of the pair can be represented in a hang. In those cases when the second part of the pair ends up on the next line (as a flow), it can sometimes become a bit tricky to separate the test and expr pair in a cond, or a destructured binding-form from the init-expr, as they will start in the same column.

While the cases where it is a bit confusing are rather rare, I find them bothersome, so by default zprint will indent the second part of these pairs by 2 columns (controlled by :pair {:indent 2} for cond and :binding {:indent 2} for binding functions).

Maps also have pairs, and perhaps suffer from the potential for confusion a bit more then binding-forms and cond functions. By default then, the map indent for the an item that placed on the next line (i.e., in a flow) is 2 (controlled by :map {:indent 2}). The default is 2 for extend and reader-conditionals as well.

Is this perfect? No, there are opportunities for confusion here too, but it works considerably better for me, and it might for you too. I find this particularly useful for :binding and :map formatting.

Should you not like what this does to your code or your s-expressions, the simple answer is to use {:style :community} as an options-map when calling zprint (specify that in your .zprintrc file, perhaps).

You can change the indent from the default of 2 to 0 individually in :binding, :map, or :pair if you want to tune it in more detail.

A note on justifying two-up printing

I have seen some code where people justify the second element of their pairs to all line up in the same column. I call this justifying for lack of a better term. Here is an example in code:

; Regular formatting

(zprint-fn compare-ordered-keys {:pair {:justify? true}})

(defn compare-ordered-keys
  "Do a key comparison that places ordered keys first."
  [key-value zdotdotdot x y]
  (cond (and (key-value x) (key-value y)) (compare (key-value x) (key-value y))
        (key-value x) -1
        (key-value y) +1
        (= zdotdotdot x) +1
        (= zdotdotdot y) -1
        :else (compare-keys x y)))

; Justified formatting

(zprint-fn compare-ordered-keys {:pair {:justify? true}})

(defn compare-ordered-keys
  "Do a key comparison that places ordered keys first."
  [key-value zdotdotdot x y]
  (cond (and (key-value x) (key-value y)) (compare (key-value x) (key-value y))
        (key-value x)                     -1
        (key-value y)                     +1
        (= zdotdotdot x)                  +1
        (= zdotdotdot y)                  -1
        :else                             (compare-keys x y)))

Zprint will optionally justify :map, :binding, and :pair elements. There are several detailed configuration parameters used to control the justification. Obviously this works best if the keys in a map are all about the same length (and relatively short), and the test expressions in a cond are about the same length, and the locals in a binding are about the same length.

I don't personally find the justified approach my favorite in code, though there are some functions where it looks good.

Try:

(czprint-fn resultset-seq {:style :justified})

and see what you think. Looks great to me, but it just happens to have nice locals.

For functions where this looks great, you can always turn it on just for that function (if you are using lein-zprint), like so:

;!zprint {:format :next {:style :justified}}

As you might gather, there is a :style :justified which you can use to turn this on for maps, pairs, and bindings.

I was surprised what justification could do for some maps, however. You can see it for yourself if you enter:

(czprint nil :explain-justified)

This prints out the regular :explain output for the current zprint options map, but justified. See what you think.

NOTE: Justification involves extra processing, and because of the way that zprint tries to do the best job possible, it can cause a bit of a combinatorial explosion that can make formatting some functions and structures take a very long time. I have put scant effort into optimizing this capability, as I have no idea how interesting it is to people in general. If you are using it and like it, and you have situations where it seems to be particularly slow for you, please enter an issue to let me know.

Formatting Large or Deep Collections

Sometimes you end up with a collection which is very large or very deep -- or both. You want to get an overview of it, but don't want to output the entire collection because it will take too much space or too much time. At one time, these were experimental capabilities, but they are now fully supported.

There are two limits that can be helpful.

:max-length 1000

Will limit the length of a sequence on output -- more than this many will yield a ....


(czprint [1 2 3 4 5] {:max-length 3})

[1 2 3 ...]

That's nice, but sometimes you want to see different amounts of a collection at different levels. Perhaps you want to see all of the keys in a map, but not much of the information lower down in the values of the map.

In this situation, the :max-length can be a vector, where the value at each level is the max-length for that level in the collection. The rightmost value in the vector is used for all of the levels below the one specified.

So, {:max-length [3 2 1 0]} would output 3 things at the top level of the collection, 2 for everything at the next level down, one for every collection at the next level, and ## for any collections below that. Since the rightmost value is used for any level beyond that explicitly specified, {:max-length n} and {:max-length [n]} are equivalent. Also {:max-depth 3} and {:max-length [1000 1000 1000 0]} are also equivalent.

(czprint [:a [:b [:c [:d [:e [:f]]]]]] {:max-length [1000 1000 1000 0]})

[:a [:b [:c ##]]]


(czprint [:a [:b [:c [:d [:e [:f]]]]]] {:max-depth 3})

[:a [:b [:c ##]]]

Here are some examples with the zprint options map (where we aren't going to examine all of the keys, but a few at the beginning):

(czprint x {:max-length [10 0]})

{:agent ##,
 :array ##,
 :atom ##,
 :auto-width? false,
 :binding ##,
 :color-map ##,
 :color? true,
 :comment ##,
 :configured? true,
 ...}

(czprint x {:max-length [10 1 0]})

{:agent {:object? false},
 :array {:hex? false, ...},
 :atom {:object? false},
 :auto-width? false,
 :binding {:flow? false, ...},
 :color-map {:brace :red, ...},
 :color? true,
 :comment {:count? false, ...},
 :configured? true,
 ...}

If you have a complex structure, a little experimentation with :max-length and a vector can often allow you to generate a useful overview of the structure without much effort.

While you might not think this would be useful for looking at code, for code that has a very regular structure, it can be helpful. For instance, if you want an overview of a deftype, you could use {:max-length [100 2 10 0]}, as below:

(czprint-fn clojure.core.match/->PatternRow {:max-length [100 2 10 0]})

(deftype PatternRow [ps action ...]
  Object
    (equals [_ other] ...)
  IVecMod
    (drop-nth [_ n] ...)
    (prepend [_ x] ...)
    (swap [_ n] ...)
  clojure.lang.Associative
    (assoc [this k v] ...)
  clojure.lang.Indexed
    (nth [_ i] ...)
    (nth [_ i x] ...)
  clojure.lang.ISeq
    (first [_] ...)
    (next [_] ...)
    (more [_] ...)
    (seq [this] ...)
    (count [_] ...)
  clojure.lang.ILookup
    (valAt [this k] ...)
    (valAt [this k not-found] ...)
  clojure.lang.IFn
    (invoke [_ n] ...)
  clojure.lang.IPersistentCollection
    (cons [_ x] ...)
    (equiv [this other] ...))

:max-depth 1000

Will limit depth of a collection.

(czprint {:a {:b {:c :d}}} {:max-depth 1})

{:a {:b ##}}

Widely Used Configuration Parameters

There are a several configuration parameters that are meaningful across a number of formatting types.

:indent

The value for indent is how far to indent the second through nth of something if it doesn't all fit on one line (and becomes a flow, see immediately below).

Note that the indent values for things with pairs (i.e., :map, :binding, :pair) are counted differently from other things. For these things, an :indent 2 will leave two blanks to the right of the left "bracket" (e.g. "{" for maps). For other things an :indent 2 will leave one blank to the right of the left bracket.

:indent-only? false

This is configurable for the major data structures: lists, maps, sets, and vectors. When enabled, zprint will not add or remove newlines from the incoming source, but will otherwise regularize whitespace. When :indent-only? is specified, other configuration parameters for the lists, maps, sets, or vectors will be ignored except for :indent (for all of the data types) and :indent-only-style (to control hang or flow, only for lists).

:respect-bl? false

This will cause zprint to respect incoming blank lines. If this is enabled, zprint will add newlines and remove newlines as necessary, but will not remove any existing blank lines from incoming source. Existing formatting configuration will be followed, of course with the constraint that existing blank lines will be included wherever they appear. Note that blank lines at the "top level" (i.e., those outside of (defn ...) and (def ...) expressions) are always respected and never changed. :respect-bl? controls what happens to blank lines within defn and def expressions.

If you wish to use zprint to enforce a particular format, using :respect-bl? might be a bad idea -- since it depends on the incoming source with regard to blank lines.

If you use blank lines a lot within function definitions in order to make them more readable, this can be a good capability to enable globally.

:respect-nl? false

This will cause zprint to respect incoming newlines. If this is enabled, zprint will add newlines, but will not remove any existing newlines from incoming source. Existing formatting configuration will be followed, of course with the constraint that existing newlines will be included wherever they appear.

hang and flow

zprint uses two concepts: hang and flow, to describe how something is to be printed.

This is a hang:

(symbol "string"
        :keyword
        5
        {:map-key :value})

This is the same information in a flow:

(symbol
  "string"
  :keyword
  5
  {:map-key :value})

zprint will try (by default) to use the hang approach when it will use the same or fewer lines than a flow. Unless the hang takes too much vertical space (which makes things less clear, instead of more clear). There are several values which will tune the output for hang and flow.

:hang? true

If :hang? is true, zprint will attempt to hang if all of the elements in the collection don't fit on one line. If it is false, it won't even try.

:hang-avoid

If :hang-avoid is non-nil, then it is used to decide if the formatting is close enough to the right margin to probably not be worth doing. This is a performance optimization for functions that are very deeply nested and take a considerable time to format. For normal functions, this has no effect, but for a few functions that take a long time to format, it can cut that time by 30%. If the value is non-nil, then avoid even trying to do a hang if the number of top-level elements in the rest of the collection is greater than the remaining columns times the hang-avoid value. The hang-avoid value defaults to 0.5, which changes only a tiny amount of output visually, but provides useful performance gains in functions which take a long time to format. At present this only affects lists, but may be implemented for other collections in the future.

:hang-expand

:hang-expand is one control used to decide whether or not to do a hang. It relates the number of lines in the hang to the number of elements in the hang thus: (/ (dec hang-lines) hang-element-count). If every element in the hang fits on one line, then this ratio will be < 1. If every element in the hang takes two lines, then this ratio will be close to 2. If this ratio is > :hang-expand, then the hang is rejected. The idea is that hangs that run on and on down the right side of the page are not ideal, even when they don't take more lines than a flow. Unless, in some cases, they are ok -- for instance for maps. The :hang-expand for :map is 1000.0, since we expect maps to have large hangs that expand a lot.

:hang-diff

The value of :hang-diff (an integer) is related to the indent for a hang and a flow. Clearly, if the indent for a hang and a flow are the same, you might as well do a hang, since a flow buys you nothing. The difference in these indents (- hang-indent flow-indent) is compared to the value of :hang-diff, and if this difference is <= then it skips the :hang-expand check. :hang-diff is by default 1, since even if a flow buys you one more space to the left, it often looks kind of odd. You could set :hang-diff to 0 if you wanted to be more "strict", and see if you like the results better. Probably you won't want to deal with this level of control.

:flow? false

If :flow? is true, all of the elements of a collection will be forced onto a new line, even if they would have fit on the same line originally. When a function has a function type of :flow, all of the arguments will be flowed below the function, each taking its own line. The :flow? options configuration key does a similar thing for data structures (both within code and just in data structures). For example:

(czprint {:a :b :c :d :e :f :g { :i {:j :k} :l :m}} {:map {:flow? false}})

{:a :b, :c :d, :e :f, :g {:i {:j :k}, :l :m}}

(czprint {:a :b :c :d :e :f :g { :i {:j :k} :l :m}} {:map {:flow? true}})

{:a
   :b,
 :c
   :d,
 :e
   :f,
 :g
   {:i
      {:j
         :k},
    :l
      :m}}

This looks a bit strange because the keys are very short, making the indentation of the second element in each pair odd. If you do this, you might want to reduce the indent, thus:

(czprint {:a :b :c :d :e :f :g { :i {:j :k} :l :m}} {:map {:indent 0 :flow? true}})

{:a
 :b,
 :c
 :d,
 :e
 :f,
 :g
 {:i
  {:j
   :k},
  :l
  :m}}

The :flow? capability was added along with :nl-separator? to make formatting :extend types work in an alternative way:

(czprint-fn ->Typetest)

; Default output, :force-nl? is true

(deftype Typetest
  [cnt _meta]
  clojure.lang.IHashEq (hasheq [this] (list this))
  clojure.lang.Counted (count [_] cnt)
  clojure.lang.IMeta (meta [_] _meta))

(czprint-fn ->Typetest {:extend {:flow? true}})

; Add :flow? true, always keeps fn defns on separate line

(deftype Typetest
  [cnt _meta]
  clojure.lang.IHashEq
    (hasheq [this] (list this))
  clojure.lang.Counted
    (count [_] cnt)
  clojure.lang.IMeta
    (meta [_] _meta))

(czprint-fn ->Typetest {:extend {:flow? true :indent 0}})

; Reduce indent

(deftype Typetest
  [cnt _meta]
  clojure.lang.IHashEq
  (hasheq [this] (list this))
  clojure.lang.Counted
  (count [_] cnt)
  clojure.lang.IMeta
  (meta [_] _meta))

(czprint-fn ->Typetest {:extend {:flow? true :indent 0 :nl-separator? true}})

; Add :nl-separator? true for an altogether different (but commonly used) look

(deftype Typetest
  [cnt _meta]
  clojure.lang.IHashEq
  (hasheq [this] (list this))

  clojure.lang.Counted
  (count [_] cnt)

  clojure.lang.IMeta
  (meta [_] _meta))

:force-nl? false

Very similar to :flow?, but operates on pairs, not individual elements of a pair. For example:

(czprint {:a :b :c :d :e :f :g { :i {:j :k} :l :m}} {:map {:force-nl? false}})

{:a :b, :c :d, :e :f, :g {:i {:j :k}, :l :m}}

(czprint {:a :b :c :d :e :f :g { :i {:j :k} :l :m}} {:map {:force-nl? true}})

{:a :b,
 :c :d,
 :e :f,
 :g {:i {:j :k},
     :l :m}}

Also works with :pair functions

(czprint "(cond abcd b cdef d)" {:parse-string? true :pair {:force-nl? false}})

(cond abcd b cdef d)

(czprint "(cond abcd b cdef d)" {:parse-string? true :pair {:force-nl? true}})

(cond abcd b
      cdef d)

:nl-separator? false

This will put a blank line between any pair where the right part of a pair was formatted with a flow. Some examples:

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}} 40 {:map {:nl-separator? false}})

{:a :b,
 :c {:e :f, :g :h, :i :j, :k :l},
 :m :n,
 :o {:p {:q :r, :s :t}}}

; No effect if all the pairs print on one line

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}} 40 {:map {:nl-separator? true}})
{:a :b,
 :c {:e :f, :g :h, :i :j, :k :l},
 :m :n,
 :o {:p {:q :r, :s :t}}}

; With a narrower width, one of them takes more than one line

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}} 30 {:map {:nl-separator? false}})

{:a :b,
 :c {:e :f,
     :g :h,
     :i :j,
     :k :l},
 :m :n,
 :o {:p {:q :r, :s :t}}}

; and even now :nl-separator? will not have any effect because none of the
; right hand pairs are formatted with a flow -- that is, none of the right
; hand parts of the pairs start all of the way to the left.  They are still
; formatted as a hang

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}} 30 {:map {:nl-separator? true}})

{:a :b,
 :c {:e :f,
     :g :h,
     :i :j,
     :k :l},
 :m :n,
 :o {:p {:q :r, :s :t}}}

; If you turn off the hang, then now if a pair doesn't fit on one line,
; you get a flow:

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}}
         30
         {:map {:nl-separator? true :hang? false}})

{:a :b,
 :c
   {:e :f,
    :g :h,
    :i :j,
    :k :l},

 :m :n,
 :o {:p {:q :r, :s :t}}}

; Most people use the :nl-separator? kind of formatting when they don't
; want the right hand side of a pair indented.  So if you turn off :hang?
; then you probably want to remove the indent as well.

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}}
         30
         {:map {:nl-separator? true :hang? false :indent 0}})

{:a :b,
 :c
 {:e :f, :g :h, :i :j, :k :l},

 :m :n,
 :o {:p {:q :r, :s :t}}}

:justify? false

Turn on justification. Default is nil (justification off).

Configurable Elements


:agent, :atom, :delay, :fn, :future, :promise

All of these elements are formatted in a readable manner by default, which shows their current value and minimizes extra information.

:object? false

All of these elements can be formatted more as Clojure represents Java objects by setting :object? to true.


:array

Arrays are formatted by default with the values of their elements.

:hex? false

If the elements are numeric, format them in hex. Useful if you are doing networking. See below for an example.

:object? false

Don't print the elements of the array, just print it as an object.

A simple example:

(require '[zprint.core :as zp])

(def ba (byte-array (range 50)))

(zp/zprint ba 75)

[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49]

(zp/zprint ba 75 {:array {:hex? true}})

[00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18
 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30
 31]

;; As an aside, notice that the 8 in 18 was in column 75, and so while the
;; 31 would have fit, the ] would not, so they go on the next line.

(zp/zprint ba 75 {:array {:object? true}})

#object["[B" "0x31ef8e0b" "[B@31ef8e0b"]

:wrap? true

Should it wrap its contents, or just list each on a separate line if they don't all fit on one line.?


:binding

Controls the formatting of the first argument of any function which has :binding as its function type. let is, of course, the canonical example.

:indent 2
:hang? true
:hang-expand 2
:hang-diff 1
:justify? false

:force-nl? true

If you never want to see multiple binding pairs on the same line, like this:

(czprint "(let [abcd b cdef d efgh f] (list a f))" {:parse-string? true}

(let [abcd b cdef d efgh f] (list a f))

You can configure :binding to have :force-nl? true, which will yield this:

(czprint "(let [abcd b cdef d efgh f] (list a f))" {:parse-string? true :binding {:force-nl? true}})

(let [abcd b
      cdef d
      efgh f]
  (list a f))

(czprint "(let [abcd b] (list a f))" {:parse-string? true :binding {:force-nl? true}})

(let [abcd b]
  (list a f))

:flow? false

:nl-separator? false

Both :flow? and :nl-separator? together with :indent can significantly alter the way binding pairs are printed:

(czprint "(let [abcd b cdef d efgh f] (list a f))" {:parse-string? true :binding {:flow? false}})

(let [abcd b cdef d efgh f] (list a f))

(czprint "(let [abcd b cdef d efgh f] (list a f))" {:parse-string? true :binding {:flow? true}})

; This isn't all that nice, but we are on the way to something different

(let [abcd
        b
      cdef
        d
      efgh
        f]
  (list a f))

(czprint "(let [abcd b cdef d efgh f] (list a f))"
         {:parse-string? true :binding {:flow? true :indent 0}})

; Remove the indent

(let [abcd
      b
      cdef
      d
      efgh
      f]
  (list a f))

(czprint "(let [abcd b cdef d efgh f] (list a f))"
         {:parse-string? true :binding {:flow? true :indent 0 :nl-separator? true}})

; Some people like their binding pairs formatted this way:

(let [abcd
      b

      cdef
      d

      efgh
      f]
  (list a f))

:comment

zprint has two fundemental regimes -- printing s-expressions and parsing a string and printing the result. There are no comments in s-expressions, except in the comment function, which is handled normally. When parsing a string, zprint will deal with comments. Comments are dealt with in one of two ways -- either they are ignored from a width standpoint while formatting, or their width is taken into account when formatting. In addition, comments can be word-wrapped if they don't fit the width, or not. These are indpendent capabilities.

:wrap? true

Wrap a comment if it doesn't fit within the width. Works hard to preserve the initial part of the line and word wraps the end. Does not pull subsequent lines up on to a wrapped line.

:inline? true

If the a comment is on the same line as some code, keep the comment on that same line. The distance from the code is preserved only when :inline-align-style :none is used. See :inline-align-style for details. If the comment extends beyond the width, it will be wrapped just like a comment which is on its own line.

:inline-align-style :aligned

There are three possible ways that inline comments can be aligned:

  • :none -- no effort is made to align inline comments. The distance from the code on input is preserved. If they are aligned, it is because the code didn't move (or moved together).

  • :aligned -- the default. Any comments that are aligned on input and are separated by less than 5 lines on output will be aligned in the output.

  • :consecutive -- Any inline comments which appear on consecutive lines in the output (not the input) will be aligned.

:count? false

Count the length of the comment when ensuring that things fit within the width. Doesn't play well with inline comments. With any kinds of comments, this tends to mess up the code more than helping, in my view.

An example (using :parse-string? true to include the comment):

(require '[zprint.core :as zp])

(def cd "(let [a (stuff with arguments)] (list (or foo bar baz) (format output now) (and a b c (bother this)) ;; Comment that doesn't fit real well, but is almost a fit to see how it works\n (format other stuff))(list a :b :c \"d\"))")

(zp/zprint cd 75 {:parse-string? true :comment {:count? nil}})

(let [a (stuff with arguments)]
  (list (or foo bar baz)
        (format output now)
        (and a b c (bother this))
        ;; Comment that doesn't fit real well, but is almost a fit to see
        ;; how it works
        (format other stuff))
  (list a :b :c "d"))

zprint.core=> (czprint cd 75 {:parse-string? true :comment {:count? true}})

(let [a (stuff with arguments)]
  (list
    (or foo bar baz)
    (format output now)
    (and a b c (bother this))
    ;; Comment that doesn't fit real well, but is almost a fit to see how
    ;; it works
    (format other stuff))
  (list a :b :c "d"))

(zp/zprint cd 75 {:parse-string? true :comment {:count? nil :wrap? nil}})

(let [a (stuff with arguments)]
  (list (or foo bar baz)
        (format output now)
        (and a b c (bother this))
        ;; Comment that doesn't fit real well, but is almost a fit to see how it works
        (format other stuff))
  (list a :b :c "d"))

:extend

When formatting functions which have extend in their function types.

:indent 2
:hang? true

:force-nl? true

Forces a new line between one type/fn defn set and the next in the extend.

:nl-separator? false

Places a blank line between one type/fn defn set and the next if the fn defn set formats with a flow.

:flow? false

Places a new line between the type and the fn defns in a single type/fn defn set in the extend.

Here are some examples of two rather different, but commonly used, ways to format extend:

(czprint-fn ->Typetest)

; Default output, :force-nl? is true

(deftype Typetest
  [cnt _meta]
  clojure.lang.IHashEq (hasheq [this] (list this))
  clojure.lang.Counted (count [_] cnt)
  clojure.lang.IMeta (meta [_] _meta))

(czprint-fn ->Typetest {:extend {:flow? true}})

; Add :flow? true, always keeps fn defns on separate line

(deftype Typetest
  [cnt _meta]
  clojure.lang.IHashEq
    (hasheq [this] (list this))
  clojure.lang.Counted
    (count [_] cnt)
  clojure.lang.IMeta
    (meta [_] _meta))

(czprint-fn ->Typetest {:extend {:flow? true :indent 0}})

; Remove all indent

(deftype Typetest
  [cnt _meta]
  clojure.lang.IHashEq
  (hasheq [this] (list this))
  clojure.lang.Counted
  (count [_] cnt)
  clojure.lang.IMeta
  (meta [_] _meta))

(czprint-fn ->Typetest {:extend {:flow? true :indent 0 :nl-separator? true}})

; Add :nl-separator? true for an altogether different (but commonly used) look

(deftype Typetest
  [cnt _meta]
  clojure.lang.IHashEq
  (hasheq [this] (list this))

  clojure.lang.Counted
  (count [_] cnt)

  clojure.lang.IMeta
  (meta [_] _meta))

:modifiers #{"static"}

Contains a set of elements that will be placed on the same line as the protocol-or-interface-or-Object. Created largely to support defui in Clojurescript om/next, but may have other utility. Elements specified by {:extend {:modifiers #{<element1> <element2>}}} are added to the set (as opposed to replacing the set entirely). You can remove elements from the set by {:remove {:extend {:modifers #{<thing-to-remove>}}}}.


:input

This controls the input that is formatted.

:range

Allows specification of the start and end of a range when using zprint-file-str (which is used by all of the file processing binaries when they format an entire file).

A range specification is itself a map:

{:start start-line-number :end end-line-number}

where start-line-number and end-line-number are zero based, and are inclusive (that is to say, both lines will be formatted).

Since zprint can only format effectively if it knows the left margin, the start-line-number and end-line-number are expanded outwards to encompass one or more top level expressions. If they both initially reference a single expression, the start is moved up to the first line beyond the previous expression, while the end is moved down to the last line of the expression referenced. This is in order to encompass any ;!zprint {} directives that might appear directly before the expression. Note that the range will never start or end on a blank line unless it is the start or end of the file.

The specifics of how the line numbers are handled are: the start-line-number is moved to the first non-blank line after the previous expression, where comments are considered non-blank lines. If there is no previous expression the start-line-number is set to the beginning of the file. The end-line-number is moved to the last line of the expression in which the end-line-number falls. If the end-line-number does not fall inside an expression, it is moved up to the first previous non-blank line. The range will never start or end on a blank line inside of a file.

If the start-line-number is negative, it is considered to be before the start of the file. If the end-line-number is negative, nothing will be formatted in the file. If the end-line-number is before the start-line-number, it will be set to the start-line-number A start-line-number beyond the end of the file will cause nothing to be included in the range, while an end-line-number beyond the end of the file will simply represent that the end of the range should be the end of the file.

If both start-line-number and end-line-number are within the same gap between expressions, nothing will be formatted.

Note that zprint will not leave trailing spaces on a line, but this is only true for lines that are part of the range -- the other lines are untouched.

If any problems occur when trying to determine the current or previous expressions (since a quick parse of the entire string (file) is required for this to happen), the entire string (file) is formatted.


:list

Lists show up in lots of places, but mostly they are code. So in addition to the various function types described above, the :list configuration affects the look of formatted code.

:indent 2
:hang? true
:hang-avoid 0.5
:hang-expand 2.0
:hang-diff 1

:indent-arg nil

The amount to indent the arguments of a function whose arguments do not contain "body" forms. See here for an explanation of what this means. If this is nil, then the value configured for :indent is used for the arguments of functions that are not "body" functions. You would configure this value only if you wanted "arg" type functions to have a different indent from "body" type functions. It is configured by :style :community.

:indent-only? false

Do not add or remove newlines. Just indent the lines that are there and regularize whitespace. The :fn-map which gives formatting and indentation information about different functions is ignored. The default indentation is flow, however based on the value of the :indent-only-style, a hang will be used in some situations. See :indent-only-style below for details.

:indent-only-style :input-hang

Controls how :indent-only indents a list. If the value is :input-hang, then if the input is formatted as a hang, it will indent the list as a hang. The input is considered to be formatted as a hang if the first two elements of the list are on the same line, and the third element of the list is on the second line aligned with the second element. The determination of alignment is not affected by the appearance of comments.

:hang-size 100

The maximum number of lines that are allowed in a hang. If the number of lines in the hang is greater than the :hang-size, it will not do the hang but instead will format this as a flow. Together with :hang-expand this will keep hangs from getting too long so that code (typically) doesn't get very distorted.

:constant-pair? true

Lists (which are frequently code) support something called constant pairing. This capability looks at the end of a list, and if the end of the list appears to contain pairs of constants followed by anything, it will print them paired up. A constant in this context is a keyword, string, or number. An example will best illustrate this.

We will use a feature of zprint, where it will parse a string prior to formatting, so that the anonymous functions show up right.

(require '[zprint.core :as zp])

(def x "(s/fdef spec-test\n :args (s/and (s/cat :start integer? :end integer?)\n #(< (:start %) (:end %)))\n :ret integer?\n :fn (s/and #(>= (:ret %) (-> % :args :start))\n #(< (:ret %) (-> % :args :end))))\n")

;;
;; Without constant pairing, it is ok...
;;

(zp/zprint x 60 {:parse-string? true :list {:constant-pair? nil}})

(s/fdef spec-test
        :args
        (s/and (s/cat :start integer? :end integer?)
               #(< (:start %) (:end %)))
        :ret
        integer?
        :fn
        (s/and #(>= (:ret %) (-> % :args :start))
               #(< (:ret %) (-> % :args :end))))

;;
;; With constant pairing it is nicer
;;

(zp/zprint x 60 {:parse-string? true :list {:constant-pair true}})

(s/fdef spec-test
        :args (s/and (s/cat :start integer? :end integer?)
                     #(< (:start %) (:end %)))
        :ret integer?
        :fn (s/and #(>= (:ret %) (-> % :args :start))
                   #(< (:ret %) (-> % :args :end))))

;;
;; We can demonstrate another configuration capability here.
;; If we tell zprint that s/fdef is an :arg1 style function, it is better
;; (note that :constant-pair? true is the default).
;;

(zp/zprint x 60 {:parse-string? true :fn-map {"s/fdef" :arg1}})

(s/fdef spec-test
  :args (s/and (s/cat :start integer? :end integer?)
               #(< (:start %) (:end %)))
  :ret integer?
  :fn (s/and #(>= (:ret %) (-> % :args :start))
             #(< (:ret %) (-> % :args :end))))

Constant pairing tends to make keyword style arguments come out looking rather better than they would otherwise. This feature was added to handle what I believed was a very narrow use case, but it has shown suprising generality, making unexpected things look much better.

In particular, try it on your specs!

Note that the formatting of the pairs in a constant pair is controlled by the :pair configuration (just like the pairs in a cond, assoc, and any function style with "pair" in the name).

:constant-pair-min 4

An integer specifying the minimum number of required elements capable of being constant paired before constant pairing is used. Note that constant pairing works from the end of the list back toward the front (not illustrated in these examples).

Using our previous example again:

(require '[zprint.core :as zp])

(def x "(s/fdef spec-test\n :args (s/and (s/cat :start integer? :end integer?)\n #(< (:start %) (:end %)))\n :ret integer?\n :fn (s/and #(>= (:ret %) (-> % :args :start))\n #(< (:ret %) (-> % :args :end))))\n")

;;
;; There are 6 elements that can be constant paired
;;

(zp/zprint x 60 {:parse-string? true :list {:constant-pair-min 6}})

(s/fdef spec-test
        :args (s/and (s/cat :start integer? :end integer?)
                     #(< (:start %) (:end %)))
        :ret integer?
        :fn (s/and #(>= (:ret %) (-> % :args :start))
                   #(< (:ret %) (-> % :args :end))))

;;
;; So, if we change the requirements to 8, it won't constant-pair
;;

(zp/zprint x 60 {:parse-string? true :list {:constant-pair-min 8}})

(s/fdef spec-test
        :args
        (s/and (s/cat :start integer? :end integer?)
               #(< (:start %) (:end %)))
        :ret
        integer?
        :fn
        (s/and #(>= (:ret %) (-> % :args :start))
               #(< (:ret %) (-> % :args :end))))

:return-altered-zipper nil

EXPERIMENTAL

This capability will let you rewrite any list that zprint encounters. It only works when zprint is formatting source code -- where :parse-string? is true. When a structure is being formatted, none of this is invoked.

This will call a function that you supply with the zipper of the list and the function should return a zipper with an altered list. Zprint will then format the altered list.

General caveats -- you can really screw things up very easily, as I'm sure is obvious. Less obvious is the relative difficulty of actually writing a function to rewrite the code. Implementing this feature was very easy, writng the first example, the style :sort-dependencies was a significant piece of work. It is hard to rewrite code using rewrite-clj (not that I have a better approach), it is hard to debug, the Clojure and Clojurescript implementations of rewrite-clj are very slightly different.

The configuration for :return-altered-zipper is a vector: [<depth> <symbol> <fn>], where <depth> is the depth to call the function (if the <symbol> matches). A <depth> of nil will call at any depth. The <symbol> is the first element of the list that is passed to the <fn>. If <symbol> is nil, then every list is passed to the <fn>. The goal here is to not severely impact the performance by calling the function to rewrite the zipper too frequently. I would recommend against configuring [nil nil <fn>]. See the configuration for the style :sort-dependencies for an example.

The <fn> requires a signature of [caller options zloc], where caller is the keyword that indicates who called called fzprint-list* (which would be useful only to check values in the option map), options is the current options map, and zloc is the zipper that can be modified. The <fn> should return a zipper which is an alteration of zloc. See the file rewrite.cljc for the current implementation of :sort-dependencies as an example.

This whole capability is experimental until further notice. There may be a better way of accomplishing this, or the API may change in important ways. In the event you write a function that works, let me know!

:respect-bl? false

This will cause zprint to respect incoming blank lines. If this is enabled, zprint will add newlines and remove newlines as necessary, but will not remove any existing blank lines when formatting lists. Existing formatting configuration will be followed, of course with the constraint that existing blank lines will be included wherever they appear. Note that blank lines at the "top level" (i.e., those outside of (defn ...) and (def ...) expressions) are always respected and never changed. :respect-bl? controls what happens to blank lines within defn and def expressions.

If you wish to use zprint to enforce a particular format, using :respect-bl? might be a bad idea -- since it depends on the incoming source with regard to blank lines.

If you use blank lines a lot within function definitions in order to make them more readable, this can be a good capability to enable globally.

:respect-nl? false

This will cause zprint to respect incoming newlines. If this is enabled, zprint will add newlines, but will not remove any existing newlines when formatting lists. Existing formatting configuration will be followed, of course with the constraint that existing newlines will be included wherever they appear.


:map

Maps support both the indent and hang values, above. The default :hang-expand value is 1000.0 because maps don't look bad with a large hangs.

:indent 2
:hang? true
:hang-expand 1000.0
:hang-diff 1
:justify? false

:flow? false

Never print the key and value of a single key/value pair on the same line.

(czprint {:abc :def :ghi :ijk})

{:abc :def, :ghi :ijk}

(czprint {:abc :def :ghi :ijk} {:map {:flow? true}})

{:abc
   :def,
 :ghi
   :ijk}

:nl-separator? false

Put an entirely blank line between any key/value pair where the value part of the pair formats as a flow.

(czprint {:abc :def :ghi :ijk})

{:abc :def, :ghi :ijk}

(czprint {:abc :def :ghi :ijk} {:map {:flow? true :indent 0}})

{:abc
 :def,
 :ghi
 :ijk}

(czprint {:abc :def :ghi :ijk} {:map {:flow? true :indent 0 :nl-separator? true}})

{:abc
 :def,

 :ghi
 :ijk}

But maybe you want to still allow the values of a key/value pair to print on the same line when possible, and only want a blank line when the key/value pair formats with the value as a flow.

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}} 40 {:map {:nl-separator? false}})

{:a :b,
 :c {:e :f, :g :h, :i :j, :k :l},
 :m :n,
 :o {:p {:q :r, :s :t}}}

; No effect if all the pairs print on one line

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}} 40 {:map {:nl-separator? true}})
{:a :b,
 :c {:e :f, :g :h, :i :j, :k :l},
 :m :n,
 :o {:p {:q :r, :s :t}}}

; With a narrower width (30 instead of 40), one of them take more than one line

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}} 30 {:map {:nl-separator? false}})

{:a :b,
 :c {:e :f,
     :g :h,
     :i :j,
     :k :l},
 :m :n,
 :o {:p {:q :r, :s :t}}}

; and even now :nl-separator? will not have any effect because none of the
; right hand pairs are formatted with a flow -- that is, none of the right
; hand parts of the pairs start all of the way to the left.  They are still
; formatted as a hang

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}} 30 {:map {:nl-separator? true}})

{:a :b,
 :c {:e :f,
     :g :h,
     :i :j,
     :k :l},
 :m :n,
 :o {:p {:q :r, :s :t}}}

; If you turn off the hang, then now if a pair doesn't fit on one line,
; you get a flow:

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}}
         30
         {:map {:nl-separator? true :hang? false}})

; Most people use the :nl-separator? kind of formatting when they don't
; want the right hand side of a pair indented.  So if you turn off :hang?
; then you probably want to remove the indent as well.

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}}
         30
         {:map {:nl-separator? true :hang? false :indent 0}})

{:a :b,
 :c
 {:e :f, :g :h, :i :j, :k :l},

 :m :n,
 :o {:p {:q :r, :s :t}}}

:comma? true

Put a comma after the value in a key-value pair, if it is not the last pair in a map.

:force-nl? false

Force a new-line between each key and value pair in a map.

(czprint {:abc :def :ghi :ijk})

{:abc :def, :ghi :ijk}

(czprint {:abc :def :ghi :ijk} {:map {:force-nl? true}})

{:abc :def,
 :ghi :ijk}

:sort? true

Sort the key-value pairs in a map prior to output. Alternatively, simply output them in the order in which they come out of the map.

:sort-in-code? false

If the map appears inside of a list that seems to be code, should it be sorted.

:key-order nil

Accepts a vector which contains keys which should sort before all other keys. Typically these keys would be keywords, strings, or integers. The value of this capability is to bring one or more key-value pairs to the top of a map when it is output, in order to aid in visually distinguishing one map from the other. This can be a significant help in debugging, when looking a lot of maps at the repl. Note that :key-order only affects the key order when keys are sorted.

Here is a vector of maps formatted with just sorting.

[{:code "58601",
  :connection "2795",
  :detail {:alternate "64:c1:2f:34",
           :ident "64:c1:2f:34",
           :interface "3.13.168.35",
           :string "datacenter"},
  :direction :received,
  :reference 14873,
  :time 1425704001,
  :type "get"}
 {:code "0xe4e9",
  :connection "X13404",
  :detail
    {:code "0xe4e9", :ident "64:c1:2f:34", :ip4 "3.13.168.151", :time "30m"},
  :direction :transmitted,
  :reference 14133,
  :time 1425704001,
  :type "post"}
 {:code "58601",
  :connection "X13404",
  :direction :transmitted,
  :reference 14227,
  :time 1425704001,
  :type "post"}
 {:code "0x1344a676",
  :connection "2796",
  :detail {:code "0x1344a676", :ident "50:56:a5:1d:61", :ip4 "3.13.171.81"},
  :direction :received,
  :reference 14133,
  :time 1425704003,
  :type "error"}
 {:code "323266166",
  :connection "2796",
  :detail {:alternate "01:50:56:a5:1d:61",
           :ident "50:56:a5:1d:61",
           :interface "3.13.168.35",
           :string "datacenter"},
  :direction :transmitted,
  :reference 14873,
  :time 1425704003,
  :type "error"}]

Lots of information -- at least it is sorted. But the type and the direction are the important parts if you were scanning this at the repl, and they are buried pretty deep.

If you were looking for received errors, and needed to see them in context, you might prefer the following presentation...

Using the following options map:

{:map {:key-order [:type :direction]}})

yields:

[{:type "get",
  :direction :received,
  :code "58601",
  :connection "2795",
  :detail {:alternate "64:c1:2f:34",
           :ident "64:c1:2f:34",
           :interface "3.13.168.35",
           :string "datacenter"},
  :reference 14873,
  :time 1425704001}
 {:type "post",
  :direction :transmitted,
  :code "0xe4e9",
  :connection "X13404",
  :detail
    {:code "0xe4e9", :ident "64:c1:2f:34", :ip4 "3.13.168.151", :time "30m"},
  :reference 14133,
  :time 1425704001}
 {:type "post",
  :direction :transmitted,
  :code "58601",
  :connection "X13404",
  :reference 14227,
  :time 1425704001}
 {:type "error",
  :direction :received,
  :code "0x1344a676",
  :connection "2796",
  :detail {:code "0x1344a676", :ident "50:56:a5:1d:61", :ip4 "3.13.171.81"},
  :reference 14133,
  :time 1425704003}
 {:type "error",
  :direction :transmitted,
  :code "323266166",
  :connection "2796",
  :detail {:alternate "01:50:56:a5:1d:61",
           :ident "50:56:a5:1d:61",
           :interface "3.13.168.35",
           :string "datacenter"},
  :reference 14873,
  :time 1425704003}]

When working with hundreds of maps, even the tiny improvement made by ordering a few keys in a better way can reduce the cognitive load, particularly when debugging.

:key-ignore nil

:key-ignore-silent nil

You can also ignore keys (or key sequences) in maps when formatting them. There are two basic approaches. :key-ignore will replace the value of the key(s) to be ignored with :zprint-ignored, where :key-ignore-silent will simply remove them from the formatted output.

NOTE: This only affects the formatting of s-expressions, and has no effect on the output when using the {:parse-string? true} capability (as is done when formatting code). Nobody wants to lose map keys when formatting code.

You might use this to remove sensitive information from output, or to remove elements that have more data than you wish to display.

You can also supply key-sequences, in addition to single keys, to either configuration parameter.

An example of the basic approach. First the unmodified data:

zprint.core=> (czprint sort-demo)

[{:code "58601",
  :connection "2795",
  :detail {:alternate "64:c1:2f:34",
           :ident "64:c1:2f:34",
           :interface "3.13.168.35",
           :string "datacenter"},
  :direction :received,
  :reference 14873,
  :time 1425704001,
  :type "get"}
 {:code "0xe4e9",
  :connection "X13404",
  :detail
    {:code "0xe4e9", :ident "64:c1:2f:34", :ip4 "3.13.168.151", :time "30m"},
  :direction :transmitted,
  :reference 14133,
  :time 1425704001,
  :type "post"}
 {:code "58601",
  :connection "X13404",
  :direction :transmitted,
  :reference 14227,
  :time 1425704001,
  :type "post"}
 {:code "0x1344a676",
  :connection "2796",
  :detail {:code "0x1344a676", :ident "50:56:a5:1d:61", :ip4 "3.13.171.81"},
  :direction :received,
  :reference 14133,
  :time 1425704003,
  :type "error"}
 {:code "323266166",
  :connection "2796",
  :detail {:alternate "01:50:56:a5:1d:61",
           :ident "50:56:a5:1d:61",
           :interface "3.13.168.35",
           :string "datacenter"},
  :direction :transmitted,
  :reference 14873,
  :time 1425704003,
  :type "error"}]

Here is the data with the :detail key ignored:

zprint.core=> (czprint sort-demo {:map {:key-ignore [:detail]}})

[{:code "58601",
  :connection "2795",
  :detail :zprint-ignored,
  :direction :received,
  :reference 14873,
  :time 1425704001,
  :type "get"}
 {:code "0xe4e9",
  :connection "X13404",
  :detail :zprint-ignored,
  :direction :transmitted,
  :reference 14133,
  :time 1425704001,
  :type "post"}
 {:code "58601",
  :connection "X13404",
  :direction :transmitted,
  :reference 14227,
  :time 1425704001,
  :type "post"}
 {:code "0x1344a676",
  :connection "2796",
  :detail :zprint-ignored,
  :direction :received,
  :reference 14133,
  :time 1425704003,
  :type "error"}
 {:code "323266166",
  :connection "2796",
  :detail :zprint-ignored,
  :direction :transmitted,
  :reference 14873,
  :time 1425704003,
  :type "error"}]

The same as above, with :key-ignore-silent instead of key-ignore:

zprint.core=> (czprint sort-demo {:map {:key-ignore-silent [:detail]}})

[{:code "58601",
  :connection "2795",
  :direction :received,
  :reference 14873,
  :time 1425704001,
  :type "get"}
 {:code "0xe4e9",
  :connection "X13404",
  :direction :transmitted,
  :reference 14133,
  :time 1425704001,
  :type "post"}
 {:code "58601",
  :connection "X13404",
  :direction :transmitted,
  :reference 14227,
  :time 1425704001,
  :type "post"}
 {:code "0x1344a676",
  :connection "2796",
  :direction :received,
  :reference 14133,
  :time 1425704003,
  :type "error"}
 {:code "323266166",
  :connection "2796",
  :direction :transmitted,
  :reference 14873,
  :time 1425704003,
  :type "error"}]

An example of the key-sequence approach. This will remove all of the elements with key :code inside of the maps with the key :detail, but not the elements with the key :code elsewhere. This example uses :key-ignore so you can see where it removed values.

zprint.core=> (czprint sort-demo {:map {:key-ignore [[:detail :code]]}})

[{:code "58601",
  :connection "2795",
  :detail {:alternate "64:c1:2f:34",
           :ident "64:c1:2f:34",
           :interface "3.13.168.35",
           :string "datacenter"},
  :direction :received,
  :reference 14873,
  :time 1425704001,
  :type "get"}
 {:code "0xe4e9",
  :connection "X13404",
  :detail {:code :zprint-ignored,
           :ident "64:c1:2f:34",
           :ip4 "3.13.168.151",
           :time "30m"},
  :direction :transmitted,
  :reference 14133,
  :time 1425704001,
  :type "post"}
 {:code "58601",
  :connection "X13404",
  :direction :transmitted,
  :reference 14227,
  :time 1425704001,
  :type "post"}
 {:code "0x1344a676",
  :connection "2796",
  :detail {:code :zprint-ignored, :ident "50:56:a5:1d:61", :ip4 "3.13.171.81"},
  :direction :received,
  :reference 14133,
  :time 1425704003,
  :type "error"}
 {:code "323266166",
  :connection "2796",
  :detail {:alternate "01:50:56:a5:1d:61",
           :ident "50:56:a5:1d:61",
           :interface "3.13.168.35",
           :string "datacenter"},
  :direction :transmitted,
  :reference 14873,
  :time 1425704003,
  :type "error"}]

:key-color nil

The value of :key-color is a map which relates keys that are 'constants' to a color in which to print that key. A constant is a keyword, string, or number. This way you can have some keys formatted in a color that is different from the color in which they would normally be formatted based on their type. It can go well with :key-order [:key1 :key2 ...] which is another way to distinguish a special key. You can place some keys at the front of the map and you can also adjust their colors to meet your needs.

:key-value-color nil

The value of :key-value-color is a map which relates keys (that don't have to be constants) to a color-map which is merged into the current color-map, and is used when formatting the value of that key. This way you can have the values of some keys formatted in a color that is different from the color in which they would normally be formatted based on their type.

:key-depth-color nil

Note that this is an EXPERIMENTAL feature. The value of :key-depth-color is a vector of colors, and these colors will be used to color the keys which are at a corresponding depth in the map. Thus, the first color in the vector will be the color for the outermost keys in the map, the second color in vector will be the color for the keys. You can place a nil in the vector, and for that depth the normal colors (based on the type of the key) will be used. If you also have defined a :key-color map, any colors speciied in that map for specific keys will override the color that they would be given by the :key-depth-color vector.

:lift-ns? false

When all of the keys in a map are namespaced, and they all have the same key, "lift" that namespace out of the keys and make it a namespaced map.

For example:

(zprint {:x/a :b :x/c :d} {:map {:lift-ns? true}})
#:x{:a :b, :c :d}

(zprint {::a :b ::c :d} {:map {:lift-ns? true}})
#:zprint.core{:a :b, :c :d}

This generally works for strings that are parsed as well, with one significant exception. If you have an implicitly namespaced keyword, like ::a, then this cannot be "lifted" when encountered in a string because there is no way to reliably infer the implicit namespace. Thus, the entire map will not be lifted if it contains a single ::a type key in it.

:lift-ns-in-code? false

Controls whether to actually lift the namespace if the map is in code.

:unlift-ns? false

This only applies when dealing with formatting code or strings. When the map was specified with the namespace "lifted", then distribute the namespace across the keys.

For example:

(zprint ":x{a :b :c :d}" {:parse-string? true :map {:lift-ns? false :unlift-ns? true}})
{:x/a :b, :x/c :d}

Note that :unlift-ns? true only works if :lifts-ns? false is present, since otherwise zprint won't know which keyword to honor, and :lift-ns? was there first.

:indent-only? false

Do not add or remove newlines. Just indent the lines that are there and regularize whitespace. This forces the indent for maps to be zero, which means that every key is indented identically since there is no assumption that key-value pairs are placed on lines in any particular way. Note that commas are not added, but existing commas will be included if :comma? flag is true.

:respect-bl? false

This will cause zprint to respect incoming blank lines. If this is enabled, zprint will add newlines and remove newlines as necessary, but will not remove any existing blank lines when formatting maps. Existing formatting configuration will be followed, of course with the constraint that existing blank lines will be included wherever they appear. Note that blank lines at the "top level" (i.e., those outside of (defn ...) and (def ...) expressions) are always respected and never changed. :respect-bl? controls what happens to blank lines in maps within defn and def expressions.

If you wish to use zprint to enforce a particular format, using :respect-bl? might be a bad idea -- since it depends on the incoming source with regard to blank lines.

:respect-nl? false

This will cause zprint to respect incoming newlines. If this is enabled, zprint will add newlines, but will not remove any existing newlines when formatting lists. Existing formatting configuration will be followed, of course with the constraint that existing newlines will be included wherever they appear.


:object

When elements are formatted with :object? true, then the output if formatted using the information specified in the :object information.

Note that :respect-nl?, :respect-bl?, and :indent-only? are not supported independently for :object -- when objects are processed, the values for :respect-nl?, :respect-bl? and :indent-only? for :vector are used.

:indent 1
:wrap-after-multi? true

Same as :vector.

:wrap-coll? true

Same as :vector.


:output

This controls the overall output that is produced.

:focus

Determines whether to highlight a part of the structure, and which part to highlight. Only one of :zloc? or :path can have a value.

Note: :focus is not supported with `{:style :indent-only}.

Contains a map with the following possible keys.

:zloc? false

If true, indicates that the first argument is a zipper, and the zipper currently "points at" the expression at which to focus. zprint will print the entire zipper, and highlight the expression at which the zipper is currently pointing.

:path nil

The path is a vector of integers, which indicates where the focus should be placed. Each number in the vector indicates moving into a structure, and the value of the number indicates the element within that structure on which the focus rests. Presently, the error handling for bad paths is some sort of exception.

If you have a structure like this: [:a [:b [:c :d] :e :f]] then the path [1 1 0] would highlight the :c. The path [1 1] would highlight the [:c :d]. The path [0] would highlight the :a.


:pair

The :pair key controls the printing of the arguments of a function which has -pair in its function type (e.g. :arg1-pair, :pair-fn, :arg2-pair). :pair

:indent 2
:hang? true
:hang-expand 2
:hang-diff 1
:justify? false

:force-nl? false

If you wish to force a newline between all things that are paired (which is more than just cond), you can use :force-nl?. For example:

(czprint "(cond abcd b cdef d)" {:parse-string? true :pair {:force-nl? false}})

(cond abcd b cdef d)

(czprint "(cond abcd b cdef d)" {:parse-string? true :pair {:force-nl? true}})

(cond abcd b
      cdef d)

You could achieve a similar result by placing the function style :pair-fn into the set of :fn-gt2-force-nl, thus:

(czprint "(cond abcd b)" {:parse-string? true :fn-gt2-force-nl #{:pair-fn}})

(cond abcd b)

(czprint "(cond abcd b cdef d)" {:parse-string? true :fn-gt2-force-nl #{:pair-fn}})

(cond abcd b
      cdef d)

:flow? false

Format the right-hand part of every pair to onto a different line from the left-hand part of every pair.

:nl-separator? false

Insert an entirely blank line between every pair where the right hand part of the pair is formatted as a flow.

Some examples of how :flow? an :nl-separator? can interact:

(czprint "(cond abcd b cdef d)" {:parse-string? true})

(cond abcd b cdef d)

(czprint "(cond abcd b cdef d)" {:parse-string? true :pair {:flow? true}})

; :flow? causes the right-hand part of each pair to move to another line

(cond abcd
        b
      cdef
        d)

(czprint "(cond abcd b cdef d)" {:parse-string? true :pair {:flow? true :indent 0}})

; Remove the indent

(cond abcd
      b
      cdef
      d)

(czprint "(cond abcd b cdef d)"
         {:parse-string? true :pair {:flow? true :indent 0 :nl-separator? true}})

; :nl-separator? places an entirely blank line between any pair that formats as a flow

(cond abcd
      b

      cdef
      d)

(czprint "(cond abcd b cdef d)"
         {:parse-string? true
          :pair {:flow? true
                 :indent 0
                 :nl-separator? true}
          :pair-fn {:hang? false}})

; Just FYI, this is how to cause cond to never hang its pairs

(cond
  abcd
  b

  cdef
  d)


:pair-fn

The :pair-fn key controls the printing of the arguments of a function which has :pair-fn as its function type (e.g. cond).

:hang? true
:hang-expand 2
:hang-diff 1

This function type exists largely to allow you to control how the pairs of a cond are formatted with respect to the function name. For example:

(czprint "(cond abcd efgh ijkl mnop)" 20 {:parse-string? true :pair-fn {:hang? true}})

(cond abcd efgh
      ijkl mnop)

(czprint "(cond abcd efgh ijkl mnop)" 20 {:parse-string? true :pair-fn {:hang? false}})

(cond
  abcd efgh
  ijkl mnop)

The formatting of the pairs themselves is controlled by :pair.


:reader-cond

Reader conditionals are controlled by the :reader-cond key. Many of the keys which are supported for :map are supported for :reader-cond (except :comma?), albeit with different defaults. By default, :sort? is nil, so the elements are not reordered. You could enable :sort? and specify a :key-order vector to order the elements of a reader conditional.

Note that :respect-nl?, :respect-bl?, and :indent-only? are not supported independently for :reader-cond -- when reader conditionals are processed, the values for :respect-nl?, :respect-bl? and :indent-only? for :map are used.

:indent 2
:hang? true
:hang-expand 1000.0
:hang-diff 1
:force-nl? true
:sort? false
:sort-in-code? false
:key-order nil

:record

Records are printed with the record-type and value of the record shown with map syntax, or by calling their toString() method.

:to-string? false

This will output a record by calling its toString() java method, which can be useful for some records. If the record contains a lot of information that you didn't want to print, for instance. If :to-string? is true, it overrides the other :record configuration options.

:hang? true

Should a hang be attempted? See example below.

:record-type? true

Should the record type be output?

An example of :hang?, :record-type?, and :to-string?

(require '[zprint.core :as zp])

(defrecord myrecord [left right])

(def x (new myrecord ["a" "lot" "of" "stuff" "so" "that" "it" "doesn't" "fit" "all" "on" "one" "line"] [:more :stuff :but :not :quite :as :much]))

(zp/zprint x 75)

#zprint.core.myrecord {:left ["a" "lot" "of" "stuff" "so" "that" "it"
                              "doesn't" "fit" "all" "on" "one" "line"],
                      :right [:more :stuff :but :not :quite :as :much]}

(zp/zprint x 75 {:record {:hang? nil}})

#zprint.core.myrecord
 {:left ["a" "lot" "of" "stuff" "so" "that" "it" "doesn't" "fit" "all" "on"
         "one" "line"],
  :right [:more :stuff :but :not :quite :as :much]}

(zp/zprint x 75 {:record {:record-type? nil}})

{:left ["a" "lot" "of" "stuff" "so" "that" "it" "doesn't" "fit" "all" "on"
        "one" "line"],
 :right [:more :stuff :but :not :quite :as :much]}

(zprint x {:record {:to-string? true}})

"zprint.core.myrecord@682a5f6b"

:script

This controls what additional options are added when a string processed by zprint-file-str is determined to be a "script".

Note that zprint-file-str is the basic driving function used by all of the pre-compiled binaries to process entire files.

A script is defined as any string (or file) whose first line begins with "#!". When scripts are processed, the first line is removed, zprint is run on the remaining lines, and the first line is restored prior to returning the result.

:more-options nil

This is a options map which will be applied on top of any existing options currently in force when a string processed by zprint-file-str is determined to be a script. It is the last set of options applied, after any that might be on a command line to one of the pre-compiled binaries. It does not replace any existing options, it is an options map applied on top of any existing options.


:set

:set supports the same keys as does vector and a few more.

:indent 1
:wrap? true
:wrap-coll? true
:wrap-after-multi? true

:indent-only? false

Do not add or remove newlines. Just indent the lines that are there and regularize whitespace.

:respect-bl? false

This will cause zprint to respect incoming blank lines. If this is enabled, zprint will add newlines and remove newlines as necessary, but will not remove any existing blank lines when formatting sets. Existing formatting configuration will be followed, of course with the constraint that existing blank lines will be included wherever they appear. Note that blank lines at the "top level" (i.e., those outside of (defn ...) and (def ...) expressions) are always respected and never changed. :respect-bl? controls what happens to blank lines in sets within defn and def expressions.

If you wish to use zprint to enforce a particular format, using :respect-bl? might be a bad idea -- since it depends on the incoming source with regard to blank lines.

If you use blank lines a lot within sets embedded in function definitions in order to make them more readable, this can be a good capability to enable globally.

:respect-nl? false

This will cause zprint to respect incoming newlines. If this is enabled, zprint will add newlines, but will not remove any existing newlines when formatting sets. Existing formatting configuration will be followed, of course with the constraint that existing newlines will be included wherever they appear.

:sort? true

Sort the elements in a set prior to output. Alternatively, simply output them in the order in which they come out of the set.

:sort-in-code? false

If the set appears inside of a list that seems to be code, should it be sorted.


:spec

:spec controls how specs are integrated into the (zprint-fn ...) and (czprint-fn ...) output. This only operates if the version of Clojure supports specs, and the function being output has a spec. If that is true, and if:

:docstring? true

is also true, then zprint will format the spec and append it to the docstring. The spec formatting is effectively identical to hand-formatted specs.


:style and :style-map

You specify a style by adding :style <style> at the top level of the options map. You can also set more than one style by enclosing the styles in a vector, for example:

(set-options! {:style [:binding-nl :extend-nl]})

When multiple styles are specified, they are applied in the order given.

Note that styles are applied before the rest of the elements of a options map, so that you can override elements of the style that you wish to change by specifying an explicit element in the options map.

Available Styles:

:all-hang

Some implementations of zprint into runnable programs turn off all hangs by default, since performance is rather better with them off. If you are using one of these programs, you can turn all of the hangs back on (which is their normal default) by using this style.

:backtranslate

The built in pretty printer for Clojure, clojure.pprint, will backtranslate (quote a) to 'a, (var a) to #'a, (clojure.core/deref a) to @a and (clojure.core/unquote a) to ~a. clojure.pprint only does this when printing data structures, (which may be code), since that is all it operates on. Zprint has been enhanced to optionally perform this backtranslation when formatting structures. Use {:style :backtranslate} to get this behavior, and see the definition of that style to see how it was implemented. Even if you use {:style :backtranslate}, there is no change to the way that source is formatted since zprint has been enhanced to distinguish between source and structures in some situations. You could configure zprint to do this backtranslation for source if you wished to, though there is not a pre-defined style to enable that operation. See the implementation for {:style :backtranslate} for a hint for how to do this (or open an issue and ask).

:community

This attempts to recreate the community standards defined in the community style guide. It is an evolving effort -- if you see something that matters to you that differs from the community style guide when using {:style :community}, please create an issue explaining the difference.

For more discussion, see Community.

:extend-nl

This sets up a different way of formatting extend styles, with a new-line between each group. For example

(czprint-fn ->Typetest1)

; Default output

(deftype Typetest1
  [cnt _meta]
  clojure.lang.IHashEq
    (hasheq [this] (list this) (list this this) (list this this this this))
  clojure.lang.Counted (count [_] cnt)
  clojure.lang.IMeta (meta [_] _meta))

(czprint-fn ->Typetest1 {:style :extend-nl})

; Alternative output with {:style :extend-nl}

(deftype Typetest1
  [cnt _meta]
  clojure.lang.IHashEq
  (hasheq [this] (list this) (list this this) (list this this this this))

  clojure.lang.Counted
  (count [_] cnt)

  clojure.lang.IMeta
  (meta [_] _meta))

:how-to-ns

Experimental: Still working out some of the details, so the specifics may change.

This will format ns declarations regarding newlines and indentation as in Stewart Sierra's "How to ns". Specifically, it will indent lists by 1 instead of 2, and not hang lists except for :import. If you follow the instructions in the "How to ns" blog post, the new lines and indentation will be correct. zprint will not reorganize the ns declaration or change lists to vectors or otherwise change the order or syntax of what you have entered -- that's still your responsibility.

:hiccup

Recognizes when the information in a vector is in hiccup format, and format just those vectors differently in order make the hiccup information more understandable.


; Here is some hiccup without using this style:

(czprint-fn header)

(defn header
  [{:keys [title icon description]}]
  [:header.container
   [:div.cols {:class "gap top", :on-click (fn [] (println "tsers"))}
    [:div {:class "shrink"} icon]
    [:div.rows {:class "gap-xs"}
     [:dov.cols {:class "middle between"} [:div title]
      [:div {:class "shrink"} [:button "x"]]] [:div description]]]])

; Here is the same hiccup using the :hiccup style:

(czprint-fn header {:style :hiccup})

(defn header
  [{:keys [title icon description]}]
  [:header.container
   [:div.cols {:class "gap top", :on-click (fn [] (println "tsers"))}
    [:div {:class "shrink"} icon]
    [:div.rows {:class "gap-xs"}
     [:dov.cols {:class "middle between"}
      [:div title]
      [:div {:class "shrink"} [:button "x"]]]
     [:div description]]]])

; Not a huge difference, but note the use of :arg1-force-nl to clean up
; the display of the elements beyond the map in each vector.  Were this more
; complex hiccup, the difference would be more valuable.

:indent-only

This is very different from classic zprint!

When :indent-only is configured, zprint will not add or remove newlines from the incoming source, but will otherwise regularize whitespace. Lists will be formatted as a hang if the incoming list was formatted as a hang (controlled by {:list {:indent-only-style :input-hang}}. The indent for maps goes to 0. Most of the other configuration parameters will be ignored when :indent-only is enabled.

See :respect-nl below for a comparison of :indent-only, :respect-nl, and classic zprint.

For more examples, see Indent Only.

:justified

This sets :justify? true in each of :binding, :pair, and :map. It is useful to see what you think about justfied output. Here is more information on justified output.

:keyword-respect-nl

This style is used to preserve most of the existing formatting for vectors with keywords as the first element. The typical example was hiccup or rum data inside of a function or in a source file. However, subsequent enhancements allowed the implementation of a better style for hiccup data -- :hiccup.

If you specify this style, every vector with a keyword as the first element will preserve the newlines present in the input to zprint. zprint will still handle the indenting, and none of the existing newlines will cause zprint to violate its basic formatting rules (e.g., lines will still fit in the width, etc.). But the existing hand-formatting will typically be preserved if it makes any sense at all.

If you are using hiccup format, you should use the :hiccup style.

This is usually only useful formatting source. Here is an example using a rum macro (taken from GitHub rum/examples/rum/examples/inputs.cljc):

; The original way it was defined:
(print re1)

(rum/defc inputs []
  (let [*ref (atom nil)]
    [:dl
      [:dt "Input"]  [:dd (reactive-input *ref)]
      [:dt "Checks"] [:dd (checkboxes *ref)]
      [:dt "Radio"]  [:dd (radio *ref)]
      [:dt "Select"] [:dd (select *ref)]
      [:dt (value *ref)] [:dd (shuffle-button *ref)]]))nil

; An unlovely transformation:

(zprint re1 {:parse-string? true})

(rum/defc inputs
  []
  (let [*ref (atom nil)]
    [:dl [:dt "Input"] [:dd (reactive-input *ref)] [:dt "Checks"]
     [:dd (checkboxes *ref)] [:dt "Radio"] [:dd (radio *ref)] [:dt "Select"]
     [:dd (select *ref)] [:dt (value *ref)] [:dd (shuffle-button *ref)]]))

; A much better approach (close, though not identical, to the input):

(zprint re1 {:parse-string? true :style :keyword-respect-nl})

(rum/defc inputs
  []
  (let [*ref (atom nil)]
    [:dl
     [:dt "Input"] [:dd (reactive-input *ref)]
     [:dt "Checks"] [:dd (checkboxes *ref)]
     [:dt "Radio"] [:dd (radio *ref)]
     [:dt "Select"] [:dd (select *ref)]
     [:dt (value *ref)] [:dd (shuffle-button *ref)]]))

The implementation of this style is as follows:

      :keyword-respect-nl {:vector
                             {:option-fn-first
                                #(let [k? (keyword? %2)]
                                   (when (not= k? (:respect-nl? (:vector %1)))
                                     {:vector {:respect-nl? k?}}))}},

which serves as an example of how to implement an :option-fn-first function for vectors.

:map-nl, :pair-nl, :binding-nl

These are convenience styles which simply allow you to set {:indent 0 :nl-separator? true} for each of the associated format elements. They simply exist to save you some typing if these styles are favorites of yours. This will add a blank line between any pairs where the rightmost element of the pair (e.g., the value in a map key-value pair) formats as a flow. It will not add a blank line between every pair, just between pairs where the rightmost element doesn't format as a hang.

Some examples:

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}} 40 {:map {:nl-separator? false}})

{:a :b,
 :c {:e :f, :g :h, :i :j, :k :l},
 :m :n,
 :o {:p {:q :r, :s :t}}}

; No effect if all the pairs print on one line

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}} 40 {:map {:nl-separator? true}})
{:a :b,
 :c {:e :f, :g :h, :i :j, :k :l},
 :m :n,
 :o {:p {:q :r, :s :t}}}

; With a narrower width, one of them takes more than one line

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}} 30 {:map {:nl-separator? false}})

{:a :b,
 :c {:e :f,
     :g :h,
     :i :j,
     :k :l},
 :m :n,
 :o {:p {:q :r, :s :t}}}

; and even now :nl-separator? will not have any effect because none of the
; right hand pairs are formatted with a flow -- that is, none of the right
; hand parts of the pairs start all of the way to the left.  They are still
; formatted as a hang

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}} 30 {:map {:nl-separator? true}})

{:a :b,
 :c {:e :f,
     :g :h,
     :i :j,
     :k :l},
 :m :n,
 :o {:p {:q :r, :s :t}}}

; If you turn off the hang, then now if a pair doesn't fit on one line,
; you get a flow:

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}}
         30
         {:map {:nl-separator? true :hang? false}})

{:a :b,
 :c
   {:e :f,
    :g :h,
    :i :j,
    :k :l},

 :m :n,
 :o {:p {:q :r, :s :t}}}

; Most people use the :nl-separator? kind of formatting when they don't
; want the right hand side of a pair indented.  So if you turn off :hang?
; then you probably want to remove the indent as well.

(czprint {:a :b :c {:e :f :g :h :i :j :k :l} :m :n :o {:p {:q :r :s :t}}}
         30
         {:map {:nl-separator? true :hang? false :indent 0}})

{:a :b,
 :c
 {:e :f, :g :h, :i :j, :k :l},

 :m :n,
 :o {:p {:q :r, :s :t}}}

:no-hang, :all-hang

These two styles will turn on or off all of the :hang? booleans in :map, :list, :extend, :pair, :pair-fn, :reader-cond, and :record. The :hang? capability almost always produces clearly better results, but can take more time (particularly in Clojurescript, as it is single-threaded). :all-hang is the effective default, but :no-hang can be used to turn it all off if you wish. If :hang? is off for some reason, you can use :all-hang to turn it back on.

:fast-hang

EXPERIMENTAL

This style does two things: it tends to prefer hangs over flows, even when the line-count of the hang might be more than that of a flow, and it tends to speed up processing -- frequently doing the same work in 80% of the time, and sometimes doing the same work in 25% or even 10% of the time as classic zprint formatting. It does this by, in many cases, accepting hangs (if they work at all) without comparing how many lines they took to the corresponding flow for the same expression. This can drastically reduce the time required to format some code or structures, particularly those that are very deeply nested. One downside is that sometimes the resulting formatted code is longer than it might otherwise be when normal formatting is used. The other downside is that, rarely, a lot of code gets pushed to the right side of the page, which can look awkward.

This is implemented by some not yet documented tuning parameters, which have been set to try to give a formatting "look" which is similar to classic zprint formatting and still yield some level of performance optimization. The optimization is greater the more deeply nested the code or structure which is being formatting.

One 6300 line file in zprint itself formats in about 5.5s on a very old MacBook Air, and with :style :fast-hang, formats in about 4.5s. It doesn't contain any particularly challenging functions, but there is still an improvement. There are about 78 more lines in the :style :fast-hang output. Nothing looks terrible, but there are a couple of places where the different "look" isn't as pleasing. There are probably more places where the differnt "look" is actually slightly better.

If you have some code or structures that take too long to format, try :style :fast-hang. If that doesn't work, you can always try :style :indent-only, which will certainly take a much shorter time.

:respect-bl

Respect blank lines.

Whenever a blank line appears in the source, it will be "respected", and will appear in the output. However, all other formatting will be applied around any blank lines that may appear.

Note that blank lines at the top level (i.e., outside of function definitions and (def ...) expressions) are always respected and never changed. This style extends that behavior into the actual function definitions.

Respect new lines (i.e., :respect-nl) sounds like a similar style, but the actual results are quite different. With :respect-nl, no lines are ever joined together. Lines that are long may be split, but that is the extent of changes allowed concerning where things appear on lines. The freedom for zprint to actually format the code is quite limited with :respect-nl.

Alternatively, with :respect-bl, there is plenty of freedom for zprint to format the code in a maximally readable manner, since only blank lines interrupt zprint's ability to flow code back and forth between lines as necessary for good formatting.

While :respect-nl? was something that you might want to configure for formatting a single function, :respect-bl? is something that is perfectly reasonable to configure for processing whole files, or perhaps all of the time in your ~/.zprintrc file. If you do that, everything will operate as normal with zprint, but if you put blank lines inside a function definition, those blank lines will continue to appear in the output. And all of the information will all be formatted correctly around those blank lines.

Note that zprint can be used in a number of ways. If you are using zprint to enforce a particular format on code (say in a group setting), then :respect-bl is probably not a great choice, since different people will want to put blank lines in different places for readability.

There are several ways to get zprint to place blank lines in particular places when formatting code, and these approaches are compatible with using zprint to enforce a particular code approach. Here are some of them:

An example from clojure.core:

; Classic zprint 

(czprint-fn ->ArrayChunk)

(deftype ArrayChunk [^clojure.core.ArrayManager am arr ^int off ^int end]
  clojure.lang.Indexed
    (nth [_ i] (.aget am arr (+ off i)))
    (count [_] (- end off))
  clojure.lang.IChunk
    (dropFirst [_]
      (if (= off end)
        (throw (IllegalStateException. "dropFirst of empty chunk"))
        (new ArrayChunk am arr (inc off) end)))
    (reduce [_ f init]
      (loop [ret init
             i off]
        (if (< i end)
          (let [ret (f ret (.aget am arr i))]
            (if (reduced? ret) ret (recur ret (inc i))))
          ret))))
;Classic zprint {:style :respect-bl}

(czprint-fn ->ArrayChunk {:style :respect-bl})

(deftype ArrayChunk [^clojure.core.ArrayManager am arr ^int off ^int end]
  
  clojure.lang.Indexed
    (nth [_ i] (.aget am arr (+ off i)))
    
    (count [_] (- end off))
  
  clojure.lang.IChunk
    (dropFirst [_]
      (if (= off end)
        (throw (IllegalStateException. "dropFirst of empty chunk"))
        (new ArrayChunk am arr (inc off) end)))
    
    (reduce [_ f init]
      (loop [ret init
             i off]
        (if (< i end)
          (let [ret (f ret (.aget am arr i))]
            (if (reduced? ret) ret (recur ret (inc i))))
          ret))))

For more examples, see Respect Blank Lines.

:respect-nl

This will cause zprint to respect incoming newlines. If this is enabled, zprint will add newlines, but will not remove any existing newlines from incoming source. Existing formatting configuration will be followed, of course with the constraint that existing newlines will be included wherever they appear. You can configure this style for just a particular function, if you have one that doesn't format well with classic zprint. See Altering the formatting inside of certain functions for details.

Some examples of classic zprint, :respect-nl, and :indent-only:


(def io2
"
(let
 [stuff
   (bother in
           addition
		    to more)
      foo (bar
          with
	  baz)]
  (if output
         stuff foo))")

; Classic zprint
;
; Attemps to format the source clearly, while also trying to get the maximum
; amount of code possible into a vertical space.  Ignores incoming whitespace
; and formats entirely based on zprint configuration.  This will regularize
; the entire format, effectively enforcing a particular format regardless
; of the input.

(zprint io2 {:parse-string? true})
(let [stuff (bother in addition to more)
      foo (bar with baz)]
  (if output stuff foo))

; :style :respect-nl
;
; Does the same thing as classic zprint, but if there is a newline, it
; will not remove it.  Based on incoming newlines, it will try to make
; the output look as much like classic zprint as possible.
;
; Note that you can configure this style for just a particular function,
; if there is a function that doesn't format well with classic zprint.
;

(czprint io2 {:parse-string? true :style :respect-nl})
(let
  [stuff (bother in
                 addition
                 to
                 more)
   foo (bar
         with
         baz)]
  (if output
    stuff
    foo))

; Notice that `:respect-nl` still knows the kinds of functions -- so
; the two clauses of the `if` won't ever be on the same line unless the
; entire `if` is on the same line. Since this `if` had a newline, that
; forced both clauses to be on separate lines.

; :style :indent-only
;
; This style doesn't add or remove newlines, and doesn't know anything
; about which functions are which.  If you put to things on the same
; line, they stay on the same line (see `to more)` and `stuff foo)`
; below).  If you hang something (see `in addition to more)`), it will
; keep the hang for you.  Compare this with the input `io2` above.
; If `addition` were one character either left or right in the input,
; `:indent-only` would not have aligned it with `in` on output, but
; would have indented it from bother (i.e., made it a flow).

(zprint io2 {:parse-string? true :style :indent-only})
(let
  [stuff
   (bother in
           addition
           to more)
   foo (bar
         with
         baz)]
  (if output
    stuff foo))

:sort-dependencies

EXPERIMENTAL

Sort the dependencies in a leiningen project.clj file in alphabetical order. Technically, it will sort the vectors in the value of any key named :dependencies inside any function (macro) named defproject. Has no effect when formatting a structure (as opposed to parsing a string and formatting code).

Defining your own styles

You can define your own styles, by adding elements to the :style-map. You can do this the same way you make other configuration changes, but probably you want to define a style in the .zprintrc file, and then use it elsewhere.

You can see the existing styles in the :style-map, and you would just add your own along the same lines. The map associated with a style must validate successfully just as if you used it as an options map in an individual call to zprint.

You might wish to define several styles with different color-maps, perhaps, allowing you to alter the colors more easily.

You cannot define a style and apply it in the same configuration pass, as styles are applied before the rest of the configuration in a options map.


:tab

zprint will expand tabs by default when parsing a string, largely in order to properly size comments. You can disable tab expansion and you can set the tab size.

:expand? true

Expand tabs.

:size 8

An integer for the tab size for tab expansion.


:vector

:indent 1

:indent-only? false

Do not add or remove newlines. Just indent the lines that are there and regularize other whitespace.

:fn-format nil

There are a rich set of formatting options available to lists through the :fn-map, which relates function names to formatting styles. Typically vectors are formatted simply as individual elements wrapped to the end of the line. However, the formatting available to lists is also available to vectors through the use of :fn-format, which when set will cause a vector to be formatted in the same way as a list whose first element is a function that maps to the same formatting style as the value of :fn-format. Thus, the various function formatting styles such as :arg1 or :arg2 are available to vectors as well as lists by setting the :vector key :fn-format to :arg1 or some other function formatting style.

While you can configure this in the options map for every vector, typically this configuration value is set by using option-fn or option-fn-first, and it is based on the information in the vector itself. When this key has a non-nil value, the vector is formatting like a list whose function (i.e., first element) is associated with a particular formatting style. When :fn-format is non-nil, the configuration elements in :vector-fn are used instead of the configuration elements in :vector or :list when formatting that vector (but only that vector, not other vectors nested inside of it).

Note that the :fn-format processing is done before testing for :indent-only? (as is the :option-fn and :option-fn-first processing as well), so if the result of the :option-fn or :option-fn-first processing sets :fn-format, then the value of :indent-only? in :vector-fn will control whether or not :indent-only? is used, not the value of :indent-only? in :vector. This is worthy of mention in any case, but particularly because :style :indent-only does not set :indent-only? for :vector-fn!

As an example, the :hiccup style is implemented as follows:

      :hiccup {:vector
                 {:option-fn
                    (fn [opts n exprs]
                      (let [hiccup? (and (>= n 2)
                                         (or (keyword? (first exprs))
                                             (symbol? (first exprs)))
                                         (map? (second exprs)))]
                        (cond (and hiccup? (not (:fn-format (:vector opts))))
                                {:vector {:fn-format :arg1-force-nl}}
                              (and (not hiccup?) (:fn-format (:vector opts)))
                                {:vector {:fn-format nil}}
                              :else nil))),
                  :wrap? false}
}

; Here is some hiccup without using this style:

(czprint-fn header)

(defn header
  [{:keys [title icon description]}]
  [:header.container
   [:div.cols {:class "gap top", :on-click (fn [] (println "tsers"))}
    [:div {:class "shrink"} icon]
    [:div.rows {:class "gap-xs"}
     [:dov.cols {:class "middle between"} [:div title]
      [:div {:class "shrink"} [:button "x"]]] [:div description]]]])

; Here is the same hiccup using the :hiccup style:

(czprint-fn header {:style :hiccup})

(defn header
  [{:keys [title icon description]}]
  [:header.container
   [:div.cols {:class "gap top", :on-click (fn [] (println "tsers"))}
    [:div {:class "shrink"} icon]
    [:div.rows {:class "gap-xs"}
     [:dov.cols {:class "middle between"}
      [:div title]
      [:div {:class "shrink"} [:button "x"]]]
     [:div description]]]])

; Not a huge difference, but note the use of :arg1-force-nl to clean up
; the display of the elements beyond the map in each vector.  Were this more
; complex hiccup, the difference would be more valuable.

This also points out a difference between enforcing and allowing a formatting style. One could use :respect-nl? to let whatever formatting was in the file inform the output for zprint, which is fine if you are using zprint to clean up the formatting of a file. But if you are using zprint to enforce a particular formatting approach, then :fn-format and :option-fn can be very useful.

:option-fn-first nil

Vectors often come with data that needs different formatting than the default or than the code around them needs. To support this capability, you can configure a function as the :option-fn-first which will be given the first element of a vector and may return an options map to be used to format this vector (and all of the data inside of it). If this function returns nil, no change is made. The function must be a function of two arguments, the first being the current options map, and the second being the first element of the vector.

Note: Functions are always allowed when using set-options! to update an options map. In addition, they are allowed on options maps that appear in calls to the zprint API. They are also allowed in the ~/.zprintrc file and an options map on the command line, but only for library use or when using the uberjar (including the appcds accelerated uberjar). The prebuilt graalVM binaries (i.e., zprintm-x.y.z and zprintl-x.y.z) in the GitHub releae do not support use of functions in options maps in any external file or on the command line! The functions defined in the default options map are always allowed -- the above restrictions only apply to functions being read in from external files or from the command line.

If you need access to additional data in the vector to determine the proper formatting, see option-fn which gives that access.

The :style :keyword-respect-nl is implemented as an :option-fn-first as follows:

:keyword-respect-nl
  {:vector {:option-fn-first #(let [k? (keyword? %2)]
                               (when (not= k? (:respect-nl? (:vector %1)))
                                 {:vector {:respect-nl? k?}}))}},

This function will decide whether or not the vector should have :respect-nl? true used, and then change the options map to have that value only if necessary. The "only if necessary" is important, as every vector will call this function, almost certainly multiple times. Every time this function returns a non-nil value, that value will be validated using spec as a valid options map. Thus, only returning a value when necessary will mitigate the performance impact of using this capability.

If the options map returned by the function is not valid, an exception will be thrown. For example:

(zprint-str "[:g :f :d :e :e \n :t :r :a :b]"
            {:parse-string? true
             :vector {:option-fn-first
                       #(do %1 %2 {:vector {:sort? true}})}})

Exception Options resulting from :vector :option-fn-first called with :g
had these errors: In the key-sequence [:vector :sort?] the key :sort? was
not recognized as valid!  zprint.zprint/internal-validate (zprint.cljc:2381)

Note that :option-fn and :option-fn-first can both be used. :option-fn-first is executed first, and the results of that are given to :option-fn as the options map.

:option-fn nil

Vectors often come with data that needs different formatting than the default or than the code around them needs. To support this capability, you can configure a function as the :option-fn which will be given the all the elements of a vector and may return an options map to be used to format this vector (and all of the data inside of it). If this function returns nil, no change is made. The function must be a function of three arguments, the first being the current options map, and the second being the count of elements in the vector, and the third being a sequence of the non-comment and non-whitespace elements of the vector (not necessarily a vector).

Note: Functions are always allowed when using set-options! to update an options map. In addition, they are allowed on options maps that appear in calls to the zprint API. They are also allowed in the ~/.zprintrc file and an options map on the command line, but only for library use or when using the uberjar (including the appcds accelerated uberjar). The prebuilt graalVM binaries (i.e., zprintm-x.y.z and zprintl-x.y.z) in the GitHub release do not support use of functions in options maps in any external file or on the command line! The functions defined in the default options map are always allowed -- the above restrictions only apply to functions being read in from external files or from the command line.

This differs from option-fn-first in that option-fn gives you access to all of the elements of the vector in order to make the decision of how to format it.

See :fn-format for one example of how to use :option-fn. :option-fn is also used in the implemenation of the :hiccup style.

Note that :option-fn and :option-fn-first can both be used. :option-fn-first is executed first, and the results of that are given to :option-fn as the options map.

:respect-bl? false

This will cause zprint to respect incoming blank lines. If this is enabled, zprint will add newlines and remove newlines as necessary, but will not remove any existing blank lines when formatting vectors. Existing formatting configuration will be followed, of course with the constraint that existing blank lines will be included wherever they appear. Note that blank lines at the "top level" (i.e., those outside of (defn ...) and (def ...) expressions) are always respected and never changed. :respect-bl? controls what happens to blank lines in vectors within defn and def expressions.

If you wish to use zprint to enforce a particular format, using :respect-bl? might be a bad idea -- since it depends on the incoming source with regard to blank lines.

If you use blank lines a lot within vectors embedded in function definitions in order to make them more readable, this can be a good capability to enable globally.

:respect-nl? false

Normally, zprint ignores all newlines when formatting. However, sometimes people will hand-format vectors to make them more understandable. This shows up with hiccup and rum html data, for instance. If you enable :respect-nl?, then newlines in a vector will be triggers to move to the next line instead of filling out the current line because the vector is wrapping the contents. The newline will come with the proper indent.

(require '[zprint.core :as zp])

(zp/zprint "[:a :b :c :d \n :e :f :g :h]" {:parse-string? true})

[:a :b :c :d :e :f :g :h]

(zp/zprint "[:a :b :c :d \n :e :f :g :h]" {:parse-string? true :vector {:respect-nl? true}})

[:a :b :c :d
 :e :f :g :h]

(zp/zprint "[:a :b [:c :d \n :e] :f :g :h]" {:parse-string? true :vector {:respect-nl? true}})

[:a :b
 [:c :d
  :e] :f :g :h]

(zp/zprint "[:a :b [:c :d \n :e] :f :g :h]" {:parse-string? true :vector {:respect-nl? true :wrap-after-multi? false}})

[:a :b
 [:c :d
  :e]
 :f :g :h]

This is usually only useful formatting source. Here is an example using a rum macro (taken from GitHub rum/examples/rum/examples/inputs.cljc):


; This is how it looked originally:

(def re1
"(rum/defc inputs []
  (let [*ref (atom nil)]
    [:dl
      [:dt \"Input\"]  [:dd (reactive-input *ref)]
      [:dt \"Checks\"] [:dd (checkboxes *ref)]
      [:dt \"Radio\"]  [:dd (radio *ref)]
      [:dt \"Select\"] [:dd (select *ref)]
      [:dt (value *ref)] [:dd (shuffle-button *ref)]]))")

; A rather unlovely transformation...

(zprint re1 {:parse-string? true})
(rum/defc inputs
  []
  (let [*ref (atom nil)]
    [:dl [:dt "Input"] [:dd (reactive-input *ref)] [:dt "Checks"]
     [:dd (checkboxes *ref)] [:dt "Radio"] [:dd (radio *ref)] [:dt "Select"]
     [:dd (select *ref)] [:dt (value *ref)] [:dd (shuffle-button *ref)]]))

; With :respect-nl? true, this is a lot better

(zprint re1 {:parse-string? true :vector {:respect-nl? true}})
(rum/defc inputs
  []
  (let [*ref (atom nil)]
    [:dl
     [:dt "Input"] [:dd (reactive-input *ref)]
     [:dt "Checks"] [:dd (checkboxes *ref)]
     [:dt "Radio"] [:dd (radio *ref)]
     [:dt "Select"] [:dd (select *ref)]
     [:dt (value *ref)] [:dd (shuffle-button *ref)]]))

Enabling this globally may cause argument vectors to become strangely formatted. The simple answer is to use :style :keyword-respect-nl which will cause only vectors whose first element is a keyword to be formatted with :respect-nl? true. The more complex answer is to employ :option-fn-first, above (which is what :style :keyword-respect-nl does).

:wrap? true

Should it wrap its contents, or just list each on a separate line if they don't all fit on one line?

Vectors wrap their contents, as distinct from maps and lists, which use hang or flow. Wrapping means that they will fill out a line and then continue on the next line.

(require '[zprint.core :as zp])

(zp/zprint (vec (range 60)) 70)

[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
 49 50 51 52 53 54 55 56 57 58 59]

:wrap-coll? true

If there is a collection in the vector, should it wrap?

(require '[zprint.core :as zp])

(def v (vec (concat (range 10) '({:key :value "key" "value"}) (range 10 20))))

(zp/zprint v 60)

[0 1 2 3 4 5 6 7 8 9 {:key :value, "key" "value"} 10 11 12
 13 14 15 16 17 18 19]

(zp/zprint v 60 {:vector {:wrap-coll? nil}})

[0
 1
 2
 3
 4
 5
 6
 7
 8
 9
 {:key :value, "key" "value"}
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19]

:wrap-after-multi? true

Should a vector continue to wrap after a multi-line element is printed?

(require '[zprint.core :as zp])

(def u (vec (concat (range 10) '({:key :value "key" "value" :stuff
"the value of stuff is hard to quantify" :bother
"few people enjoy being bothered"}) (range 10 20))))

(zp/zprint u)

[0 1 2 3 4 5 6 7 8 9
 {:bother "few people enjoy being bothered",
  :key :value,
  :stuff "the value of stuff is hard to quantify",
  "key" "value"} 10 11 12 13 14 15 16 17 18 19]

(zp/zprint u {:vector {:wrap-after-multi? nil}})

[0 1 2 3 4 5 6 7 8 9
 {:bother "few people enjoy being bothered",
  :key :value,
  :stuff "the value of stuff is hard to quantify",
  "key" "value"}
 10 11 12 13 14 15 16 17 18 19

:vector-fn

This is the configuration used when :fn-format is enabled for a vector. By default it is largely the same as :list, but it exists so that you can change it for this specific case.

Note that the :fn-format processing is done before testing for :indent-only? (as is the :option-fn and :option-fn-first processing as well), so if the result of the :option-fn or :option-fn-first processing sets :fn-format, then the value of :indent-only? in :vector-fn will control whether or not :indent-only? is used, not the value of :indent-only? in :vector. This is worthy of mention in any case, but particularly because :style :indent-only does not set :indent-only? for :vector-fn!

:indent 2
:hang? true
:hang-avoid 0.5
:hang-expand 2.0
:hang-diff 1

:indent-arg nil

The amount to indent the arguments of a function whose arguments do not contain "body" forms. For vectors, this will depend on the value of :fn-format -- what kind of "function" it is using to format this vector.

If this is nil, then the value configured for :indent is used for the arguments of functions that are not "body" functions. You would configure this value only if you wanted "arg" type functions to have a different indent from "body" type functions. It is configured by :style :community.

:indent-only? false

Do not add or remove newlines. Just indent the lines that are there and regularize whitespace. The :fn-map which gives formatting and indentation information about different functions is ignored. The default indentation is flow, however based on the value of the :indent-only-style, a hang will be used in some situations. See :indent-only-style below for details.

:indent-only-style :input-hang

Controls how :indent-only indents a vector. If the value is :input-hang, then if the input is formatted as a hang, it will indent the vector as a hang. The input is considered to be formatted as a hang if the first two elements of the vector are on the same line, and the third element of the vector is on the second line aligned with the second element. The determination of alignment is not affected by the appearance of comments.

:hang-size 100

The maximum number of lines that are allowed in a hang. If the number of lines in the hang is greater than the :hang-size, it will not do the hang but instead will format this as a flow. Together with :hang-expand this will keep hangs from getting too long so that code (typically) doesn't get very distorted.

:constant-pair? true

Vectors being formatted like lists also support something called constant pairing. This capability looks at the end of the vector, and if the end of the vector appears to contain pairs of constants followed by anything, it will print them paired up. A constant in this context is a keyword, string, or number.

Note that the formatting of the pairs in a constant pair is controlled by the :pair configuration (just like the pairs in a cond, assoc, and any function style with "pair" in the name).

:constant-pair-min 4

An integer specifying the minimum number of required elements capable of being constant paired before constant pairing is used. Note that constant pairing works from the end of the vector back toward the front (not illustrated in these examples).

:respect-bl? false

This will cause zprint to respect incoming blank lines. If this is enabled, zprint will add newlines and remove newlines as necessary, but will not remove any existing blank lines when formatting vectors similarly to lists. Existing formatting configuration will be followed, of course with the constraint that existing blank lines will be included wherever they appear. Note that blank lines at the "top level" (i.e., those outside of (defn ...) and (def ...) expressions) are always respected and never changed. :respect-bl? controls what happens to blank lines in vectors within defn and def expressions.

If you wish to use zprint to enforce a particular format, using :respect-bl? might be a bad idea -- since it depends on the incoming source with regard to blank lines.

:respect-nl? false

This will cause zprint to respect incoming newlines. If this is enabled, zprint will add newlines, but will not remove any existing newlines when formatting lists. Existing formatting configuration will be followed, of course with the constraint that existing newlines will be included wherever they appear.



Debugging the configuration

When something is configurable in a lot of different ways, it can sometimes be challenging to determine just how it got configured. To aid in figuring out how zprint got configured in a particular way, you can use the special call:

(zprint nil :explain)

which will output the entire current configuration, and indicate exactly where each value came from. If there is no information about a configuration value, it is the default. For all values that have been changed from the default value, the :explain output will show the current value and indicate how this value was set. Calls to set-options! are numbered. For example, if the call is made:

(require '[zprint.core :as zp])

(zp/set-options! {:map {:indent 0}})

(zp/czprint nil :explain)

...
:map {:comma? true,
      :force-nl? nil,
      :hang-diff 1,
      :hang-expand 1000.0,
      :hang? true,
      :indent {:set-by "set-options! call 3", :value 0},
      :key-order nil,
      :sort-in-code? nil,
      :sort? true},
...

You can see from this fragment of the output that the indent for a map has been changed to 0 by a call to set-options!.

This will distinguish values set by the .zprintrc from values set by environment variables from values set by Java properties, so you can more easily determine where a particular value came from if you wish.

At any time, the (zprint nil :explain) or (czprint nil :explain) will show you the entire current configuration of zprint, allowing you to see all of the default values or any changes that have been made to them. Anything you can see with the :explain option can be changed by set-options! or by any of the other configuration approaches.

Testing this library

Clojure

Currently, the testing framework in use is expectations/cljc-test "2.0.0-SNAPSHOT", which is in development. This will make testing difficult until it is released.

With Leiningen installed, run lein test from the root directory of this repo. There are over Clojure 1000 tests.
It will take upwards of 30 to 45 seconds for them all to run.

As an additional test of the uberjar (all in the root directory):

  • Build the uberjar with lein clean, lein uberjar

  • Format the file src/zprint/zprint.cljc with lein zprint src/zprint/zprint.cljc. You have to be running a version of lein-zprint which uses the current zprint library built into the uberjar.

  • ./test_uberjar

If tests pass, the script will return with no output.

Another test:

  • You can also run .test_config 1.0.1 uberjar, (or use the current version), and it will run another series of more complex tests. Each test will say what it is testing. It does produce output, but the lines starting with "...." are simply a report of what is being tested. Any failures are clearly delineated as failures.

Clojurescript

Requirements:

  • You will have to have installed planck

  • You will need the library expectations/cljc-test "2.0.0-SNAPSHOT", which is yet to be released.

To run the Clojurescript tests, run:

clj -A:cljs-runner

There are over 900 tests. They take a really long time to execute.

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close