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//! - **The crate root has no dependencies.** Three published cdylibs link it statically, so
22//! a dependency here is a dependency in all of them. Everything in [`ffi`], [`kind`] and
23//! [`scaffold`] is `std`-only. Optional capabilities each bring their own dependency
24//! behind their own feature flag instead of widening this baseline.
25//!
26//! It is `std`-only at the root — [`ffi::LastErrorSlot`] holds a `CString` and lives in a
27//! `thread_local!` — which suits every consumer including `wasm32-unknown-unknown`
28//! (the `refine` feature does too; `ai` is native-only — see its module docs).
29//!
30//! # Modules
31//!
32//! - [`kind`] — the error-kind values the family already shares, and the bands that keep
33//! future ones from colliding. Read its docs before adding a kind anywhere.
34//! - [`ffi`] — [`ffi::LastErrorSlot`], the [`ffi::catch`] panic guard, and the macros that
35//! export the ABI over them.
36//! - [`refine`] *(feature `refine`)* — a lossless, idempotent markdown shape-refinement pass:
37//! table shape normalization, ordered-list renumbering, link/image path normalization,
38//! frontmatter normalization, and section anchors. Composes with a renderer's own
39//! `cleanup` stage in either order.
40//! - [`ai`] *(feature `ai`, native only)* — VLM-based image understanding and
41//! AI-assisted markdown refine, active only when a caller supplies an API key.
42//! - [`scaffold`] — macros that assemble an entry point out of those primitives. Reach for
43//! these before writing the five steps by hand; the sentinel differs by return type and
44//! that is where hand-written entry points drift.
45//!
46//! # A minimal library
47//!
48//! ```
49//! use std::ffi::c_int;
50//! use unparser_shared::ffi::{self, FfiError, LastErrorSlot};
51//! use unparser_shared::kind;
52//!
53//! // The slot lives here, not in unparser-shared — that is what keeps it per-library. See
54//! // `ffi`'s module docs.
55//! thread_local! {
56//! static LAST_ERROR: LastErrorSlot = const { LastErrorSlot::new() };
57//! }
58//!
59//! unparser_shared::export_last_error_abi!(LAST_ERROR, mylib_last_error, mylib_last_error_kind);
60//!
61//! /// Returns the page count, or -1 on failure.
62//! #[no_mangle]
63//! pub extern "C" fn mylib_page_count(handle: *const u8) -> c_int {
64//! let result: Result<c_int, FfiError> = ffi::catch(|| {
65//! if handle.is_null() {
66//! return Err(ffi::invalid_argument("handle was null"));
67//! }
68//! Ok(7)
69//! });
70//!
71//! match result {
72//! Ok(count) => {
73//! LAST_ERROR.with(|slot| slot.clear());
74//! count
75//! }
76//! Err(error) => {
77//! LAST_ERROR.with(|slot| slot.set_error(&error));
78//! -1
79//! }
80//! }
81//! }
82//!
83//! assert_eq!(mylib_page_count(std::ptr::null()), -1);
84//! assert_eq!(mylib_last_error_kind(), kind::INVALID_ARGUMENT);
85//!
86//! let data = 0u8;
87//! assert_eq!(mylib_page_count(&data), 7);
88//! assert_eq!(mylib_last_error_kind(), kind::NONE);
89//! ```
90
91#![doc(html_root_url = "https://docs.rs/unparser-shared/0.2.0")]
92#![cfg_attr(
93 not(all(feature = "refine", feature = "ai")),
94 allow(rustdoc::broken_intra_doc_links)
95)]
96
97#[cfg(feature = "ai")]
98pub mod ai;
99pub mod ffi;
100pub mod kind;
101#[cfg(feature = "refine")]
102pub mod refine;
103pub mod scaffold;