1use crate::config::Config;
8use crate::feed::{Error as FeedError, *};
9use crate::parser::{Error as ParseError, Parser as PostParser};
10use crate::write::{Error as WriteError, *};
11use gtmpl::Template;
12use std::fmt;
13use std::fs::File;
14use std::path::{Path, PathBuf};
15
16pub fn build_site(config: Config) -> Result<()> {
21 let post_parser = PostParser::new(
22 &config.index_url,
23 &config.posts_url,
24 &config.posts_output_directory,
25 );
26
27 let posts = post_parser.parse_posts(&config.posts_source_directory)?;
29
30 let index_template = parse_template(config.index_template.iter())?;
32 let posts_template = parse_template(config.posts_template.iter())?;
33
34 rmdir(&config.posts_output_directory)?;
43 rmdir(&config.index_output_directory)?;
44 rmdir(&config.static_output_directory)?;
45
46 let writer = Writer {
48 posts_template: &posts_template,
49 index_template: &index_template,
50 index_page_size: config.index_page_size,
51 index_base_url: &config.index_url,
52 index_output_directory: &config.index_output_directory,
53 home_page: &config.home_page,
54 static_url: &config.static_url,
55 atom_url: &config.atom_url,
56 };
57 writer.write_posts(&posts)?;
58
59 copy_dir(
61 &config.static_source_directory,
62 &config.static_output_directory,
63 )?;
64
65 let _ = std::fs::copy(
67 config.index_output_directory.join("index.html"),
68 config.root_output_directory.join("index.html"),
69 )?;
70
71 write_feed(
73 FeedConfig {
74 title: config.title,
75 id: config.home_page.to_string(),
76 author: config.author,
77 home_page: config.home_page,
78 },
79 &posts,
80 File::create(config.root_output_directory.join("feed.atom"))?,
81 )?;
82
83 Ok(())
84}
85
86fn copy_dir(src: &Path, dst: &Path) -> Result<()> {
87 std::fs::create_dir(dst)?;
88 for entry in std::fs::read_dir(src)? {
89 let entry = entry?;
90 if entry.file_type()?.is_dir() {
91 copy_dir(src, &dst.join(entry.file_name()))?;
92 } else {
93 std::fs::copy(
94 src.join(entry.file_name()),
95 dst.join(entry.file_name()),
96 )?;
97 }
98 }
99
100 Ok(())
101}
102
103fn parse_template<P: AsRef<Path>>(
106 template_files: impl Iterator<Item = P>,
107) -> Result<Template> {
108 let mut contents = String::new();
109 for template_file in template_files {
110 use std::io::Read;
111 let template_file = template_file.as_ref();
112 File::open(template_file)
113 .map_err(|e| Error::OpenTemplateFile {
114 path: template_file.to_owned(),
115 err: e,
116 })?
117 .read_to_string(&mut contents)?;
118 contents.push(' ');
119 }
120
121 let mut template = Template::default();
122 template.parse(&contents).map_err(Error::ParseTemplate)?;
123 Ok(template)
124}
125
126type Result<T> = std::result::Result<T, Error>;
127
128#[derive(Debug)]
131pub enum Error {
132 Parse(ParseError),
134
135 Write(WriteError),
138
139 Clean { path: PathBuf, err: std::io::Error },
141
142 OpenTemplateFile { path: PathBuf, err: std::io::Error },
144
145 ParseTemplate(String),
147
148 Feed(FeedError),
150
151 Io(std::io::Error),
153}
154
155impl fmt::Display for Error {
156 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
158 match self {
159 Error::Parse(err) => err.fmt(f),
160 Error::Write(err) => err.fmt(f),
161 Error::Clean { path, err } => {
162 write!(f, "Cleaning directory '{}': {}", path.display(), err)
163 }
164 Error::OpenTemplateFile { path, err } => {
165 write!(
166 f,
167 "Opening template file '{}': {}",
168 path.display(),
169 err
170 )
171 }
172 Error::ParseTemplate(err) => err.fmt(f),
173 Error::Feed(err) => err.fmt(f),
174 Error::Io(err) => err.fmt(f),
175 }
176 }
177}
178
179impl std::error::Error for Error {
180 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
182 match self {
183 Error::Parse(err) => Some(err),
184 Error::Write(err) => Some(err),
185 Error::Clean { path: _, err } => Some(err),
186 Error::OpenTemplateFile { path: _, err } => Some(err),
187 Error::ParseTemplate(_) => None,
188 Error::Feed(err) => Some(err),
189 Error::Io(err) => Some(err),
190 }
191 }
192}
193
194impl From<std::io::Error> for Error {
195 fn from(err: std::io::Error) -> Error {
198 Error::Io(err)
199 }
200}
201
202impl From<ParseError> for Error {
203 fn from(err: ParseError) -> Error {
206 Error::Parse(err)
207 }
208}
209
210impl From<WriteError> for Error {
211 fn from(err: WriteError) -> Error {
214 Error::Write(err)
215 }
216}
217
218impl From<FeedError> for Error {
219 fn from(err: FeedError) -> Error {
222 Error::Feed(err)
223 }
224}
225
226fn rmdir(dir: &Path) -> Result<()> {
227 match std::fs::remove_dir_all(dir) {
228 Ok(x) => Ok(x),
229 Err(e) => match e.kind() {
230 std::io::ErrorKind::NotFound => Ok(()),
231 _ => Err(Error::Clean {
232 path: dir.to_owned(),
233 err: e,
234 }),
235 },
236 }
237}