rustsitemd/
lib.rs

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}