Liking cljdoc? Tell your friends :D

clj-format DSL Reference

A complete definition of the s-expression DSL that clj-format compiles into cl-format format strings. Covers all 33 cl-format directives.

Structure

A format specification is a body — a vector of elements. Each element is one of:

  • string — literal text, emitted verbatim ("hello"hello)
  • bare keyword — a directive with no options (:str~A)
  • directive vector[:keyword opts] (simple) or [:keyword opts & body] (compound)

The vector form follows the Hiccup convention: if the second element is a map, it is the options map; everything after is the body/children.

;; Bare keyword — no options
:str                              ;; ~A

;; Directive with options
[:str {:width 10}]                ;; ~10A

;; Compound directive: keyword, opts, then body elements
[:each {:sep ", "} :str]          ;; ~{~A~^, ~}

;; Mixed body
["Name: " :str ", Age: " :int]   ;; Name: ~A, Age: ~D

Special Parameter Values

Anywhere an option accepts a numeric or character value, two special values are also accepted:

  • :V — take the value from the next format argument at runtime
  • :# — use the count of remaining format arguments
[:str {:width :V}]                ;; ~vA  — width from arg
[:str {:width :#}]                ;; ~#A  — width = remaining arg count

Argument Consumption

When reading a format body, it helps to know which directives consume arguments and which don't. The rule of thumb: directives that name a data type consume an argument; directives that name an action don't.

Consume one argument (print it)

KeywordWhat it prints
:strAny value, human-readable
:prAny value, readable form
:writeAny value, via pprint
:charA character
:int :bin :oct :hexAn integer in the given base
:radixAn integer in an arbitrary base
:cardinal :ordinalAn integer as English words
:roman :old-romanAn integer as Roman numerals
:float :exp :gfloatA floating-point number
:moneyA floating-point number as currency
:pluralAn integer (tests it for "s"/"ies", doesn't print it)

Consume no arguments (layout and control)

KeywordWhat it does
:nl :fresh :pageEmit whitespace / page breaks
:tabMove to a column position
:tildeEmit literal ~
:stopExit iteration or format
:breakConditional newline (pretty printer)
:indentSet indentation (pretty printer)

Modify the argument pointer (don't print)

KeywordWhat it does
:skipAdvance past arguments without printing
:backMove the argument pointer backward
:gotoJump to an absolute argument position

Special

KeywordConsumption
:recurConsumes a format string + an argument list (or shares parent args with {:from :rest})
:eachConsumes a list argument (or remaining args with {:from :rest}) and iterates
:whenConsumes one argument (tests truthiness), body may consume more
:ifConsumes one argument (tests truthiness), selected clause may consume more
:chooseConsumes one argument (as index), selected clause may consume more
:case optionConsumes nothing extra — wraps output of the directive it's attached to

Directive Reference

Output

Keywordcl-formatDescription
:str~APrint argument (human-readable, no quotes)
:pr~SPrint argument (readable form, with quotes)
:write~WPrint via pprint write
:char~CPrint a character

:str and :pr options:

OptionTypeDefaultDescription
:widthinteger0Minimum column width
:pad-stepinteger1Padding column increment
:min-padinteger0Minimum padding characters
:fillcharspacePadding character
:pad:left:rightPadding direction (:left = right-align)

:write options:

OptionTypeDescription
:prettybooleanEnable pretty-printing
:fullbooleanIgnore *print-level* and *print-length*

:char options:

OptionTypeDescription
:namebooleanEmit the character name (for example "Space")
:readablebooleanEmit the readable form (for example \space)
:format:name / :readableBackward-compatible shorthand for setting one modifier

Integers

Keywordcl-formatDescription
:int~DDecimal
:bin~BBinary
:oct~OOctal
:hex~XHexadecimal

All four share the same options:

OptionTypeDefaultDescription
:widthinteger0Minimum column width
:fillcharspacePadding character
:groupbooleanfalseEnable digit grouping (e.g., 1,000,000)
:sign:alwaysnilAlways show +/- sign
:group-sepchar,Separator character between groups
:group-sizeinteger3Digits per group
:int                                         ;; ~D
[:int {:width 8 :fill \0}]                   ;; ~8,'0D
[:int {:group true :sign :always}]           ;; ~:@D
[:hex {:width 4 :fill \0}]                   ;; ~4,'0X
[:bin {:width 8 :fill \0 :group true
       :group-sep \space :group-size 4}]     ;; ~8,'0,' ,4:B

Radix

Keywordcl-formatDescription
:radix~nRPrint in arbitrary base n
:cardinal~REnglish cardinal ("forty-two")
:ordinal~:REnglish ordinal ("forty-second")
:roman~@RRoman numeral ("XLII")
:old-roman~:@ROld Roman, no subtractive ("XXXXII")

:radix accepts integer options (:base plus :width, :fill, etc.). The other four are bare keywords — no options.

[:radix {:base 16}]              ;; ~16R
[:radix {:base 2 :width 8 :fill \0}]
:cardinal                        ;; ~R
:ordinal                         ;; ~:R
:roman                           ;; ~@R
:old-roman                       ;; ~:@R

Pluralization

Keywordcl-formatDescription
:plural~PPrint "s" or "" based on argument
OptionTypeDescription
:rewindbooleanBack up one argument before testing
:form:iesUse "y"/"ies" instead of ""/"s"
:plural                          ;; ~P   "s" or ""
[:plural {:form :ies}]           ;; ~@P  "y" or "ies"
[:plural {:rewind true}]         ;; ~:P  back up, then "s" or ""

Floating Point

Keywordcl-formatDescription
:float~FFixed-point
:exp~EExponential notation
:gfloat~GGeneral (auto-selects fixed/exp)
:money~$Monetary (dollars and cents)

:float options:

OptionTypeDefaultDescription
:widthintegernilTotal field width
:decimalsintegernilDigits after decimal point
:scaleinteger0Multiply by 10^scale before printing
:overflowcharnilPrint this char when value overflows width
:fillcharspacePadding character
:sign:alwaysnilAlways show sign

:exp and :gfloat additional options:

OptionTypeDefaultDescription
:exp-digitsintegernilDigits in exponent
:exp-charcharEExponent marker character

:money options:

OptionTypeDefaultDescription
:decimalsinteger2Digits after decimal
:int-digitsinteger1Minimum digits before decimal
:widthinteger0Minimum total width
:fillcharspacePadding character
:sign:alwaysnilAlways show sign
:sign-firstbooleanfalsePlace sign before padding
[:float {:width 8 :decimals 2}]             ;; ~8,2F
[:exp {:width 10 :decimals 4 :exp-digits 2}] ;; ~10,4,2E
[:money {:decimals 2 :sign :always}]         ;; ~2@$

Layout

Keywordcl-formatDescription
:nl~%Emit newline(s)
:fresh~&Emit newline only if not at column 0
:page~\|Emit page separator (formfeed)
:tab~TMove to column position
:tilde~~Emit literal tilde character(s)

:nl, :fresh, :page, and :tilde accept {:count n}.

:tab options:

OptionTypeDefaultDescription
:colinteger1Column number (absolute) or spaces (relative)
:stepinteger1Column increment for rounding
:relativebooleanfalseRelative mode (move forward, not to absolute column)
:nl                              ;; ~%
[:nl {:count 3}]                 ;; ~3%
[:tab {:col 20}]                 ;; ~20T
[:tab {:col 4 :relative true}]  ;; ~4@T

Navigation

Keywordcl-formatDescription
:skip~*Skip forward n arguments (default 1)
:back~:*Skip backward n arguments (default 1)
:goto~@*Jump to absolute argument position n (default 0)
:recur~?Recursive format — next arg is format string, next is arg list

:skip, :back, and :goto accept {:n count}. :recur accepts {:from :rest} to use remaining args instead of a sublist.

:skip                            ;; ~*   skip 1
[:skip {:n 3}]                   ;; ~3*  skip 3
:back                            ;; ~:*  back up 1
[:goto {:n 5}]                   ;; ~5@* jump to arg 5
[:recur {:from :rest}]           ;; ~@?  use remaining args

Iteration

Keywordcl-formatDescription
:each~{...~}Iterate body over arguments

The body elements follow the keyword and optional opts map (Hiccup convention).

OptionTypeDefaultDescription
:sepstringnilSeparator between iterations (abstracts ~^sep)
:fromkeywordnilArgument source (see below)
:maxintegernilMaximum number of iterations
:minintegernilMinimum iterations (1 = execute at least once)

:from values:

Valuecl-formatDescription
(nil)~{Iterate over a single list argument
:rest~@{Iterate over remaining arguments
:sublists~:{Each element of the list is a sublist of arguments
:rest-sublists~:@{Remaining args, each is a sublist
[:each {:sep ", "} :str]                          ;; ~{~A~^, ~}
[:each {:sep ", " :from :rest} :str]              ;; ~@{~A~^, ~}
[:each {:from :sublists} :str ": " :int :nl]      ;; ~:{~A: ~D~%~}
[:each {:max 5} :str ", "]                         ;; ~5{~A, ~}
[:each {:min 1} :str]                              ;; ~{~A~:}

For complex cases where :sep is insufficient, use :stop directly in the body:

[:each :str :stop " " :str "=\"" :str "\""]       ;; ~{~A~^ ~A="~A"~}

Conditionals

Keywordcl-formatDescription
:when~@[...~]Print body only if argument is truthy
:if~:[...~;...~]Boolean dispatch — true-clause first
:choose~[...~;...~]Numeric dispatch — select clause by index

:when — body elements are inline:

[:when "value: " :str]           ;; ~@[value: ~A~]

:if — two clauses, true first (opposite of cl-format's false-first order). Each clause is a single element: a string, a bare keyword, or a multi-element body vector.

[:if "yes" "no"]                 ;; ~:[no~;yes~]
[:if :str "none"]                ;; ~:[none~;~A~]
[:if [:str " found"] "nothing"]  ;; ~:[nothing~;~A found~]

:choose — clauses are inline, opts map (if any) in second position.

OptionTypeDescription
:defaultelementDefault clause when index is out of range
:selectorinteger/:V/:#Override the argument as selector
[:choose "zero" "one" "two"]                     ;; ~[zero~;one~;two~]
[:choose {:default "other"} "zero" "one" "two"]  ;; ~[zero~;one~;two~:;other~]
[:choose {:selector :#} "none" "one" "some"]     ;; ~#[none~;one~;some~]

Case Conversion

Case conversion can be applied two ways:

As a :case option on any directive (preferred — avoids nesting):

Valuecl-formatEffect
:downcase~(all lowercase
:capitalize~:(Capitalize Each Word
:titlecase~@(Capitalize first word only
:upcase~:@(ALL UPPERCASE
[:str {:case :capitalize}]                   ;; ~:(~A~)
[:str {:case :upcase}]                       ;; ~:@(~A~)
[:each {:sep ", " :case :capitalize} :str]   ;; ~:(~{~A~^, ~}~)
[:roman {:case :downcase}]                   ;; ~(~@R~)

The parser automatically flattens case conversion into the :case option when the body contains a single element.

As compound directives (for multi-element bodies):

Keywordcl-formatEffect
:downcase~(all lowercase
:capitalize~:(Capitalize Each Word
:titlecase~@(Capitalize first word only
:upcase~:@(ALL UPPERCASE
[:downcase "the " :str " is " :str]         ;; ~(the ~A is ~A~)
[:capitalize "hello " :str]                  ;; ~:(hello ~A~)

Justification

Keywordcl-formatDescription
:justify~<...~>Justify/pad text within a field
:logical-block~<...~:>Pretty-printer logical block

:justify — clauses are inline, separated by padding.

OptionTypeDefaultDescription
:widthinteger0Minimum total width
:pad-stepinteger1Padding increment
:min-padinteger0Minimum padding
:fillcharspacePadding character
:pad-beforebooleanfalsePad before first clause
:pad-afterbooleanfalsePad after last clause
[:justify {:width 10} "foo" "bar"]           ;; ~10<foo~;bar~>
[:justify {:width 10 :pad-before true
           :pad-after true} "hello"]         ;; ~10:@<hello~>  (centered)
[:justify {:width 40} :str :int :money]      ;; ~40<~A~;~D~;~$~>

:logical-block — clauses define prefix, body, and suffix for the pretty printer.

[:logical-block :str]                        ;; ~<~A~:>
[:logical-block "(" :str ")"]                ;; ~<(~;~A~;)~:>
[:logical-block {:colon true} :str]          ;; ~:<~A~:>

Control

Keywordcl-formatDescription
:stop~^Exit enclosing iteration (or top-level format)
:break~_Conditional newline (pretty printer)
:indent~ISet indentation (pretty printer)

:stop options:

OptionTypeDescription
:outerbooleanExit the enclosing ~:{ iteration, not just the current sublist
:arg1integerOne-arg test: exit if arg1 = 0
:arg2integerTwo-arg test: exit if arg1 = arg2
:arg3integerThree-arg test: exit if arg1 ≤ arg2 ≤ arg3

Usually abstracted by :sep on :each. Use directly for complex cases.

:stop                            ;; ~^   exit if no args remain
[:stop {:outer true}]            ;; ~:^  exit enclosing iteration
[:stop {:arg1 0}]                ;; ~0^  exit if param = 0

:break options:

OptionTypeDescription
:modekeyword:fill, :miser, or :mandatory (default: linear)
:break                           ;; ~_   linear
[:break {:mode :fill}]           ;; ~:_
[:break {:mode :miser}]          ;; ~@_
[:break {:mode :mandatory}]      ;; ~:@_

:indent options:

OptionTypeDescription
:nintegerIndentation amount (default 0)
:relative-to:currentRelative to current position instead of logical block
[:indent {:n 4}]                             ;; ~4I
[:indent {:n 2 :relative-to :current}]       ;; ~2:I

Complete Keyword Table

Keywordcl-formatCategory
:str~AOutput
:pr~SOutput
:write~WOutput
:char~COutput
:int~DInteger
:bin~BInteger
:oct~OInteger
:hex~XInteger
:radix~nRInteger
:cardinal~RInteger
:ordinal~:RInteger
:roman~@RInteger
:old-roman~:@RInteger
:plural~PInteger
:float~FFloat
:exp~EFloat
:gfloat~GFloat
:money~$Float
:nl~%Layout
:fresh~&Layout
:page~\|Layout
:tab~TLayout
:tilde~~Layout
:skip~*Navigation
:back~:*Navigation
:goto~@*Navigation
:recur~?Navigation
:each~{...~}Iteration
:when~@[...~]Conditional
:if~:[...~]Conditional
:choose~[...~]Conditional
:downcase~(...~)Case
:capitalize~:(...~)Case
:titlecase~@(...~)Case
:upcase~:@(...~)Case
:justify~<...~>Justification
:logical-block~<...~:>Pretty printer
:stop~^Control
:break~_Control
:indent~IControl

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close