1use std::path::{Path, PathBuf};
2
3use anyhow::{Result, bail};
4
5use crate::frontmatter;
6use crate::slug::Slug;
7
8pub fn read_page(slug: &Slug, wiki_root: &Path, no_frontmatter: bool) -> Result<String> {
11 let path = slug.resolve(wiki_root)?;
12 let content = std::fs::read_to_string(&path)?;
13
14 let page = frontmatter::parse(&content);
15 let notice = page
16 .superseded_by()
17 .map(|s| format!("\n> **Superseded** by [{s}](wiki://{s})\n"));
18
19 if no_frontmatter {
20 let body = &page.body;
21 let mut out = body.to_string();
22 if let Some(n) = notice {
23 out.push_str(&n);
24 }
25 Ok(out)
26 } else {
27 let mut out = content;
28 if let Some(n) = notice {
29 out.push_str(&n);
30 }
31 Ok(out)
32 }
33}
34
35pub fn write_page(slug: &str, content: &str, wiki_root: &Path) -> Result<PathBuf> {
38 if let Ok(s) = Slug::try_from(slug)
40 && let Ok(path) = s.resolve(wiki_root)
41 {
42 std::fs::write(&path, content)?;
43 return Ok(path);
44 }
45
46 let path = wiki_root.join(format!("{slug}.md"));
48 if let Some(parent) = path.parent() {
49 std::fs::create_dir_all(parent)?;
50 }
51 std::fs::write(&path, content)?;
52 Ok(path)
53}
54
55pub fn list_assets(slug: &Slug, wiki_root: &Path) -> Result<Vec<String>> {
57 let bundle_dir = wiki_root.join(slug.as_str());
58 if !bundle_dir.is_dir() || !bundle_dir.join("index.md").is_file() {
59 return Ok(Vec::new());
60 }
61 let mut assets = Vec::new();
62 for entry in std::fs::read_dir(&bundle_dir)? {
63 let entry = entry?;
64 let name = entry.file_name().to_string_lossy().into_owned();
65 if name != "index.md" && entry.file_type()?.is_file() {
66 assets.push(format!("wiki://{slug}/{name}"));
67 }
68 }
69 assets.sort();
70 Ok(assets)
71}
72
73pub fn read_asset(slug: &Slug, filename: &str, wiki_root: &Path) -> Result<Vec<u8>> {
75 let path = wiki_root.join(slug.as_str()).join(filename);
76 if !path.is_file() {
77 bail!("asset not found: {slug}/{filename}");
78 }
79 Ok(std::fs::read(&path)?)
80}
81
82pub fn create_page(
90 slug: &Slug,
91 bundle: bool,
92 wiki_root: &Path,
93 name_override: Option<&str>,
94 type_override: Option<&str>,
95 body_template: Option<&str>,
96) -> Result<PathBuf> {
97 let slug_str = slug.as_str();
98
99 let parts: Vec<&str> = slug_str.split('/').collect();
101 if parts.len() > 1 {
102 for i in 1..parts.len() {
103 let parent_slug = parts[..i].join("/");
104 let parent_dir = wiki_root.join(&parent_slug);
105 if !parent_dir.exists() {
106 std::fs::create_dir_all(&parent_dir)?;
107 let parent_s = Slug::try_from(parent_slug.as_str())?;
108 let fm = frontmatter::scaffold(&parent_s, true);
109 let content = frontmatter::write(&fm, "");
110 std::fs::write(parent_dir.join("index.md"), content)?;
111 }
112 }
113 }
114
115 let mut fm = frontmatter::scaffold(slug, false);
116 if let Some(name) = name_override {
117 fm.insert("title".into(), serde_yaml::Value::String(name.to_string()));
118 }
119 if let Some(t) = type_override {
120 fm.insert("type".into(), serde_yaml::Value::String(t.to_string()));
121 }
122 let body = body_template.unwrap_or("");
123 let content = frontmatter::write(&fm, body);
124
125 let path = if bundle {
126 let dir = wiki_root.join(slug_str);
127 std::fs::create_dir_all(&dir)?;
128 let p = dir.join("index.md");
129 std::fs::write(&p, content)?;
130 p
131 } else {
132 if let Some(parent) = wiki_root.join(slug_str).parent() {
133 std::fs::create_dir_all(parent)?;
134 }
135 let p = wiki_root.join(format!("{slug_str}.md"));
136 std::fs::write(&p, content)?;
137 p
138 };
139
140 Ok(path)
141}
142
143pub fn create_section(
145 slug: &Slug,
146 wiki_root: &Path,
147 body_template: Option<&str>,
148) -> Result<PathBuf> {
149 let dir = wiki_root.join(slug.as_str());
150 std::fs::create_dir_all(&dir)?;
151
152 let fm = frontmatter::scaffold(slug, true);
153 let body = body_template.unwrap_or("");
154 let content = frontmatter::write(&fm, body);
155 let path = dir.join("index.md");
156 std::fs::write(&path, content)?;
157 Ok(path)
158}
159
160pub fn promote_to_bundle(slug: &Slug, wiki_root: &Path) -> Result<()> {
162 let flat = wiki_root.join(format!("{}.md", slug.as_str()));
163 if !flat.is_file() {
164 bail!("flat page not found for slug: {slug}");
165 }
166 let bundle_dir = wiki_root.join(slug.as_str());
167 std::fs::create_dir_all(&bundle_dir)?;
168 let dest = bundle_dir.join("index.md");
169 std::fs::rename(&flat, &dest)?;
170 Ok(())
171}
172
173pub fn delete_page(slug: &str, wiki_root: &Path) -> Result<bool> {
176 let flat_path = wiki_root.join(format!("{slug}.md"));
178 if flat_path.exists() {
179 std::fs::remove_file(&flat_path)?;
180 return Ok(true);
181 }
182
183 let bundle_path = wiki_root.join(slug).join("index.md");
185 if bundle_path.exists() {
186 let bundle_dir = wiki_root.join(slug);
188 std::fs::remove_dir_all(&bundle_dir)?;
189 return Ok(true);
190 }
191
192 Ok(false)
193}