133 lines
4.0 KiB
Rust
133 lines
4.0 KiB
Rust
#![forbid(unsafe_code)]
|
|
|
|
use clap::{crate_authors, crate_version, App, AppSettings, Arg};
|
|
use crossbeam::crossbeam_channel::unbounded;
|
|
use json5::from_str;
|
|
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
|
use serde_json::Value;
|
|
use std::env;
|
|
use std::error::Error;
|
|
use std::fs::{create_dir_all, read_dir, read_to_string, write};
|
|
use std::process;
|
|
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.json5")?)?;
|
|
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 = Tera::new(&format!("{}/**/*", template_dir))?;
|
|
let mut context = Context::new();
|
|
context.insert("config", &config);
|
|
let outputs: Vec<String> = matches
|
|
.values_of("output-format")
|
|
.unwrap_or_default()
|
|
.map(String::from)
|
|
.collect();
|
|
|
|
output(&tera, &context, &output_dir, &outputs, &template_dir)?;
|
|
|
|
if matches.is_present("watch") {
|
|
watch_mode(tera, context, output_dir, outputs, template_dir)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 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>(
|
|
mut engine: Tera,
|
|
context: Context,
|
|
dir: String,
|
|
outputs: Vec<String>,
|
|
template_dir: String,
|
|
) -> Result<(), Box<dyn Error>> {
|
|
let (tx, rx) = unbounded();
|
|
let mut watcher: RecommendedWatcher = Watcher::new_immediate(move |res| tx.send(res).unwrap())?;
|
|
watcher.watch(&template_dir, RecursiveMode::Recursive)?;
|
|
|
|
for res in rx {
|
|
match res {
|
|
Err(e) => println!("{}", e),
|
|
Ok(event) => {
|
|
println!("got event {:?}", event);
|
|
engine.full_reload().expect("Failed to perform full reload");
|
|
output(&engine, &context, &dir, &outputs, &template_dir)
|
|
.expect("Failed to call output");
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 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)?,
|
|
)
|
|
.expect("to be able to write to output folder");
|
|
Ok(())
|
|
}
|