use crate::datatypes::values::Value;
use crate::okf::frontmatter;
use crate::okf::links;
use crate::okf::model::{Link, Profile};
use crate::okf::structure::block::Directive;
use crate::okf::structure::profile::is_reserved_property;
use crate::okf::structure::{BlockTree, Derived};
const MARKER_KEYS: [&str; 2] = ["chunk", "heading"];
pub(crate) struct NoteSide<'a> {
pub profile: &'a Profile,
pub source_dir: &'a str,
pub props: &'a mut Vec<(String, Value)>,
pub links: &'a mut Vec<Link>,
pub errors: &'a mut Vec<String>,
}
pub(crate) fn apply(tree: &BlockTree, derived: &mut Derived, note: &mut NoteSide<'_>) {
for directive in &tree.directives {
if directive.key.is_empty() || MARKER_KEYS.contains(&directive.key.as_str()) {
continue;
}
if is_reserved_property(&directive.key) || directive.key == note.profile.body_property {
note.errors.push(format!(
"`<!-- kglite {key}: … -->` names `{key}`, which a note or a derived node \
defines itself (VAULT.md §4.1, §5.8, §7.1)",
key = directive.key
));
continue;
}
let raw = directive.raw_value.as_deref().unwrap_or("").trim();
if raw.is_empty() {
derived.warnings.push(format!(
"`<!-- kglite {} -->` carries no value, so it states nothing (VAULT.md §5.8)",
directive.key
));
continue;
}
let target = target_of(tree, derived, directive);
match read_value(raw, note.profile) {
Read::Edges(targets) => emit_edges(&directive.key, targets, target, derived, note),
Read::Property(value) => set_property(&directive.key, value, target, derived, note),
}
}
}
fn target_of(tree: &BlockTree, derived: &Derived, directive: &Directive) -> Option<String> {
let heading = tree.blocks[directive.block].heading?;
derived.section_suffixes.get(heading).cloned()
}
enum Read {
Edges(Vec<String>),
Property(Value),
}
fn read_value(raw: &str, profile: &Profile) -> Read {
if let Some(targets) = bare_wikilinks(raw) {
return Read::Edges(targets);
}
let parsed = match frontmatter::parse_yaml(raw) {
Ok(Value::Map(_) | Value::Null) | Err(_) => Value::String(raw.to_string()),
Ok(value) => value,
};
if let Some(targets) = links::wikilink_targets(&parsed) {
return Read::Edges(targets);
}
Read::Property(if profile.infer_temporal {
frontmatter::infer_temporal(parsed)
} else {
parsed
})
}
fn bare_wikilinks(raw: &str) -> Option<Vec<String>> {
let one = |text: &str| links::wikilink_targets(&Value::String(text.trim().to_string()));
if let Some(single) = one(raw) {
return Some(single);
}
let parts: Vec<&str> = raw.split(',').collect();
if parts.len() < 2 {
return None;
}
parts
.into_iter()
.map(|part| one(part).filter(|t| t.len() == 1).map(|mut t| t.remove(0)))
.collect()
}
fn emit_edges(
key: &str,
targets: Vec<String>,
target: Option<String>,
derived: &mut Derived,
note: &mut NoteSide<'_>,
) {
let conn_type = links::upper_snake(key);
if conn_type.is_empty() {
derived.warnings.push(format!(
"`<!-- kglite {key}: … -->` names no edge type, so its wikilinks state \
nothing (VAULT.md §5.3)"
));
return;
}
for name in targets {
if note.profile.path_safety {
links::record_wikilink_path_error(note.errors, &name, note.source_dir);
}
let link = Link::plain(name, conn_type.clone(), false);
match &target {
Some(suffix) => derived.links_from.push((suffix.clone(), link)),
None => links::push_unique(note.links, link),
}
}
}
fn set_property(
key: &str,
value: Value,
target: Option<String>,
derived: &mut Derived,
note: &mut NoteSide<'_>,
) {
let props = match &target {
Some(suffix) => match derived.nodes.iter_mut().find(|n| n.suffix == *suffix) {
Some(node) => &mut node.props,
None => return,
},
None => &mut *note.props,
};
let replaced = match props.iter_mut().find(|(k, _)| k == key) {
Some(slot) => {
slot.1 = value;
true
}
None => {
props.push((key.to_string(), value));
false
}
};
if replaced {
derived.warnings.push(format!(
"`{key}` is stated more than once on the same node; the last \
`<!-- kglite {key}: … -->` wins (VAULT.md §5.8)"
));
}
}