use crate::cache::Cache;
use crate::change::{Departed, Prior};
use crate::context::{Context, Date};
use crate::instance::{Input, Instance, Outcome};
use headwater_census::census::{Census, Outcome as Classification};
use headwater_census::resolve::Step;
use headwater_doc::Body;
use headwater_graph::{Direction, Edge, Graph, Target, Trouble};
use headwater_yaml::{Mapping, Span};
const NO_DOCUMENT: &str = "the row carries no document to read";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Grain {
Document,
Edge,
Neighbourhood { depth: u8 },
Corpus,
Taxonomy,
}
impl Grain {
pub fn name(self) -> &'static str {
match self {
Grain::Document => "document",
Grain::Edge => "edge",
Grain::Neighbourhood { .. } => "neighbourhood",
Grain::Corpus => "corpus",
Grain::Taxonomy => "taxonomy",
}
}
pub fn routes(self) -> bool {
match self {
Grain::Document | Grain::Edge | Grain::Neighbourhood { .. } => true,
Grain::Corpus | Grain::Taxonomy => false,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Scope {
grain: Grain,
needs_body: bool,
needs_phase_a: bool,
needs_clock: bool,
needs_prior: bool,
needs_claims: bool,
needs_observations: bool,
}
impl Scope {
pub(crate) const fn document(
needs_body: bool,
needs_phase_a: bool,
needs_clock: bool,
needs_prior: bool,
) -> Self {
Scope {
grain: Grain::Document,
needs_body,
needs_phase_a,
needs_clock,
needs_prior,
needs_claims: false,
needs_observations: false,
}
}
pub(crate) const fn edge(needs_clock: bool, needs_observations: bool) -> Self {
Scope {
grain: Grain::Edge,
needs_body: false,
needs_phase_a: false,
needs_clock,
needs_prior: false,
needs_claims: false,
needs_observations,
}
}
pub(crate) const fn neighbourhood(needs_clock: bool) -> Self {
Scope {
grain: Grain::Neighbourhood { depth: 1 },
needs_body: false,
needs_phase_a: false,
needs_clock,
needs_prior: false,
needs_claims: false,
needs_observations: false,
}
}
pub(crate) const fn corpus(needs_phase_a: bool, needs_prior: bool, needs_claims: bool) -> Self {
Scope {
grain: Grain::Corpus,
needs_body: false,
needs_phase_a,
needs_clock: false,
needs_prior,
needs_claims,
needs_observations: false,
}
}
pub(crate) const fn taxonomy() -> Self {
Scope {
grain: Grain::Taxonomy,
needs_body: false,
needs_phase_a: false,
needs_clock: false,
needs_prior: false,
needs_claims: false,
needs_observations: false,
}
}
pub fn grain(&self) -> Grain {
self.grain
}
pub fn needs_body(&self) -> bool {
self.needs_body
}
pub fn needs_phase_a(&self) -> bool {
self.needs_phase_a
}
pub fn needs_clock(&self) -> bool {
self.needs_clock
}
pub fn needs_prior(&self) -> bool {
self.needs_prior
}
pub fn needs_claims(&self) -> bool {
self.needs_claims
}
pub fn needs_observations(&self) -> bool {
self.needs_observations
}
pub fn render(&self) -> String {
let carries = match (self.grain, self.needs_body) {
(Grain::Document, false) => "one document and its front matter",
(Grain::Document, true) => "one document, its front matter and its body",
(Grain::Edge, _) => "one relation instance and both endpoints",
(Grain::Neighbourhood { .. }, _) => {
"one document and the documents one relation away from it"
}
(Grain::Corpus, _) => "every row of the census",
(Grain::Taxonomy, _) => "the resolved taxonomy, and no document",
};
let phase_a = match (self.needs_phase_a, self.grain) {
(false, _) => "",
(true, Grain::Corpus) => {
", and what phase A could not make of each document's identity"
}
(true, _) => ", and what phase A could not make of it",
};
let clock = match self.needs_clock {
true => ", and the injected clock",
false => "",
};
let prior = match (self.needs_prior, self.grain) {
(false, _) => "",
(true, Grain::Corpus) => {
", and the version of each path the change named that no row of this corpus holds"
}
(true, _) => ", and the version of it that stood before the change",
};
let claims = match self.needs_claims {
true => ", and the identifier claim store",
false => "",
};
let observations = match self.needs_observations {
true => ", and the committed observation snapshot",
false => "",
};
let barrier = match self.grain {
Grain::Corpus => ", and it is a barrier",
_ => "",
};
format!(
"{} scope, {carries}{phase_a}{clock}{prior}{claims}{observations}{barrier}",
self.grain.name()
)
}
}
pub type ExportTargets = &'static [&'static str];
pub trait DocumentCheck {
const RULE: &'static str;
const VERSION: u32;
const EXPORTABLE_AS: ExportTargets = &[];
const NEEDS_BODY: bool = false;
const NEEDS_PHASE_A: bool = false;
const NEEDS_CLOCK: bool = false;
const NEEDS_PRIOR: bool = false;
fn instantiates(&self, kind: &str) -> bool {
let _ = kind;
true
}
fn selects(&self, path: &str) -> bool {
let _ = path;
true
}
fn evaluate(&self, view: &DocumentView<'_>) -> Outcome;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EdgeUnit {
Pair,
Entry,
}
pub trait EdgeCheck {
const RULE: &'static str;
const VERSION: u32;
const EXPORTABLE_AS: ExportTargets = &[];
const NEEDS_CLOCK: bool = false;
const NEEDS_OBSERVATIONS: bool = false;
const UNIT: EdgeUnit = EdgeUnit::Pair;
fn instantiates(&self, relation: &str) -> bool {
let _ = relation;
true
}
fn evaluate(&self, view: &EdgeView<'_>) -> Outcome;
}
pub trait CorpusCheck {
const RULE: &'static str;
const VERSION: u32;
const EXPORTABLE_AS: ExportTargets = &[];
const NEEDS_PHASE_A: bool = false;
const NEEDS_PRIOR: bool = false;
const NEEDS_CLAIMS: bool = false;
const NEEDS_LINKS: bool = false;
const NEEDS_ANCHORS: bool = false;
const NEEDS_GRAPH: bool = false;
const NEEDS_ORPHANED: bool = false;
fn evaluate(&self, view: &CorpusView<'_>) -> Outcome;
}
pub trait NeighbourhoodCheck {
const RULE: &'static str;
const VERSION: u32;
const EXPORTABLE_AS: ExportTargets = &[];
const NEEDS_CLOCK: bool = false;
fn instantiates(&self, kind: &str) -> bool {
let _ = kind;
true
}
fn evaluate(&self, view: &NeighbourhoodView<'_>) -> Outcome;
}
pub fn document_scope<C: DocumentCheck>() -> Scope {
Scope::document(
C::NEEDS_BODY,
C::NEEDS_PHASE_A,
C::NEEDS_CLOCK,
C::NEEDS_PRIOR,
)
}
pub fn edge_scope<C: EdgeCheck>() -> Scope {
Scope::edge(C::NEEDS_CLOCK, C::NEEDS_OBSERVATIONS)
}
pub fn neighbourhood_scope<C: NeighbourhoodCheck>() -> Scope {
Scope::neighbourhood(C::NEEDS_CLOCK)
}
pub fn corpus_scope<C: CorpusCheck>() -> Scope {
Scope::corpus(C::NEEDS_PHASE_A, C::NEEDS_PRIOR, C::NEEDS_CLAIMS)
}
pub fn document_version<C: DocumentCheck>() -> u32 {
C::VERSION
}
pub fn edge_version<C: EdgeCheck>() -> u32 {
C::VERSION
}
pub fn neighbourhood_version<C: NeighbourhoodCheck>() -> u32 {
C::VERSION
}
pub fn corpus_version<C: CorpusCheck>() -> u32 {
C::VERSION
}
pub fn document_exports<C: DocumentCheck>() -> ExportTargets {
C::EXPORTABLE_AS
}
pub fn edge_exports<C: EdgeCheck>() -> ExportTargets {
C::EXPORTABLE_AS
}
pub fn neighbourhood_exports<C: NeighbourhoodCheck>() -> ExportTargets {
C::EXPORTABLE_AS
}
pub fn corpus_exports<C: CorpusCheck>() -> ExportTargets {
C::EXPORTABLE_AS
}
fn clock_for(scope: Scope, ctx: &Context) -> Option<Date> {
match scope.needs_clock() {
true => Some(ctx.now()),
false => None,
}
}
enum PriorFor<'a> {
NotDeclared,
Bound(Prior<'a>),
Skip(String),
}
pub const CHANGE_SCOPED_ONLY: &str = "change-scoped-only";
fn prior_for<'a>(scope: Scope, ctx: &'a Context, path: &str) -> PriorFor<'a> {
if !scope.needs_prior() {
return PriorFor::NotDeclared;
}
let Some(change) = ctx.change() else {
return PriorFor::Skip(format!(
"{CHANGE_SCOPED_ONLY}: the prior version is available only in change-scoped \
evaluation, and this run carries no change"
));
};
match change.prior_of(path) {
Ok(prior) => PriorFor::Bound(prior),
Err(why) => PriorFor::Skip(why.to_string()),
}
}
pub struct DocumentView<'a> {
path: &'a str,
digest: Option<&'a str>,
kind: &'a str,
placed_on: Option<&'a str>,
facets: &'a Mapping,
body: Option<&'a Body>,
phase_a: Option<Trouble<'a>>,
clock: Option<Date>,
prior: Option<Prior<'a>>,
regime: Option<&'a str>,
}
impl<'a> DocumentView<'a> {
pub fn path(&self) -> &'a str {
self.path
}
pub fn kind(&self) -> &'a str {
self.kind
}
pub fn outside_regime(&self) -> Option<&'a str> {
self.regime
}
pub fn placed_on(&self) -> Option<&'a str> {
self.placed_on
}
pub fn facets(&self) -> &'a Mapping {
self.facets
}
pub fn body(&self) -> Option<&'a Body> {
self.body
}
pub fn phase_a(&self) -> Option<&Trouble<'a>> {
self.phase_a.as_ref()
}
pub fn now(&self) -> Option<Date> {
self.clock
}
pub fn prior(&self) -> Option<Prior<'a>> {
self.prior
}
pub fn reads(&self) -> Vec<Input> {
vec![Input::new(self.path, self.digest)]
}
}
pub struct EdgeView<'a> {
relation: &'a str,
declared: Option<&'a Edge>,
inverse: Option<&'a Edge>,
ends: Option<(EdgeEnd<'a>, EdgeEnd<'a>)>,
declarer: Option<&'a Mapping>,
clock: Option<Date>,
reads: Vec<Input>,
resolution: String,
}
#[derive(Clone, Copy, Debug)]
pub struct EdgeEnd<'a> {
pub id: &'a str,
pub path: &'a str,
pub kind: &'a str,
facets: Option<&'a Mapping>,
generated: bool,
digest: Option<&'a str>,
}
impl<'a> EdgeEnd<'a> {
pub fn facets(&self) -> Option<&'a Mapping> {
self.facets
}
pub fn generated(&self) -> bool {
self.generated
}
pub fn digest(&self) -> Option<&'a str> {
self.digest
}
}
impl<'a> EdgeView<'a> {
fn over(
halves: &[&'a Edge],
census: &'a Census,
digests: &Digests,
clock: Option<Date>,
) -> Option<Self> {
let declared = halves
.iter()
.copied()
.find(|edge| edge.direction == Direction::AsDeclared);
let inverse = halves
.iter()
.copied()
.find(|edge| edge.direction == Direction::Inverse);
let anchor = declared.or(inverse)?;
let mut reads = vec![digests.input(&anchor.source.path)];
if let Target::Document { path, .. } = &anchor.target {
if !reads.iter().any(|input| &input.path == path) {
reads.push(digests.input(path));
}
}
let ends = match &anchor.target {
Target::Document { id, path, kind } => {
let (writer_facets, writer_generated, writer_digest) =
read_at(census, &anchor.source.path);
let writer = EdgeEnd {
id: &anchor.source.id,
path: &anchor.source.path,
kind: &anchor.source.kind,
facets: writer_facets,
generated: writer_generated,
digest: writer_digest,
};
let (other_facets, other_generated, other_digest) = read_at(census, path);
let other = EdgeEnd {
id,
path,
kind,
facets: other_facets,
generated: other_generated,
digest: other_digest,
};
Some(match anchor.direction {
Direction::AsDeclared => (writer, other),
Direction::Inverse => (other, writer),
})
}
_ => None,
};
Some(EdgeView {
relation: anchor.declared.as_str(),
declared,
inverse,
ends,
declarer: read_at(census, &anchor.source.path).0,
clock,
reads,
resolution: anchor.target.resolution(),
})
}
fn resolution(&self) -> &str {
&self.resolution
}
pub fn relation(&self) -> &'a str {
self.relation
}
pub fn declared_half(&self) -> Option<&'a Edge> {
self.declared
}
pub fn inverse_half(&self) -> Option<&'a Edge> {
self.inverse
}
pub fn ends(&self) -> Option<(EdgeEnd<'a>, EdgeEnd<'a>)> {
self.ends
}
pub fn declarer_facets(&self) -> Option<&'a Mapping> {
self.declarer
}
pub fn now(&self) -> Option<Date> {
self.clock
}
pub fn reads(&self) -> &[Input] {
&self.reads
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum End {
Source,
Target,
}
#[derive(Clone, Copy, Debug)]
pub struct Neighbour<'a> {
pub relation: &'a str,
pub end: End,
pub path: &'a str,
pub kind: &'a str,
pub id: &'a str,
pub span: Span,
}
pub struct NeighbourhoodView<'a> {
path: &'a str,
kind: &'a str,
facets: &'a Mapping,
neighbours: Vec<Neighbour<'a>>,
clock: Option<Date>,
reads: Vec<Input>,
}
impl<'a> NeighbourhoodView<'a> {
pub fn path(&self) -> &'a str {
self.path
}
pub fn kind(&self) -> &'a str {
self.kind
}
pub fn facets(&self) -> &'a Mapping {
self.facets
}
pub fn neighbours(&self) -> &[Neighbour<'a>] {
&self.neighbours
}
pub fn now(&self) -> Option<Date> {
self.clock
}
pub fn reads(&self) -> &[Input] {
&self.reads
}
}
pub struct CorpusView<'a> {
identity: Option<&'a [headwater_graph::index::Reported]>,
departed: &'a [Departed<'a>],
claims: Option<&'a crate::claim::Claims>,
links: Option<&'a [headwater_graph::links::Link]>,
anchors: Option<&'a crate::fragment::Anchors>,
edges: Option<&'a [Edge]>,
generated: Option<Vec<Generated<'a>>>,
orphaned: Option<&'a std::collections::BTreeSet<String>>,
reads: Vec<Input>,
}
#[derive(Clone, Copy, Debug)]
pub struct Generated<'a> {
pub path: &'a str,
pub id: &'a str,
pub kind: &'a str,
pub facets: &'a Mapping,
}
impl<'a> CorpusView<'a> {
pub fn edges(&self) -> Option<&'a [Edge]> {
self.edges
}
pub fn generated(&self) -> Option<&[Generated<'a>]> {
self.generated.as_deref()
}
pub fn orphaned(&self) -> Option<&'a std::collections::BTreeSet<String>> {
self.orphaned
}
pub fn identity(&self) -> Option<&'a [headwater_graph::index::Reported]> {
self.identity
}
pub fn departed(&self) -> &[Departed<'a>] {
self.departed
}
pub fn claims(&self) -> Option<&'a crate::claim::Claims> {
self.claims
}
pub fn links(&self) -> Option<&'a [headwater_graph::links::Link]> {
self.links
}
pub fn anchors(&self) -> Option<&'a crate::fragment::Anchors> {
self.anchors
}
pub fn reads(&self) -> &[Input] {
&self.reads
}
#[cfg(test)]
pub(crate) fn only_links(links: Option<&'a [headwater_graph::links::Link]>) -> Self {
CorpusView {
identity: None,
departed: &[],
claims: None,
links,
anchors: None,
edges: None,
generated: None,
orphaned: None,
reads: Vec::new(),
}
}
#[cfg(test)]
pub(crate) fn only_claims(claims: Option<&'a crate::claim::Claims>) -> Self {
CorpusView {
identity: None,
departed: &[],
claims,
links: None,
anchors: None,
edges: None,
generated: None,
orphaned: None,
reads: Vec::new(),
}
}
#[cfg(test)]
pub(crate) fn only_links_and_anchors(
links: Option<&'a [headwater_graph::links::Link]>,
anchors: Option<&'a crate::fragment::Anchors>,
) -> Self {
CorpusView {
identity: None,
departed: &[],
claims: None,
links,
anchors,
edges: None,
generated: None,
orphaned: None,
reads: Vec::new(),
}
}
}
pub struct Digests {
by_path: Vec<(String, Option<String>)>,
}
impl Digests {
pub fn of(census: &Census) -> Self {
Digests {
by_path: census
.rows
.iter()
.map(|row| (row.path.clone(), row.digest.clone()))
.collect(),
}
}
pub fn input(&self, path: &str) -> Input {
let digest = self
.by_path
.binary_search_by(|(known, _)| known.as_str().cmp(path))
.ok()
.and_then(|index| self.by_path[index].1.as_deref());
Input::new(path, digest)
}
}
fn read_at<'a>(census: &'a Census, path: &str) -> (Option<&'a Mapping>, bool, Option<&'a str>) {
let Ok(index) = census
.rows
.binary_search_by(|row| row.path.as_str().cmp(path))
else {
return (None, false, None);
};
let row = &census.rows[index];
(
row.document.as_ref().map(|document| &document.facets),
matches!(row.outcome, Classification::Generated { .. }),
row.digest.as_deref(),
)
}
pub fn over_documents<C: DocumentCheck>(
check: &C,
census: &Census,
graph: &Graph,
ctx: &Context,
cache: &mut Cache,
) -> Vec<Instance> {
let scope = document_scope::<C>();
let clock = clock_for(scope, ctx);
let mut instances = Vec::new();
for row in &census.rows {
let Classification::Typed { kind, derivation } = &row.outcome else {
continue;
};
if !check.instantiates(kind) || !check.selects(&row.path) {
continue;
}
let prior = match prior_for(scope, ctx, &row.path) {
PriorFor::NotDeclared => None,
PriorFor::Bound(prior) => Some(prior),
PriorFor::Skip(why) => {
cache.undecided();
instances.push(Instance::skipped(
C::RULE,
Grain::Document,
vec![Input::new(&row.path, row.digest.as_deref())],
&why,
));
continue;
}
};
let Some(document) = &row.document else {
cache.undecided();
instances.push(Instance::skipped(
C::RULE,
Grain::Document,
vec![Input::new(&row.path, row.digest.as_deref())],
NO_DOCUMENT,
));
continue;
};
let view = DocumentView {
path: &row.path,
digest: row.digest.as_deref(),
kind,
placed_on: derivation.steps.iter().find_map(|step| match step {
Step::PlacementCarriesTheKind { shelf, .. } => Some(shelf.as_str()),
_ => None,
}),
facets: &document.facets,
body: match C::NEEDS_BODY {
true => Some(&document.body),
false => None,
},
phase_a: match C::NEEDS_PHASE_A {
true => Some(graph.about(&row.path)),
false => None,
},
clock,
prior,
regime: None,
};
let reads = view.reads();
let outcome = cache.outcome(
C::RULE,
C::VERSION,
scope,
&row.path,
&reads,
clock,
prior,
None,
|| check.evaluate(&view),
);
instances.push(Instance::of(C::RULE, Grain::Document, reads, outcome));
}
instances
}
pub trait OutsideCheck: DocumentCheck {
fn binds_regime(&self, regime: &str) -> bool;
}
pub fn over_outside_root<C: OutsideCheck>(
check: &C,
census: &Census,
cache: &mut Cache,
) -> Vec<Instance> {
const {
assert!(
!C::NEEDS_PHASE_A && !C::NEEDS_CLOCK && !C::NEEDS_PRIOR,
"an outside path carries no phase-A report, clock or prior version"
);
}
let scope = document_scope::<C>();
let mut instances = Vec::new();
for row in &census.outside.rows {
if !check.binds_regime(&row.regime) {
continue;
}
let Some(document) = &row.document else {
cache.undecided();
instances.push(Instance::skipped(
C::RULE,
Grain::Document,
vec![Input::new(&row.path, row.digest.as_deref())],
row.unread.as_deref().unwrap_or(NO_DOCUMENT),
));
continue;
};
let view = DocumentView {
path: &row.path,
digest: row.digest.as_deref(),
kind: "",
placed_on: None,
facets: &document.facets,
body: match C::NEEDS_BODY {
true => Some(&document.body),
false => None,
},
phase_a: None,
clock: None,
prior: None,
regime: Some(&row.regime),
};
let reads = view.reads();
let outcome = cache.outcome(
C::RULE,
C::VERSION,
scope,
&row.path,
&reads,
None,
None,
None,
|| check.evaluate(&view),
);
instances.push(Instance::of(C::RULE, Grain::Document, reads, outcome));
}
instances
}
pub fn over_edges<C: EdgeCheck>(
check: &C,
census: &Census,
graph: &Graph,
digests: &Digests,
observations: &crate::observation::Observations,
ctx: &Context,
cache: &mut Cache,
) -> Vec<Instance> {
let scope = edge_scope::<C>();
let clock = clock_for(scope, ctx);
let mut pairs: Vec<(String, Vec<&Edge>)> = Vec::new();
for edge in &graph.edges {
let Some(key) = (match C::UNIT {
EdgeUnit::Pair => edge
.declared_triple()
.and_then(
|(source, relation, target)| match check.instantiates(&relation) {
true => Some(format!("{source}\u{1f}{relation}\u{1f}{target}")),
false => None,
},
),
EdgeUnit::Entry => match check.instantiates(&edge.declared) {
true => {
let (source, name, target) = edge.triple();
Some(format!("{source}\u{1f}{name}\u{1f}{target}"))
}
false => None,
},
}) else {
continue;
};
match pairs.iter_mut().find(|(known, _)| known == &key) {
Some((_, halves)) => halves.push(edge),
None => pairs.push((key, vec![edge])),
}
}
let mut instances = Vec::with_capacity(pairs.len());
for (triple, halves) in &pairs {
let Some(view) = EdgeView::over(halves, census, digests, clock) else {
continue;
};
let mut reads = view.reads().to_vec();
if C::NEEDS_OBSERVATIONS {
if let Some(digest) = observations.read_set_digest() {
reads.push(Input::new(crate::observation::PATH, digest));
}
}
let outcome = cache.outcome(
C::RULE,
C::VERSION,
scope,
triple,
&reads,
clock,
None,
Some(view.resolution()),
|| check.evaluate(&view),
);
instances.push(Instance::of(C::RULE, Grain::Edge, reads, outcome));
}
instances
}
pub fn over_neighbourhoods<C: NeighbourhoodCheck>(
check: &C,
census: &Census,
graph: &Graph,
digests: &Digests,
ctx: &Context,
cache: &mut Cache,
) -> Vec<Instance> {
let scope = neighbourhood_scope::<C>();
let clock = clock_for(scope, ctx);
let adjacency = Adjacency::of(graph);
let mut instances = Vec::new();
for row in &census.rows {
let Classification::Typed { kind, .. } = &row.outcome else {
continue;
};
if !check.instantiates(kind) {
continue;
}
let Some(document) = &row.document else {
cache.undecided();
instances.push(Instance::skipped(
C::RULE,
Grain::Neighbourhood { depth: 1 },
vec![Input::new(&row.path, row.digest.as_deref())],
NO_DOCUMENT,
));
continue;
};
let neighbours = adjacency.of_path(&row.path);
let mut reads = vec![Input::new(&row.path, row.digest.as_deref())];
for neighbour in &neighbours {
if !neighbour.path.is_empty() && !reads.iter().any(|input| input.path == neighbour.path)
{
reads.push(digests.input(neighbour.path));
}
}
let view = NeighbourhoodView {
path: &row.path,
kind,
facets: &document.facets,
neighbours,
clock,
reads: reads.clone(),
};
let outcome = cache.outcome(
C::RULE,
C::VERSION,
scope,
&row.path,
&reads,
clock,
None,
adjacency.anchor_resolution_of_path(&row.path),
|| check.evaluate(&view),
);
instances.push(Instance::of(
C::RULE,
Grain::Neighbourhood { depth: 1 },
reads,
outcome,
));
}
instances
}
const CORPUS: &str = "the corpus";
pub fn over_corpus<C: CorpusCheck>(
check: &C,
census: &Census,
graph: &Graph,
claims: &crate::claim::Claims,
ctx: &Context,
cache: &mut Cache,
) -> Vec<Instance> {
let scope = corpus_scope::<C>();
let mut reads: Vec<Input> = census
.rows
.iter()
.filter(|row| row.document.is_some())
.map(|row| Input::new(&row.path, row.digest.as_deref()))
.collect();
let departed = match (C::NEEDS_PRIOR, ctx.change()) {
(false, _) => Vec::new(),
(true, Some(change)) => change.departed(),
(true, None) => {
cache.undecided();
return vec![Instance::skipped(
C::RULE,
Grain::Corpus,
reads,
format!(
"{CHANGE_SCOPED_ONLY}: the prior version is available only in change-scoped \
evaluation, and this run carries no change"
),
)];
}
};
reads.extend(
departed
.iter()
.map(|entry| Input::new(entry.path, Some(entry.digest))),
);
if C::NEEDS_CLAIMS {
let digest = claims.digest();
reads.push(Input::new(crate::claim::STORE, Some(digest.as_str())));
}
let anchors = match C::NEEDS_ANCHORS {
true => Some(crate::fragment::Anchors::of(census)),
false => None,
};
let view = CorpusView {
identity: match C::NEEDS_PHASE_A {
true => Some(&graph.index.defects),
false => None,
},
departed: &departed,
claims: match C::NEEDS_CLAIMS {
true => Some(claims),
false => None,
},
links: match C::NEEDS_LINKS {
true => Some(&graph.links),
false => None,
},
anchors: anchors.as_ref(),
edges: match C::NEEDS_GRAPH {
true => Some(&graph.edges),
false => None,
},
generated: match C::NEEDS_GRAPH {
true => Some(
census
.rows
.iter()
.filter_map(|row| match (&row.outcome, &row.document) {
(
Classification::Generated {
kind: Some(kind), ..
},
Some(document),
) => graph
.index
.typed
.iter()
.find(|node| node.path == row.path)
.map(|node| Generated {
path: &row.path,
id: &node.id,
kind,
facets: &document.facets,
}),
_ => None,
})
.collect(),
),
false => None,
},
orphaned: match C::NEEDS_ORPHANED {
true => Some(ctx.orphaned()),
false => None,
},
reads: reads.clone(),
};
let orphaned = C::NEEDS_ORPHANED.then(|| {
let listing: String = ctx
.orphaned()
.iter()
.map(|path| format!("{path}\n"))
.collect();
format!("orphaned {}", headwater_hash::digest(listing.as_bytes()))
});
let outcome = cache.outcome(
C::RULE,
C::VERSION,
scope,
CORPUS,
&reads,
None,
None,
orphaned.as_deref(),
|| check.evaluate(&view),
);
vec![Instance::of(C::RULE, Grain::Corpus, reads, outcome)]
}
struct Adjacency<'a> {
by_path: Vec<(&'a str, Vec<Neighbour<'a>>)>,
anchor_resolutions_by_path: Vec<(&'a str, String)>,
}
impl<'a> Adjacency<'a> {
fn of(graph: &'a Graph) -> Self {
let mut adjacency = Adjacency {
by_path: Vec::new(),
anchor_resolutions_by_path: Vec::new(),
};
for edge in &graph.edges {
match &edge.target {
Target::Document { id, path, kind } => {
let (near, far) = match edge.direction {
Direction::AsDeclared => (End::Source, End::Target),
Direction::Inverse => (End::Target, End::Source),
};
adjacency.push(
&edge.source.path,
Neighbour {
relation: &edge.declared,
end: near,
path,
kind,
id,
span: edge.span,
},
);
adjacency.push(
path,
Neighbour {
relation: &edge.declared,
end: far,
path: &edge.source.path,
kind: &edge.source.kind,
id: &edge.source.id,
span: edge.span,
},
);
}
Target::Anchor {
anchor_kind,
normalized,
..
} => {
let near = match edge.direction {
Direction::AsDeclared => End::Source,
Direction::Inverse => End::Target,
};
adjacency.push(
&edge.source.path,
Neighbour {
relation: &edge.declared,
end: near,
path: "",
kind: anchor_kind,
id: normalized,
span: edge.span,
},
);
adjacency.push_resolution(&edge.source.path, edge.target.resolution());
}
_ => continue,
}
}
adjacency
}
fn push(&mut self, path: &'a str, neighbour: Neighbour<'a>) {
let entry = match self.by_path.iter_mut().find(|(known, _)| *known == path) {
Some(entry) => entry,
None => {
self.by_path.push((path, Vec::new()));
self.by_path.last_mut().expect("just pushed")
}
};
if !entry.1.iter().any(|known| {
known.relation == neighbour.relation
&& known.end == neighbour.end
&& known.path == neighbour.path
&& known.id == neighbour.id
}) {
entry.1.push(neighbour);
}
}
fn push_resolution(&mut self, path: &'a str, resolution: String) {
match self
.anchor_resolutions_by_path
.iter_mut()
.find(|(known, _)| *known == path)
{
Some((_, existing)) => {
existing.push('\n');
existing.push_str(&resolution);
}
None => self.anchor_resolutions_by_path.push((path, resolution)),
}
}
fn of_path(&self, path: &str) -> Vec<Neighbour<'a>> {
self.by_path
.iter()
.find(|(known, _)| *known == path)
.map(|(_, neighbours)| neighbours.clone())
.unwrap_or_default()
}
fn anchor_resolution_of_path(&self, path: &str) -> Option<&str> {
self.anchor_resolutions_by_path
.iter()
.find(|(known, _)| *known == path)
.map(|(_, resolution)| resolution.as_str())
}
}