panres/src/main.rs

130 lines
3.9 KiB
Rust

#![forbid(unsafe_code)]
use clap::{crate_authors, crate_version, App, AppSettings, Arg};
use crossbeam::crossbeam_channel::unbounded;
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use serde_json::{from_str, Value};
use std::{
env,
error::Error,
fs::{create_dir_all, read_dir, read_to_string, write},
process,
time::Duration,
};
use tera::{Context, Error as TeraError, Tera};
const DEFAULT_TEMPLATE_DIR: &str = "templates";
const DEFAULT_OUTPUT_DIR: &str = "output";
fn main() -> Result<(), Box<dyn Error>> {
process::exit(match run() {
Ok(_) => 0,
Err(e) => {
eprintln!("{}", e);
1
}
})
}
fn env_or_default(env_name: &str, default: &str) -> String {
env::var(env_name).unwrap_or_else(|_| String::from(default))
}
fn run() -> Result<(), Box<dyn Error>> {
let config: Value = from_str(&read_to_string("config.json")?)?;
let template_dir = &env_or_default("PANRES_TEMPLATE_DIR", DEFAULT_TEMPLATE_DIR);
let output_dir = &env_or_default("PANRES_OUTPUT_DIR", DEFAULT_OUTPUT_DIR);
let matches = get_args();
let tera = &mut Tera::new(&format!("{}/**/*", template_dir))?;
let context = &mut Context::new();
context.insert("config", &config);
let outputs: Vec<String> = matches
.values_of("output-format")
.unwrap_or_default()
.map(String::from)
.collect();
if matches.is_present("watch") {
watch_mode(tera, context, output_dir, &outputs, template_dir)
} else {
output(tera, context, output_dir, &outputs, template_dir)
}
}
/// Returns the args passed by the user.
fn get_args() -> clap::ArgMatches<'static> {
App::new("panres")
.version(crate_version!())
.author(crate_authors!())
.about("Universal resume formatter")
.arg(Arg::with_name("watch").short("w").help("Watch for changes"))
.arg(
Arg::with_name("output-format")
.help("Specifies which output format you want")
.required(true)
.multiple(true),
)
.settings(&[AppSettings::ArgRequiredElseHelp])
.get_matches()
}
/// Usually never returns, unless there was an error initializing the watcher.
/// Handles watching for file changes, and reloads tera if there's a change.
fn watch_mode<'a>(
engine: &mut Tera,
context: &mut Context,
dir: &str,
outputs: &[String],
template_dir: &'a str,
) -> Result<(), Box<dyn Error>> {
let (tx, rx) = unbounded();
let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(2))?;
watcher.watch(template_dir, RecursiveMode::Recursive)?;
loop {
match rx.recv() {
Err(e) => println!("{}", e),
Ok(event) => {
println!("got event {:?}", event);
engine.full_reload()?;
output(engine, context, dir, outputs, template_dir)?;
}
}
}
}
/// Parses the output values and generates a file for each format specified or
/// found, if told to generate all outputs.
fn output<'a>(
engine: &Tera,
context: &Context,
dir: &str,
outputs: &[String],
template_dir: &'a str,
) -> Result<(), Box<dyn Error>> {
if outputs.contains(&String::from("all")) {
for output in read_dir(template_dir)? {
write_file(engine, context, dir, &output?.file_name().to_str().unwrap())?;
}
} else {
for output in outputs {
write_file(engine, context, dir, &output)?;
}
}
Ok(())
}
/// Write out the post-template file to the output dir.
fn write_file(engine: &Tera, context: &Context, dir: &str, format: &str) -> Result<(), TeraError> {
create_dir_all(dir).expect("Could not create output dir");
write(
format!("{}/{}", dir, format),
engine.render(format, context.clone())?,
)
.expect("to be able to write to output folder");
Ok(())
}