Assistance for formatting data into columns. Each column has a width, and data within the column may be left or right justified. Generally, columns are sized to the largest item in the column. When a value is provided in a column, it may be associated with an explicit width which is helpful when the value contains non-printing characters (such as those defined in the io.aviso.ansi namespace).


(format-columns & column-defs)

Converts a number of column definitions into a formatting function. Each column definition may be:

  • a string, to indicate a non-consuming column that outputs a fixed value. This is often just a space character or two, to separate columns.
  • a number, to indicate a consuming column that outputs a left justified value of the given width.
  • a vector containing a keyword and a number; the number is the width, the keyword is the justification.
  • :none, to indicate a consuming column with no explicit width
  • nil, which is treated like an empty string

With explicit justification, the keyword may be :left, :right, or :none.

Pads the column with spaces after the value. Truncates long values from the right, displaying initial character and discarding trailing characters.
Pads the column with spaces before the value. Truncates long values from the left.
Does not pad with spaces at all, and should only be used in the final column.

Generally speaking, truncation does not occur because columns are sized to fit their contents.

A column width is required for :left or :right. Column width is optional and ignored for :none.

Values are normally string, but any type is accepted and will be converted to a string. This code is aware of ANSI codes and ignores them to calculate the length of a value for formatting and indentation purposes.

There will likely be problems if a long string with ANSI codes is truncated, however.

The returned function accepts the column values and writes each column value, with appropriate padding, to out.


(let [formatter (format-columns [:right 20] ": " [:right 20] ": " :none)]
  (write-rows formatter [:last-name :first-name :age] customers))


(max-length coll)

Find the maximum length of the strings in the collection, based on their visual length (that is, omitting ANSI escape codes).


(max-value-length coll key)

A convenience for computing the maximum length of one string property from a collection of values.

collection of values
key that is passed one value and returns the property, typically a keyword when the values are maps


(write-rows column-formatter extractors row-data)(write-rows extended-columns-defs row-data)

A convenience for writing rows of columns using a prepared column formatter.

In the 3-arity version of the function, the extended-column-defs is used to automatically compute the column-formatter and extractors.

Extended column definitions are like the column definitions used with format-columns except:

  • The first value in each vector is the extractor (a function or keyword)
  • If the column layout is :left or :right, then the width of the column is computed as the max-value-length of that column (using the extractor and the row-data).
  • An extended column definition may not be a vector, but instead:
    • A string, which is treated as literal text
    • nil, which is ignored
    • otherwise, assumed to be an extractor whose values will be right justified
used to compute column-formatter and extractors taking into account row-data
formatter function created by format-columns
seq of functions that extract a column value from a row; typically a keyword when the row is a map
a seq of row data


(write-rows [:last-name ", " :first-name ": " [:age :none]]

This will write three columns, separated by literal text. The first column will be right justified, and as wide as longest last name. The second column will also be right justified, and as wide as the longest first name. The final column will not be justified at all, so it will be varying width.