cardbox/lib.rs
1//! The project as a library: the Rust host, the Teal modules embedded beside it, and
2//! `preload`, which hands both to an `Htl`. Every entry point — the binary, a test,
3//! another crate embedding this one — goes through `preload`.
4//!
5//! The split this crate is built on: **Rust is mechanism, Teal is policy.** [`store`] owns
6//! the IO, the transaction, the invariant that must hold at the instant a write lands, and
7//! the exactly-once fold that keeps the read models in step with the log; which kinds
8//! exist, what a card is called, what a card may be filtered on and how a refusal is
9//! worded are the Teal side's, where changing them costs no rebuild.
10
11pub mod store;
12
13use std::path::Path;
14
15use htl::Htl;
16
17pub use store::json::Value;
18pub use store::{Blob, Recorded, Store};
19
20// The Teal modules, type-checked at `cargo build` and embedded as stripped bytecode. Keep
21// these after the `#[host_module]` (which is in `store`, compiled first by the `mod store;`
22// above) so the declaration exists when the modules are checked. Every module the binary
23// reaches has to be here: `preload` is the only searcher an embedded run has, and a
24// `require` of a name nobody registered fails at the line that needs it rather than at
25// startup.
26//
27// The one dependency comes from where `htl pkg install` put it: the link htl keeps at the
28// dependency's entry, so it is the same file `htl check` and `htl test` read. The path is
29// machine-local, like `node_modules/` — a fresh clone runs `htl pkg install` before
30// `cargo build`.
31// From the project's own copy of the dependency (`htl pkg patch htlx`, `patches/htlx/`),
32// not from `.htl/modules/`: the latter is written by `htl pkg install` and is not in the
33// crate tarball, so a build from crates.io would have nothing to embed.
34const HTLX_LIST: &[u8] = htl::include_tl_bytes!("patches/htlx/src/htlx/list.tl");
35const FIND: &[u8] = htl::include_tl_bytes!("src/cardbox/find.tl");
36const ALIAS: &[u8] = htl::include_tl_bytes!("src/cardbox/alias.tl");
37const PRUNE: &[u8] = htl::include_tl_bytes!("src/cardbox/prune.tl");
38const CARDS: &[u8] = htl::include_tl_bytes!("src/cardbox/cards.tl");
39const COMPAT: &[u8] = htl::include_tl_bytes!("src/cardbox/compat.tl");
40const MODULE: &[u8] = htl::include_tl_bytes!("src/cardbox/init.tl");
41const CLI: &[u8] = htl::include_tl_bytes!("src/cardbox/cli.tl");
42
43/// Register what this crate provides on a fresh `Htl`: the Rust `store` module opened on
44/// `root`, htl's own native modules as `require("std.*")`, the dependency as
45/// `require("htlx.list")`, then the Teal modules as
46/// `require("cardbox.find")`, `require("cardbox.alias")`,
47/// `require("cardbox.prune")`, `require("cardbox.cards")`, `require("cardbox")` and
48/// `require("cardbox.cli")`.
49pub fn preload(h: &Htl, root: &Path) -> anyhow::Result<()> {
50 Store::open(root)?.htl_preload(h)?;
51 // `std.*`: mlua-batteries, which htl assembles under that name. Four of its modules are
52 // reached for here — `json` for the array tag, `string` for the text helpers, `argparse`
53 // for the command line, `time` for the millisecond clock a prune's cutoff is on — and
54 // this one call registers every module the build carries, so a Teal file that requires
55 // another needs no change on this side.
56 //
57 // `json` is also the crate `store::json` converts with, so a table `std.json.array()`
58 // tagged on the Teal side is still a list when the store weighs it, which a bare `{}`
59 // is not.
60 h.install_std()?;
61 // Stripped bytecode: small, and with neither line numbers nor a chunk name, so a
62 // failure inside these modules reads `?: in function 'cards.open'`. `htl run
63 // src/cardbox/init.tl` and `htl test` run the Teal itself and name file and line.
64 //
65 // Leaves first, because each requires the ones above it as it loads. Lua only consults
66 // `package.preload` when the `require` runs, so the order is not what makes this work
67 // — it is what keeps the file honest about the dependencies.
68 h.preload_bytes("htlx.list", HTLX_LIST)?;
69 h.preload_bytes("cardbox.find", FIND)?;
70 h.preload_bytes("cardbox.alias", ALIAS)?;
71 h.preload_bytes("cardbox.prune", PRUNE)?;
72 h.preload_bytes("cardbox.cards", CARDS)?;
73 h.preload_bytes("cardbox.compat", COMPAT)?;
74 h.preload_bytes("cardbox", MODULE)?;
75 // Last, because it is the only one that requires the whole of the module above it. It
76 // is registered by `preload` rather than by the binary so that a test, or another
77 // crate, reaches the same dispatch the command line does (`src/tests/cli.rs`).
78 h.preload_bytes("cardbox.cli", CLI)?;
79 Ok(())
80}
81
82#[cfg(test)]
83mod tests;