quatrain 0.6.0

Not intended to be a static site generator
Documentation
use anyhow;
use dirs::home_dir;
use getopts::{self, Options};
use quatrain::{contrib, HTMLWriter, MarkdownExt, Parser, Event};
use std::cell::RefCell;
use std::env;
use std::fs::File;
use std::io::{self, BufWriter, Read, Write};
use std::rc::Rc;
use tera::{Context, Tera};
use std::path::{Path, PathBuf};

#[macro_use]
extern crate lazy_static;

lazy_static! {
    static ref TEMPLATES: Tera = {
        let mut path = home_dir().unwrap();
        path.push(".quatrain/**/*.html");
        let mut tera = match Tera::new(path.to_str().unwrap()) {
            Ok(t) => t,
            Err(e) => {
                println!("Failed to parse template with error: {}", e);
                std::process::exit(1);
            }
        };
        tera.autoescape_on(vec!["html"]);
        tera
    };
}

const VERSION: &'static str = env!("CARGO_PKG_VERSION");

fn expand_tilde<P: AsRef<Path>>(path: P) -> Option<PathBuf> {
    let p = path.as_ref();
    if !p.starts_with("~") {
        return Some(p.to_path_buf());
    }
    if p == Path::new("~") {
        return dirs::home_dir();
    }
    dirs::home_dir().map(|mut h| {
        if h == Path::new("/") {
            p.strip_prefix("~").unwrap().to_path_buf()
        } else {
            h.push(p.strip_prefix("~/").unwrap());
            h
        }
    })
}

fn render<'e, I: Iterator<Item = Event<'e>>>(iter: I) -> anyhow::Result<Vec<u8>> {
    let mut v = Vec::new();
    HTMLWriter::new(iter, &mut v).run()?;
    Ok(v)
}

fn main() -> anyhow::Result<()> {
    let args: Vec<String> = env::args().collect();

    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help");
    opts.optflag("v", "version", "show current version");
    opts.optflag("", "STDOUT", "print to STDOUT regarding settings");

    let matches = match opts.parse(&args[1..]) {
        Ok(matches) => matches,
        Err(fail) => panic!(fail.to_string()),
    };

    if matches.opt_present("h") {
        println!("{}", include_str!("./help.txt"));
        return Ok(());
    }

    if matches.opt_present("v") {
        println!("{}", VERSION);
        return Ok(());
    }

    let mut raw = String::new();
    std::io::stdin().read_to_string(&mut raw)?;

    let ref toc = contrib::toc::ToC::new(2..5);

    let mut frontmatter = Rc::new(RefCell::new(None));
    let html = Parser::new(&raw)?
        .head(contrib::heading::heading)
        .after(contrib::outdate::predict, contrib::outdate::event)
        .within(|e| toc.start(e), |e| toc.end(e), |_, e| toc.within(e))
        .inspect_frontmatter(&mut frontmatter);

    let borrow = frontmatter.borrow();
    let mut out: BufWriter<Box<dyn io::Write>> = BufWriter::new(
        match (
            borrow.as_ref().and_then(|fm| fm.render.target.as_ref()),
            matches.opt_present("STDOUT"),
        ) {
            (None, _) | (_, true) => Box::new(io::stdout()),
            (Some(path), _) => Box::new(File::create(expand_tilde(path).unwrap())?),
        },
    );

    let template = borrow.as_ref().and_then(|fm| Some(&fm.render.template));
    match template {
        None => HTMLWriter::new(html, out).run(),
        Some(temp) => {
            let mut context = Context::from_serialize(&frontmatter.borrow().as_ref().unwrap())?;
            context.insert("Article", std::str::from_utf8(&render(html)?)?);
            context.insert("ToC", std::str::from_utf8(&render(toc.generate())?)?);
            context.insert("Version", VERSION);
            out.write_all(
                TEMPLATES
                    .render(&format!("{}.html", temp), &context)?
                    .as_bytes(),
            )?;
            Ok(())
        }
    }
}