Fmt[V, S @ {*, *(Int | Sym): Value}]
Preserves literal text, bound interpolations, and unbound parameters from a
t"..." string.
V is the union of the interpolated values' types, and S is the schema of
the arguments that format needs to fill the sequence's
parameters, by position or by name. Since a sequence is immutable, one whose
values have narrower types can stand in for one whose values are wider.
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, so it can't
be expanded by accident before it reaches its consumer. str and plain
interpolation ("$seq", "${seq}", "${seq:s}") raise a
TypeError. dbg and verbatim, and so the ! conversion
and command arguments, give 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.
format() is the only expansion.
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}")
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]
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.
let std.FmtValue(segment) = seq[1]
assert_eq $segment.value $user
assert_eq $segment.width 8
Implements: Index[FmtSegment[V]], Spread[FmtSegment[V]], Unpack[FmtSegment[V], Rest: BaseSeq[FmtSegment[V]]]
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. The type checker doesn't know which
parameters the result has.
Parameters
| Name | Type | Description |
|---|---|---|
segments |
Iterable[FmtSegment[V]] |
Errors
| Exception | Condition |
|---|---|
TypeError |
A segment is not a Str, FmtValue, or FmtParam |
Example
Fields
len @ Int[get]The number of segments.
Methods
(eq) other -> Bool
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.
Parameters
| Name | Type | Description |
|---|---|---|
other |
Value |
(fmt) spec -> Str
Formats the sequence's source form for dbg and verbatim. Display
formatting raises TypeError; see
Trust.
Parameters
| Name | Type | Description |
|---|---|---|
spec |
FmtSpec |
(index)[K] key -> IndexItem[{*FmtSegment[V]}, K] overload 1 of 2
Returns a segment: 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.
Parameters
| Name | Type | Description |
|---|---|---|
key |
K |
(iter)() -> UnpackIter[FmtSegment[V]]
Returns an iterator over 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.
(spread)() -> Spread[FmtSegment[V]]
Spreads the segments.
(unpack)() -> Unpack[FmtSegment[V], Rest: BaseSeq[FmtSegment[V]]]
Destructures the segments positionally.
format[B @ {*Value, **Value}] ...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, by the same arguments: a nested${#0}is filled by argument0as well. The type checker sees only the outer holes, so it may report an argument for a nested hole as unused. - A filled hole keeps the parameter's specification and
source. - A hole left unfilled, or an argument no hole consumed, is an error.
Parameters
| Name | Type | Description |
|---|---|---|
self |
Fmt[V, B] |
Receiver. |
...bindings |
B |
Errors
| Exception | Condition |
|---|---|
MissingPosError |
A numbered parameter unfilled |
MissingKeyError |
A named parameter unfilled |
UnexpectedPosError |
A positional argument unused |
UnexpectedKeyError |
A keyword argument unused |