PgBiel / typst-oxifmt

Convenient Rust-like string formatting in Typst (previously "typst-strfmt")
MIT No Attribution
26 stars 0 forks source link
string-formatting string-manipulation typst

typst-oxifmt (v0.2.1)

A Typst library that brings convenient string formatting and interpolation through the strfmt function. Its syntax is taken directly from Rust's format! syntax, so feel free to read its page for more information (https://doc.rust-lang.org/std/fmt/); however, this README should have enough information and examples for all expected uses of the library. Only a few things aren't supported from the Rust syntax, such as the p (pointer) format type, or the .* precision specifier.

A few extras (beyond the Rust-like syntax) will be added over time, though (feel free to drop suggestions at the repository: https://github.com/PgBiel/typst-oxifmt). The first "extra" so far is the fmt-decimal-separator: "string" parameter, which lets you customize the decimal separator for decimal numbers (floats) inserted into strings. E.g. strfmt("Result: {}", 5.8, fmt-decimal-separator: ",") will return the string "Result: 5,8" (comma instead of dot). See more below.

Compatible with: Typst v0.4.0+

Table of Contents

Usage

You can use this library through Typst's package manager (for Typst v0.6.0+):

#import "@preview/oxifmt:0.2.1": strfmt

For older Typst versions, download the oxifmt.typ file either from Releases or directly from the repository. Then, move it to your project's folder, and write at the top of your Typst file(s):

#import "oxifmt.typ": strfmt

Doing the above will give you access to the main function provided by this library (strfmt), which accepts a format string, followed by zero or more replacements to insert in that string (according to {...} formats inserted in that string), an optional fmt-decimal-separator parameter, and returns the formatted string, as described below.

Its syntax is almost identical to Rust's format! (as specified here: https://doc.rust-lang.org/std/fmt/). You can escape formats by duplicating braces ({{ and }} become { and }). Here's an example (see more examples in the file tests/strfmt-tests.typ):

#import "@preview/oxifmt:0.2.1": strfmt

#let s = strfmt("I'm {}. I have {num} cars. I'm {0}. {} is {{cool}}.", "John", "Carl", num: 10)
#assert.eq(s, "I'm John. I have 10 cars. I'm John. Carl is {cool}.")

Note that {} extracts positional arguments after the string sequentially (the first {} extracts the first one, the second {} extracts the second one, and so on), while {0}, {1}, etc. will always extract the first, the second etc. positional arguments after the string. Additionally, {bananas} will extract the named argument "bananas".

Formatting options

You can use {:spec} to customize your output. See the Rust docs linked above for more info, but a summary is below.

(You may also want to check out the examples at Examples.)

Some examples:

#import "@preview/oxifmt:0.2.1": strfmt

#let s1 = strfmt("{0:?}, {test:+012e}, {1:-<#8x}", "hi", -74, test: 569.4)
#assert.eq(s1, "\"hi\", +00005.694e2, -0x4a---")

#let s2 = strfmt("{:_>+11.5}", 59.4)
#assert.eq(s2, "__+59.40000")

#let s3 = strfmt("Dict: {:!<10?}", (a: 5))
#assert.eq(s3, "Dict: (a: 5)!!!!")

Examples

let s = strfmt("First: {}, Second: {}, Fourth: {3}, Banana: {banana} (brackets: {{escaped}})", 1, 2.1, 3, label("four"), banana: "Banana!!")

assert.eq(s, "First: 1, Second: 2.1, Fourth: four, Banana: Banana!! (brackets: {escaped})")


- **Forcing `repr()` with `{:?}`** (which adds quotes around strings, and other things - basically represents a Typst value):
```typ
#import "@preview/oxifmt:0.2.1": strfmt

#let s = strfmt("The value is: {:?} | Also the label is {:?}", "something", label("label"))
#assert.eq(s, "The value is: \"something\" | Also the label is <label>")

let s = strfmt("Values: {:?}, {1:?}, {stuff:?}", (test: 500), ("a", 5.1), stuff: [a])

assert.eq(s, "Values: (test: 500), (\"a\", 5.1), [a]")


- **Padding to a certain width with characters:** Use `{:x<8}`, where `x` is the **character to pad with** (e.g. space or `_`, but can be anything), `<` is the **alignment of the original text** relative to the padding (can be `<` for left aligned (padding goes to the right), `>` for right aligned (padded to its left) and `^` for center aligned (padded at both left and right)), and `8` is the **desired total width** (padding will add enough characters to reach this width; if the replacement string already has this width, no padding will be added):
```typ
#import "@preview/oxifmt:0.2.1": strfmt

#let s = strfmt("Left5 {:-<5}, Right6 {:=>6}, Center10 {centered: ^10?}, Left3 {tleft:_<3}", "xx", 539, tleft: "okay", centered: [a])
#assert.eq(s, "Left5 xx---, Right6 ===539, Center10     [a]    , Left3 okay")
// note how 'okay' didn't suffer any padding at all (it already had at least the desired total width).

let s = strfmt("Left-padded7 numbers: {:07} {:07} {:07} {3:07}", 123, -344, 44224059, 45.32)

assert.eq(s, "Left-padded7 numbers: 0000123 -000344 44224059 0045.32")


- **Defining padding-to width using parameters, not literals:** If you want the desired replacement width (the `8` in `{:08}` or `{: ^8}`) to be passed via parameter (instead of being hardcoded into the format string), you can specify `parameter$` in place of the width, e.g. `{:02$}` to take it from the third positional parameter, or `{:a>banana$}` to take it from the parameter named `banana` - note that the chosen parameter **must be an integer** (desired total width):
```typ
#import "@preview/oxifmt:0.2.1": strfmt

#let s = strfmt("Padding depending on parameter: {0:02$} and {0:a>banana$}", 432, 0, 5, banana: 9)
#assert.eq(s, "Padding depending on parameter: 00432 aaaaaa432")  // widths 5 and 9

let s = strfmt("Some numbers: {:+} {:+08}; With fill and align: {:_<+8}; Negative (no-op): {neg:+}", 123, 456, 4444, neg: -435)

assert.eq(s, "Some numbers: +123 +0000456; With fill and align: +4444___; Negative (no-op): -435")


- **Converting numbers to bases 2, 8 and 16:** Use one of the following specifier types (i.e., characters which always go at the very end of the format): `b` (binary), `o` (octal), `x` (lowercase hexadecimal) or `X` (uppercase hexadecimal). You can also add a `#` between `+` and `0` (see the exact position at the [Grammar](#grammar)) to display a **base prefix** before the number (i.e. `0b` for binary, `0o` for octal and `0x` for hexadecimal):
```typ
#import "@preview/oxifmt:0.2.1": strfmt

#let s = strfmt("Bases (10, 2, 8, 16(l), 16(U):) {0} {0:b} {0:o} {0:x} {0:X} | W/ prefixes and modifiers: {0:#b} {0:+#09o} {0:_>+#9X}", 124)
#assert.eq(s, "Bases (10, 2, 8, 16(l), 16(U):) 124 1111100 174 7c 7C | W/ prefixes and modifiers: 0b1111100 +0o000174 ____+0x7C")

let s = strfmt("{0:.8} {0:.2$} {0:.potato$}", 1.234, 0, 2, potato: 5)

assert.eq(s, "1.23400000 1.23 1.23400")


- **Scientific notation:** Use `e` (lowercase) or `E` (uppercase) as specifier types (can be combined with precision):
```typ
#import "@preview/oxifmt:0.2.1": strfmt

#let s = strfmt("{0:e} {0:E} {0:+.9e} | {1:e} | {2:.4E}", 124.2312, 50, -0.02)
#assert.eq(s, "1.242312e2 1.242312E2 +1.242312000e2 | 5e1 | -2.0000E-2")

let s = strfmt("{0} {0:.6} {0:.5e}", 1.432, fmt-decimal-separator: ",")

assert.eq(s, "1,432 1,432000 1,43200e0")


### Grammar

Here's the grammar specification for valid format `spec`s (in `{name:spec}`), which is basically Rust's format:

format_spec := [[fill]align][sign]['#']['0'][width]['.' precision]type fill := character align := '<' | '^' | '>' sign := '+' | '-' width := count precision := count | '*' type := '' | '?' | 'x?' | 'X?' | identifier count := parameter | integer parameter := argument '$'


Note, however, that precision of type `.*` is not supported yet and will raise an error.

## Issues and Contributing

Please report any issues or send any contributions (through pull requests) to the repository at https://github.com/PgBiel/typst-oxifmt

## Testing

If you wish to contribute, you may clone the repository and test this package with the following commands (from the project root folder):

```sh
git clone https://github.com/PgBiel/typst-oxifmt
cd typst-oxifmt/tests
typst c strfmt-tests.typ --root ..

The tests succeeded if you received no error messages from the last command (please ensure you're using a supported Typst version).

Changelog

v0.2.1

v0.2.0

v0.1.0

License

MIT-0 license (see the LICENSE file).