Skip to main content

unparser_shared/
lib.rs

1//! Shared plumbing for the `un*` document extraction family — [unpdf], [undoc], [unhwp].
2//!
3//! [unpdf]: https://crates.io/crates/unpdf
4//! [undoc]: https://crates.io/crates/undoc
5//! [unhwp]: https://crates.io/crates/unhwp
6//!
7//! # What this crate is for
8//!
9//! Those three libraries have the same shape — parse a container, build an intermediate
10//! representation, render Markdown — and expose it the same way: a Rust API, a C ABI, and
11//! C#, Python and WebAssembly bindings over that ABI. The *document* parts differ
12//! entirely and belong in each library. The parts that do not differ live here: thread-local
13//! last-error storage, catching panics before they cross `extern "C"`, and the integer space
14//! the error classifications live in. VLM/AI-assisted extraction is also here now, as an
15//! opt-in feature — see [Modules](#modules) below.
16//!
17//! # Scope, and what is kept out
18//!
19//! - **No domain content.** No document model, no rendering, no format knowledge. If a
20//!   type would mention a page, a paragraph or a spreadsheet cell, it belongs upstream.
21//!   Facts about the *output* syntax are not domain content: where CommonMark lets an
22//!   emphasis delimiter stand is the same answer for every writer ([`markdown`]), and so is
23//!   how RFC 4180 quotes a field and where a merged cell's value goes on a CSV grid ([`csv`] —
24//!   its `Cell` is the writer's input shape, not a document model).
25//! - **The crate root has no dependencies.** Three published cdylibs link it statically, so
26//!   a dependency here is a dependency in all of them. Everything in [`csv`], [`ffi`], [`kind`],
27//!   [`markdown`] and [`scaffold`] is `std`-only. Optional capabilities each bring their own dependency
28//!   behind their own feature flag instead of widening this baseline.
29//!
30//! It is `std`-only at the root — [`ffi::LastErrorSlot`] holds a `CString` and lives in a
31//! `thread_local!` — which suits every consumer including `wasm32-unknown-unknown`
32//! (the `refine` feature does too; `ai` is native-only — see its module docs).
33//!
34//! # Modules
35//!
36//! - [`csv`] — a table as CSV (RFC 4180) or tab-separated text: [`csv::to_csv`] and
37//!   [`csv::to_delimited`] lay the rows on their grid, a merged cell's value at its top-left
38//!   position and the positions it covers empty, and quote the fields.
39//! - [`grid`] — where a table's cells sit: [`grid::place`] gives each cell's grid column and
40//!   the table's width from the cells' spans, a merged cell recorded once on the cell that owns
41//!   it.
42//! - [`kind`] — the error-kind values the family already shares, and the bands that keep
43//!   future ones from colliding. Read its docs before adding a kind anywhere.
44//! - [`ffi`] — [`ffi::LastErrorSlot`], the [`ffi::catch`] panic guard, and the macros that
45//!   export the ABI over them.
46//! - [`markdown`] — CommonMark syntax facts the Markdown writers share:
47//!   [`markdown::emphasis_span`], the part of a styled run emphasis delimiters can wrap;
48//!   [`markdown::image`], [`markdown::link`] and [`markdown::link_destination`], which write a
49//!   picture's description, a link's text and title, and a link's target so that they stay
50//!   inside the construct and read back as given.
51//! - [`refine`] *(feature `refine`)* — a lossless, idempotent markdown shape-refinement pass:
52//!   table shape normalization, ordered-list renumbering, link/image path normalization,
53//!   frontmatter normalization, and section anchors. Composes with a renderer's own
54//!   `cleanup` stage in either order.
55//! - [`ai`] *(feature `ai`, native only)* — VLM-based image understanding and
56//!   AI-assisted markdown refine, active only when a caller supplies an API key.
57//! - [`cli_update`] *(feature `cli-update`, for the `*-cli` crates)* — the update notice
58//!   (cached for a day; off by flag, environment variable, CI or a non-terminal stderr) and the
59//!   `update` command's self-replacement from the tool's GitHub release.
60//! - [`scaffold`] — macros that assemble an entry point out of those primitives. Reach for
61//!   these before writing the five steps by hand; the sentinel differs by return type and
62//!   that is where hand-written entry points drift.
63//!
64//! # A minimal library
65//!
66//! ```
67//! use std::ffi::c_int;
68//! use unparser_shared::ffi::{self, FfiError, LastErrorSlot};
69//! use unparser_shared::kind;
70//!
71//! // The slot lives here, not in unparser-shared — that is what keeps it per-library. See
72//! // `ffi`'s module docs.
73//! thread_local! {
74//!     static LAST_ERROR: LastErrorSlot = const { LastErrorSlot::new() };
75//! }
76//!
77//! unparser_shared::export_last_error_abi!(LAST_ERROR, mylib_last_error, mylib_last_error_kind);
78//!
79//! /// Returns the page count, or -1 on failure.
80//! #[no_mangle]
81//! pub extern "C" fn mylib_page_count(handle: *const u8) -> c_int {
82//!     let result: Result<c_int, FfiError> = ffi::catch(|| {
83//!         if handle.is_null() {
84//!             return Err(ffi::invalid_argument("handle was null"));
85//!         }
86//!         Ok(7)
87//!     });
88//!
89//!     match result {
90//!         Ok(count) => {
91//!             LAST_ERROR.with(|slot| slot.clear());
92//!             count
93//!         }
94//!         Err(error) => {
95//!             LAST_ERROR.with(|slot| slot.set_error(&error));
96//!             -1
97//!         }
98//!     }
99//! }
100//!
101//! assert_eq!(mylib_page_count(std::ptr::null()), -1);
102//! assert_eq!(mylib_last_error_kind(), kind::INVALID_ARGUMENT);
103//!
104//! let data = 0u8;
105//! assert_eq!(mylib_page_count(&data), 7);
106//! assert_eq!(mylib_last_error_kind(), kind::NONE);
107//! ```
108
109#![doc(html_root_url = "https://docs.rs/unparser-shared/0.10.0")]
110#![cfg_attr(
111    not(all(feature = "refine", feature = "ai")),
112    allow(rustdoc::broken_intra_doc_links)
113)]
114
115#[cfg(feature = "ai")]
116pub mod ai;
117#[cfg(feature = "cli-update")]
118pub mod cli_update;
119pub mod csv;
120pub mod ffi;
121pub mod grid;
122pub mod kind;
123pub mod markdown;
124#[cfg(feature = "refine")]
125pub mod refine;
126pub mod scaffold;