prov_graph/lib.rs
1//! # prov-graph
2//!
3//! The read core of a [prov](https://docs.rs/prov) workspace: plaintext
4//! documents, the links declared in their own embedded metadata, and the
5//! traversal over them.
6//!
7//! ## What this crate is for
8//!
9//! A prov workspace describes itself. Follow the links in a document's
10//! frontmatter and body and the whole structure unfolds — no index to trust
11//! instead of the documents, no sidecar folder that has to be kept in step.
12//! This crate is that unfolding, and *only* that.
13//!
14//! Everything here reads. The filesystem port it asks for
15//! ([`fs::ReadStorage`]) has no method that writes a byte; the id index it asks
16//! for ([`index::IdIndex`]) has no method that changes a registration. Nor is
17//! the vocabulary for writing merely unused — it is *absent*, declared a layer
18//! up in `prov-store` instead. So a consumer that must not modify a workspace —
19//! a language server, a static renderer, a browser viewer — can depend on this
20//! crate and be *unable* to, rather than merely intending not to. That is the
21//! whole reason the split exists, and it is why the write halves are not here
22//! behind a feature flag someone could leave switched on.
23//!
24//! The write surface is `prov-store`: `Storage`, the metadata editor, and the
25//! `IndexStore` registries. The verbs are `prov`: creating, renaming, deleting,
26//! attaching, the change/journal machinery that makes a mutation crash-atomic,
27//! the config layer, the validation and repair passes.
28//! `prov` owns one [`Graph`] and forwards every read to it, so the two are the
29//! same traversal — not a reimplementation that can drift.
30//!
31//! `prov-views` is what that promise looks like taken up: a whole view engine —
32//! parse a declared view, resolve its scope by walking the spanning relation,
33//! group the documents it reaches — built on this crate and nothing else, and
34//! therefore unable to modify a byte of what it reads.
35//!
36//! ## The shape of it
37//!
38//! - [`Document`] — a plaintext file split into its embedded metadata block and
39//! its body.
40//! - [`relation::RelationSet`] — which metadata fields are links. Exactly one
41//! may be **spanning**: the single-parent tree that gives a workspace its
42//! discovery spine. Every other relation may be many-to-many, so the tree is a
43//! backbone, never a ceiling.
44//! - [`Graph`] — a root, a [`fs::ReadStorage`], an [`index::IdIndex`], and the
45//! [`graph::ReadSettings`] that say how links are spelled. Its two walks are
46//! the [`census`](Graph::census) (every forward link, flat, each tagged with
47//! where it is written and how it resolves) and the [`tree`](Graph::tree)
48//! (the spanning relation only, as a materialized outline).
49//!
50//! The census is ground truth. Reachability, the backlinks map, and prov's own
51//! validation findings are all views over it, and any stored index heals
52//! *toward* it, never the reverse.
53
54// At least one embedded-metadata format backend must be compiled in, otherwise
55// nothing here can parse a document at all. The format features (`yaml`,
56// `json`, `toml`, `fig-lang`) forward to the matching `fig` parser.
57#[cfg(not(any(
58 feature = "yaml",
59 feature = "json",
60 feature = "toml",
61 feature = "fig-lang"
62)))]
63compile_error!(
64 "prov-graph needs at least one metadata-format feature enabled: \
65 `yaml` (the default), `json`, `toml`, or `fig-lang`. \
66 You have disabled the default feature without selecting a replacement."
67);
68
69pub mod content;
70pub mod document;
71pub mod error;
72pub mod exec;
73pub mod field;
74pub mod fixity;
75pub mod fs;
76pub mod graph;
77pub mod identity;
78pub mod index;
79pub mod link;
80pub mod manifest;
81pub mod memo;
82pub mod meta;
83pub mod peer;
84pub mod relation;
85pub mod title;
86
87pub use content::{ContentFormat, code_spans, render_html};
88pub use document::{
89 Body, Document, EmbedStyle, EmbedType, MetaCarrier, embed_carrier, embed_style_of,
90 is_opaque_payload, require_whole_file,
91};
92pub use error::{Error, Result};
93pub use exec::block_on;
94pub use field::{Address, FieldPath};
95pub use fig::ExtKind;
96pub use fig::Format;
97pub use fixity::Fixity;
98pub use fs::{DirEntry, FileType, Metadata, ReadStorage, StdFs};
99pub use graph::{
100 Backlink, CensusEntry, Graph, LinkSite, Node, NodeKind, ReadSettings, Resolution,
101 StructuralFact, Target, TreeOptions, Walk, reachable_set,
102};
103pub use identity::{Id, IdStorage};
104pub use index::{Collision, IdIndex, NoIndex};
105pub use link::{
106 Addressing, BodyLink, Link, LinkStyle, Notation, PathStyle, ReferenceStyle, Wikilink, Wrapper,
107 escapes_root, format_link, is_valid_workspace_id, path_to_title,
108};
109pub use manifest::{Manifest, ManifestEntry, manifest_sibling};
110pub use memo::ReadScope;
111pub use meta::{Mapping, Value};
112pub use peer::{NoPeers, PeerLocation, PeerLookup, PeerResolver, Unconfirmed};
113pub use relation::{Cardinality, Edge, Relation, RelationSet};
114pub use title::{TitleIndex, TitleMatch};
115
116/// The body-prose parser, re-exported whole.
117///
118/// [`content`] uses twig to answer prov's own two questions — render a body to
119/// HTML ([`render_html`]) and find the spans a parser calls code
120/// ([`code_spans`]) — and both hand back plain strings and offsets. That is the
121/// whole of what prov needs, and for a long time it was the whole of what
122/// anyone could reach: twig was an implementation detail with no path out.
123///
124/// It is re-exported because the consumers this crate was built for — a
125/// language server, a static renderer, a browser viewer — need the *tree*, not
126/// a rendering of it. A static site generator filtering `:::vis{...}` regions
127/// by audience, or an editor addressing a node to splice it, is asking twig
128/// questions prov has no opinion about and should not grow one about.
129///
130/// Without this they would depend on `twig-doc` directly, pin it themselves,
131/// and resolve to a different [`twig::Document`] than the one [`content`]
132/// parses with — two AST vocabularies in one build, disagreeing silently about
133/// what a document is.
134///
135/// **This makes `twig-doc` a public dependency**, which is a real cost and the
136/// reason it was not done sooner: twig's major version is now part of prov's
137/// semver contract, so a twig 4 is a breaking change for prov whether or not
138/// prov's own surface moves. Accepted deliberately — the alternative is not
139/// "no coupling", it is the same coupling spelled separately by every
140/// downstream crate and enforced by nobody.
141///
142/// ```
143/// use prov_graph::twig::{Document, Format, MarkdownExtensions};
144///
145/// // What [`content`] cannot ask for: an opt-in extension. prov parses with
146/// // defaults, so a consumer that needs directives reaches past it — and,
147/// // through this re-export, reaches the same twig.
148/// let directives = MarkdownExtensions { directives: true, ..Default::default() };
149/// let mut doc = Document::parse_str_with(
150/// ":::vis{.public}\nHello\n:::\n",
151/// Format::Markdown,
152/// directives,
153/// )?;
154/// // The directive's name becomes the element tag and its attributes ride
155/// // along — which is also why a consumer publishing HTML unwraps these
156/// // rather than rendering them.
157/// let html = String::from_utf8(doc.render_html()?).unwrap();
158/// assert!(html.contains("<vis class=\"public\">"), "{html}");
159/// # Ok::<(), prov_graph::twig::Error>(())
160/// ```
161pub use twig;