Something went wrong. Try again.
CLI for indexing Zettelkasten Folgezettel identifiers
Something went wrong. Try again.
3.0 kB · 99 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100use std::collections::BTreeSet;use std::path::Path;
use crate::{ FolgezettelId, Zettel, error::{Diagnostic, DiagnosticSeverity},};
#[derive(Debug, Clone, PartialEq, Eq)]pub struct Analysis { notes: Vec<Zettel>, diagnostics: Vec<Diagnostic>,}
impl Analysis { pub fn from_files<I, S>(files: I) -> Self where I: IntoIterator<Item = S>, S: AsRef<str>, { let mut notes = Vec::new(); let mut diagnostics = Vec::new();
for file in files { let file = file.as_ref();
match Zettel::parse(file) { Ok(note) => notes.push(note), // A stem that starts with a digit is a malformed zettel (error); // anything else is simply not a zettel file (info). Err(error) if stem_starts_with_digit(file) => { diagnostics.push(Diagnostic::InvalidFileName { file: file.to_owned(), error, }) } Err(error) => diagnostics.push(Diagnostic::NonZettelFile { file: file.to_owned(), error, }), } } notes.sort(); diagnostics.extend(duplicate_id_diagnostics(¬es)); diagnostics.extend(missing_parent_diagnostics(¬es));
Self { notes, diagnostics } }
pub fn notes(&self) -> &[Zettel] { &self.notes }
pub fn diagnostics(&self) -> &[Diagnostic] { &self.diagnostics }
pub fn has_errors(&self) -> bool { self.diagnostics .iter() .any(|diagnostic| diagnostic.severity() == DiagnosticSeverity::Error) }}
/// Whether a file name's stem begins with an ASCII digit — the signal that an/// unparseable name was *meant* to be a zettel (error) rather than an ordinary/// non-zettel file like `index.md` (info).fn stem_starts_with_digit(file: &str) -> bool { Path::new(file) .file_stem() .and_then(|stem| stem.to_str()) .and_then(|stem| stem.chars().next()) .is_some_and(|first| first.is_ascii_digit())}
fn duplicate_id_diagnostics(notes: &[Zettel]) -> impl Iterator<Item = Diagnostic> { notes .chunk_by(|left, right| left.id == right.id) .filter(|group| group.len() > 1) .map(|group| Diagnostic::DuplicateId { id: group[0].id.clone(), files: group.iter().map(|note| note.file.to_owned()).collect(), })}
fn missing_parent_diagnostics(notes: &[Zettel]) -> impl Iterator<Item = Diagnostic> { let present_ids: BTreeSet<&FolgezettelId> = notes.iter().map(|zettel| &zettel.id).collect(); notes.iter().filter_map(move |note| { let parent = note.id.parent()?; if present_ids.contains(&parent) { return None; } Some(Diagnostic::MissingParent { id: note.id.clone(), parent, file: note.file.clone(), }) })}