use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use super::nfd;
const FRONTMATTER_DELIM_START: &str = "---";
const INLINE_TAG_PATTERN: &str = r"(?u)(^|\s)#([\p{L}\p{N}_/.-]+)";
const WIKILINK_PATTERN: &str = r"\[\[([^\]]+)\]\]";
const FENCE_PATTERN: &str = r"(?u)^(```+|~~~+)\s*.*$";
const HEADING_PATTERN: &str = r"(?u)^#{1,6}\s+(.*)$";
const MIN_QUOTED_LENGTH: usize = 2;
pub const TOKEN_CHAR_RATIO: u8 = 4;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FrontmatterExtract {
pub body: String,
pub frontmatter: BTreeMap<String, FrontmatterValue>,
pub frontmatter_raw: String,
pub aliases: Vec<String>,
pub tags: Vec<String>,
pub links: Vec<WikiLink>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum FrontmatterValue {
String(String),
Number(f64),
Boolean(bool),
Date(String),
List(Vec<String>),
}
impl Eq for FrontmatterValue {}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WikiLink {
pub alias: Option<String>,
pub char_end: usize,
pub char_start: usize,
pub heading: Option<String>,
pub line_end: u32,
pub line_start: u32,
pub raw_target: String,
pub text: String,
pub target: String,
}
#[must_use]
pub fn normalize_keyword(value: &str) -> String {
nfd::normalize(value.trim())
.to_lowercase()
.replace(char::is_whitespace, "")
}
#[must_use]
pub fn normalize_vault_path(value: &str) -> String {
nfd::normalize(&value.replace('\\', "/")).to_lowercase()
}
mod links;
mod parse;
pub use links::extract_wikilinks;
pub use parse::parse_frontmatter;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FrontmatterEntry {
pub path: String,
pub key: String,
#[serde(rename = "type")]
pub value_type: FrontmatterValueType,
pub value: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FrontmatterValueType {
String,
Number,
Bool,
Date,
List,
}
impl FrontmatterValueType {
#[must_use]
pub const fn as_db_str(self) -> &'static str {
match self {
Self::String => "string",
Self::Number => "number",
Self::Bool => "bool",
Self::Date => "date",
Self::List => "list",
}
}
}
impl From<FrontmatterValue> for FrontmatterValueType {
fn from(value: FrontmatterValue) -> Self {
match value {
FrontmatterValue::String(_) => Self::String,
FrontmatterValue::Number(_) => Self::Number,
FrontmatterValue::Boolean(_) => Self::Bool,
FrontmatterValue::Date(_) => Self::Date,
FrontmatterValue::List(_) => Self::List,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct FrontmatterReverseIndex {
pub index: std::collections::BTreeMap<String, std::collections::BTreeSet<String>>,
}
impl FrontmatterReverseIndex {
pub fn insert(&mut self, path: &str, key: &str, value: &str) {
let normalized_key = normalize_keyword(key);
let normalized_value = normalize_keyword(value);
let composite = format!("{normalized_key}:{normalized_value}");
self.index
.entry(composite)
.or_default()
.insert(path.to_string());
}
#[must_use]
pub fn lookup(&self, key: &str, value: &str) -> Option<&std::collections::BTreeSet<String>> {
let normalized_key = normalize_keyword(key);
let normalized_value = normalize_keyword(value);
self.index
.get(&format!("{normalized_key}:{normalized_value}"))
}
#[must_use]
pub fn lookup_key(&self, key: &str) -> std::collections::BTreeSet<String> {
let normalized_key = normalize_keyword(key);
let mut result = std::collections::BTreeSet::new();
for (composite, paths) in &self.index {
if composite.starts_with(&format!("{normalized_key}:")) {
result.extend(paths.iter().cloned());
}
}
result
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReverseSourceIndex {
pub sources: std::collections::BTreeMap<String, std::collections::BTreeSet<String>>,
}
impl ReverseSourceIndex {
pub fn insert(&mut self, source: String, target: String) {
self.sources.entry(target).or_default().insert(source);
}
#[must_use]
pub fn get(&self, target: &str) -> Option<&std::collections::BTreeSet<String>> {
self.sources.get(target)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests;