Nim fmt
— Kaushal ModiNim 0.18.0+ ships with the strformat
library that provides the fmt
template which allows one to format strings using format specifiers
like in Python 3’s f-strings.
The strformat
library provides string interpolation inspired by
Python 3’s Formatted String Literals or f-strings. I find myself
almost always importing strformat
to get the beautiful string
formatting using its fmt
template.
Basics #
Use the fmt
template to get an interpolated string.
You can also use the unary operator &
instead of fmt
, which works
better for the \n
explained in fmt
and &
– Printing newline using \n
inside fmt
.
import strformat
let
fname = "Kaushal"
lname = "Modi"
echo fmt"My name is {fname} {lname}."
echo &"My name is {fname} {lname}."
My name is Kaushal Modi.
My name is Kaushal Modi.
The fmt
can be used like a proc too:
import strformat
let str = "abc"
echo fmt("{str}")
echo "{str}".fmt
abc
abc
Format Specifier #
The general syntax for fmt
is fmt"{VARorSTRING:FORMATSPECIFIER}"
(or &"{VARorSTRING:FORMATSPECIFIER}"
).
The “FORMATSPECIFIER” is further broken down into (ref):
[[fill]align][sign][#][0][minimumwidth][.precision][type]
The square brackets [] indicate an optional element.
Below sub-sections delve into the explanation of each of those format specifier fields, with examples.
Alignment #
[[fill] align ][sign][#][0][ minimumwidth ][.precision][type]
Alignment + Min width | Description |
---|---|
N | Left aligns strings, right aligns numbers (ints and floats) |
>N | Right align, or pad spaces to the left |
<N | Left align, or pad spaces to the right |
^N | Center align, or pad spaces on both sides |
N
is the number of characters.- Spaces are padded only if
N
is greater than the string length.
- Spaces are padded only if
- If
N
is not defined, the field width will always be the same size as the data to fill it. So the alignment field has no meaning in that case. >
,<
and^
are the right, left and center alignment flags respectively.
Right align #
- Number type values (ints, floats) get right aligned by default.
>N
forces the right align.
import strformat
let
str: string = "abc"
flt: float = 123.456
int1: int = -12345
int2: int = -1
int3: int = 16
echo "String formatting:"
echo &" `{str:>5}'"
echo &""" `{"def":>5}'"""
echo "\nFloat formatting:"
echo fmt" `{flt:9.3f}'"
echo fmt" `{flt:9.4f}'"
echo fmt" `{flt:>9.3f}'"
echo fmt" `{flt:>9.0f}'"
echo fmt" `{flt:e}'"
echo fmt" `{flt:13e}'"
echo fmt" `{flt:>13e}'"
echo "\nInteger formatting:"
echo fmt" `{int1:9}'"
echo fmt" `{int1:>9}'"
echo fmt" `{int2:4}'"
String formatting:
` abc'
` def'
Float formatting:
` 123.456'
` 123.4560'
` 123.456'
` 123.'
`1.234560e+02'
` 1.234560e+02'
` 1.234560e+02'
Integer formatting:
` -12345'
` -12345'
` -1'
Left align #
- String type values get left aligned by default.
<N
forces the left align.
import strformat
let
str: string = "abc"
flt: float = 123.456
int1: int = -12345
int2: int = -1
int3: int = 16
echo "String formatting:"
echo &" `{str:5}'"
echo &""" `{str:<5}'"""
echo &""" `{"def":<5}'"""
echo "\nFloat formatting:"
echo fmt" `{flt}'"
echo fmt" `{flt:<9.4f}'"
echo "\nInteger formatting:"
echo fmt" `{int1:<10}'"
echo fmt" `{int2:<4}'"
echo fmt" `{int3:<4}'"
String formatting:
`abc '
`abc '
`def '
Float formatting:
`123.456'
`123.4560 '
Integer formatting:
`-12345 '
`-1 '
`16 '
Center align #
import strformat
let
str: string = "abc"
flt: float = 123.4
int1: int = -12345
echo "String formatting:"
echo &" `{str:^5}'"
echo &" `{str:^6}'"
echo &" `{str:^7}'"
echo "\nFloat formatting:"
echo fmt" `{flt:^11.3f}'"
echo fmt" `{flt:^11.0f}'"
echo fmt" `{flt:^13e}'"
echo fmt" `{flt:^11.4f}'"
echo "\nInteger formatting:"
echo fmt" `{int1:^10}'"
String formatting:
` abc '
` abc '
` abc '
Float formatting:
` 123.400 '
` 123. '
`1.234000e+02 '
` 123.4000 '
Integer formatting:
` -12345 '
If exact centering cannot be done, the one extra space is added to the right.
Fill #
[[ fill ] align ][sign][#][0][ minimumwidth ][.precision][type]
Fill + Alignment + Min width | Description |
---|---|
F>N | Right align and pad the “F” char to the left |
F<N | Left align and pad the “F” char to the right |
F^N | Center align and pad the “F” char on both sides |
N
is the number of characters.- Filling happens only if
N
is greater than the string length.
- Filling happens only if
F
is the optional ‘fill’ character.- The fill character, if present, must be followed by an alignment
flag (
<
,>
,^
).
- The fill character, if present, must be followed by an alignment
flag (
Right Align + Fill #
import strformat
let
str: string = "abc"
flt: float = 123.456
int1: int = 123
echo "String formatting:"
echo &" `{str:x>5}'"
echo "\nFloat formatting:"
echo fmt" `{flt:x>9.3f}'"
echo "\nInteger formatting:"
echo fmt" `{int1:x>9}'"
String formatting:
`xxabc'
Float formatting:
`xx123.456'
Integer formatting:
`xxxxxx123'
Left Align + Fill #
import strformat
let
str: string = "abc"
flt: float = 123.456
int1: int = 123
echo "String formatting:"
echo &" `{str:x<5}'"
echo "\nFloat formatting:"
echo fmt" `{flt:x<9.3f}'"
echo "\nInteger formatting:"
echo fmt" `{int1:x<9}'"
String formatting:
`abcxx'
Float formatting:
`123.456xx'
Integer formatting:
`123xxxxxx'
Center Align + Fill #
import strformat
let
str: string = "abc"
flt: float = 123.456
int1: int = 123
echo "String formatting:"
echo &" `{str:x^5}'"
echo "\nFloat formatting:"
echo fmt" `{flt:x^9.3f}'"
echo "\nInteger formatting:"
echo fmt" `{int1:x^9}'"
String formatting:
`xabcx'
Float formatting:
`x123.456x'
Integer formatting:
`xxx123xxx'
Sign #
[[fill]align][ sign ][#][0][minimumwidth][.precision][type]
The ‘sign’ field is only valid for numeric types, and can be one of the following:
Sign | Meaning |
---|---|
- (default) | Indicates that a sign should be used only for negative numbers. |
+ | Indicates that a sign should be used for both positive as well as negative numbers. |
(space) | Indicates that a leading space should be used on positive numbers. |
The ‘sign’ field affects the formatting of only positive numbers.
import strformat
let
intp: int = 32
intn: int = -44
fltp: float = 1.123
fltn: float = -4.56
echo "Positive integer formatting:"
echo fmt" Default: `{intp}'"
echo fmt" -: `{intp:-}'"
echo fmt" +: `{intp:+}'"
echo fmt" space: `{intp: }'"
echo "Negative integer formatting:"
echo fmt" Default: `{intn}'"
echo fmt" -: `{intn:-}'"
echo fmt" +: `{intn:+}'"
echo fmt" space: `{intn: }'"
echo "\nPositive float formatting:"
echo fmt" Default: `{fltp}'"
echo fmt" -: `{fltp:-}'"
echo fmt" +: `{fltp:+}'"
echo fmt" space: `{fltp: }'"
echo "Negative float formatting:"
echo fmt" Default: `{fltn}'"
echo fmt" -: `{fltn:-}'"
echo fmt" +: `{fltn:+}'"
echo fmt" space: `{fltn: }'"
Positive integer formatting:
Default: `32'
-: `32'
+: `+32'
space: ` 32'
Negative integer formatting:
Default: `-44'
-: `-44'
+: `-44'
space: `-44'
Positive float formatting:
Default: `1.123'
-: `1.123'
+: `+1.123'
space: ` 1.123'
Negative float formatting:
Default: `-4.56'
-: `-4.56'
+: `-4.56'
space: `-4.56'
FIXED Negative zero #
import strformat
echo fmt"{-0.0:+}"
-0
- Older issue
- Nim Issue #7923
Thanks to @skilchen from GitHub for fixing this in 230692a22f!
Hash sign “#” (only for integers) #
[[fill]align][sign][ # ][0][minimumwidth][.precision][ type ]
If the ‘#’ character is present, integers use an alternate form for formatting. This means that binary, octal, and hexadecimal output will be prefixed with ‘0b’, ‘0o’, and ‘0x’, respectively.
The presentation type for the integer is specified in the ’type’ field.
Type | Description |
---|---|
d (or none) | (default) Decimal Integer. Outputs the number in base 10. |
b | Binary. Outputs the number in base 2. |
o | Octal format. Outputs the number in base 8. |
x | Hex format. Outputs the number in base 16, using lower-case letters for the digits above 9. |
X | Hex format. Outputs the number in base 16, using uppercase letters for the digits above 9. |
If the ’type’ field is not specified, “#” does not do anything (it defaults to the decimal presentation).
import strformat
let i_seq: seq[int] = @[-16, 0, 57005, 48879]
for i in i_seq:
echo fmt"value = {i}"
echo fmt" none: `{i}'"
echo fmt" d: `{i:#d}'"
echo fmt" b: `{i:#b}'"
echo fmt" o: `{i:#o}'"
echo fmt" x: `{i:#x}'"
echo fmt" X: `{i:#X}'"
value = -16
none: `-16'
d: `-16'
b: `-0b10000'
o: `-0o20'
x: `-0x10'
X: `-0x10'
value = 0
none: `0'
d: `0'
b: `0b0'
o: `0o0'
x: `0x0'
X: `0x0'
value = 57005
none: `57005'
d: `57005'
b: `0b1101111010101101'
o: `0o157255'
x: `0xdead'
X: `0xDEAD'
value = 48879
none: `48879'
d: `48879'
b: `0b1011111011101111'
o: `0o137357'
x: `0xbeef'
X: `0xBEEF'
Zero-padding (only for numbers) #
[[fill]align][sign][#][ 0 ][minimumwidth][.precision][type]
Zero-padding is enabled if the ‘minimumwidth’ field is preceded by a zero (‘0’) character.
Integers #
import strformat
let
int1: int = -12345
int2: int = 1
echo fmt"`{int1:08}'"
echo fmt"`{int2:04}'"
`-0012345'
`0001'
Floats #
import strformat
echo fmt"{-1.5:08}"
echo fmt"{1.5:08}"
-00001.5
000001.5
- Older issue
- Thanks to @skilchen from GitHub, this issue was fixed
in 07ff9940f4.
This was Nim Issue #7932 – Zero padding did not work with floats.
Precision #
[[fill]align][sign][#][0][minimumwidth][ .precision ][type]
- For floats
- The ‘precision’ is a decimal number indicating how many digits should be displayed after the decimal point in a floating point conversion.
- For integers
- This field is ignored.
- For non-numeric types
- This field indicates the maximum field size - in other words, how many characters will be used from the field content.
Floats #
import strformat
echo fmt" .1 :: {12.345678:.1}"
echo fmt" 7.1 :: {12.345678:7.1}"
echo fmt" 7.1g :: {12.345678:7.1g}"
echo fmt" 7.1f :: {12.345678:7.1f}"
echo fmt" 07.1f :: {12.345678:07.1f}"
echo fmt"+07.1f :: {12.345678:+07.1f}"
echo fmt"+07.2f :: {12.345678:+07.2f}"
echo fmt"+07.3f :: {12.345678:+07.3f}"
echo fmt"+07.4f :: {12.345678:+07.4f}"
echo fmt"+07.5f :: {12.345678:+07.5f}"
echo fmt"+07.6f :: {12.345678:+07.6f}"
.1 :: 1.e+01
7.1 :: 1.e+01
7.1g :: 1.e+01
7.1f :: 12.3
07.1f :: 00012.3
+07.1f :: +0012.3
+07.2f :: +012.35
+07.3f :: +12.346
+07.4f :: +12.3457
+07.5f :: +12.34568
+07.6f :: +12.345678
FIXED Strings #
import strformat
let str = "abc"
echo fmt">7.1 :: `{str:>7.1}'"
echo fmt" 7.1 :: `{str:7.1}'"
echo fmt" 7.2 :: `{str:7.2}'"
echo fmt" 7.3 :: `{str:7.3}'"
>7.1 :: ` a'
7.1 :: `a '
7.2 :: `ab '
7.3 :: `abc '
- Older issue
- Nim Issue #7933 – The .precision field should
behave as maxwidth for strings as per the documentation, but did not work.
Thanks to @skilchen from GitHub for fixing this in fd102f39bb!
Type (only for numbers) #
[[fill]align][sign][#][0][minimumwidth][.precision][ type ]
Integers #
See Hash sign “#” (only for integers).
Floats #
Type | Description |
---|---|
e | Exponent notation. Prints the number in scientific notation using the letter ’e’ to indicate the exponent. |
E | Exponent notation. Same as ’e’ except it converts the number to uppercase. |
f | Fixed point. Displays the number as a fixed-point number. |
F | Fixed point. Same as ‘f’ except it converts the number to uppercase. |
g | General format. This prints the number as a fixed-point number, unless the number is too large, in which case it switches to ’e’ exponent notation. |
G | General format. Same as ‘g’ except switches to ‘E’ if the number gets to large. |
(None) | (default) similar to ‘g’, except that it prints at least one digit after the decimal point. |
import strformat
let f_seq: seq[float] = @[-1234.567, 0.0, 1.2345, 5678901234.5678]
for flt in f_seq:
echo fmt"value = {flt}"
echo fmt" none: `{flt}'"
echo fmt" e: `{flt:e}'"
echo fmt" E: `{flt:E}'"
echo fmt" f: `{flt:f}'"
echo fmt" F: `{flt:F}'"
echo fmt" g: `{flt:g}'"
echo fmt" G: `{flt:G}'"
value = -1234.567
none: `-1234.567'
e: `-1.234567e+03'
E: `-1.234567E+03'
f: `-1234.567000'
F: `-1234.567000'
g: `-1234.57'
G: `-1234.57'
value = 0.0
none: `0.0'
e: `0.000000e+00'
E: `0.000000E+00'
f: `0.000000'
F: `0.000000'
g: `0'
G: `0'
value = 1.2345
none: `1.2345'
e: `1.234500e+00'
E: `1.234500E+00'
f: `1.234500'
F: `1.234500'
g: `1.2345'
G: `1.2345'
value = 5678901234.5678
none: `5678901234.5678'
e: `5.678901e+09'
E: `5.678901E+09'
f: `5678901234.567800'
F: `5678901234.567800'
g: `5.6789e+09'
G: `5.6789E+09'
Strings #
For strings, the ’type’ field is implicitly ’s’, the only valid value for strings.
import strformat
let str = "abc"
echo fmt"{str}"
echo fmt"{str:s}"
abc
abc
fmt
Limitations #
fmt
and &
– Printing newline using \n
inside fmt
#
As you see above, you can use either fmt
or the unary &
operator
for formatting. The difference between them is subtle but important.
The fmt"{expr}"
syntax is more aesthetically pleasing, but it hides
a small gotcha — The string is a generalized raw string
literal. So the below won’t work:
import strformat
echo fmt"line1\nline2"
line1\nline2
But there are multiple ways to get around that limitation:
Use
&
insteadimport strformat echo &"line1\nline2"
line1 line2
Wrap the
\n
in bracesimport strformat echo fmt"line1{'\n'}line2"
line1 line2
Call
fmt
like a procimport strformat echo fmt("line1\nline2") echo "line1\nline2".fmt
line1 line2 line1 line2
TO BE FIXED fmt
and &
formatting strings cannot be a variable #
Nim Issue #18218
import std/[strformat]
let
str = "abc"
f = "{abc}"
echo fmt(f)
# echo &f # We get the same error when using `&` as well
Trying to evaluate the above gives this error:
stack trace: (most recent call last)
lib/pure/strformat.nim(281) &
nim_src_rJX8ZE.nim(8, 10) Error: string formatting (fmt(), &) only works with string literals
FIXED fmt
does not handle open arrays #
Nim Issue #7940
Thanks to @data-man from GitHub, this issue is now fixed!
import strformat
proc foo(x: openArray[int]) =
echo fmt"{x}"
foo([1, 2, 3])
[1, 2, 3]
See this section in Nim notes for details about the failure before this fix.