1mod frontmatter;
2mod page;
3mod post;
4
5pub use page::Page;
6pub use post::{ContentType, Post};
7
8#[cfg(test)]
10pub use frontmatter::Frontmatter;
11
12use crate::config::PathsConfig;
13use anyhow::Result;
14use ignore::WalkBuilder;
15use std::collections::HashMap;
16use std::path::Path;
17use walkdir::WalkDir;
18
19#[derive(Debug)]
21pub struct Section {
22 pub name: String,
23 pub posts: Vec<Post>,
24}
25
26#[derive(Debug)]
28pub struct Content {
29 pub home: Option<Page>,
30 pub sections: HashMap<String, Section>,
31}
32
33pub fn discover_content(paths: &PathsConfig) -> Result<Content> {
35 let content_dir = Path::new(&paths.content);
36
37 let mut excluded: Vec<&str> = vec![&paths.styles, &paths.static_files, &paths.templates];
39 excluded.extend(paths.exclude.iter().map(|s| s.as_str()));
40
41 let home_path = content_dir.join(&paths.home);
43 let home = if home_path.exists() {
44 Some(Page::from_file(&home_path)?)
45 } else {
46 None
47 };
48
49 let mut sections = HashMap::new();
51
52 let section_paths: Vec<_> = if paths.respect_gitignore {
54 WalkBuilder::new(content_dir)
55 .max_depth(Some(1))
56 .hidden(false) .build()
58 .filter_map(|e| e.ok())
59 .filter(|e| e.depth() == 1 && e.path().is_dir())
60 .map(|e| e.into_path())
61 .collect()
62 } else {
63 WalkDir::new(content_dir)
64 .min_depth(1)
65 .max_depth(1)
66 .into_iter()
67 .filter_map(|e| e.ok())
68 .filter(|e| e.path().is_dir())
69 .map(|e| e.into_path())
70 .collect()
71 };
72
73 for path in section_paths {
75 process_section(&path, &excluded, &mut sections, paths)?;
76 }
77
78 Ok(Content { home, sections })
79}
80
81fn process_section(
83 path: &Path,
84 excluded: &[&str],
85 sections: &mut HashMap<String, Section>,
86 paths: &PathsConfig,
87) -> Result<()> {
88 let section_name = path
89 .file_name()
90 .and_then(|n| n.to_str())
91 .unwrap_or("")
92 .to_string();
93
94 if excluded
96 .iter()
97 .any(|ex| section_name == *ex || path.ends_with(ex))
98 {
99 return Ok(());
100 }
101
102 let post_paths: Vec<_> = if paths.respect_gitignore {
104 WalkBuilder::new(path)
105 .max_depth(Some(1))
106 .hidden(false)
107 .build()
108 .filter_map(|e| e.ok())
109 .filter(|e| {
110 e.depth() == 1
111 && e.path()
112 .extension()
113 .is_some_and(|ext| ext == "md" || ext == "html" || ext == "htm")
114 })
115 .map(|e| e.into_path())
116 .collect()
117 } else {
118 WalkDir::new(path)
119 .min_depth(1)
120 .max_depth(1)
121 .into_iter()
122 .filter_map(|e| e.ok())
123 .filter(|e| {
124 e.path()
125 .extension()
126 .is_some_and(|ext| ext == "md" || ext == "html" || ext == "htm")
127 })
128 .map(|e| e.into_path())
129 .collect()
130 };
131
132 let mut posts = Vec::new();
134 for post_path in post_paths {
135 let post = Post::from_file_with_section(&post_path, §ion_name)?;
136 if !post.frontmatter.draft.unwrap_or(false) {
137 posts.push(post);
138 }
139 }
140
141 posts.sort_by(|a, b| b.frontmatter.date.cmp(&a.frontmatter.date));
143
144 if !posts.is_empty() {
145 sections.insert(
146 section_name.clone(),
147 Section {
148 name: section_name,
149 posts,
150 },
151 );
152 }
153
154 Ok(())
155}