1use std::collections::HashMap;
2use std::fs;
3use std::io::{self, Write};
4use std::path::Path;
5
6pub fn generate_site(source_dir: &str, destination_dir: &str, template_file: &str) -> io::Result<()> {
7 let template = fs::read_to_string(template_file)?;
8
9 for entry in fs::read_dir(source_dir)? {
10 let entry = entry?;
11 let path = entry.path();
12
13 if path.is_file() && path.extension().map_or(false, |e| e == "md") {
14 let content = fs::read_to_string(&path)?;
15 let (metadata, body) = parse_front_matter(&content);
16 let html_content = markdown_to_html(&body);
17 let html = render_template(&template, &metadata, &html_content);
18
19 let relative_path = match path.strip_prefix(source_dir) {
20 Ok(relative) => relative.to_str().unwrap_or("unknown"),
21 Err(_) => {
22 eprintln!("Failed to strip prefix from path: {:?}", path);
23 continue;
24 }
25 };
26
27 let destination_path = Path::new(destination_dir)
28 .join(relative_path)
29 .with_extension("html");
30
31 if let Some(parent) = destination_path.parent() {
32 fs::create_dir_all(parent)?;
33 }
34 let mut file = fs::File::create(destination_path)?;
35 file.write_all(html.as_bytes())?;
36 }
37 }
38
39 Ok(())
40}
41
42fn parse_front_matter(content: &str) -> (HashMap<String, String>, String) {
43 let mut metadata = HashMap::new();
44 let mut body = String::new();
45 let mut in_front_matter = false;
46
47 for line in content.lines() {
48 if line.trim() == "---" {
49 in_front_matter = !in_front_matter;
50 continue;
51 }
52
53 if in_front_matter {
54 if let Some((key, value)) = line.split_once(':') {
55 metadata.insert(key.trim().to_string(), value.trim().to_string());
56 }
57 } else {
58 body.push_str(line);
59 body.push('\n');
60 }
61 }
62
63 (metadata, body)
64}
65
66fn markdown_to_html(markdown: &str) -> String {
67 let mut html = String::new();
68
69 for line in markdown.lines() {
70 if line.starts_with("# ") {
71 html.push_str(&format!("<h1>{}</h1>\n", &line[2..]));
72 } else if line.starts_with("## ") {
73 html.push_str(&format!("<h2>{}</h2>\n", &line[3..]));
74 } else if line.starts_with("### ") {
75 html.push_str(&format!("<h3>{}</h3>\n", &line[4..]));
76 } else if line.starts_with("* ") {
77 html.push_str(&format!("<li>{}</li>\n", &line[2..]));
78 } else if line.trim().is_empty() {
79 html.push_str("<br/>\n");
80 } else {
81 html.push_str(&format!("<p>{}</p>\n", line));
82 }
83 }
84
85 html
86}
87
88fn render_template(template: &str, metadata: &HashMap<String, String>, content: &str) -> String {
89 let mut html = template.to_string();
90
91 for (key, value) in metadata {
92 html = html.replace(&format!("{{{{ {} }}}}", key), value);
93 }
94
95 html = html.replace("{{ content }}", content);
96
97 html
98}