Skip to main content

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 fixity;
74pub mod fs;
75pub mod graph;
76pub mod identity;
77pub mod index;
78pub mod link;
79pub mod manifest;
80pub mod memo;
81pub mod meta;
82pub mod peer;
83pub mod relation;
84pub mod title;
85
86pub use content::{ContentFormat, code_spans, render_html};
87pub use document::{
88    Body, Document, EmbedStyle, EmbedType, MetaCarrier, embed_carrier, embed_style_of,
89    is_opaque_payload, require_whole_file,
90};
91pub use error::{Error, Result};
92pub use exec::block_on;
93pub use fig::ExtKind;
94pub use fig::Format;
95pub use fixity::Fixity;
96pub use fs::{DirEntry, FileType, Metadata, ReadStorage, StdFs};
97pub use graph::{
98    Backlink, CensusEntry, Graph, LinkSite, Node, NodeKind, ReadSettings, Resolution,
99    StructuralFact, Target, TreeOptions, Walk, reachable_set,
100};
101pub use identity::{Id, IdStorage};
102pub use index::{Collision, IdIndex, NoIndex};
103pub use link::{
104    Addressing, BodyLink, Link, LinkStyle, Notation, PathStyle, ReferenceStyle, Wikilink, Wrapper,
105    escapes_root, format_link, is_valid_workspace_id, path_to_title,
106};
107pub use manifest::{Manifest, ManifestEntry, manifest_sibling};
108pub use memo::ReadScope;
109pub use meta::{Mapping, Value};
110pub use peer::{NoPeers, PeerLocation, PeerLookup, PeerResolver, Unconfirmed};
111pub use relation::{Cardinality, Edge, Relation, RelationSet};
112pub use title::{TitleIndex, TitleMatch};
113
114/// The body-prose parser, re-exported whole.
115///
116/// [`content`] uses twig to answer prov's own two questions — render a body to
117/// HTML ([`render_html`]) and find the spans a parser calls code
118/// ([`code_spans`]) — and both hand back plain strings and offsets. That is the
119/// whole of what prov needs, and for a long time it was the whole of what
120/// anyone could reach: twig was an implementation detail with no path out.
121///
122/// It is re-exported because the consumers this crate was built for — a
123/// language server, a static renderer, a browser viewer — need the *tree*, not
124/// a rendering of it. A static site generator filtering `:::vis{...}` regions
125/// by audience, or an editor addressing a node to splice it, is asking twig
126/// questions prov has no opinion about and should not grow one about.
127///
128/// Without this they would depend on `twig-doc` directly, pin it themselves,
129/// and resolve to a different [`twig::Document`] than the one [`content`]
130/// parses with — two AST vocabularies in one build, disagreeing silently about
131/// what a document is.
132///
133/// **This makes `twig-doc` a public dependency**, which is a real cost and the
134/// reason it was not done sooner: twig's major version is now part of prov's
135/// semver contract, so a twig 4 is a breaking change for prov whether or not
136/// prov's own surface moves. Accepted deliberately — the alternative is not
137/// "no coupling", it is the same coupling spelled separately by every
138/// downstream crate and enforced by nobody.
139///
140/// ```
141/// use prov_graph::twig::{Document, Format, MarkdownExtensions};
142///
143/// // What [`content`] cannot ask for: an opt-in extension. prov parses with
144/// // defaults, so a consumer that needs directives reaches past it — and,
145/// // through this re-export, reaches the same twig.
146/// let directives = MarkdownExtensions { directives: true, ..Default::default() };
147/// let mut doc = Document::parse_str_with(
148///     ":::vis{.public}\nHello\n:::\n",
149///     Format::Markdown,
150///     directives,
151/// )?;
152/// // The directive's name becomes the element tag and its attributes ride
153/// // along — which is also why a consumer publishing HTML unwraps these
154/// // rather than rendering them.
155/// let html = String::from_utf8(doc.render_html()?).unwrap();
156/// assert!(html.contains("<vis class=\"public\">"), "{html}");
157/// # Ok::<(), prov_graph::twig::Error>(())
158/// ```
159pub use twig;