Improves documentation for strformat module.
* Adds description of the difference between ``fmt`` and ``&``. * Moves runnable examples to bottom of file. * Separates examples at the top of module better.
This commit is contained in:
parent
a6c7972086
commit
fa07bb1356
1 changed files with 192 additions and 149 deletions
|
|
@ -11,29 +11,78 @@
|
||||||
String `interpolation`:idx: / `format`:idx: inspired by
|
String `interpolation`:idx: / `format`:idx: inspired by
|
||||||
Python's ``f``-strings.
|
Python's ``f``-strings.
|
||||||
|
|
||||||
Examples:
|
``fmt`` vs. ``&``
|
||||||
|
=================
|
||||||
|
|
||||||
|
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 <manual.html#lexical-analysis-generalized-raw-string-literals>`_.
|
||||||
|
This has some surprising effects:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
|
import strformat
|
||||||
|
let msg = "hello"
|
||||||
|
doAssert fmt"{msg}\n" == "hello\\n"
|
||||||
|
|
||||||
|
Because the literal is a raw string literal, the ``\n`` is not interpreted as
|
||||||
|
an escape sequence.
|
||||||
|
|
||||||
|
There are multiple ways to get around this, including the use of the ``&``
|
||||||
|
operator:
|
||||||
|
|
||||||
|
.. code-block:: nim
|
||||||
|
|
||||||
|
import strformat
|
||||||
|
let msg = "hello"
|
||||||
|
|
||||||
|
doAssert &"{msg}\n" == "hello\n"
|
||||||
|
|
||||||
|
doAssert fmt"{msg}{'\n'}" == "hello\n"
|
||||||
|
doAssert fmt("{msg}\n") == "hello\n"
|
||||||
|
doAssert "{msg}\n".fmt == "hello\n"
|
||||||
|
|
||||||
|
The choice of style is up to you.
|
||||||
|
|
||||||
|
Formatting strings
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. code-block:: nim
|
||||||
|
|
||||||
|
import strformat
|
||||||
|
|
||||||
doAssert &"""{"abc":>4}""" == " abc"
|
doAssert &"""{"abc":>4}""" == " abc"
|
||||||
doAssert &"""{"abc":<4}""" == "abc "
|
doAssert &"""{"abc":<4}""" == "abc "
|
||||||
|
|
||||||
doAssert &"{-12345:08}" == "-0012345"
|
Formatting floats
|
||||||
doAssert &"{-1:3}" == " -1"
|
=================
|
||||||
doAssert &"{-1:03}" == "-01"
|
|
||||||
doAssert &"{16:#X}" == "0x10"
|
|
||||||
|
|
||||||
doAssert &"{123.456}" == "123.456"
|
.. code-block:: nim
|
||||||
doAssert &"{123.456:>9.3f}" == " 123.456"
|
|
||||||
doAssert &"{123.456:9.3f}" == " 123.456"
|
|
||||||
doAssert &"{123.456:9.4f}" == " 123.4560"
|
|
||||||
doAssert &"{123.456:>9.0f}" == " 123."
|
|
||||||
doAssert &"{123.456:<9.4f}" == "123.4560 "
|
|
||||||
|
|
||||||
doAssert &"{123.456:e}" == "1.234560e+02"
|
import strformat
|
||||||
doAssert &"{123.456:>13e}" == " 1.234560e+02"
|
|
||||||
doAssert &"{123.456:13e}" == " 1.234560e+02"
|
|
||||||
|
|
||||||
|
doAssert fmt"{-12345:08}" == "-0012345"
|
||||||
|
doAssert fmt"{-1:3}" == " -1"
|
||||||
|
doAssert fmt"{-1:03}" == "-01"
|
||||||
|
doAssert fmt"{16:#X}" == "0x10"
|
||||||
|
|
||||||
|
doAssert fmt"{123.456}" == "123.456"
|
||||||
|
doAssert fmt"{123.456:>9.3f}" == " 123.456"
|
||||||
|
doAssert fmt"{123.456:9.3f}" == " 123.456"
|
||||||
|
doAssert fmt"{123.456:9.4f}" == " 123.4560"
|
||||||
|
doAssert fmt"{123.456:>9.0f}" == " 123."
|
||||||
|
doAssert fmt"{123.456:<9.4f}" == "123.4560 "
|
||||||
|
|
||||||
|
doAssert fmt"{123.456:e}" == "1.234560e+02"
|
||||||
|
doAssert fmt"{123.456:>13e}" == " 1.234560e+02"
|
||||||
|
doAssert fmt"{123.456:13e}" == " 1.234560e+02"
|
||||||
|
|
||||||
|
|
||||||
|
Implementation details
|
||||||
|
======================
|
||||||
|
|
||||||
An expression like ``&"{key} is {value:arg} {{z}}"`` is transformed into:
|
An expression like ``&"{key} is {value:arg} {{z}}"`` is transformed into:
|
||||||
|
|
||||||
|
|
@ -86,8 +135,8 @@ For strings and numeric types the optional argument is a so-called
|
||||||
"standard format specifier".
|
"standard format specifier".
|
||||||
|
|
||||||
|
|
||||||
Standard format specifier
|
Standard format specifier for strings, integers and floats
|
||||||
=========================
|
==========================================================
|
||||||
|
|
||||||
|
|
||||||
The general form of a standard format specifier is::
|
The general form of a standard format specifier is::
|
||||||
|
|
@ -228,131 +277,6 @@ template callFormatOption(res, arg, option) {.dirty.} =
|
||||||
|
|
||||||
macro `&`*(pattern: string): untyped =
|
macro `&`*(pattern: string): untyped =
|
||||||
## For a specification of the ``&`` macro, see the module level documentation.
|
## For a specification of the ``&`` macro, see the module level documentation.
|
||||||
runnableExamples:
|
|
||||||
template check(actual, expected: string) =
|
|
||||||
doAssert actual == expected
|
|
||||||
|
|
||||||
from strutils import toUpperAscii, repeat
|
|
||||||
|
|
||||||
# Basic tests
|
|
||||||
let s = "string"
|
|
||||||
check &"{0} {s}", "0 string"
|
|
||||||
check &"{s[0..2].toUpperAscii}", "STR"
|
|
||||||
check &"{-10:04}", "-010"
|
|
||||||
check &"{-10:<04}", "-010"
|
|
||||||
check &"{-10:>04}", "-010"
|
|
||||||
check &"0x{10:02X}", "0x0A"
|
|
||||||
|
|
||||||
check &"{10:#04X}", "0x0A"
|
|
||||||
|
|
||||||
check &"""{"test":#>5}""", "#test"
|
|
||||||
check &"""{"test":>5}""", " test"
|
|
||||||
|
|
||||||
check &"""{"test":#^7}""", "#test##"
|
|
||||||
|
|
||||||
check &"""{"test": <5}""", "test "
|
|
||||||
check &"""{"test":<5}""", "test "
|
|
||||||
check &"{1f:.3f}", "1.000"
|
|
||||||
check &"Hello, {s}!", "Hello, string!"
|
|
||||||
|
|
||||||
# Tests for identifers without parenthesis
|
|
||||||
check &"{s} works{s}", "string worksstring"
|
|
||||||
check &"{s:>7}", " string"
|
|
||||||
doAssert(not compiles(&"{s_works}")) # parsed as identifier `s_works`
|
|
||||||
|
|
||||||
# Misc general tests
|
|
||||||
check &"{{}}", "{}"
|
|
||||||
check &"{0}%", "0%"
|
|
||||||
check &"{0}%asdf", "0%asdf"
|
|
||||||
check &("\n{\"\\n\"}\n"), "\n\n\n"
|
|
||||||
check &"""{"abc"}s""", "abcs"
|
|
||||||
|
|
||||||
# String tests
|
|
||||||
check &"""{"abc"}""", "abc"
|
|
||||||
check &"""{"abc":>4}""", " abc"
|
|
||||||
check &"""{"abc":<4}""", "abc "
|
|
||||||
check &"""{"":>4}""", " "
|
|
||||||
check &"""{"":<4}""", " "
|
|
||||||
|
|
||||||
# Int tests
|
|
||||||
check &"{12345}", "12345"
|
|
||||||
check &"{ - 12345}", "-12345"
|
|
||||||
check &"{12345:6}", " 12345"
|
|
||||||
check &"{12345:>6}", " 12345"
|
|
||||||
check &"{12345:4}", "12345"
|
|
||||||
check &"{12345:08}", "00012345"
|
|
||||||
check &"{-12345:08}", "-0012345"
|
|
||||||
check &"{0:0}", "0"
|
|
||||||
check &"{0:02}", "00"
|
|
||||||
check &"{-1:3}", " -1"
|
|
||||||
check &"{-1:03}", "-01"
|
|
||||||
check &"{10}", "10"
|
|
||||||
check &"{16:#X}", "0x10"
|
|
||||||
check &"{16:^#7X}", " 0x10 "
|
|
||||||
check &"{16:^+#7X}", " +0x10 "
|
|
||||||
|
|
||||||
# Hex tests
|
|
||||||
check &"{0:x}", "0"
|
|
||||||
check &"{-0:x}", "0"
|
|
||||||
check &"{255:x}", "ff"
|
|
||||||
check &"{255:X}", "FF"
|
|
||||||
check &"{-255:x}", "-ff"
|
|
||||||
check &"{-255:X}", "-FF"
|
|
||||||
check &"{255:x} uNaffeCteD CaSe", "ff uNaffeCteD CaSe"
|
|
||||||
check &"{255:X} uNaffeCteD CaSe", "FF uNaffeCteD CaSe"
|
|
||||||
check &"{255:4x}", " ff"
|
|
||||||
check &"{255:04x}", "00ff"
|
|
||||||
check &"{-255:4x}", " -ff"
|
|
||||||
check &"{-255:04x}", "-0ff"
|
|
||||||
|
|
||||||
# Float tests
|
|
||||||
check &"{123.456}", "123.456"
|
|
||||||
check &"{-123.456}", "-123.456"
|
|
||||||
check &"{123.456:.3f}", "123.456"
|
|
||||||
check &"{123.456:+.3f}", "+123.456"
|
|
||||||
check &"{-123.456:+.3f}", "-123.456"
|
|
||||||
check &"{-123.456:.3f}", "-123.456"
|
|
||||||
check &"{123.456:1g}", "123.456"
|
|
||||||
check &"{123.456:.1f}", "123.5"
|
|
||||||
check &"{123.456:.0f}", "123."
|
|
||||||
#check &"{123.456:.0f}", "123."
|
|
||||||
check &"{123.456:>9.3f}", " 123.456"
|
|
||||||
check &"{123.456:9.3f}", " 123.456"
|
|
||||||
check &"{123.456:>9.4f}", " 123.4560"
|
|
||||||
check &"{123.456:>9.0f}", " 123."
|
|
||||||
check &"{123.456:<9.4f}", "123.4560 "
|
|
||||||
|
|
||||||
# Float (scientific) tests
|
|
||||||
check &"{123.456:e}", "1.234560e+02"
|
|
||||||
check &"{123.456:>13e}", " 1.234560e+02"
|
|
||||||
check &"{123.456:<13e}", "1.234560e+02 "
|
|
||||||
check &"{123.456:.1e}", "1.2e+02"
|
|
||||||
check &"{123.456:.2e}", "1.23e+02"
|
|
||||||
check &"{123.456:.3e}", "1.235e+02"
|
|
||||||
|
|
||||||
# Note: times.format adheres to the format protocol. Test that this
|
|
||||||
# works:
|
|
||||||
import times
|
|
||||||
|
|
||||||
var nullTime: DateTime
|
|
||||||
check &"{nullTime:yyyy-mm-dd}", "0000-00-00"
|
|
||||||
|
|
||||||
# Unicode string tests
|
|
||||||
check &"""{"αβγ"}""", "αβγ"
|
|
||||||
check &"""{"αβγ":>5}""", " αβγ"
|
|
||||||
check &"""{"αβγ":<5}""", "αβγ "
|
|
||||||
check &"""a{"a"}α{"α"}€{"€"}𐍈{"𐍈"}""", "aaαα€€𐍈𐍈"
|
|
||||||
check &"""a{"a":2}α{"α":2}€{"€":2}𐍈{"𐍈":2}""", "aa αα €€ 𐍈𐍈 "
|
|
||||||
# Invalid unicode sequences should be handled as plain strings.
|
|
||||||
# Invalid examples taken from: https://stackoverflow.com/a/3886015/1804173
|
|
||||||
let invalidUtf8 = [
|
|
||||||
"\xc3\x28", "\xa0\xa1",
|
|
||||||
"\xe2\x28\xa1", "\xe2\x82\x28",
|
|
||||||
"\xf0\x28\x8c\xbc", "\xf0\x90\x28\xbc", "\xf0\x28\x8c\x28"
|
|
||||||
]
|
|
||||||
for s in invalidUtf8:
|
|
||||||
check &"{s:>5}", repeat(" ", 5-s.len) & s
|
|
||||||
|
|
||||||
if pattern.kind notin {nnkStrLit..nnkTripleStrLit}:
|
if pattern.kind notin {nnkStrLit..nnkTripleStrLit}:
|
||||||
error "& only works with string literals", pattern
|
error "& only works with string literals", pattern
|
||||||
let f = pattern.strVal
|
let f = pattern.strVal
|
||||||
|
|
@ -409,14 +333,6 @@ macro `&`*(pattern: string): untyped =
|
||||||
|
|
||||||
template fmt*(pattern: string): untyped =
|
template fmt*(pattern: string): untyped =
|
||||||
## An alias for ``&``.
|
## An alias for ``&``.
|
||||||
## **Examples:**
|
|
||||||
##
|
|
||||||
## .. code-block:: nim
|
|
||||||
## import json
|
|
||||||
## import strformat except `&`
|
|
||||||
##
|
|
||||||
## let example = "oh, look no conflicts anymore"
|
|
||||||
## echo fmt"{example}"
|
|
||||||
bind `&`
|
bind `&`
|
||||||
&pattern
|
&pattern
|
||||||
|
|
||||||
|
|
@ -633,6 +549,133 @@ proc format*(value: string; specifier: string; res: var string) =
|
||||||
res.add alignString(value, spec.minimumWidth, spec.align, spec.fill)
|
res.add alignString(value, spec.minimumWidth, spec.align, spec.fill)
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
|
template check(actual, expected: string) =
|
||||||
|
doAssert actual == expected
|
||||||
|
|
||||||
|
from strutils import toUpperAscii, repeat
|
||||||
|
|
||||||
|
# Basic tests
|
||||||
|
let s = "string"
|
||||||
|
check &"{0} {s}", "0 string"
|
||||||
|
check &"{s[0..2].toUpperAscii}", "STR"
|
||||||
|
check &"{-10:04}", "-010"
|
||||||
|
check &"{-10:<04}", "-010"
|
||||||
|
check &"{-10:>04}", "-010"
|
||||||
|
check &"0x{10:02X}", "0x0A"
|
||||||
|
|
||||||
|
check &"{10:#04X}", "0x0A"
|
||||||
|
|
||||||
|
check &"""{"test":#>5}""", "#test"
|
||||||
|
check &"""{"test":>5}""", " test"
|
||||||
|
|
||||||
|
check &"""{"test":#^7}""", "#test##"
|
||||||
|
|
||||||
|
check &"""{"test": <5}""", "test "
|
||||||
|
check &"""{"test":<5}""", "test "
|
||||||
|
check &"{1f:.3f}", "1.000"
|
||||||
|
check &"Hello, {s}!", "Hello, string!"
|
||||||
|
|
||||||
|
# Tests for identifers without parenthesis
|
||||||
|
check &"{s} works{s}", "string worksstring"
|
||||||
|
check &"{s:>7}", " string"
|
||||||
|
doAssert(not compiles(&"{s_works}")) # parsed as identifier `s_works`
|
||||||
|
|
||||||
|
# Misc general tests
|
||||||
|
check &"{{}}", "{}"
|
||||||
|
check &"{0}%", "0%"
|
||||||
|
check &"{0}%asdf", "0%asdf"
|
||||||
|
check &("\n{\"\\n\"}\n"), "\n\n\n"
|
||||||
|
check &"""{"abc"}s""", "abcs"
|
||||||
|
|
||||||
|
# String tests
|
||||||
|
check &"""{"abc"}""", "abc"
|
||||||
|
check &"""{"abc":>4}""", " abc"
|
||||||
|
check &"""{"abc":<4}""", "abc "
|
||||||
|
check &"""{"":>4}""", " "
|
||||||
|
check &"""{"":<4}""", " "
|
||||||
|
|
||||||
|
# Int tests
|
||||||
|
check &"{12345}", "12345"
|
||||||
|
check &"{ - 12345}", "-12345"
|
||||||
|
check &"{12345:6}", " 12345"
|
||||||
|
check &"{12345:>6}", " 12345"
|
||||||
|
check &"{12345:4}", "12345"
|
||||||
|
check &"{12345:08}", "00012345"
|
||||||
|
check &"{-12345:08}", "-0012345"
|
||||||
|
check &"{0:0}", "0"
|
||||||
|
check &"{0:02}", "00"
|
||||||
|
check &"{-1:3}", " -1"
|
||||||
|
check &"{-1:03}", "-01"
|
||||||
|
check &"{10}", "10"
|
||||||
|
check &"{16:#X}", "0x10"
|
||||||
|
check &"{16:^#7X}", " 0x10 "
|
||||||
|
check &"{16:^+#7X}", " +0x10 "
|
||||||
|
|
||||||
|
# Hex tests
|
||||||
|
check &"{0:x}", "0"
|
||||||
|
check &"{-0:x}", "0"
|
||||||
|
check &"{255:x}", "ff"
|
||||||
|
check &"{255:X}", "FF"
|
||||||
|
check &"{-255:x}", "-ff"
|
||||||
|
check &"{-255:X}", "-FF"
|
||||||
|
check &"{255:x} uNaffeCteD CaSe", "ff uNaffeCteD CaSe"
|
||||||
|
check &"{255:X} uNaffeCteD CaSe", "FF uNaffeCteD CaSe"
|
||||||
|
check &"{255:4x}", " ff"
|
||||||
|
check &"{255:04x}", "00ff"
|
||||||
|
check &"{-255:4x}", " -ff"
|
||||||
|
check &"{-255:04x}", "-0ff"
|
||||||
|
|
||||||
|
# Float tests
|
||||||
|
check &"{123.456}", "123.456"
|
||||||
|
check &"{-123.456}", "-123.456"
|
||||||
|
check &"{123.456:.3f}", "123.456"
|
||||||
|
check &"{123.456:+.3f}", "+123.456"
|
||||||
|
check &"{-123.456:+.3f}", "-123.456"
|
||||||
|
check &"{-123.456:.3f}", "-123.456"
|
||||||
|
check &"{123.456:1g}", "123.456"
|
||||||
|
check &"{123.456:.1f}", "123.5"
|
||||||
|
check &"{123.456:.0f}", "123."
|
||||||
|
#check &"{123.456:.0f}", "123."
|
||||||
|
check &"{123.456:>9.3f}", " 123.456"
|
||||||
|
check &"{123.456:9.3f}", " 123.456"
|
||||||
|
check &"{123.456:>9.4f}", " 123.4560"
|
||||||
|
check &"{123.456:>9.0f}", " 123."
|
||||||
|
check &"{123.456:<9.4f}", "123.4560 "
|
||||||
|
|
||||||
|
# Float (scientific) tests
|
||||||
|
check &"{123.456:e}", "1.234560e+02"
|
||||||
|
check &"{123.456:>13e}", " 1.234560e+02"
|
||||||
|
check &"{123.456:<13e}", "1.234560e+02 "
|
||||||
|
check &"{123.456:.1e}", "1.2e+02"
|
||||||
|
check &"{123.456:.2e}", "1.23e+02"
|
||||||
|
check &"{123.456:.3e}", "1.235e+02"
|
||||||
|
|
||||||
|
# Note: times.format adheres to the format protocol. Test that this
|
||||||
|
# works:
|
||||||
|
import times
|
||||||
|
|
||||||
|
var nullTime: DateTime
|
||||||
|
check &"{nullTime:yyyy-mm-dd}", "0000-00-00"
|
||||||
|
|
||||||
|
# Unicode string tests
|
||||||
|
check &"""{"αβγ"}""", "αβγ"
|
||||||
|
check &"""{"αβγ":>5}""", " αβγ"
|
||||||
|
check &"""{"αβγ":<5}""", "αβγ "
|
||||||
|
check &"""a{"a"}α{"α"}€{"€"}𐍈{"𐍈"}""", "aaαα€€𐍈𐍈"
|
||||||
|
check &"""a{"a":2}α{"α":2}€{"€":2}𐍈{"𐍈":2}""", "aa αα €€ 𐍈𐍈 "
|
||||||
|
# Invalid unicode sequences should be handled as plain strings.
|
||||||
|
# Invalid examples taken from: https://stackoverflow.com/a/3886015/1804173
|
||||||
|
let invalidUtf8 = [
|
||||||
|
"\xc3\x28", "\xa0\xa1",
|
||||||
|
"\xe2\x28\xa1", "\xe2\x82\x28",
|
||||||
|
"\xf0\x28\x8c\xbc", "\xf0\x90\x28\xbc", "\xf0\x28\x8c\x28"
|
||||||
|
]
|
||||||
|
for s in invalidUtf8:
|
||||||
|
check &"{s:>5}", repeat(" ", 5-s.len) & s
|
||||||
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
doAssert fmt"{'a'} {'b'}" == "a b"
|
doAssert fmt"{'a'} {'b'}" == "a b"
|
||||||
|
|
||||||
|
echo("All tests ok")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue