Author: | Frank Fischer |
---|---|
License: | MIT |
Version: | 0.10 |
Introduction
Basic helpers for formatting values as strings in a configurable way. It is inspired by and similar to Python's format function.
The most important functions and macros provided are:- the format functions to render a single value as a string,
- the fmt macro to construct a string containing several formatted values
- the writefmt and printfmt family of macros to write a formatted string to a file and stdout, respectively
- the interp and $$ string interpolation macros to render expressions embedded in the string itself
These functions are described in the following sections.
This package is hosted here.
Formatting a single value: format
The format function transforms a single value to a string according to a given format string, e.g.
42.23.format("06.1f")
The syntax of the format specification string is similar to Python's Format Specification Mini-Language.
The general form of a format specifier is
format_spec ::= [[fill]align][sign][#][0][width][,][.precision][type][a[array_sep]] fill ::= rune align ::= "<" | ">" | "^" | "=" sign ::= "+" | "-" | " " width ::= integer precision ::= integer type ::= "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%" array_sep ::= "" | (<level separating character> string )+
The meaning of the various fields is as follows.
- fill
- this character (or rune) is used to for the additional characters to be written until the formatted string is at least width characters long. The fill character defaults to SPACE.
- align
- Special character to specify alignment.
Option Meaning < Left alignment, additional characters are added to the right (default for string). > Right alignment, additional characters are added to the left. ^ Centered , the same amount of characters is added to the left and the right. = Padding. If a numeric value is printed with a sign, then additional characters are added after the sign. Otherwise it behaves like ">". This option is only available for numbers (default for numbers). - sign
- The sign character is only used for numeric values.
Option Meaning + All numbers (including positive ones) are preceded by a sign. - Only negative numbers are preceded by a sign. SPACE Negative numbers are preceded by a sign, positive numbers are preceded by a space. - #
- If this character is present then the integer values in the formats b, o, x and X are preceded by 0b, 0o or 0x, respectively. In all other formats this character is ignored.
- width
- The minimal width of the resulting string. The result string is padded with extra characters (according the align field) until at least width characters have been written.
- ,
- Add , as a thousands separator
- precision
- The meaning of the precision field depends on the formatting type.
Type Meaning s The maximal number of characters written. f, F, e and E The number of digits after the decimal point. g, G The number of significant digits written (i.e. the number of overall digits). Note that in all cases the decimal point is printed if and only if there is at least one digit following the point.
The precision field is ignored in all other cases.
- type
- The formatting type. The valid types depend on the type of the value to be printed. For strings the following types are valid.
Type Meaning s A string. This is the default format for strings. The following types are valid for integers.
Type Meaning d A decimal integer number. This is the default for integers. b A binary integer (base 2). o An octal integer (base 8). x A hexadecimal integer (base 16), all letters are lower case. X A hexadecimal integer (base 16), all letters are upper case. n The same as d. The following types are valid for real numbers.
Type Meaning f Fixed point format. F The same as f. e Scientific format, exactly one digit before the decimal point. The exponent is written with a lower case 'e'. The exponent always has a sign as at least two digits. E The same as e but with an upper case 'E'. g General format. The number is written either in fixed point format or in scientific format depending on the precision and the exponent in scientific format. The exact rule is as follows. Suppose exp is the exponent in scientific format and p the desired precision. If -4 <= exp <= p-1 then the number is formatted in fixed point format f with precision p-1-exp. Otherwise the number if formatted in scientific format e with precision p-1. Trailing zeros are removed in all cases and the decimal point is removed as well if there are no remaining digits following it. G The same as g but works like E if scientific format is used. % The number if multiplied by 100, formatted in fixed point format f and followed by a percent sign. - array_sep
If an array is formatted, the format specifications above apply to each element of the array. The elements are printed in succession separated by a separator string. If the array is nested then this applies recursively.
The array_sep field specifies the separator string for all levels of a nested array. The first character after the a is the level separator and works as separator between the string for successive levels. It is never used in the resulting string. All characters between two level separators are the separator between two elements of the respective array level. See Array formatting below.
Array formatting
A format string may contain a separator string for formatting arrays. Because arrays might be nested the separator field contains the separator strings to be used between two successive elements of each level. The strings for each level are separated (in the format string itself) by a special separating character. This character is the first character after the a in the format string. The following example should make this clear:
[[2, 3, 4], [5, 6, 7]].format("02da|; |, ")
This code returns the string "02, 03, 04; 05, 06, 07". The special character separating the strings of different levels is the first character after the a, i.e. the pipe character | in this example. Following the first pipe character is the separator string for the outer most level, "; ". This means that after printing the first element of the outermost array the string "; " is printed. After the second pipe character comes the separator string for the second level, in this example ", ". Between each two elements of the second level the separator string ", " is printed. Because the elements of the second level array are integers, the format string "02d" applies to all of them. Thus, each number is printed with a leading 0. After the 4 has been printed the complete first element of the outer array (namely in array [2, 3, 4]) has been printed, so the separator string of the outer level follows, in this case a semicolon and a space. Finally the second array [6, 7, 8] is printed with the separator ", " between each two elements.
A string containing formatted values: fmt
The fmt macro allows to interpolate a string with several formatted values. This macro takes a format string as its first argument and the values to be formatted in the remaining arguments. The result is a formatted string expression. Note that the format string must be a literal string.
A format string contains a replacement field within curly braces {…}. Anything that is not contained in braces is considered literal text. Literal braces can be escaped by doubling the brace character {{ and }}, respectively.
A format string has the following form: :
replacement_spec ::= "{" [<argument>] ["." <field>] ["[" <index> "]"] [":" format_spec] "}"
The single fields have the following meaning.
- argument
- A number denoting the argument passed to fmt. The first argument (after the format string) has number 0. This number can be used to refer to a specific argument. The same argument can be referred by multiple replacement fields:
"{0} {1} {0}".fmt(1, 0)
gives the string "1 0 1".
If no argument number is given, the replacement fields refer to the arguments passed to fmt in order. Note that this is an always-or-never option: either all replacement fields use explicit argument numbers or none.
- field
- If the argument is a structured type (e.g. a tuple), this specifies which field of the argument should be formatted, e.g.
"{0.x} {0.y}".fmt((x: 1, y:"foo"))
gives "1 foo".
- index
- If the argument is a sequence type the index refers to the elements of the sequence to be printed:
"<{[1]}>".fmt([23, 42, 81])
gives "<42>".
- format_spec
- This is the format specification for the argument as described in Formatting a single value: format.
Nested format strings
Format strings must be literal strings. Although this might be a restriction (format strings cannot be constructed during runtime), nested format strings give back a certain flexibility.
A nested format string is a format string in which the format specifier part of a replacement field contains further replacement fields, e.g.
"{:{}{}{}x}".fmt(66, ".", "^", 6)
Results in the string "..42..".
fmt allows exactly one nested level. Note that the resulting code is slightly more inefficient than without nesting (but only for those arguments that actually use nested fields), because after construction of the outer format specification, the format string must be parsed again at runtime. Furthermore, the constructed format string requires an additional temporary string.
The following example demonstrates how fmt together with array separators can be used to format a nested in array in a Matlab-like style:
"A=[{:6ga|;\n |, }]".fmt([[1.0,2.0,3.0], [4.0,5.0,6.0]])
results in
A=[ 1, 2, 3; 4, 5, 6]
How fmt works
The fmt macros transforms the format string and its arguments into a sequence of commands that build the resulting string. The format specifications are parsed and transformed into a Format structure at compile time so that no overhead remains at runtime. For instance, the following expression
"This {} the number {:_^3} example".fmt("is", 1)
is roughly transformed to
(let arg0 = "is"; let arg1 = 1; var ret = newString(0); addformat(ret, "This "); addformat(ret, arg0, DefaultFmt); addformat(ret, " the number "); addformat(ret, arg1, Format(...)); addformat(ret, " example "); ret)
(Note that this is a statement-list-expression). The functions addformat are defined within strfmt and add formatted output to the string ret.
String interpolation interp
Warning: This feature is highly experimental.
The interp macro interpolates a string with embedded expressions. If the string to be interpolated contains a $, then the following characters are interpreted as expressions.
let x = 2 let y = 1.0/3.0 echo interp"Equation: $x + ${y:.2f} == ${x.float + y}"
The macro interp supports the following interpolations expressions:
String | Meaning |
---|---|
$<ident> | The value of the variable denoted by <ident> is substituted into the string according to the default format for the respective type. |
${<expr>} | The expression <expr> is evaluated and its result is substituted into the string according to the default format of its type. |
${<expr>:<format>} | The expression <expr> is evaluated and its result is substituted into the string according to the format string <format>. The format string has the same structure as for the format function. |
$$ | A literal $ |
How interp works
The macro interp is quite simple. A string with embedded expressions is simply transformed to an equivalent expression using the fmt macro:
echo interp"Equation: $x + ${y:.2f} == ${x.float + y}"
is transformed to
echo fmt("Equation: {} + {:.2f} == {}", x, y, x.float + y)
Writing formatted output to a file: writefmt
The writefmt family of macros are convenience helpers to write formatted output to a file. A call
writefmt(f, fmtstr, arg1, arg2, ...)
is equivalent to
write(f, fmtstr.fmt(arg1, arg2, ...))
However, the former avoids the creation of temporary intermediate strings (the variable ret in the example above) but writes directly to the output file. The printfmt family of functions does the same but writes to stdout.
Adding new formatting functions
In order to add a new formatting function for a type T one has to define a new function
proc writeformat(o: var Writer; x: T; fmt: Format)
The following example defines a formatting function for a simple 2D-point data type. The format specification is used for formatting the two coordinate values.
type Point = tuple[x, y: float] proc writeformat*(o: var Writer; p: Point; fmt: Format) = write(o, '(') writeformat(o, p.x, fmt) write(o, ',') write(o, ' ') writeformat(o, p.y, fmt) write(o, ')')
Types
FormatError = object of Exception
- Error in the format string.
Writer = concept W ## Writer to output a character `c`. write(W, ' ')
FmtAlign = enum faDefault, ## default for given format type faLeft, ## left aligned faRight, ## right aligned faCenter, ## centered faPadding ## right aligned, fill characters after sign (numbers only)
- Format alignment
FmtSign = enum fsMinus, ## only unary minus, no reserved sign space for positive numbers fsPlus, ## unary minus and unary plus fsSpace ## unary minus and reserved space for positive numbers
- Format sign
FmtType = enum ftDefault, ## default format for given parameter type ftStr, ## string ftChar, ## character ftDec, ## decimal integer ftBin, ## binary integer ftOct, ## octal integer ftHex, ## hexadecimal integer ftFix, ## real number in fixed point notation ftSci, ## real number in scientific notation ftGen, ## real number in generic form (either fixed point or scientific) ftPercent ## real number multiplied by 100 and % added
- Format type
Format = tuple[typ: FmtType, ## format type precision: int, ## floating point precision width: int, ## minimal width fill: string, ## the fill character, UTF8 align: FmtAlign, ## alignment sign: FmtSign, ## sign notation baseprefix: bool, ## whether binary, octal, hex should be prefixed by 0b, 0x, 0o upcase: bool, ## upper case letters in hex or exponential formats comma: bool, arysep: string]
- Formatting information.
Consts
DefaultFmt: Format = (ftDefault, -1, -1, "", faDefault, fsMinus, false, false, false, "\t")
-
Default format corresponding to the empty format string, i.e.
x.format("") == x.format(DefaultFmt).
Procs
proc write(s: var string; c: char) {...}{.raises: [], tags: [].}
proc parse(fmt: string): Format {...}{.nosideeffect, raises: [FormatError, ValueError], tags: [].}
- Converts the format string fmt into a Format structure.
proc getalign(fmt: Format; defalign: FmtAlign; slen: int): tuple[left, right: int] {...}{. nosideeffect, raises: [], tags: [].}
-
Returns the number of left and right padding characters for a given format alignment and width of the object to be printed.
- fmt
- the format data
- default
- if fmt.align == faDefault, then this alignment is used
- slen
- the width of the object to be printed.
The returned values (left, right) will be as minimal as possible so that left + slen + right >= fmt.width.
proc writeformat(o: var Writer; s: string; fmt: Format)
- Write string s according to format fmt using output object o and output function add.
proc writeformat(o: var Writer; c: char; fmt: Format)
- Write character c according to format fmt using output object o and output function add.
proc writeformat(o: var Writer; c: Rune; fmt: Format)
- Write rune c according to format fmt using output object o and output function add.
proc writeformat(o: var Writer; i: SomeInteger; fmt: Format)
- Write integer i according to format fmt using output object o and output function add.
proc writeformat(o: var Writer; p: pointer; fmt: Format)
-
Write pointer i according to format fmt using output object o and output function add.
Pointers are casted to unsigned int and formatted as hexadecimal with prefix unless specified otherwise.
proc writeformat(o: var Writer; x: SomeFloat; fmt: Format)
- Write real number x according to format fmt using output object o and output function add.
proc writeformat(o: var Writer; b: bool; fmt: Format)
- Write boolean value b according to format fmt using output object o. A boolean may be formatted numerically or as string. In the former case true is written as 1 and false as 0, in the latter the strings "true" and "false" are shown, respectively. The default is string format.
proc writeformat(o: var Writer; ary: openArray[any]; fmt: Format)
- Write array ary according to format fmt using output object o and output function add.
proc addformat[T](o: var Writer; x: T; fmt: Format = DefaultFmt) {...}{.inline.}
- Write x formatted with fmt to o.
proc addformat[T](o: var Writer; x: T; fmt: string) {...}{.inline.}
- The same as addformat(o, x, parse(fmt)).
proc addformat(s: var string; x: string) {...}{.inline, raises: [], tags: [].}
- Write x to s. This is a fast specialized version for appending unformatted strings.
proc addformat(f: File; x: string) {...}{.inline, raises: [IOError], tags: [WriteIOEffect].}
- Write x to f. This is a fast specialized version for writing unformatted strings to a file.
proc addformat[T](f: File; x: T; fmt: Format = DefaultFmt) {...}{.inline.}
- Write x to file f using format fmt.
proc addformat[T](f: File; x: T; fmt: string) {...}{.inline.}
- Write x to file f using format string fmt. This is the same as addformat(f, x, parse(fmt))
proc addformat(s: Stream; x: string) {...}{.inline, raises: [Defect, IOError, OSError], tags: [WriteIOEffect].}
- Write x to s. This is a fast specialized version for writing unformatted strings to a stream.
proc addformat[T](s: Stream; x: T; fmt: Format = DefaultFmt) {...}{.inline.}
- Write x to stream s using format fmt.
proc addformat[T](s: Stream; x: T; fmt: string) {...}{.inline.}
- Write x to stream s using format string fmt. This is the same as addformat(s, x, parse(fmt))
proc format[T](x: T; fmt: Format): string
- Return x formatted as a string according to format fmt.
proc format[T](x: T; fmt: string): string
- Return x formatted as a string according to format string fmt.
proc format[T](x: T): string {...}{.inline.}
- Return x formatted as a string according to the default format. The default format corresponds to an empty format string.
Macros
macro fmt(fmtstr: string{lit}; args: varargs[untyped]): untyped
- Formats arguments args according to the format string fmtstr.
macro writefmt(f: File; fmtstr: string{lit}; args: varargs[untyped]): untyped
- The same as write(f, fmtstr.fmt(args...)) but faster.
macro writelnfmt(f: File; fmtstr: string{lit}; args: varargs[untyped]): untyped
- The same as writeln(f, fmtstr.fmt(args...)) but faster.
macro printfmt(fmtstr: string{lit}; args: varargs[untyped]): untyped
- The same as writefmt(stdout, fmtstr, args...).
macro printlnfmt(fmtstr: string{lit}; args: varargs[untyped]): untyped
- The same as writelnfmt(stdout, fmtstr, args...).
macro writefmt(s: Stream; fmtstr: string{lit}; args: varargs[untyped]): untyped
- The same as write(s, fmtstr.fmt(args...)) but faster.
macro writelnfmt(s: Stream; fmtstr: string{lit}; args: varargs[untyped]): untyped
- The same as writeln(s, fmtstr.fmt(args...)) but faster.
macro addfmt(s: var string; fmtstr: string{lit}; args: varargs[untyped]): untyped
- The same as s.add(fmtstr.fmt(args...)) but faster.
macro interp(fmtstr: string{lit}): untyped
- Interpolate fmtstr with expressions.
macro `$$`(fmtstr: string{lit}): untyped
- Interpolate fmtstr with expressions.