use config;
use error::{Result, ResultExt};
use handlebars::Handlebars;
use glob::glob;
use self::post::Post;
use serde_json::Value;
use slug;
use std::collections::HashSet;
use std::env;
use std::fs::{self, File};
use std::path::Path;
mod post;
pub fn build() -> Result<()> {
let mut handlebars = Handlebars::new();
let mut base = config::path()?;
let config = config::parse(&base)?;
base.pop();
let recents = match config.get("recents") {
Some(&Value::Number(ref value)) => {
let value = value.as_u64();
value.chain_err(|| "`config.recents` must be natural")?
},
_ => {
return Err("`config.recents` missing".into());
}
} as usize;
env::set_current_dir(&base)
.chain_err(|| "couldn't set current directory")?;
let templates = glob("templates/*.hbs")
.chain_err(|| "couldn't read templates directory")?;
for path in templates.filter_map(|x| x.ok()) {
let stem = path.file_stem().unwrap().to_string_lossy();
handlebars.register_template_file(&stem, &path)
.chain_err(|| "couldn't register template")?;
}
let paths: Vec<_> = glob("posts/*.md")
.chain_err(|| "couldn't read posts directory")?
.filter_map(|x| x.ok())
.collect();
let mut used = HashSet::new();
used.insert("index".to_owned());
used.insert("archive".to_owned());
let mut get_slug = move |title| {
let mut slug = slug::slugify(title);
if used.contains(&slug) {
for i in 2.. {
let next = format!("{}-{}", slug, i);
if !used.contains(&next) {
slug = next;
break;
}
}
}
used.insert(slug.clone());
slug
};
let posts: Vec<_> = paths.iter()
.filter_map(|path| {
if let Some(post) = Post::new(path, &mut get_slug) {
Some(post)
} else {
warn!(
"Ignoring '{}' because it has an invalid filename.",
path.display()
);
None
}
})
.collect();
if !Path::new("out").is_dir() {
fs::create_dir("out")
.chain_err(|| "couldn't create out directory")?;
}
let mut a = None;
let mut b = None;
for c in posts.iter() {
if let Some(post) = b {
render_post(&handlebars, post, a, Some(c))?;
}
a = b;
b = Some(c);
}
if let Some(post) = b {
render_post(&handlebars, post, a, None)?;
}
let archive_data: Vec<_> = posts.iter()
.map(|post| json!({
"title": post.title,
"date": post.ymd,
"slug": post.slug,
}))
.collect();
handlebars.renderw(
"archive",
&json!({ "posts": archive_data }),
&mut File::create("out/archive.html")
.chain_err(|| "couldn't create archive")?
).chain_err(|| "couldn't render archive")?;
let mut index_data = Vec::with_capacity(recents);
for post in posts.iter().rev().take(recents) {
let content: String = post.content()
.chain_err(|| format!("couldn't process '{}'", post.title))?;
index_data.push(json!({
"title": post.title,
"date": post.ymd,
"slug": post.slug,
"content": content
}))
}
handlebars.renderw(
"index",
&json!({ "posts": index_data }),
&mut File::create("out/index.html")
.chain_err(|| "couldn't create index")?
).chain_err(|| "couldn't render index")?;
info!("Blog successfully compiled!");
Ok(())
}
fn render_post(
handlebars: &Handlebars,
post: &Post,
prev: Option<&Post>,
next: Option<&Post>,
) -> Result<()> {
let data = json!({
"prev": prev.map(|prev| {
json!({
"title": prev.title,
"date": prev.ymd,
"slug": prev.slug,
})
}),
"next": next.map(|next| {
json!({
"title": next.title,
"date": next.ymd,
"slug": next.slug,
})
}),
"title": post.title,
"date": post.ymd,
"slug": post.slug,
"content": post.content()
.chain_err(|| format!("couldn't process '{}'", post.title))?,
});
let mut file = File::create(format!("out/{}.html", post.slug))
.chain_err(|| format!("couldn't open 'out/{}.html'", post.slug))?;
handlebars.renderw("post", &data, &mut file)
.chain_err(|| format!("couldn't render '{}'", post.title))?;
Ok(())
}