llmwiki-tooling 0.3.0

CLI for managing LLM-wikis with Obsidian-style wikilinks.
Documentation
use std::path::{Path, PathBuf};

use crate::edit_plan::{DryRunOutput, EditPlan, EditPlanMode};
use crate::error::WikiError;
use crate::markdown_links;
use crate::page::PageId;
use crate::wiki::Wiki;

pub fn move_page(
    wiki: &mut Wiki,
    page_name: &str,
    dest_dir: &Path,
    write: bool,
) -> Result<(), WikiError> {
    let page_id = PageId::from(page_name);
    let Some((_, rel_path)) = wiki.find(page_name) else {
        return Err(WikiError::PageNotFound(page_id));
    };

    let old_path = wiki.abs_path(rel_path);
    let dest_dir = markdown_links::normalize_path(wiki.abs_path(dest_dir));
    if !dest_dir.starts_with(wiki.root().path()) {
        return Err(WikiError::PathOutsideRoot { path: dest_dir });
    }
    let new_path = dest_dir.join(old_path.file_name().expect("markdown file has filename"));
    if new_path.exists() {
        return Err(WikiError::TargetPathExists { path: new_path });
    }

    let plan = plan_move(wiki, old_path, new_path)?;
    plan.execute(
        wiki,
        EditPlanMode::from_write_flag(
            write,
            DryRunOutput::Summary {
                title: "Planned move (dry-run):",
                moves_heading: "File move",
                edits_heading: "Markdown link updates",
            },
        ),
    )?;
    Ok(())
}

fn plan_move(wiki: &Wiki, old_path: PathBuf, new_path: PathBuf) -> Result<EditPlan, WikiError> {
    let mut plan = EditPlan::new();
    plan.move_file(old_path.clone(), new_path.clone());

    plan.add_scannable_edits(wiki, |file_path, _source| {
        let links = wiki.file(file_path)?.markdown_links();
        Ok(if file_path == old_path.as_path() {
            markdown_links::rebase_relative_links(links, &old_path, &new_path)
        } else {
            markdown_links::retarget_relative_links(links, file_path, &old_path, &new_path)
        })
    })?;
    Ok(plan)
}