use crate::content::NavItem;
use pulldown_cmark::{Event, Parser, Tag, TagEnd};
use std::path::Path;
pub fn parse_nav(content: &str) -> Vec<NavItem> {
let mut items = Vec::new();
let parser = Parser::new(content);
let mut in_link = false;
let mut current_url = String::new();
let mut current_label = String::new();
for event in parser {
match event {
Event::Start(Tag::Link { dest_url, .. }) => {
in_link = true;
current_url = dest_url.to_string();
}
Event::End(TagEnd::Link) => {
if in_link && !current_label.is_empty() {
items.push(NavItem {
label: current_label.trim().to_string(),
url: current_url.clone(),
});
}
in_link = false;
current_label.clear();
current_url.clear();
}
Event::Text(text) if in_link => {
current_label.push_str(&text);
}
_ => {}
}
}
items
}
pub fn render_nav_html(items: &[NavItem]) -> String {
if items.is_empty() {
return String::new();
}
let mut html = String::from("<nav>\n <ul>\n");
for item in items {
html.push_str(&format!(
" <li><a href=\"{}\">{}</a></li>\n",
html_escape::encode_double_quoted_attribute(&item.url),
html_escape::encode_text(&item.label)
));
}
html.push_str(" </ul>\n</nav>");
html
}
pub fn load_nav(source_dir: &Path) -> (Vec<NavItem>, String) {
let nav_path = source_dir.join("nav.md");
if nav_path.exists() {
match std::fs::read_to_string(&nav_path) {
Ok(content) => {
let items = parse_nav(&content);
let html = render_nav_html(&items);
return (items, html);
}
Err(e) => {
tracing::warn!("Failed to read nav.md: {}", e);
}
}
}
(Vec::new(), String::new())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_nav() {
let content = r#"
- [Home](/)
- [Blog](/blog/)
- [Notes](/notes/)
"#;
let items = parse_nav(content);
assert_eq!(items.len(), 3);
assert_eq!(items[0].label, "Home");
assert_eq!(items[0].url, "/");
assert_eq!(items[1].label, "Blog");
assert_eq!(items[1].url, "/blog/");
}
#[test]
fn test_render_nav_html() {
let items = vec![
NavItem {
label: "Home".to_string(),
url: "/".to_string(),
},
NavItem {
label: "Blog".to_string(),
url: "/blog/".to_string(),
},
];
let html = render_nav_html(&items);
assert!(html.contains("<nav>"));
assert!(html.contains("href=\"/\""));
assert!(html.contains("Home"));
}
}