use inker::{Block, BlockProvenanceMap, DocumentProvenance, DocumentTrustState, EngineDocument};
pub fn build_clip_knot(
blocks: &[Block],
source: &DocumentProvenance,
trust: DocumentTrustState,
note_kind: Option<&str>,
) -> String {
build_clip_knot_inner(blocks, source, trust, note_kind, None)
}
pub fn build_clip_knot_with_block_provenance(
blocks: &[Block],
source: &DocumentProvenance,
trust: DocumentTrustState,
note_kind: Option<&str>,
block_provenance: &BlockProvenanceMap,
) -> String {
build_clip_knot_inner(blocks, source, trust, note_kind, Some(block_provenance))
}
fn build_clip_knot_inner(
blocks: &[Block],
source: &DocumentProvenance,
trust: DocumentTrustState,
note_kind: Option<&str>,
block_provenance: Option<&BlockProvenanceMap>,
) -> String {
use inker::EngineDocument;
let mut out = String::new();
out.push_str("---\n");
if let Some(uri) = &source.canonical_uri {
out.push_str(&format!("source: {uri}\n"));
}
if let Some(when) = &source.fetched_at {
out.push_str(&format!("captured: {when}\n"));
}
if let Some(label) = &source.source_label {
out.push_str(&format!("source_label: {label}\n"));
} else if let Some(kind) = &source.source_kind {
out.push_str(&format!("source_label: {kind}\n"));
}
out.push_str(&format!("trust: {}\n", trust_to_string(trust)));
if let Some(kind) = note_kind {
out.push_str(&format!("note_kind: {kind}\n"));
}
if let Some(map) = block_provenance {
let entries = collect_block_source_entries(map, source);
if !entries.is_empty() {
out.push_str("block_sources: [");
for (i, entry) in entries.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
out.push('"');
out.push_str(entry);
out.push('"');
}
out.push_str("]\n");
}
}
out.push_str("---\n\n");
let document = EngineDocument {
address: source
.canonical_uri
.clone()
.unwrap_or_else(|| "knot:clip".to_string()),
title: None,
content_type: "text/x-knot".to_string(),
lang: None,
provenance: source.clone(),
trust,
diagnostics: Vec::new(),
blocks: blocks.to_vec(),
};
document.write_knot_body(&mut out);
out
}
fn collect_block_source_entries(
map: &BlockProvenanceMap,
document_source: &DocumentProvenance,
) -> Vec<String> {
let mut entries: Vec<(usize, String)> = map
.iter()
.filter_map(|(index, block_prov)| {
let same_uri = block_prov.provenance.canonical_uri == document_source.canonical_uri;
if same_uri && block_prov.anchor.is_none() {
return None;
}
let uri = block_prov.provenance.canonical_uri.as_deref()?;
let encoded = match block_prov.anchor.as_deref() {
Some(anchor) => format!("{index}|{uri}|{anchor}"),
None => format!("{index}|{uri}"),
};
Some((index, encoded))
})
.collect();
entries.sort_by_key(|(i, _)| *i);
entries.into_iter().map(|(_, s)| s).collect()
}
fn trust_to_string(trust: DocumentTrustState) -> &'static str {
match trust {
DocumentTrustState::Trusted => "trusted",
DocumentTrustState::Tofu => "tofu",
DocumentTrustState::Insecure => "insecure",
DocumentTrustState::Broken => "broken",
DocumentTrustState::Unknown => "unknown",
}
}