csan
Cutter call numbers for library cataloging, as a Rust library and a Typst plugin.
csan derives shelf-ready cutter numbers from author names (or, for
anonymous works, titles) using two methods:
- Cutter-Sanborn three-figure — a lookup against the bundled Cutter-Sanborn table (12,000+ entries), with an optional call number prefix and a lowercase work mark derived from the title.
- Library of Congress — the LoC character-branching rules (Cutter table, CSM G 63), producing an initial plus a digit sequence.
Input names are normalized first: whitespace and punctuation are stripped,
diacritics and ligatures are folded to ASCII (Čapek → Capek, Strauß →
Strauss), Mc/M' prefixes expand to Mac, and St/Ste expand to
Saint/Sainte, matching the conventions of the underlying tables.
Workspace layout
| Path | Contents |
|---|---|
crates/csan-core |
The library: normalization, models, generators, the table. |
crates/csan |
The csan command-line tool (clap). |
crates/csan-typst |
The Typst WASM plugin wrapping the library. |
crates/csan-typst/pkg |
The Typst package (typst.toml, lib.typ, plugin binary). |
crates/csan-py |
Python bindings and the PyPI package (pyo3 + maturin). |
crates/csan-web |
JavaScript/npm bindings via WebAssembly (wasm-bindgen). |
Rust usage
Add the library with cargo add csan-core. (Up to 0.5.x the library was
published as csan; that name now belongs to the command-line tool.)
use ;
let entry = Both ;
let sanborn = generate?;
assert_eq!;
let loc = generate?;
assert_eq!;
# Ok::
CutterConfig tunes the output: prefix_len and suffix_len control the
Cutter-Sanborn prefix and work mark, and max_len controls the LoC digit
count (clamped to 4). Works without an author use
Attribution::OpusOnly(...) and are cuttered by title, including the LoC
numeral rule ("100 best recipes" → A153).
Expected values
Generated by cargo run --example showcase with default configuration:
| Author | Cutter-Sanborn | Library of Congress |
|---|---|---|
| Jane Austen | A933 |
A978 |
| Eric Blair | B635 |
B535 |
| Agatha Christie | C555 |
C475 |
| Samuel Clemens | C625 |
C546 |
| Jorge De la Cruz | D332 |
D453 |
| Charles Dickens | D548 |
D535 |
| Emily Dickinson | D553 |
D535 |
| Fyodor Dostoyevsky | D724 |
D678 |
| Stephen King | K54 |
K564 |
| VERA LVX | L979 |
L89 |
| Herman Melville | M531 |
M458 |
| George Orwell | O79 |
O794 |
| William Shakespeare | S527 |
S535 |
| Lord Sith | S623 |
S584 |
| Ivan Smith | S649 |
S658 |
| William Smith | S664 |
S658 |
| Leo Tolstoy | T654 |
T657 |
| Mark Twain | T969 |
T835 |
| Virginia Woolf | W913 |
W665 |
| Emile Zola | Z86 |
Z653 |
Command line
The csan crate (crates/csan) provides the csan binary. Install it
with cargo install csan, the shell/PowerShell installers or archives
attached to each
GitHub release, or the Python
package below:
Select at least one method (--cutter-sanborn, --cutter-loc) and provide
at least one non-blank input (--forename, --surname, --title).
--prefix-len/--suffix-len tune the Cutter-Sanborn output, --max-len
the LoC digits, and --json emits the parts as a JSON object. Errors exit
non-zero with a message on stderr.
Python
crates/csan-py packages the library for Python (CPython 3.9+, abi3
wheels) under the PyPI name csan, including the CLI as a console
script:
# 'D548o'
# {'alpha_prefix': 'C', 'numeric': 378, 'formatted': 'C378'}
See crates/csan-py/README.md for the
full API and the maturin build/publish flow.
JavaScript (npm)
crates/csan-web exposes the library to JavaScript via WebAssembly,
packaged for npm with wasm-pack:
import from "csan-web";
; // "D548o"
; // "C378"
See crates/csan-web/README.md for
build, examples (Node and browser), and npm publishing instructions.
Typst usage
The Typst package lives in crates/csan-typst/pkg and exposes two
functions:
// Replace <version> with the installed release.
#import "@local/csan:<version>": cutter-sanborn, cutter-loc
#cutter-sanborn(
forename: "Charles",
surname: "Dickens",
title: "Oliver Twist",
) // -> "D548o"
#cutter-loc(surname: "Carter") // -> "C378"
Both accept forename, surname, and title (at least one of
author/title must be non-blank; blank strings count as absent).
cutter-sanborn also takes prefix-len and suffix-len; cutter-loc
takes max-len. Pass detailed: true to receive a dictionary of the parts
(alpha_prefix, numeric, …) instead of the formatted string. Requires
Typst 0.14.2 or later.
Until the package is published to Typst Universe, install it as a
local package: extract
the csan_typst-<version>.tar.gz attached to each GitHub release into
{data-dir}/typst/packages/local/csan/<version>/ (or copy
crates/csan-typst/pkg there), then import @local/csan:<version>. The
plugin binary alone is attached as csan_typst.wasm.
Building the plugin
The compiled plugin (csan_typst.wasm) is committed alongside the
package, so Typst users don't need a Rust toolchain. To rebuild it after
changing the Rust code:
Releasing
just release <major|minor|patch> (on an up-to-date, clean main) bumps
the workspace version, the csan-core dependency pin, and the Typst
package version, prepends the release's section to CHANGELOG.md with
git-cliff, then commits, tags, and pushes. It refuses a bump smaller than
the unreleased breaking changes require. The tag starts the Release workflow, which uses
dist to build the csan binaries
and installers and attaches, to the same GitHub release:
- the Typst plugin (
csan_typst.wasm) and package (csan_typst-<version>.tar.gz); - the Python wheels (abi3, Linux/musllinux/macOS/Windows on x86_64 and aarch64) and sdist;
- the Node npm package (
csan-web-<version>.tgz).
Once the release succeeds, the Publish to PyPI workflow uploads its wheels
and sdist to PyPI (trusted publishing;
the one-time setup is described in .github/workflows/publish-pypi.yml).
Crates are published to crates.io by hand, library first:
cargo publish -p csan-core && cargo publish -p csan.
Development
The repository pins Rust 1.97 (rust-toolchain.toml). just checks runs
the core CI gates: cargo check/test/clippy -D warnings (all
targets, with the cargo, pedantic, and restriction clippy groups
enabled), cargo fmt --check, cargo doc -D warnings, cargo audit,
dprint check, and cog check for Conventional Commits. CI additionally builds and smoke-tests the
Typst plugin, the Python wheel, and the npm package. Lint policy —
including the reason for every allowed lint — lives in the workspace
Cargo.toml and clippy.toml.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.