use kimun_core::nfs::VaultPath;
use kimun_core::{expand_bare_note_prefixes, quote_query_term, strip_order_directive};
pub const VAR_NOTE: &str = "{note}";
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, current_note: Option<&VaultPath>) -> bool {
if current_note.is_some_and(|p| !p.is_root_or_empty()) {
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, current_note: Option<&VaultPath>) -> String {
let note_name = current_note
.map(|p| quote_query_term(&p.get_clean_name()))
.unwrap_or_default();
expand_bare_note_prefixes(template, VAR_NOTE).replace(VAR_NOTE, ¬e_name)
}
#[cfg(test)]
mod tests {
use super::*;
#[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}", Some(&p)), "<spec");
assert_eq!(resolve_query("#todo <{note}", Some(&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}", Some(&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("<", Some(&p)), "<spec");
assert_eq!(resolve_query(">", Some(&p)), ">spec");
assert_eq!(resolve_query("=", Some(&p)), "=spec");
assert_eq!(resolve_query("#todo <", Some(&p)), "#todo <spec");
assert_eq!(resolve_query("< #todo", Some(&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:", Some(&p)), "lk:spec");
assert_eq!(resolve_query("fwd:", Some(&p)), "fwd:spec");
assert_eq!(resolve_query("name:", Some(&p)), "name:spec");
assert_eq!(resolve_query("-<", Some(&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 <", Some(&p)), "don't <spec");
assert_eq!(resolve_query("= don't <", Some(&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", Some(&p)), "<projects");
assert_eq!(resolve_query(">projects", Some(&p)), ">projects");
assert_eq!(resolve_query("=projects", Some(&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\"", Some(&p)), "\"a < b\"");
assert_eq!(resolve_query("'a = b'", Some(&p)), "'a = b'");
}
#[test]
fn unresolvable_only_when_purely_note_dependent() {
let p = VaultPath::note_path_from("work/spec.md");
assert!(!query_is_unresolvable("<", Some(&p)));
assert!(!query_is_unresolvable("<{note}", Some(&p)));
assert!(query_is_unresolvable("<", None));
assert!(query_is_unresolvable("<{note}", None));
assert!(query_is_unresolvable("< or:title", None));
assert!(query_is_unresolvable("<", Some(&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\""));
}
}