Let serde handle config magic for us!
This commit is contained in:
parent
a70bd4af7f
commit
51d82ff192
9 changed files with 74 additions and 302 deletions
|
@ -3,7 +3,6 @@ use std::error::Error;
|
||||||
use utils::get_exe_dir;
|
use utils::get_exe_dir;
|
||||||
use config::{Config, References};
|
use config::{Config, References};
|
||||||
|
|
||||||
|
|
||||||
fn execute_pandoc(
|
fn execute_pandoc(
|
||||||
input: String,
|
input: String,
|
||||||
references: Option<References>,
|
references: Option<References>,
|
||||||
|
@ -17,9 +16,9 @@ fn execute_pandoc(
|
||||||
renderer.add_option(pandoc::PandocOption::Standalone);
|
renderer.add_option(pandoc::PandocOption::Standalone);
|
||||||
renderer.add_pandoc_path_hint(&get_exe_dir());
|
renderer.add_pandoc_path_hint(&get_exe_dir());
|
||||||
if references.is_some() {
|
if references.is_some() {
|
||||||
let References { csl, bibliography } = references.unwrap();
|
let mut references_data = references.unwrap();
|
||||||
renderer.set_bibliography(&bibliography);
|
renderer.set_bibliography(&references_data.absolute_bibliography());
|
||||||
renderer.set_csl(&csl);
|
renderer.set_csl(&references_data.absolute_csl());
|
||||||
}
|
}
|
||||||
return renderer.execute();
|
return renderer.execute();
|
||||||
}
|
}
|
||||||
|
@ -28,7 +27,9 @@ fn execute_pandoc(
|
||||||
pub fn render(config: Config, input: String) -> Result<String, String> {
|
pub fn render(config: Config, input: String) -> Result<String, String> {
|
||||||
let output = execute_pandoc(input, config.references);
|
let output = execute_pandoc(input, config.references);
|
||||||
if output.is_err() {
|
if output.is_err() {
|
||||||
return Err(output.err().unwrap().description().into());
|
let err = output.err();
|
||||||
|
println!("{:?}", err);
|
||||||
|
return Err(err.unwrap().description().into());
|
||||||
}
|
}
|
||||||
return match output.unwrap() {
|
return match output.unwrap() {
|
||||||
pandoc::PandocOutput::ToBuffer(out) => Ok(out),
|
pandoc::PandocOutput::ToBuffer(out) => Ok(out),
|
||||||
|
|
|
@ -2,54 +2,89 @@ use serde_yaml;
|
||||||
use serde_yaml::Value;
|
use serde_yaml::Value;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use utils::result_prefix;
|
use utils::{result_prefix, resolve_path, result_override};
|
||||||
use std::fs::remove_file;
|
use std::fs::{remove_file, File};
|
||||||
|
use std::env::current_dir;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
pub mod read;
|
|
||||||
pub mod validate;
|
|
||||||
pub mod consts;
|
pub mod consts;
|
||||||
pub mod validate_types;
|
|
||||||
pub mod csl;
|
pub mod csl;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub input: Vec<PathBuf>,
|
input: Vec<PathBuf>,
|
||||||
pub output: HashMap<String, PathBuf>,
|
output: HashMap<String, PathBuf>,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
|
||||||
|
#[serde(default = "default_verbosity")]
|
||||||
pub verbosity: u64,
|
pub verbosity: u64,
|
||||||
|
|
||||||
pub references: Option<References>
|
pub references: Option<References>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||||
pub struct References {
|
pub struct References {
|
||||||
pub bibliography: PathBuf,
|
bibliography: PathBuf,
|
||||||
pub csl: PathBuf
|
csl: String,
|
||||||
|
pub inner_csl: Option<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
fn new(raw: Value) -> Config {
|
pub fn absolute_output(&self, output_type: String) -> PathBuf {
|
||||||
return Config {
|
return resolve_path(self.output.get(&output_type).unwrap());
|
||||||
input: read::get_input_files(raw.clone()),
|
|
||||||
output: read::get_output_files(raw.clone()),
|
|
||||||
title: read::get_string(&raw, "title"),
|
|
||||||
references: read::get_references(raw.clone()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_output(&self, output_type: String) -> bool {
|
||||||
|
return self.output.contains_key(&output_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn absolute_inputs(&self) -> Vec<PathBuf> {
|
||||||
|
let working_dir = current_dir().unwrap();
|
||||||
|
return self.input.iter().map(|x| working_dir.join(&x)).collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl References {
|
||||||
|
pub fn absolute_bibliography(&self) -> PathBuf {
|
||||||
|
return resolve_path(&self.bibliography);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn absolute_csl(&mut self) -> PathBuf {
|
||||||
|
let tmp_csl_path = csl::unpack_csl(self.csl.as_str().into());
|
||||||
|
self.inner_csl = Some(tmp_csl_path.as_path().to_str().unwrap().into());
|
||||||
|
return tmp_csl_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_verbosity() -> u64 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config_path() -> PathBuf {
|
||||||
|
let mut working_dir = current_dir().unwrap();
|
||||||
|
working_dir.push(consts::CONFIG_FILE_NAME);
|
||||||
|
return working_dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read() -> Result<String, String> {
|
||||||
|
let config_path = get_config_path();
|
||||||
|
let mut config_file = try!(result_override(
|
||||||
|
File::open(&config_path),
|
||||||
|
format!("Unable to find config file at {}", config_path.display())
|
||||||
|
));
|
||||||
|
let mut contents = String::new();
|
||||||
|
try!(result_override(
|
||||||
|
config_file.read_to_string(&mut contents),
|
||||||
|
format!("Failed to read config file at {}.", config_path.display())
|
||||||
|
));
|
||||||
|
return Ok(contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn get_config() -> Result<Config, String> {
|
pub fn get_config() -> Result<Config, String> {
|
||||||
let config_str = try!(read::read());
|
let config_str = try!(read());
|
||||||
let config: Value =
|
let config: Config =
|
||||||
try!(result_prefix(serde_yaml::from_str(&config_str), "Config Parse Error".into()));
|
try!(result_prefix(serde_yaml::from_str(&config_str), "Config Parse Error".into()));
|
||||||
try!(result_prefix(validate::validate(config.clone()), "Config Validation Error".into()));
|
return Ok(config);
|
||||||
return Ok(Config::new(config));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn cleanup_config(config: Config) {
|
|
||||||
if config.references.is_some() {
|
|
||||||
remove_file(config.references.unwrap().csl).expect("Failed to remove file");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
use std::env::current_dir;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Read;
|
|
||||||
use serde_yaml::Value;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use config::csl::unpack_csl;
|
|
||||||
use config::consts;
|
|
||||||
use config::References;
|
|
||||||
use utils::{result_override, resolve_path};
|
|
||||||
|
|
||||||
fn get_config_path() -> PathBuf {
|
|
||||||
let mut working_dir = current_dir().unwrap();
|
|
||||||
working_dir.push(consts::CONFIG_FILE_NAME);
|
|
||||||
return working_dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read() -> Result<String, String> {
|
|
||||||
let config_path = get_config_path();
|
|
||||||
let mut config_file = try!(result_override(
|
|
||||||
File::open(&config_path),
|
|
||||||
format!("Unable to find config file at {}", config_path.display())
|
|
||||||
));
|
|
||||||
let mut contents = String::new();
|
|
||||||
try!(result_override(
|
|
||||||
config_file.read_to_string(&mut contents),
|
|
||||||
format!("Failed to read config file at {}.", config_path.display())
|
|
||||||
));
|
|
||||||
return Ok(contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn to_string(data: &Value) -> String {
|
|
||||||
return data.as_str().unwrap().to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn get_string(conf: &Value, key: &str) -> String {
|
|
||||||
return to_string(conf.get(key).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn get_input_files(conf: Value) -> Vec<PathBuf> {
|
|
||||||
let working_dir = current_dir().unwrap();
|
|
||||||
let input_values = conf.get("input").unwrap().as_sequence().unwrap().to_vec();
|
|
||||||
return input_values.into_iter().map(|x| working_dir.join(to_string(&x))).collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn get_output_files(conf: Value) -> HashMap<String, PathBuf> {
|
|
||||||
let working_dir = current_dir().unwrap();
|
|
||||||
let output_raw = conf.get("output").unwrap().as_mapping().unwrap();
|
|
||||||
let mut output_map: HashMap<String, PathBuf> = HashMap::new();
|
|
||||||
for output in output_raw.into_iter() {
|
|
||||||
output_map.insert(to_string(output.0), working_dir.join(to_string(output.1)));
|
|
||||||
}
|
|
||||||
return output_map;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_references(config: Value) -> Option<References> {
|
|
||||||
if config.get("references").is_none() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let references = config.get("references").unwrap();
|
|
||||||
return Some(References {
|
|
||||||
bibliography: resolve_path(
|
|
||||||
references.get("bibliography").unwrap().as_str().unwrap().into()
|
|
||||||
),
|
|
||||||
csl: unpack_csl(references.get("csl").unwrap().as_str().unwrap().into())
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
use serde_yaml::Value;
|
|
||||||
use std::vec::Vec;
|
|
||||||
use config::read;
|
|
||||||
use config::validate_types::check_config_types;
|
|
||||||
use config::csl::is_valid_csl;
|
|
||||||
use utils::resolve_path;
|
|
||||||
|
|
||||||
|
|
||||||
pub type ValidationResult = Result<(), String>;
|
|
||||||
|
|
||||||
|
|
||||||
fn check_required_keys(config: Value) -> ValidationResult {
|
|
||||||
for key in vec!["input", "output", "title"].iter() {
|
|
||||||
if config.get(key).is_none() {
|
|
||||||
return Err(format!("Missing required key {}.", key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_input_files(config: Value) -> ValidationResult {
|
|
||||||
let files = read::get_input_files(config);
|
|
||||||
|
|
||||||
for file in files.iter() {
|
|
||||||
if !file.exists() || !file.is_file() {
|
|
||||||
return Err(format!("Cannot find input file at {}.", file.as_path().display()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_output_files(config: Value) -> ValidationResult {
|
|
||||||
let files = read::get_output_files(config);
|
|
||||||
let output_types = vec!["pdf".into()];
|
|
||||||
if files.is_empty() {
|
|
||||||
return Err("You need to provide at least 1 output format".into());
|
|
||||||
}
|
|
||||||
for file_def in files.iter() {
|
|
||||||
let dir = file_def.1.parent().unwrap();
|
|
||||||
if !dir.exists() || !dir.is_dir() {
|
|
||||||
return Err(format!("Cannot find output directory at {}.", dir.display()));
|
|
||||||
}
|
|
||||||
if !output_types.contains(file_def.0) {
|
|
||||||
return Err(format!("Invalid output type {}.", file_def.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_references(config: Value) -> ValidationResult {
|
|
||||||
if config.get("references").is_none() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let references = config.get("references").unwrap();
|
|
||||||
let bibliography =
|
|
||||||
resolve_path(references.get("bibliography").unwrap().as_str().unwrap().into());
|
|
||||||
let valid_extensions = vec![
|
|
||||||
"bib",
|
|
||||||
"bibtex",
|
|
||||||
"copac",
|
|
||||||
"json",
|
|
||||||
"yaml",
|
|
||||||
"enl",
|
|
||||||
"xml",
|
|
||||||
"wos",
|
|
||||||
"medline",
|
|
||||||
"mods",
|
|
||||||
"ris",
|
|
||||||
];
|
|
||||||
if !bibliography.exists() {
|
|
||||||
return Err(format!("Can't find bibliography at {}.", bibliography.display()));
|
|
||||||
}
|
|
||||||
if !valid_extensions.contains(&bibliography.extension().unwrap().to_str().unwrap()) {
|
|
||||||
return Err(format!("Bibliography extension must be one of {:?}.", valid_extensions));
|
|
||||||
}
|
|
||||||
let csl = references.get("csl").unwrap().as_str().unwrap();
|
|
||||||
if !is_valid_csl(csl.into()) {
|
|
||||||
return Err(format!("{} isnt a valid CSL config.", csl));
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unwrap_group(config: Value, funcs: Vec<&Fn(Value) -> ValidationResult>) -> ValidationResult {
|
|
||||||
for func in funcs.iter() {
|
|
||||||
try!(func(config.clone()));
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn validate(config: Value) -> ValidationResult {
|
|
||||||
return unwrap_group(
|
|
||||||
config,
|
|
||||||
vec![
|
|
||||||
&check_required_keys,
|
|
||||||
&check_config_types,
|
|
||||||
&check_input_files,
|
|
||||||
&check_output_files,
|
|
||||||
&check_references,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
use config::validate::{unwrap_group, ValidationResult};
|
|
||||||
use serde_yaml::Value;
|
|
||||||
|
|
||||||
|
|
||||||
fn check_root(config: Value) -> ValidationResult {
|
|
||||||
if !config.is_mapping() {
|
|
||||||
return Err("Config should be a mapping".into());
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_input(config: Value) -> ValidationResult {
|
|
||||||
let input = config.get("input").unwrap();
|
|
||||||
if !input.is_sequence() {
|
|
||||||
return Err("Input must be sequence".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.as_sequence().into_iter().count() == 0 {
|
|
||||||
return Err("Must provide input files".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
for input_file in input.as_sequence().unwrap() {
|
|
||||||
if !input_file.is_string() {
|
|
||||||
return Err("Input must be string".into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_output(config: Value) -> ValidationResult {
|
|
||||||
let output = config.get("output").unwrap();
|
|
||||||
if !output.is_mapping() {
|
|
||||||
return Err("Output must be mapping".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if output.as_mapping().into_iter().count() == 0 {
|
|
||||||
return Err("Must provide output files".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
for output_def in output.as_mapping().unwrap() {
|
|
||||||
if !output_def.0.is_string() {
|
|
||||||
return Err("Output keys must be strings".into());
|
|
||||||
}
|
|
||||||
if !output_def.1.is_string() {
|
|
||||||
return Err("Output values must be strings".into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_title(config: Value) -> ValidationResult {
|
|
||||||
if !config.get("title").unwrap().is_string() {
|
|
||||||
return Err("Title should be a string".into());
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_references(config: Value) -> ValidationResult {
|
|
||||||
if config.get("references").is_none() {
|
|
||||||
return Ok(()); // references is optional, dont type it if it's not there
|
|
||||||
}
|
|
||||||
if !config.get("references").unwrap().is_mapping() {
|
|
||||||
return Err("References should be mapping".into());
|
|
||||||
}
|
|
||||||
let references = config.get("references").unwrap().as_mapping().unwrap();
|
|
||||||
let csl = references.get(&Value::String("csl".into()));
|
|
||||||
let bibliography = references.get(&Value::String("bibliography".into()));
|
|
||||||
if csl.is_none() {
|
|
||||||
return Err("Missing CSL file".into());
|
|
||||||
}
|
|
||||||
if bibliography.is_none() {
|
|
||||||
return Err("Missing Bibliography".into());
|
|
||||||
}
|
|
||||||
if !csl.unwrap().is_string() {
|
|
||||||
return Err("CSL file must be a string".into());
|
|
||||||
}
|
|
||||||
if !bibliography.unwrap().is_string() {
|
|
||||||
return Err("Bibliography must be a string".into());
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn check_config_types(config: Value) -> ValidationResult {
|
|
||||||
return unwrap_group(
|
|
||||||
config,
|
|
||||||
vec![&check_root, &check_input, &check_output, &check_title, &check_references]
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -35,7 +35,7 @@ use utils::ok_or_exit;
|
||||||
|
|
||||||
|
|
||||||
fn build(config: Config) -> Result<(), String> {
|
fn build(config: Config) -> Result<(), String> {
|
||||||
let input = try!(read_input_files(config.input.clone()));
|
let input = try!(read_input_files(config.absolute_inputs()));
|
||||||
let raw_html = try!(build_input(config.clone(), input));
|
let raw_html = try!(build_input(config.clone(), input));
|
||||||
println!("{}", raw_html);
|
println!("{}", raw_html);
|
||||||
try!(output(config, raw_html));
|
try!(output(config, raw_html));
|
||||||
|
@ -57,7 +57,6 @@ fn main() {
|
||||||
"build" => {
|
"build" => {
|
||||||
let config = get_config(args.clone());
|
let config = get_config(args.clone());
|
||||||
utils::ok_or_exit(build(config.clone()));
|
utils::ok_or_exit(build(config.clone()));
|
||||||
config::cleanup_config(config);
|
|
||||||
}
|
}
|
||||||
cmd => {
|
cmd => {
|
||||||
writeln!(io::stderr(), "Unknown command {}.", cmd).unwrap();
|
writeln!(io::stderr(), "Unknown command {}.", cmd).unwrap();
|
||||||
|
|
|
@ -3,7 +3,7 @@ use config::Config;
|
||||||
pub mod pdf;
|
pub mod pdf;
|
||||||
|
|
||||||
pub fn output(config: Config, output: String) -> Result<(), String> {
|
pub fn output(config: Config, output: String) -> Result<(), String> {
|
||||||
if config.output.contains_key("pdf") {
|
if config.has_output("pdf".into()) {
|
||||||
try!(pdf::output(config, output));
|
try!(pdf::output(config, output));
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
|
@ -25,7 +25,7 @@ fn create_builder<'a>(config: Config, builder: &'a mut PdfBuilder) -> &'a mut Pd
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn output(config: Config, html: String) -> Result<(), String> {
|
pub fn output(config: Config, html: String) -> Result<(), String> {
|
||||||
let output_location = &config.output["pdf"];
|
let output_location = &config.absolute_output("pdf".into());
|
||||||
let mut pdf_app =
|
let mut pdf_app =
|
||||||
try!(result_override(PdfApplication::new(), "Failed to create PDF Application".into()));
|
try!(result_override(PdfApplication::new(), "Failed to create PDF Application".into()));
|
||||||
let mut base_builder = pdf_app.builder();
|
let mut base_builder = pdf_app.builder();
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::fmt::Debug;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::env::{current_exe, current_dir};
|
use std::env::{current_exe, current_dir};
|
||||||
use std::path::PathBuf;
|
use std::path::{PathBuf, Path};
|
||||||
use mktemp::Temp;
|
use mktemp::Temp;
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ pub fn get_exe_dir() -> PathBuf {
|
||||||
.to_path_buf();
|
.to_path_buf();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_path(path: String) -> PathBuf {
|
pub fn resolve_path<P: AsRef<Path>>(path: P) -> PathBuf {
|
||||||
let base_dir = current_dir().unwrap();
|
let base_dir = current_dir().unwrap();
|
||||||
return base_dir.join(path);
|
return base_dir.join(path);
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue