Fmt
An immutable sequence of literal text, bound interpolations, and unbound
parameters, produced by a
t"..." string.
Every segment is a Str of literal text, a
FmtValue interpolation, or a FmtParam
still waiting to be filled. This type behaves like an immutable array:
indexable, iterable, spreadable, and destructurable.
Trust
A sequence written as t"..." carries a guarantee: its Str segments are
program text, and everything interpolated into it is a FmtValue or
FmtParam. That is what lets a consumer treat the literal segments as the
trusted skeleton of a command and each interpolation as data to be bound or
quoted. sqlite is a prime example, as it relies on
this guarantee to exclude SQL injection vulnerabilities.
Toward that end, no conversion implicitly expands a sequence. str and
plain interpolation ("$seq", "${seq}", "${seq:s}") raise a
TypeError; verbatim and dbg give the source form
This avoids accidental expansion before the sequences reaches its consumer
for safe structural quoting or binding of interpolations.
format() must be used to explicitly expand a sequence.
Building a sequence yourself must uphold this guarantee. The
constructor accepts whatever segments it is given, so a
Str built from untrusted input would subsequently be treated as trusted.
Programmatic construction of Fmt must be done with care.
# Fine: the query text is literal, the value is bound.
let query = t"select * from users where name = $name"
# Fine: assembled at runtime, but the untrusted value is still bound.
let assembled = Fmt ["select * from users where name = ", (FmtValue name)]
# Danger: untrusted input is treated as a trusted string literal
let injected = Fmt ["select * from users where name = ", name]
dbg gives the source form, reconstructed from the literal text and each
interpolation's source. An interpolation built at
runtime has no source, so its bound value's debug form stands in.
verbatim — and with it the ! conversion and command-argument position —
gives the same source form, since that is what the sequence was written as.
str and the display conversion raise a TypeError.
format() is the only expansion. See Trust.
let count = 3
assert_eq (dbg t"n=${count:03d}") r#"t"n=${count:03d}""#
assert_eq (verbatim t"n=${count:03d}") (dbg t"n=${count:03d}")
Example
let user = "root"
let seq = t"user: ${user:>8}"
# Expanding gives what the equivalent `"..."` would have.
assert_eq $seq.format() "user: root"
# The interpolated value is still there to be inspected.
assert_eq $seq[1].value $user
assert_eq $seq[1].width 8
Implements: Index[{...Int: FmtSegment, ...Range[Int]: Array[FmtSegment]}], Spread[FmtSegment], Unpack[FmtSegment]
Constructor
Fmt segments
Builds a sequence from an iterable of segments.
Writing a t"..." is the usual way to get one; the constructor is for
assembling a sequence at runtime.
Errors
| Exception | Condition |
|---|---|
TypeError |
A segment is not a Str, FmtValue, or FmtParam |
Example
Methods
(eq) other
Two sequences are equal when their segments are. How a sequence was assembled
does not show. The text each interpolation records does: a segment carries its
source, so t"$name" and t"${name}" are not
equal.
(fmt)()
dbg gives the source form, reconstructed from the literal text and each
interpolation's source. An interpolation built at
runtime has no source, so its bound value's debug form stands in.
verbatim — and with it the ! conversion and command-argument position —
gives the same source form, since that is what the sequence was written as.
str and the display conversion raise a TypeError.
format() is the only expansion. See Trust.
let count = 3
assert_eq (dbg t"n=${count:03d}") r#"t"n=${count:03d}""#
assert_eq (verbatim t"n=${count:03d}") (dbg t"n=${count:03d}")
(index) index
seq[i] returns segment i: a Str of literal text, a
FmtValue, or a FmtParam.
let greeting = t"hello $name!"
assert_eq $greeting[0] "hello "
assert_eq $greeting[1].source r"$name"
Assigning to a segment raises an ImmutableError.
(iter)()
Iterating yields the segments in order, so a consumer can decide what to do with each.
The console is the worked example of such a consumer:
term walks a sequence segment by segment rather
than converting it, which is what lets styling interpolated into one survive
to the terminal.
format ...bindings -> Str
Expands the sequence and returns the result: literal text as it stands, and each interpolation through its own specification. This is the only way to get the expansion — see Trust.
Given arguments, it fills the parameters first. Positional
argument i fills ${#i}, and a keyword argument fills ${#name}:
- Holes inside a bound
Fmtare filled too, depth first. Names are never renumbered, so a nested${#0}is filled by argument0as well. - A filled hole keeps the parameter's specification and
source. - A hole left unfilled, or an argument no hole consumed, is an error.
Errors
| Exception | Condition |
|---|---|
MissingPosError |
A numbered parameter unfilled |
MissingKeyError |
A named parameter unfilled |
UnexpectedPosError |
A positional argument unused |
UnexpectedKeyError |
A keyword argument unused |
Example
let count = 3
assert_eq $(t"n=${count:03d}").format() "n=003"
let stmt = t"select * from t where a = ${#0} and c = ${#name}"
assert_eq $stmt.format(1, name: "n") "select * from t where a = 1 and c = n"
len() -> Int
The number of segments.