String Formatting

Atomo provides printf-like string formatting extended with the power of Common Lisp's FORMAT system (with a different but directly translateable syntax).

To create a Formatter object, you use the f macro-quoter.

Example:

> f{foo bar} is-a?: Formatter
True

A Formatter object is a pre-parsed formatting string. Because it's a macro-quote, any parse errors are caught at macroexpansion time, rather than when it's used.

Like Python, we use the % operator for sending values to this Formatter and getting a formatted String back.

Example:

> f{%s} % "foo"
"foo"
> f{%.2f: %s} % (10.579, "ten point something")
"10.58: ten point something"
> f{%0.4f} % 5.6
"5.6000"

Syntax

Formatters use % as their special character, and some use any of [](){} for wrapping their arguments. Thus, you may occasionally have to escape these, which can be done with a backslash.

Example:

> f"\%" % ()
"%"
> f"%l(\(FoO\))" % ()
"(foo)"

There are various flags you can use to individually modify the behaviour of the following formatters. The rules are as follows:

(number), #

A numeric flag. The # shortcut means "the number of remaining inputs", and acts as if it were a number flag.

Example:

> f"%10s" % "hello"
"hello     "
> f"%#s" % ("hi", 1, 2, 3)
"hi "
> f"%#s" % ("hi", 1, 2, 3, 4, 5, 6)
"hi    "
.(number)

A "precision" flag, named for its use in floating-point formatters. This flag is also used for %r.

Example:

> f"%.2f" % 1.567
"1.57"
> f"%10.2f" % 1.567
"      1.57"
0

Zero-pad flag, which must precede either a numeric or a precision flag.

Example:

> f"%05d" % 10
"00010"
> f"%010.2f" % 10.50
"0000010.50"
,, +, *, =, <, >, and ?

Various symbols with special meanings for whatever formatter they're paired with.

Basic Formatters

These basic formatters render a value to the string, with flags to control padding and alignment. See Alignment.

%s

Format a string.

Example:

> f"%s" % "hello!"
"hello!"
%d

Format an integer in decimal notation.

Example:

> f"%d" % 10
"10"
%x

Format an integer in hexadecimal notation.

Example:

> f"%x" % 10
"a"
%o

Format an integer in octal notation.

Example:

> f"%o" % 10
"12"
%b

Format an integer in binary notation.

Example:

> f"%b" % 10
"1010"
%r

Format an integer with a radix specified by a "precision" flag, which must be between 2 and 36.

Example:

> f"%.32r" % 62
"1u"
%f

Format a double in decimal floating-point notation. The number of decimal points to show is controlled by a precision flag.

Example:

> f"%f" % 12.345678
"12.345678"
> f"%.2f" % 12.345678
"12.35"
%e

Format a double in scientific notation. The number of decimal points to show is controlled by a precision flag.

Example:

> f"%e" % 12.345678
"1.2345678e1"
> f"%.2e" % 12.345678
"1.23e1"
%g

Format a double in decimal or scientific notation. The number of decimal points to show is controlled by a precision flag.

Example:

> f"%g" % 12.345678
"12.345678"
> f"%.2g" % 12.345678
"12.35"
> f"%g" % 12345678901234567890.123456
"1.2345678901234567e19"
> f"%.2g" % 12345678901234567890.123456
"1.23e19"
%c

Format a character.

Example:

> f"%c" % $a
"a"
%a

Format any value, converting it to a string via as: String.

Example:

> f"%a" % "hello!"
"hello!"
> f"%a" % $a
"$a"
> f"%a" % (1 -> $a)
"1 -> $a"
%v

Format any value, converting it to a string via show.

Example:

> f"%v" % "hello!"
"\"hello!\""
> f"%v" % $a
"$a"
> f"%v" % (1 -> $a)
"1 -> $a"

Alignment

The basic formatters above support various flags for controlling padding and alignment.

Numeric formatters align to the right by default.

numeric

Pad to a given width.

Example:

> f"%10s" % "hello!"
"hello!    "
> f"%5d" % 10
"   10"
0

Pad with 0 rather than spaces. Usually used for numbers.

Example:

> f"%05d" % 10
"00010"
> f"%010s" % "hello!"
"hello!0000"
<

Align left.

Example:

> f"%<10s" % "hello!"
"hello!    "
> f"%<5d" % 10
"10   "
>

Align right.

Example:

> f"%>10s" % "hello!"
"    hello!"
> f"%>5d" % 10
"   10"
=

Centered horizontally.

Example:

> f"%=10s" % "hello!"
"  hello!  "
> f"%=5d" % 10
"  10 "

Pluralization

The %p formatter is used to pluralize a word based on an integer input. The basic form is %p(foo), where foo is the word you want pluralized if the input is not 1.

Example:

> f"%p(cat)" % 0
"cats"
> f"%p(cat)" % 1
"cat"
> f"%p(cat)" % 2
"cats"

A > flag will cause it to peek ahead, grabbing the number without consuming it.

Example:

> f"%>p(cat): %d" % 0
"cats: 0"
> f"%>p(cat): %d" % 1
"cat: 1"
> f"%>p(cat): %d" % 2
"cats: 2"

The pluralization is by no means comprehensive. It knows about basic rules, but should it fail for your word you can provide your own plural form in a second pair of parentheses.

Example:

> f"%p(person)" % 2
"persons"
> f"%p(person)(people)" % 2
"people"

Case Conversion

%l(...)

Converts the formatted contents to lowercase.

Example:

> f"%l(FoO %s)" % "HELLO!"
"foo hello!"
%u(...)

Converts the formatted contents to uppercase.

Example:

> f"%u(FoO %s)" % "HELLO!"
"FOO HELLO!"
%c(...)

Capitalizes each word in the string. Note that the words are converted to lowercase before capitalization.

Example:

> f"%c(FoO %s)" % "HELLO!"
"Foo Hello!"

A numeric flag may be provided to limit the capitalization to a certain amount of words.

Example:

> f"%1c(FOO bar %s)" % "BaZ"
"Foo bar BaZ"
> f"%2c(FOO bar %s)" % "BaZ"
"Foo Bar BaZ"

Skipping & Indirection

%_

Skips an input value. With a numeric flag, it skips a certain number of inputs. With the < flag, it skips backwards.

Example:

> f"%_%d" % (1, 2, 3)
"2"
> f"%2_%d" % (1, 2, 3)
"3"
> f"%d,%<_%d" % (1, 2, 3)
"1,1"
> f"%d,%d,%<2_%d" % (1, 2, 3)
"1,2,1"
%%

"Splices" a formatter given as an input, followed by the formatter's inputs as a list.

Example:

> f"%%" % (f"%d", [1])
"1"
> f"%%" % (f"%d, %d", [1, 2])
"1, 2"

With a * flag, it uses the rest of the main inputs as the spliced formatter's inputs.

Example:

> f"%*%" % (f"%d", 1)
"1"
> f"%*%" % (f"%d, %d", 1, 2)
"1, 2"

Iteration & Breaking

This type of formatter, the basic form being %{...}, iterates over a list, formatting the iteration's contents for each of its values.

Example:

> f"%{%a, }" % [1, $a, "hi"]
"1, $a, hi, "

A break, %^, can be used to stop the formatting when it's at the end of the iteration.

Example:

> f"%{%a%^, }" % [1, $a, "hi"]
"1, $a, hi"

Various flags are supported:

*

Iterate over the rest of the inputs.

Example:

> f"%*{%a%^, }" % (1, $a, "hi")
"1, $a, hi"
.

Treat each value being iterated over as a list of the inputs for its iteration.

Example:

> f"%.{%a, %a; }" % [[1, $a], [2, $b]]
"1, $a; 2, $b; "

Note that when using this flag, %^ breaks based on the sublist's inputs; it will not break the entire iteration. For this you must use %.^.

Example:

> f"%.{%a, %a; %^%a, }" % [[1, $a], [2, $b, "dos"], [3, $c, "foo"]]
"1, $a; 2, $b; dos, 3, $c; foo, "
> f"%.{%a, %a%.^; }" % [[1, $a], [2, $b], [3, $c]]
"1, $a; 2, $b; 3, $c"
+

Always iterate at least once. This is often used with Conditionals to output something if there are no values.

Example:

> f"%+{%#[empty](%d%^, )}" % [1, 2, 3]
"1, 2, 3"
> f"%+{%#[empty](%d%^, )}" % []
"empty"
numeric

Limits the number of iterations.

Example:

> f"%2{%d, }" % [1, 2, 3]
"1, 2, "

Note that a limit of 0 overrides the + flag. Also, %^ will not be triggered when it reaches the limit; it always waits for the end.

Example:

> f"%0+{hi}" % []
""
> f"%2{%d%^, }" % [1, 2, 3]
"1, 2, "

Conditionals

A conditional formatter looks like this: %[...]+(...)?, where + means one or more and ? means optional. The required portion is the branches, and the optional portion is the default.

By default, using a conditional consumes an integer input, and uses that to decide what to format. If it's 0, it formats the first branch, if it's 1 the second, and so on. If it goes out of the range of the branches you've provided, it formats the default, or if there is no default, nothing is formatted.

Example:

> f"%[zero][one](two or more)" % 0
"zero"
> f"%[zero][one](two or more)" % 1
"one"
> f"%[zero][one](two or more)" % 2
"two or more"

A numeric flag can be used to determine the branch (or default) to format. Using a number will be a bit useless, forcing it to always format one branch, but using # is very useful. See Syntax.

Example:

> f"%0[zero!]" % ()
"zero!"
> f"%0[zero!]" % 1
"zero!"
> f"%#[zero][one](two or more)" % ()
"zero"
> f"%#[zero][one](two or more)" % 1
"one"
> f"%#[zero][one](two or more)" % (1, 2)
"two or more"

With a ? flag, the conditional is expects a boolean input, and one or two branches. With one branch, its contents are formatted only if the input is True. With two, it acts like a "if-then-else".

Example:

> f"%?[hi]" % False
""
> f"%?[hi]" % True
"hi"
> f"%?[hi][bye]" % False
"bye"
> f"%?[hi][bye]" % True
"hi"

Justification

Justifying multiple format segments to a given with can be done via a numeric flag and the %j(...)+ form, where + means one or more segments. The numeric flag is the width to justify to, and each segment is a block of text to justify after formatting.

With two segments, they are justified to the left and right, respectively. As a special case, justifying one segment is aligns it to the right.

Example:

> f"%20j(one)(two)(three)" % ()
"one    two     three"
> f"%30j(one)(two)(three)(four)(five)" % ()
"one  two   three   four   five"
> f"%25j(foo: %s)(bar: %d)(baz: %c)" % ("hi", 1, $x)
"foo: hi   bar: 1   baz: x"

If the number given is smaller than the width of the combined segments, they are just concatentated.

Example:

> f"%10j(hello)(goodbye)" % ()
"hellogoodbye"

Three flags are supported for controlling the spacing. These override the defaults mentioned above.

<

Add spacing before the first segment.

Example:

> f"%<20j(one)(two)(three)" % ()
"   one   two   three"
> f"%<20j(one)(two)" % ()
"       one       two"
> f"%<20j(one)" % ()
"one                 "
>

Add spacing after the last segment.

Example:

> f"%>20j(one)(two)(three)" % ()
"one   two   three   "
> f"%>20j(one)(two)" % ()
"one       two       "
> f"%>20j(one)" % ()
"                 one"
=

Add spacing before and after the first and last segments. This is equivalent to providing both < and > flags.

Example:

> f"%=20j(one)(two)(three)" % ()
"  one  two  three   "
> f"%=20j(one)(two)" % ()
"    one     two     "
> f"%=20j(one)" % ()
"         one        "
> f"%<>20j(one)(two)(three)" % ()
"  one  two  three   "
> f"%<>20j(one)(two)" % ()
"    one     two     "
> f"%<>20j(one)" % ()
"         one        "