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(())
}
}
}