wavepeek 2.1.0

Command-line tool for RTL waveform inspection with deterministic machine-friendly output.
Documentation
use std::collections::BTreeMap;

use super::*;

fn summary(id: &str, title: &str, description: &str) -> TopicSummary {
    TopicSummary {
        id: id.to_string(),
        title: title.to_string(),
        description: description.to_string(),
        section: "commands".to_string(),
        see_also: vec!["reference/schema".to_string()],
    }
}

fn record(id: &str, title: &str, body: &str) -> TopicRecord {
    TopicRecord {
        topic: summary(id, title, "compact description"),
        raw_markdown: format!("---\nid: {id}\n---\n{body}"),
        body: body.to_string(),
        headings: extract_headings(body),
        source_relpath: format!("{id}.md"),
    }
}

#[test]
fn derives_search_edges_and_catalog_sorting() {
    let topic = summary("commands/change", "Change", "Find changes");
    let topic_clone = topic.clone();
    assert_eq!(topic, topic_clone);
    assert!(format!("{topic:?}").contains("commands/change"));
    assert!(
        serde_json::to_string(&topic)
            .unwrap()
            .contains("Find changes")
    );

    let rec = record(
        "commands/change",
        "Change",
        "# Change\n\n## Details\nbody token",
    );
    let rec_clone = rec.clone();
    assert_eq!(rec, rec_clone);
    assert!(format!("{rec:?}").contains("Details"));

    let id_exact = search_match(&rec, "commands/change", &[]).expect("id exact");
    assert_eq!(id_exact.match_kind, MatchKind::IdExact);
    let title_exact = search_match(&rec, "change", &["change".to_string()]).expect("title exact");
    assert_eq!(title_exact.match_kind, MatchKind::TitleExact);
    let heading = search_match(&rec, "details", &["details".to_string()]).expect("heading");
    assert_eq!(heading.match_kind, MatchKind::Heading);
    let body = search_match(&rec, "token", &["token".to_string()]).expect("body");
    assert_eq!(body.match_kind, MatchKind::Body);
    assert!(search_match(&rec, "missing", &["missing".to_string()]).is_none());

    let match_clone = body.clone();
    assert_eq!(body, match_clone);
    assert!(format!("{body:?}").contains("Body"));
    assert!(
        serde_json::to_string(&body)
            .unwrap()
            .contains("matched_tokens")
    );

    let manifest = ExportManifest {
        kind: EXPORT_KIND.to_string(),
        export_format_version: EXPORT_FORMAT_VERSION,
        cli_name: "wavepeek".to_string(),
        cli_version: "0.test".to_string(),
        topics: vec![topic.clone()],
    };
    let manifest_clone = manifest.clone();
    assert_eq!(manifest, manifest_clone);
    assert!(format!("{manifest:?}").contains("wavepeek-docs-export"));
    assert!(
        serde_json::to_string(&manifest)
            .unwrap()
            .contains("cli_version")
    );

    let export = ExportSummary {
        out_dir: "out".to_string(),
        topics: vec![topic.clone()],
    };
    assert_eq!(export.clone(), export);
    assert!(format!("{export:?}").contains("out"));
    assert!(serde_json::to_string(&export).unwrap().contains("topics"));

    let mut topics = BTreeMap::new();
    topics.insert(
        "workflows/a".to_string(),
        record("workflows/a", "Workflow", "# Workflow"),
    );
    topics.insert("intro/a".to_string(), record("intro/a", "Intro", "# Intro"));
    let catalog = DocsCatalog { topics };
    let sorted = catalog.logical_topic_summaries();
    assert_eq!(sorted[0].section, "commands");
    assert!(format!("{catalog:?}").contains("workflows/a"));
    assert_eq!(topic_section_rank("future"), 5);

    let front = FrontMatter {
        id: "x".to_string(),
        title: "X".to_string(),
        description: "S".to_string(),
        section: "intro".to_string(),
        see_also: Vec::new(),
    };
    assert!(format!("{front:?}").contains("intro"));
}

#[test]
fn filesystem_error_handlers_are_exercised() {
    let tmp = tempfile::tempdir().expect("tempdir");
    let blocker = tmp.path().join("blocker");
    std::fs::write(&blocker, "not a directory").expect("blocker file");
    assert!(export_catalog(&blocker.join("out"), false).is_err());

    let manifest = ExportManifest {
        kind: EXPORT_KIND.to_string(),
        export_format_version: EXPORT_FORMAT_VERSION,
        cli_name: "wavepeek".to_string(),
        cli_version: "test".to_string(),
        topics: Vec::new(),
    };
    let catalog = DocsCatalog {
        topics: BTreeMap::new(),
    };
    assert!(write_export_tree(&blocker, &catalog, &manifest).is_err());

    let tree = tmp.path().join("tree");
    std::fs::create_dir(&tree).expect("tree dir");
    let mut topics = BTreeMap::new();
    topics.insert(
        "blocked/topic".to_string(),
        TopicRecord {
            topic: summary("blocked/topic", "Blocked", "blocked"),
            raw_markdown: "# Blocked\n".to_string(),
            body: "# Blocked\n".to_string(),
            headings: vec!["Blocked".to_string()],
            source_relpath: "blocked/topic.md".to_string(),
        },
    );
    let catalog = DocsCatalog { topics };
    std::fs::write(tree.join("blocked"), "file blocks directory").expect("blocking file");
    assert!(write_export_tree(&tree, &catalog, &manifest).is_err());

    let manifest_block = tmp.path().join("manifest-block");
    std::fs::create_dir(&manifest_block).expect("manifest tree");
    std::fs::create_dir(manifest_block.join("manifest.json")).expect("manifest directory");
    let catalog = DocsCatalog {
        topics: BTreeMap::new(),
    };
    assert!(write_export_tree(&manifest_block, &catalog, &manifest).is_err());
}