use kimun_core::nfs::VaultPath;
use kimun_core::{expand_bare_note_prefixes, quote_query_term, strip_order_directive};
pub const VAR_NOTE: &str = "{note}";
#[derive(Clone, Default)]
pub struct QueryContext {
pub current_note: Option<VaultPath>,
}
impl QueryContext {
pub fn with_note(current_note: Option<VaultPath>) -> Self {
Self { current_note }
}
fn has_note(&self) -> bool {
self.current_note
.as_ref()
.is_some_and(|p| !p.is_root_or_empty())
}
fn note_term(&self) -> String {
self.current_note
.as_ref()
.map(|p| quote_query_term(&p.get_clean_name()))
.unwrap_or_default()
}
}
pub fn query_has_variables(template: &str) -> bool {
expand_bare_note_prefixes(template, VAR_NOTE).contains(VAR_NOTE)
}
pub fn query_is_unresolvable(template: &str, ctx: &QueryContext) -> bool {
if ctx.has_note() {
return false;
}
let expanded = expand_bare_note_prefixes(template, VAR_NOTE);
expanded.contains(VAR_NOTE)
&& strip_order_directive(&expanded)
.split_whitespace()
.all(|token| token.contains(VAR_NOTE))
}
pub fn resolve_query(template: &str, ctx: &QueryContext) -> String {
expand_bare_note_prefixes(template, VAR_NOTE).replace(VAR_NOTE, &ctx.note_term())
}
#[cfg(test)]
mod tests {
use super::*;
fn with(p: &VaultPath) -> QueryContext {
QueryContext::with_note(Some(p.clone()))
}
fn none() -> QueryContext {
QueryContext::default()
}
#[test]
fn detects_variables() {
assert!(query_has_variables("<{note}"));
assert!(query_has_variables("#todo <{note}"));
assert!(!query_has_variables("#todo"));
}
#[test]
fn resolves_note_variable() {
let p = VaultPath::note_path_from("work/spec.md");
assert_eq!(resolve_query("<{note}", &with(&p)), "<spec");
assert_eq!(resolve_query("#todo <{note}", &with(&p)), "#todo <spec");
}
#[test]
fn resolves_note_with_spaces_quoted() {
let p = VaultPath::note_path_from("work/my note.md");
assert_eq!(resolve_query("<{note}", &with(&p)), "<\"my note\"");
}
#[test]
fn resolves_to_empty_without_note() {
assert_eq!(resolve_query("<{note}", &none()), "<");
assert_eq!(resolve_query("#todo", &none()), "#todo");
}
#[test]
fn bare_operators_expand_to_note_variable() {
let p = VaultPath::note_path_from("work/spec.md");
assert_eq!(resolve_query("<", &with(&p)), "<spec");
assert_eq!(resolve_query(">", &with(&p)), ">spec");
assert_eq!(resolve_query("=", &with(&p)), "=spec");
assert_eq!(resolve_query("#todo <", &with(&p)), "#todo <spec");
assert_eq!(resolve_query("< #todo", &with(&p)), "<spec #todo");
}
#[test]
fn bare_long_forms_and_exclusions_expand_too() {
let p = VaultPath::note_path_from("work/spec.md");
assert_eq!(resolve_query("lk:", &with(&p)), "lk:spec");
assert_eq!(resolve_query("fwd:", &with(&p)), "fwd:spec");
assert_eq!(resolve_query("name:", &with(&p)), "name:spec");
assert_eq!(resolve_query("-<", &with(&p)), "-<spec");
}
#[test]
fn apostrophe_in_term_does_not_suppress_expansion() {
let p = VaultPath::note_path_from("work/spec.md");
assert_eq!(resolve_query("don't <", &with(&p)), "don't <spec");
assert_eq!(resolve_query("= don't <", &with(&p)), "=spec don't <spec");
}
#[test]
fn operators_with_targets_stay_untouched() {
let p = VaultPath::note_path_from("work/spec.md");
assert_eq!(resolve_query("<projects", &with(&p)), "<projects");
assert_eq!(resolve_query(">projects", &with(&p)), ">projects");
assert_eq!(resolve_query("=projects", &with(&p)), "=projects");
}
#[test]
fn bare_operator_inside_quotes_stays_untouched() {
let p = VaultPath::note_path_from("work/spec.md");
assert_eq!(resolve_query("\"a < b\"", &with(&p)), "\"a < b\"");
assert_eq!(resolve_query("'a = b'", &with(&p)), "'a = b'");
}
#[test]
fn unresolvable_only_when_purely_note_dependent() {
let p = VaultPath::note_path_from("work/spec.md");
assert!(!query_is_unresolvable("<", &with(&p)));
assert!(!query_is_unresolvable("<{note}", &with(&p)));
assert!(query_is_unresolvable("<", &none()));
assert!(query_is_unresolvable("<{note}", &none()));
assert!(query_is_unresolvable("< or:title", &none()));
assert!(query_is_unresolvable("<", &with(&VaultPath::empty())));
assert!(!query_is_unresolvable("widget <", &none()));
assert!(!query_is_unresolvable("#todo <{note}", &none()));
assert!(!query_is_unresolvable("widget", &none()));
assert!(!query_is_unresolvable("", &none()));
}
#[test]
fn bare_operators_count_as_variables() {
assert!(query_has_variables("<"));
assert!(query_has_variables(">"));
assert!(query_has_variables("="));
assert!(query_has_variables("#todo <"));
assert!(!query_has_variables("<projects"));
assert!(!query_has_variables("\"a < b\""));
}
}