pub struct Graph<FS, Ix> { /* private fields */ }Expand description
A readable workspace: a root, a filesystem to read it through, an id index
to resolve id: references against, and the ReadSettings that say how
its links are spelled.
This is the whole of what traversal needs, and — because FS is only ever
bounded by ReadStorage and Ix by
IdIndex — the whole of what it can do. There is
no method here that changes a byte on disk or a registration in the index,
and no way to add one without changing a trait bound in this crate.
prov’s Workspace owns one of these and forwards every read to it, adding
the identity policy, the change/journal machinery, and the config layer on
top. A consumer that only needs to see the workspace — a language server,
a renderer, a viewer — can hold a Graph directly and link none of that.
Implementations§
Source§impl<FS: ReadStorage, Ix: IdIndex> Graph<FS, Ix>
impl<FS: ReadStorage, Ix: IdIndex> Graph<FS, Ix>
Sourcepub async fn reachable_documents(
&self,
start: &Path,
census: &[CensusEntry],
content_bodies: &[PathBuf],
) -> Result<BTreeSet<PathBuf>>
pub async fn reachable_documents( &self, start: &Path, census: &[CensusEntry], content_bodies: &[PathBuf], ) -> Result<BTreeSet<PathBuf>>
reachable_set, minus any shadowed attachment payload
(attach --opaque) — the population a pass may parse as a document.
A shadowed payload is still reachable (it must not be reported as an
orphan, and it is still fixity-checked through its sidecar), but its
bytes are an exhibit prov promised never to interpret. That is the same
bound is_shadowed_payload already
holds the flat title and id scans to; this is its reachability-walk
counterpart, for prov’s vocabulary_findings and
prov’s fixity_findings — the two passes that load
every reachable path and read its frontmatter.
The listing is_shadowed_payload needs is built the same way
prov’s orphans builds one: the direct children of every
directory the reachable set occupies, so a shadow check costs a set
lookup per candidate extension rather than a stat.
Sourcepub async fn reachable_files(
&self,
start: impl AsRef<Path>,
) -> Result<BTreeSet<PathBuf>>
pub async fn reachable_files( &self, start: impl AsRef<Path>, ) -> Result<BTreeSet<PathBuf>>
Every file the workspace reaches from start that actually exists on
disk — reachable_set over a fresh walk, filtered to real files.
This is §8’s bounded walk expressed as a file set rather than a findings
list: the same population check validates. prov’s Workspace::history_capture captures
it (minus prov’s two byte-parking stores) precisely so that an event is a
consistent cut across everything the workspace considers its own.
prov’s Workspace::history_capture: prov’s Workspace::history_capture
Sourcepub async fn reachable_files_within(
&self,
start: impl AsRef<Path>,
parked: &[PathBuf],
) -> Result<BTreeSet<PathBuf>>
pub async fn reachable_files_within( &self, start: impl AsRef<Path>, parked: &[PathBuf], ) -> Result<BTreeSet<PathBuf>>
reachable_files, told which directories are
parked — see title_index_scoped.
Sourcepub async fn census(&self, start: impl AsRef<Path>) -> Result<Vec<CensusEntry>>
pub async fn census(&self, start: impl AsRef<Path>) -> Result<Vec<CensusEntry>>
Take a census of every forward link reachable from start: one
CensusEntry per frontmatter relation edge and per body [[…]]
wikilink, each carrying its LinkSite and Resolution.
This is the one traversal the backlink map, the integrity findings, and
(via mutate) inbound-rename maintenance are all views over. Because it
is read from the documents, it is ground truth: a stored backlink index
heals toward the census, never the reverse.
Sourcepub async fn census_within(
&self,
start: impl AsRef<Path>,
parked: &[PathBuf],
) -> Result<Vec<CensusEntry>>
pub async fn census_within( &self, start: impl AsRef<Path>, parked: &[PathBuf], ) -> Result<Vec<CensusEntry>>
census, told which directories are parked — see
title_index_scoped.
Sourcepub async fn backlinks(
&self,
start: impl AsRef<Path>,
) -> Result<BTreeMap<PathBuf, Vec<Backlink>>>
pub async fn backlinks( &self, start: impl AsRef<Path>, ) -> Result<BTreeMap<PathBuf, Vec<Backlink>>>
The backlink map for the workspace reachable from start: every resolved
target to the inbound references (Backlinks) that reach it, path- and
id-form alike. This is the census inverted — recomputed from the
documents, so it is always fresh (the Route-N “reconcile-on-load”: no
stored index to drift). Each target’s backlinks are sorted by source.
Sourcepub async fn backlinks_to(
&self,
start: impl AsRef<Path>,
target: impl AsRef<Path>,
) -> Result<Vec<Backlink>>
pub async fn backlinks_to( &self, start: impl AsRef<Path>, target: impl AsRef<Path>, ) -> Result<Vec<Backlink>>
The inbound references to a single target (workspace-relative) reachable
from start, sorted by source. The focused form of
backlinks for “who links here?”.
Sourcepub async fn walk(&self, start: &Path, parked: &[PathBuf]) -> Result<Walk>
pub async fn walk(&self, start: &Path, parked: &[PathBuf]) -> Result<Walk>
The shared spanning-tree walk: gathers the forward-link census and the
structural facts (StructuralFact, which depend on traversal state,
not on a single link’s resolution) in one pass. Frontmatter edges may
be spanning and so drive descent, the single-parent check, and the
inverse check; body wikilinks are always overlay references —
censused, never spanning.
“One pass” describes what it reports, not how many times it opens a
file: descent reads each document, the inverse check reads every spanning
child again to see whether it points back, and a workspace using
[[alias]] links pays a third read per document for the title index.
Three reads of everything, for one walk. So the walk opens a scope of its
own rather than waiting to be given one — a caller with no interest in
memos still gets a walk that reads each document once, and a caller that
already opened one (check, a mutate verb) nests inside it and keeps
everything the walk read.
Source§impl<FS: ReadStorage, Ix> Graph<FS, Ix>
impl<FS: ReadStorage, Ix> Graph<FS, Ix>
Sourcepub async fn load(&self, path: &Path) -> Result<(String, Document)>
pub async fn load(&self, path: &Path) -> Result<(String, Document)>
Read and parse the workspace-relative document at path, returning the
raw text alongside. The building block traversal, validation, and
mutation share.
Sourcepub async fn document(&self, path: impl AsRef<Path>) -> Result<Document>
pub async fn document(&self, path: impl AsRef<Path>) -> Result<Document>
Read and parse the workspace-relative document at path, returning its
full Document — the public counterpart to load, for
a caller walking a Node tree who needs more than
Node::title (the rest of the frontmatter,
the body, the carrier) without re-reading and re-parsing the file by
hand.
Unlike the traversal, which degrades a bad target to a
NodeKind::Unreadable node, this
surfaces the Error directly — a caller who names a path expects to
know why it failed, not to receive a placeholder.
Source§impl<FS: ReadStorage, Ix: IdIndex> Graph<FS, Ix>
impl<FS: ReadStorage, Ix: IdIndex> Graph<FS, Ix>
Sourcepub async fn manifest_of(
&self,
node: &Path,
) -> Result<Option<(PathBuf, Manifest)>>
pub async fn manifest_of( &self, node: &Path, ) -> Result<Option<(PathBuf, Manifest)>>
The manifest document node declares, loaded and parsed, with its
workspace-relative path. None when node declares no manifest.
A manifest is a record store (spec §5): prov re-lays-out its rows, so a markdown carrier has no stable home for them and is refused here, at the one choke point every reader passes through.
Sourcepub async fn read_manifest(&self, path: &Path) -> Result<Manifest>
pub async fn read_manifest(&self, path: &Path) -> Result<Manifest>
Read and parse the manifest document at path itself.
Sourcepub async fn manifest_claims(&self, candidate: &Path, dir: &Path) -> bool
pub async fn manifest_claims(&self, candidate: &Path, dir: &Path) -> bool
Whether the document at candidate is a manifest node whose manifest
covers the directory dir — the authoritative half of the reverse
lookup below.
Unreadable, unparsable and non-manifest candidates simply do not claim:
this runs inside best-effort scans, where the question is “is this
directory already accounted for”, and a damaged manifest is a finding
check raises rather than a reason to abort a walk.
Sourcepub async fn manifest_node_for(&self, dir: &Path) -> Result<Option<PathBuf>>
pub async fn manifest_node_for(&self, dir: &Path) -> Result<Option<PathBuf>>
The node covering the directory dir, or None when nothing does —
the counterpart of attachment_for for a whole directory. Probes the
<dir>.<ext> convention and confirms each hit through the node’s own
manifest pointer.
Sourcepub async fn under_manifest(&self, path: &Path) -> Result<bool>
pub async fn under_manifest(&self, path: &Path) -> Result<bool>
Whether any directory on path’s way down from the workspace root is
covered by a manifest — the guard the loose-attachment sweeps use so a
covered directory is never offered up for ten thousand sidecars.
Walks the ancestors rather than only the immediate parent, because a
manifest claims its root recursively: photos/2019/a.jpg is covered by
the node beside photos/.
Probe-only, and bounded on purpose. A node renamed away from its
directory leaves no local evidence beside that directory (moving the
archive to keep the convention is the thing rename deliberately does
not do), so this can answer “no” where a census would answer “yes”. The
caller is attach, which runs per file inside --all; making each one
authoritative would cost a census per file. The residue is a covered file
that also gains a sidecar — duplicated bookkeeping, not a contradiction,
since both records are derived from the same bytes. The operation where a
wrong “no” would matter — minting a second manifest over a whole
archive — asks manifest_node_covering instead and pays for the census.
Sourcepub async fn scan_covered(&self, root: &Path) -> Result<Vec<PathBuf>>
pub async fn scan_covered(&self, root: &Path) -> Result<Vec<PathBuf>>
The opaque payloads under the covered directory root, as paths relative
to it, sorted — what a manifest is built from and compared against.
Three exclusions, each deliberate. Hidden entries are skipped, as in
every other prov walk. Files prov can read (a .md note, a .yaml
store) are not payloads and stay ordinary documents — a manifest covers
bytes, never shadows a document. And a nested manifest’s directory is
left to its own node, so two manifests never claim the same file.
Sourcepub async fn manifest_roots(
&self,
walk_docs: &BTreeSet<PathBuf>,
) -> BTreeSet<PathBuf>
pub async fn manifest_roots( &self, walk_docs: &BTreeSet<PathBuf>, ) -> BTreeSet<PathBuf>
The covered roots of every manifest reachable in walk_docs — the set a
scan consults to know which directories are already accounted for.
Damaged manifests contribute nothing (their damage is check’s to
report).
Source§impl<FS: ReadStorage, Ix> Graph<FS, Ix>
impl<FS: ReadStorage, Ix> Graph<FS, Ix>
Sourcepub async fn exists(&self, path: &Path) -> Result<bool>
pub async fn exists(&self, path: &Path) -> Result<bool>
Whether the workspace-relative path exists. Mirrors
ReadStorage::try_exists, joined to the workspace root.
Sourcepub async fn read_bytes(&self, path: &Path) -> Result<Vec<u8>>
pub async fn read_bytes(&self, path: &Path) -> Result<Vec<u8>>
Read the entire contents of the workspace-relative path as bytes.
Mirrors ReadStorage::read, joined to the workspace root.
Sourcepub async fn read_text(&self, path: &Path) -> Result<String>
pub async fn read_text(&self, path: &Path) -> Result<String>
Read the entire contents of the workspace-relative path as a
string. Mirrors ReadStorage::read_to_string, joined to the workspace
root.
Source§impl<FS, Ix: IdIndex> Graph<FS, Ix>
impl<FS, Ix: IdIndex> Graph<FS, Ix>
Sourcepub fn resolve_link(&self, doc: &Path, link: &Link) -> Target
pub fn resolve_link(&self, doc: &Path, link: &Link) -> Target
Resolve link (declared in the document at doc) to a workspace target,
without nominal (alias) resolution — path and id: targets only. Use
resolve_link_with when a TitleIndex is
available and [[My File]]-style aliases should resolve.
Sourcepub fn resolve_link_with(
&self,
doc: &Path,
link: &Link,
titles: Option<&TitleIndex>,
) -> Target
pub fn resolve_link_with( &self, doc: &Path, link: &Link, titles: Option<&TitleIndex>, ) -> Target
Resolve link to a workspace target. Path targets resolve relative to
doc’s directory; an id:<id> target resolves through the registry (the
location-independent path that stays valid across moves); an
alias-shaped target (a bare name) resolves through titles when one is
supplied — Unique to its path, Ambiguous to
Target::AmbiguousAlias, and Unknown falling through to a path (so a
nominal link to nothing surfaces as a missing/broken path, exactly as
before aliases existed). With titles None, alias resolution is off
and this is the pure path/id resolver.
Source§impl<FS: ReadStorage, Ix: IdIndex> Graph<FS, Ix>
impl<FS: ReadStorage, Ix: IdIndex> Graph<FS, Ix>
Sourcepub async fn title_index(&self) -> Result<TitleIndex>
pub async fn title_index(&self) -> Result<TitleIndex>
Build the workspace’s TitleIndex by scanning every document under the
root and registering it under its title and its file stem. This is a
derived cache (DESIGN §5): rebuilt on demand, never persisted. It is
what makes nominal ([[My File]]) references resolvable — a flat
filesystem scan, deliberately independent of link resolution so that
alias links can themselves be spanning (contents: alias) without a
chicken-and-egg between “walk the tree” and “resolve the walk’s links.”
Sourcepub async fn title_index_scoped(
&self,
start: &Path,
parked: &[PathBuf],
) -> Result<TitleIndex>
pub async fn title_index_scoped( &self, start: &Path, parked: &[PathBuf], ) -> Result<TitleIndex>
The title index bounded to the directories the workspace reaches from
start (DESIGN §8) — the reachability-scoped counterpart to
title_index. Only documents in a directory some
link path/id-reaches are indexed, so a [[alias]] resolves within the
workspace without scanning target/, a vendored tree, or a nested
workspace at the repo root.
Falls back to the full title_index when the
spanning relation is addressed by alias: descending the tree then needs
every title up front, so the scan cannot be bounded (the chicken-and-egg
the flat scan was written to avoid). An overlay alias to an orphan (a doc
no path/id link reaches) likewise falls outside the scope and reads as
broken — which it effectively is.
parked names the directories whose interiors are prov’s own
bookkeeping — a history store’s events and blobs, the recycle bin’s items.
They are reached like anything else (the root points at each store’s index
document) but a title found inside one is not a place a reader can go, so
indexing it would let [[Some Note]] resolve to a deleted copy or an old
version — silently, since neither is anywhere the reader can see. The
caller supplies them because which directories those are is a question
about prov’s storage layout, and this crate has no opinion about it.
Sourcepub async fn scan_ids(&self) -> Result<Vec<(Id, PathBuf)>>
pub async fn scan_ids(&self) -> Result<Vec<(Id, PathBuf)>>
Scan every document under the root for a self-stored id frontmatter
field, returning the (id, path) pairs — the rebuildable id→path map for
the frontmatter-only identity storage mode (IdStorage::FrontmatterOnly).
Like title_index this is a flat filesystem scan,
deliberately independent of link resolution (so it can bootstrap the very
index that id links resolve through, with no chicken-and-egg).
Sourcepub async fn content_documents(&self) -> Result<Vec<PathBuf>>
pub async fn content_documents(&self) -> Result<Vec<PathBuf>>
Every content document (Markdown/Djot/HTML) under the root, as sorted
workspace-relative paths — the on-disk population the orphan check diffs
against what the spanning tree reaches (DESIGN §8). Deliberately restricted
to content documents: whole-file metadata sidecars (a config or registry
document, a stray .yaml) are not prose a user orphans, so they are not
candidates. A flat filesystem scan (hidden entries skipped), independent of
link resolution, like the title/id scans beside it.
Sourcepub async fn direct_child_files(
&self,
dirs: &BTreeSet<PathBuf>,
) -> Result<Vec<PathBuf>>
pub async fn direct_child_files( &self, dirs: &BTreeSet<PathBuf>, ) -> Result<Vec<PathBuf>>
The workspace-relative direct-child files of each directory in dirs
(non-recursive), skipping hidden entries and unreadable directories.
The bounded-scan primitive behind reachability-scoped discovery (DESIGN
§8): it opens only the directories it is handed and never descends into
subdirectories, so an unreached directory — a vendored tree, a nested
prov workspace — is neither read nor reported. Callers filter the
result for the file kind they care about (content documents for the orphan
check, opaque payloads for attach --all).
Sourcepub fn reached_dirs(reachable: &BTreeSet<PathBuf>) -> BTreeSet<PathBuf>
pub fn reached_dirs(reachable: &BTreeSet<PathBuf>) -> BTreeSet<PathBuf>
The directories the reachable set reachable occupies — each reached
document’s own directory (the workspace root’s directory always among
them, since the root document is reachable). The scope
direct_child_files is bounded to: a directory
is “known” precisely when a linked document lives directly in it.
Source§impl<FS: ReadStorage, Ix: IdIndex> Graph<FS, Ix>
impl<FS: ReadStorage, Ix: IdIndex> Graph<FS, Ix>
Sourcepub async fn sidecar_claims(&self, candidate: &Path, payload: &Path) -> bool
pub async fn sidecar_claims(&self, candidate: &Path, payload: &Path) -> bool
Whether the document at candidate is an attachment sidecar whose
content resolves to payload — the authoritative half of the reverse
lookup, the <payload>.<ext> convention above being only the probe.
Requires is_attachment, so a separated
prose node never reads as one: its body is a document in its own right,
and prov must keep scanning it. Unreadable or unparsable candidates simply
do not claim (this runs inside best-effort scans).
Sourcepub async fn is_shadowed_payload(
&self,
path: &Path,
listing: &BTreeSet<PathBuf>,
) -> bool
pub async fn is_shadowed_payload( &self, path: &Path, listing: &BTreeSet<PathBuf>, ) -> bool
Whether path — a file prov can read — has been deliberately shadowed:
claimed as an opaque payload by an attachment sidecar beside it. The
promise attach --opaque makes, enforced: prov links, moves and fixity-
checks the file (through its sidecar’s own content_hash) but never
reads it as a document, so its title stays out of the title index, any
id it shows stays out of the registry, any fields value it carries is
never checked against a vocabulary, and any content_hash it shows is
never treated as its own.
listing is the set of workspace-relative files the calling scan already
enumerated (its directory read), so a shadow check costs a set lookup
rather than a stat per metadata extension — this runs per file in the flat
title and id scans, and per reachable path in the vocabulary and fixity
passes (validate::Workspace::reachable_documents). A sidecar outside
the listing therefore does not shadow, which is the same bound the scans
themselves observe.
Source§impl<FS: ReadStorage, Ix: IdIndex> Graph<FS, Ix>
impl<FS: ReadStorage, Ix: IdIndex> Graph<FS, Ix>
Sourcepub async fn tree(&self, start: impl AsRef<Path>) -> Result<Node>
pub async fn tree(&self, start: impl AsRef<Path>) -> Result<Node>
Materialize the spanning tree rooted at start (a workspace-relative
path). Missing, unreadable, cyclic, unresolved-ID, and ambiguous-alias
targets become marked nodes. id:<id> targets resolve through the
registry; nominal ([[My File]]) targets resolve through the title
index, built once for the whole walk so spanning alias links (a
contents: alias vocabulary) descend like any other.
Sourcepub async fn tree_with(
&self,
start: impl AsRef<Path>,
options: TreeOptions,
) -> Result<Node>
pub async fn tree_with( &self, start: impl AsRef<Path>, options: TreeOptions, ) -> Result<Node>
Materialize the spanning tree rooted at start, as tree,
with TreeOptions controlling how an unresolved spanning target is
represented. TreeOptions::default() is exactly tree()’s behavior.
Sourcepub async fn tree_within(
&self,
start: impl AsRef<Path>,
options: TreeOptions,
parked: &[PathBuf],
) -> Result<Node>
pub async fn tree_within( &self, start: impl AsRef<Path>, options: TreeOptions, parked: &[PathBuf], ) -> Result<Node>
tree_with, told which directories are parked — see
title_index_scoped.
Source§impl<FS, Ix> Graph<FS, Ix>
impl<FS, Ix> Graph<FS, Ix>
Sourcepub fn new(
fs: FS,
root: impl Into<PathBuf>,
index: Ix,
settings: ReadSettings,
) -> Self
pub fn new( fs: FS, root: impl Into<PathBuf>, index: Ix, settings: ReadSettings, ) -> Self
A graph over fs, rooted at root, resolving ids through index.
Sourcepub fn fs_path(&self, rel: impl AsRef<Path>) -> PathBuf
pub fn fs_path(&self, rel: impl AsRef<Path>) -> PathBuf
The absolute path of a workspace-relative one — root joined to rel.
The two path forms are deliberately kept apart: everything this crate
returns (Node::path, Target::Path, a CensusEntry’s source) is
workspace-relative and root-independent, so a graph can be re-rooted to a
different directory without touching a single stored path. fs_path is
the one place that independence is given up, for the caller that actually
needs to open the file.
Sourcepub fn index_mut(&mut self) -> &mut Ix
pub fn index_mut(&mut self) -> &mut Ix
The id index, mutably — for an owner that also writes to it.
Sourcepub fn settings(&self) -> &ReadSettings
pub fn settings(&self) -> &ReadSettings
The settings this graph reads by.
Sourcepub fn relations(&self) -> &RelationSet
pub fn relations(&self) -> &RelationSet
The relation vocabulary — which metadata fields are links.
Sourcepub fn workspace_id(&self) -> &str
pub fn workspace_id(&self) -> &str
What this workspace calls itself; empty means anonymous.
Sourcepub fn id_storage(&self) -> IdStorage
pub fn id_storage(&self) -> IdStorage
Where a document’s stable id is persisted.
Sourcepub fn read_scope(&self) -> ReadScope
pub fn read_scope(&self) -> ReadScope
Open a read scope: within it, a document read twice is parsed once. See
crate::memo.
Sourcepub fn memo_lock(&self) -> MutexGuard<'_, ReadMemo>
pub fn memo_lock(&self) -> MutexGuard<'_, ReadMemo>
The memo itself, locked — for an owner that must forget what it wrote.