Skip to main content

llmwiki_tooling/cmd/
frontmatter_cmd.rs

1use std::path::Path;
2
3use crate::error::WikiError;
4use crate::wiki::Wiki;
5
6/// Run `frontmatter get <file> [field]`: extract frontmatter as JSON.
7pub fn get(wiki: &Wiki, file: &Path, field: Option<&str>) -> Result<(), WikiError> {
8    let file_path = wiki.abs_path(file);
9
10    let fm_result = wiki.frontmatter(&file_path)?;
11
12    let fm = match fm_result {
13        Ok(Some(fm)) => fm,
14        Ok(None) => {
15            eprintln!("no frontmatter in {}", file_path.display());
16            return Ok(());
17        }
18        Err(e) => {
19            eprintln!("frontmatter error in {}: {e}", file_path.display());
20            return Ok(());
21        }
22    };
23
24    match field {
25        Some(name) => {
26            if let Some(val) = fm.get(name) {
27                let json: serde_json::Value =
28                    serde_json::to_value(val).unwrap_or(serde_json::Value::Null);
29                println!("{json}");
30            } else {
31                eprintln!("field '{name}' not found");
32            }
33        }
34        None => {
35            let json =
36                serde_json::to_value(fm.data()).expect("valid YAML frontmatter serializes to JSON");
37            println!("{}", serde_json::to_string_pretty(&json).unwrap());
38        }
39    }
40
41    Ok(())
42}
43
44/// Run `frontmatter set <file> <field> <value>`: modify a frontmatter field.
45pub fn set(wiki: &mut Wiki, file: &Path, field: &str, value: &str) -> Result<(), WikiError> {
46    let file_path = wiki.abs_path(file);
47
48    // Read phase
49    let cached = wiki.file(&file_path)?;
50    let source = cached.source();
51
52    let fm = match cached.frontmatter() {
53        Ok(Some(fm)) => fm,
54        Ok(None) => {
55            eprintln!("no frontmatter in {}", file_path.display());
56            return Ok(());
57        }
58        Err(e) => {
59            eprintln!("frontmatter error in {}: {e}", file_path.display());
60            return Ok(());
61        }
62    };
63
64    let mut yaml_value: serde_yml::Value =
65        serde_yml::from_str(&fm.raw_yaml).map_err(|e| WikiError::Frontmatter {
66            path: file_path.clone(),
67            source: crate::error::FrontmatterError::Yaml {
68                source: e,
69                context: field.to_owned(),
70            },
71        })?;
72
73    let parsed_value: serde_yml::Value =
74        serde_yml::from_str(value).unwrap_or(serde_yml::Value::String(value.to_owned()));
75
76    if let serde_yml::Value::Mapping(ref mut map) = yaml_value {
77        map.insert(serde_yml::Value::String(field.to_owned()), parsed_value);
78    }
79
80    let new_yaml = serde_yml::to_string(&yaml_value).map_err(|e| WikiError::Frontmatter {
81        path: file_path.clone(),
82        source: crate::error::FrontmatterError::Yaml {
83            source: e,
84            context: field.to_owned(),
85        },
86    })?;
87
88    let body = &source[fm.byte_range.end..];
89    let new_source = format!("---\n{new_yaml}---\n{body}");
90
91    // Write phase
92    wiki.write_file(&file_path, &new_source)?;
93
94    println!("updated '{}' in {}", field, file_path.display());
95    Ok(())
96}