这是indexloc提供的服务,不要输入任何密码
Skip to content

Release 0.3.0 #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MIT OR Apache-2.0
101 changes: 83 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,44 @@
# typst-oxifmt (v0.2.1)
# typst-oxifmt (v0.3.0)

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 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. Check out the ["Examples" section](#examples) for more.

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.
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). We also provide thousands separator support with `fmt-thousands-separator: "_"` for example. See more at ["Custom options"](#custom-options).

**Compatible with:** [Typst](https://github.com/typst/typst) v0.4.0+
**Compatible with:** [Typst](https://github.com/typst/typst) v0.7.0+

## Quick examples

```typ
#import "@preview/oxifmt:0.3.0": strfmt

// "User John has 10 apples."
#strfmt("User {} has {} apples.", "John", 10)

// "if exp > 100 { true }"
#strfmt("if {var} > {num} {{ true }}", var: "exp", num: 100)

// "1.10e2 meters"
#strfmt("{:.2e} meters", 110.0)

// "20_000 players have more than +002,300 points."
#strfmt(
"{} players have more than {:+08.3} points.",
20000,
2.3,
fmt-decimal-separator: ",",
fmt-thousands-separator: "_"
)

// "The byte value is 0x8C or 10001100"
#strfmt("The byte value is {:#02X} or {0:08b}", 140)
```

## Table of Contents

- [Usage](#usage)
- [Formatting options](#formatting-options)
- [Examples](#examples)
- [Custom options](#custom-options)
- [Grammar](#grammar)
- [Issues and Contributing](#issues-and-contributing)
- [Testing](#testing)
Expand All @@ -22,7 +50,7 @@ A few extras (beyond the Rust-like syntax) will be added over time, though (feel
You can use this library through Typst's package manager (for Typst v0.6.0+):

```typ
#import "@preview/oxifmt:0.2.1": strfmt
#import "@preview/oxifmt:0.3.0": 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):
Expand All @@ -36,7 +64,7 @@ Doing the above will give you access to the main function provided by this libra
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`):

```typ
#import "@preview/oxifmt:0.2.1": strfmt
#import "@preview/oxifmt:0.3.0": 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}.")
Expand Down Expand Up @@ -67,14 +95,16 @@ You can use `{:spec}` to customize your output. See the Rust docs linked above f
- Add `e` or `E` at the end of the `spec` to ensure the number is represented in scientific notation (with `e` or `E` as the exponent separator, respectively).
- For decimal numbers (floats), you can specify `fmt-decimal-separator: ","` to `strfmt` to have the decimal separator be a comma instead of a dot, for example.
- To have this be the default, you can alias `strfmt`, such as using `#let strfmt = strfmt.with(fmt-decimal-separator: ",")`.
- You can enable thousands separators for numbers with `fmt-thousands-separator: "_"` to separate with an underscore, for example.
- By default, thousands separators are inserted after every third digit from the end of the number. Use `fmt-thousands-count: 2` to change that to every second digit as an example.
- Number spec arguments (such as `.5`) are ignored when the argument is not a number, but e.g. a string, even if it looks like a number (such as `"5"`).
- Note that all spec arguments above **have to be specified in order** - if you mix up the order, it won't work properly!
- Check the grammar below for the proper order, but, in summary: fill (character) with align (`<`, `>` or `^`) -> sign (`+` or `-`) -> `#` -> `0` (for 0 left-padding of numbers) -> width (e.g. `8` from `08` or `9` from `-<9`) -> `.precision` -> spec type (`?`, `x`, `X`, `b`, `o`, `e`, `E`)).

Some examples:

```typ
#import "@preview/oxifmt:0.2.1": strfmt
#import "@preview/oxifmt:0.3.0": strfmt

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

- **Inserting labels, text and numbers into strings:**
```typ
#import "@preview/oxifmt:0.2.1": strfmt
#import "@preview/oxifmt:0.3.0": strfmt

#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
#import "@preview/oxifmt:0.3.0": 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>")
```

- **Inserting other types than numbers and strings** (for now, they will always use `repr()`, even without `{...:?}`, although that is more explicit):
```typ
#import "@preview/oxifmt:0.2.1": strfmt
#import "@preview/oxifmt:0.3.0": strfmt

#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
#import "@preview/oxifmt:0.3.0": 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")
Expand All @@ -123,23 +153,23 @@ Some examples:

- **Padding numbers with zeroes to the left:** It's a similar functionality to the above, however you write `{:08}` for 8 characters (for instance) - note that any characters in the number's representation matter for width (including sign, dot and decimal part):
```typ
#import "@preview/oxifmt:0.2.1": strfmt
#import "@preview/oxifmt:0.3.0": strfmt

#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
#import "@preview/oxifmt:0.3.0": 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
```

- **Displaying `+` on positive numbers:** Just add a `+` at the "beginning", i.e., before the `#0` (if either is there), or after the custom fill and align (if it's there and not `0` - see [Grammar](#grammar) for the exact positioning), like so:
```typ
#import "@preview/oxifmt:0.2.1": strfmt
#import "@preview/oxifmt:0.3.0": strfmt

#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")
Expand All @@ -148,36 +178,56 @@ Some examples:

- **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
#import "@preview/oxifmt:0.3.0": 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")
```

- **Picking float precision (right-extending with zeroes):** Add, at the end of the format (just before the spec type (such as `?`), if there's any), either `.precision` (hardcoded, e.g. `.8` for 8 decimal digits) or `.parameter$` (taking the precision value from the specified parameter, like with `width`):
```typ
#import "@preview/oxifmt:0.2.1": strfmt
#import "@preview/oxifmt:0.3.0": strfmt

#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
#import "@preview/oxifmt:0.3.0": 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")
```

### Custom options

Oxifmt has some additional formatting options laid on top of Rust's, listed below with examples:

- **Customizing the decimal separator on floats:** Just specify `fmt-decimal-separator: ","` (comma as an example):
```typ
#import "@preview/oxifmt:0.2.1": strfmt
#import "@preview/oxifmt:0.3.0": strfmt

#let s = strfmt("{0} {0:.6} {0:.5e}", 1.432, fmt-decimal-separator: ",")
#assert.eq(s, "1,432 1,432000 1,43200e0")
```

- **Displaying thousands separators on numbers:** Specify `fmt-thousands-separator: "_"` (underscore as an example - the default is `""` which disables the feature):
```typ
#import "@preview/oxifmt:0.3.0": strfmt

#let s = strfmt("{}", 20000, fmt-thousands-separator: "_")
#assert.eq(s, "20_000")
```

- **Customizing the distance between thousands separators:** Specify `fmt-thousands-count: 2` (2 as an example - the default is 3):
```typ
#import "@preview/oxifmt:0.3.0": strfmt

#let s = strfmt("{}", 20000, fmt-thousands-count: 2, fmt-thousands-separator: "_")
#assert.eq(s, "2_00_00")
```

### Grammar

Here's the grammar specification for valid format `spec`s (in `{name:spec}`), which is basically Rust's format:
Expand Down Expand Up @@ -214,6 +264,21 @@ The tests succeeded if you received no error messages from the last command (ple

## Changelog

### v0.3.0

- **Breaking change:** Named replacements prefixed with `fmt-` are now an error. Those are reserved for future `oxifmt` options. ([Issue #15](https://github.com/PgBiel/typst-oxifmt/issues/15))
- For example, instead of `strfmt("{fmt-x}", fmt-x: 10)`, write `strfmt("{_fmt-x}, _fmt-x: 10")` instead (or some other name with a different prefix).
- Added thousands separator support, configurable with `strfmt(format, fmt-thousands-count: 3, fmt-thousands-separator: "")`. The first option defines how many digits should appear between each separator, and the second option controls the separator itself (default is empty string, disabling it). ([Issue #5](https://github.com/PgBiel/typst-oxifmt/issues/5))
- For example, `strfmt("{}", 2000, fmt-thousands-separator: ",")` displays `"2,000"`.
- Within the same example, adding `fmt-thousands-count: 2` would display `20,00` instead.
- Numeric systems with irregular thousands separator distances will be supported in a future release.
- Added support for numeric formatting of [fixed-point `decimal` numbers](https://typst.app/docs/reference/foundations/decimal/). They support the same format specifiers as floats, e.g. `{:e}` for exponential notation, `{:.3}` for a fixed precision and so on. ([Issue #11](https://github.com/PgBiel/typst-oxifmt/issues/11))
- oxifmt is now dual-licensed as MIT or Apache-2.0 (previously just MIT).
- Fixed some bugs when formatting `inf` and `NaN`.
- Fixed a rare case of wrong usage of types in strings in internal code, which could cause oxifmt to generate an error in upcoming Typst v0.14. **It is recommended to upgrade oxifmt to avoid this problem.**
- However, this was only triggered when a very rare formatting option was used (dynamic precision specifiers, which have a dollar sign `$`, e.g. `{:.prec$}`), so existing code is unlikely to be affected. Still a good idea to upgrade, though.
- Fixed exponential notation formatting with very large numbers. Note that they might need rounding to look good (e.g. `strfmt("{:.2e}", number)` instead of just `{:e}`), but they will no longer cause an error. ([Issue #16](https://github.com/PgBiel/typst-oxifmt/issues/16))

### v0.2.1

- Fixed formatting of UTF-8 strings. Before, strings with multi-byte UTF-8 codepoints would cause formatting inconsistencies or even crashes. ([Issue #6](https://github.com/PgBiel/typst-oxifmt/issues/6))
Expand Down
17 changes: 17 additions & 0 deletions bin/package.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/sh
if [ $# -lt 1 ]; then
echo "Please specify the path to your fork of 'https://github.com/typst/packages'."
exit 1
fi

version=${OXIFMT_VERSION:-"$(grep "version = " typst.toml | sed -e 's/^version = "\|"$//g')"}
orig_dir="$(realpath "$(dirname "$0")/..")"
dest_dir="$1/packages/preview/oxifmt/${version}"
mkdir -p "${dest_dir}"
IFS=$' '
for file in LICENSE LICENSE-MIT LICENSE-APACHE README.md oxifmt.typ typst.toml
do
cp "${orig_dir}/${file}" -t "${dest_dir}"
done

echo "Copied files to ${dest_dir}"
47 changes: 47 additions & 0 deletions tests/strfmt-tests.typ
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,44 @@
}
// DOC TESTS
#{
// --- Quick examples ---
{
// "User John has 10 apples."
assert.eq(
"User John has 10 apples.",
strfmt("User {} has {} apples.", "John", 10)
)

// "if exp > 100 { true }"
assert.eq(
"if exp > 100 { true }",
strfmt("if {var} > {num} {{ true }}", var: "exp", num: 100)
)

// "1.10e2 meters"
assert.eq(
"1.10e2 meters",
strfmt("{:.2e} meters", 110.0)
)

// "20_000 players have more than +002,3 points."
assert.eq(
"20_000 players have more than +002,300 points.",
strfmt(
"{} players have more than {:+08.3} points.",
20000,
2.3,
fmt-decimal-separator: ",",
fmt-thousands-separator: "_"
)
)

// "The byte value is 0x8C or 10001100"
assert.eq(
"The byte value is 0x8C or 10001100",
strfmt("The byte value is {:#02X} or {0:08b}", 140)
)
}
// --- Usage ---
{
let s = strfmt("I'm {}. I have {num} cars. I'm {0}. {} is {{cool}}.", "John", "Carl", num: 10)
Expand Down Expand Up @@ -205,8 +243,17 @@
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")
}
// Custom options
{
let s = strfmt("{0} {0:.6} {0:.5e}", 1.432, fmt-decimal-separator: ",")
assert.eq(s, "1,432 1,432000 1,43200e0")
}
{
let s = strfmt("{}", 20000, fmt-thousands-separator: "_")
assert.eq(s, "20_000")
}
{
let s = strfmt("{}", 20000, fmt-thousands-count: 2, fmt-thousands-separator: "_")
assert.eq(s, "2_00_00")
}
}
2 changes: 1 addition & 1 deletion typst.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "oxifmt"
version = "0.2.1"
version = "0.3.0"
authors = ["PgBiel <https://github.com/PgBiel>"]
license = "MIT OR Apache-2.0"
description = "Convenient Rust-like string formatting in Typst"
Expand Down