# csan
`csan` generates Cutter-Sanborn and Library of Congress cutter numbers for
library cataloging from author names and titles. It is a command-line tool
and a library for Rust, Python, Ruby, JavaScript (npm), and Typst.
```console
$ csan --forename Charles --surname Dickens --title "Oliver Twist"
D548o
$ csan --method cutter-loc --forename Charles --surname Dickens
D535
```
It implements 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.
Its table comes from https://github.com/veralvx/cutter-sanborn-table
- **Library of Congress:** the LoC character-branching rules
([Cutter table](https://www.loc.gov/aba/pcc/053/table.html),
[CSM G 63](https://www.loc.gov/aba/publications/FreeCSM/G063.pdf)),
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 a surname of
`St`/`Ste` (as in Saint Patrick) expands to `Saint`/`Sainte`, matching the
conventions of the underlying tables.
Anonymous works are cuttered by title.
## Install
- **Command-line tool:** `cargo install csan`, or prebuilt with
`cargo binstall csan` or the installers and archives on each
[GitHub release](https://github.com/veralvx/csan/releases). With Nix
(Linux, aarch64 macOS): `nix run github:veralvx/csan -- --help`. Without
installing: `npx @veralvx/csan --help`.
- **Rust library:** `cargo add csan-core`.
- **Python:** `pip install csan` (library and CLI), or
`uv tool install csan` for the CLI alone.
- **Ruby:** `gem install csan` (library and CLI).
- **JavaScript (Node):** `npm install @veralvx/csan` (library and CLI).
- **Typst:** see [Typst](#typst).
## Command line
```sh
csan --surname Dickens --title "Oliver Twist" # D548o
csan --method cutter-loc --surname Carter # C378
csan --method cutter-loc --title "100 best recipes" # A153
csan --surname Borges --json # {"cutter_sanborn": {"formatted": "B732", ...}}
```
`--method` selects the method, `cutter-sanborn` (the default) or
`cutter-loc`, and at least one non-blank input (`--forename`, `--surname`,
`--title`) is required. `--prefix-len`/`--suffix-len` tune the
Cutter-Sanborn output, `--max-len` the LoC digits, and `--json` emits the
parts as a JSON object keyed by method (`cutter_sanborn` or
`cutter_loc`). Errors exit non-zero with a message on stderr.
With [Nix](https://nixos.org), the flake builds the binary with the
toolchain that `rust-toolchain.toml` pins:
```sh
nix run github:veralvx/csan -- --help
nix build github:veralvx/csan # ./result/bin/csan
nix profile install github:veralvx/csan
```
## Rust
The library is the [`csan-core`](https://crates.io/crates/csan-core) crate
(up to 0.5.x it was published as `csan`, a name that now belongs to the
command-line tool):
```rust
use csan_core::{
Attribution, Author, CutterConfig, CutterGenerator, CutterLocMethod,
CutterSanbornMethod, Opus,
};
fn main() -> Result<(), csan_core::NormalizationError> {
let entry = Attribution::Both {
author: Author::new("Charles", "Dickens"),
opus: Opus::new("Oliver Twist"),
};
let sanborn = CutterSanbornMethod::generate(&entry, CutterConfig::default())?;
assert_eq!(sanborn.to_string(), "D548o");
let loc = CutterLocMethod::generate(&entry, CutterConfig::default())?;
assert_eq!(loc.to_string(), "D535");
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`). The API reference is on
[docs.rs](https://docs.rs/csan-core).
## Python
The PyPI package [`csan`](https://pypi.org/project/csan/) (CPython 3.9+,
abi3 wheels) provides the library and the `csan` command:
```python
import csan
csan.cutter_sanborn(surname="Dickens", title="Oliver Twist") # 'D548o'
csan.cutter_loc_detailed(surname="Carter")
# {'alpha_prefix': 'C', 'formatted': 'C378', 'numeric': 378}
```
See the [Python package README](https://github.com/veralvx/csan/blob/main/crates/csan-py/README.md)
for the full API.
## Ruby
The gem [`csan`](https://rubygems.org/gems/csan) (Ruby 3.3+, precompiled
for Linux, macOS, and Windows; elsewhere it builds from source with a Rust
toolchain) provides the library and the `csan` command:
```ruby
require "csan"
Csan.cutter_sanborn(surname: "Dickens", title: "Oliver Twist") # => "D548o"
Csan.cutter_loc_detailed(surname: "Carter")
# => {alpha_prefix: "C", formatted: "C378", numeric: 378}
```
See the [gem README](https://github.com/veralvx/csan/blob/main/crates/csan-rb/README.md)
for the full API.
## JavaScript
The npm package [`@veralvx/csan`](https://www.npmjs.com/package/@veralvx/csan)
is the library compiled to WebAssembly, built for Node (CommonJS, with
TypeScript declarations):
```js
const { cutterLoc, cutterSanborn } = require("@veralvx/csan");
cutterSanborn({ surname: "Dickens", title: "Oliver Twist" }); // "D548o"
cutterLoc({ surname: "Carter" }); // "C378"
```
It also provides the `csan` command, with the same interface as the native
binary. See the [npm package README](https://github.com/veralvx/csan/blob/main/crates/csan-web/README.md)
for the full API and the browser build.
## Typst
The Typst package exposes two functions:
```typ
// 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 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](https://github.com/typst/packages#local-packages): extract
the `csan_typst-<version>.tar.gz` attached to each
[GitHub release](https://github.com/veralvx/csan/releases) into
`{data-dir}/typst/packages/local/csan/<version>/`, then import
`@local/csan:<version>`. The plugin binary alone is attached as
`csan_typst.wasm`.
## Expected values
Generated by `cargo run --example showcase` with the default configuration:
| 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` |
## Contributing
| `crates/csan-core` | The library: normalization, models, generators, the table. |
| `crates/csan` | The `csan` command-line tool (clap). |
| `crates/csan-py` | Python bindings and the PyPI package (pyo3 + maturin). |
| `crates/csan-rb` | Ruby bindings and the gem (magnus + rb-sys). |
| `crates/csan-web` | JavaScript/npm bindings via WebAssembly (wasm-bindgen). |
| `crates/csan-typst` | The Typst WASM plugin, and the package's `just` harness. |
| `crates/csan-typst/pkg` | The Typst package (`typst.toml`, `lib.typ`, plugin binary). |
[CONTRIBUTING.md](https://github.com/veralvx/csan/blob/main/CONTRIBUTING.md)
covers the development setup, the checks CI runs, building the Typst
plugin, and the release process. Changes are listed in the
[changelog](https://github.com/veralvx/csan/blob/main/CHANGELOG.md).
## License
Licensed under either of
[Apache License, Version 2.0](https://github.com/veralvx/csan/blob/main/LICENSE-APACHE)
or [MIT license](https://github.com/veralvx/csan/blob/main/LICENSE-MIT)
at your option.