From 51d82ff192005c685d7b1a1f821b3239457f1d3b Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Tue, 21 Nov 2017 22:12:46 +0000 Subject: [PATCH] Let serde handle config magic for us! --- src/build/pandoc.rs | 11 ++-- src/config/mod.rs | 91 +++++++++++++++++++++---------- src/config/read.rs | 72 ------------------------- src/config/validate.rs | 102 ----------------------------------- src/config/validate_types.rs | 89 ------------------------------ src/main.rs | 3 +- src/output/mod.rs | 2 +- src/output/pdf/mod.rs | 2 +- src/utils.rs | 4 +- 9 files changed, 74 insertions(+), 302 deletions(-) delete mode 100644 src/config/read.rs delete mode 100644 src/config/validate.rs delete mode 100644 src/config/validate_types.rs diff --git a/src/build/pandoc.rs b/src/build/pandoc.rs index f5d7ace..0f2c284 100644 --- a/src/build/pandoc.rs +++ b/src/build/pandoc.rs @@ -3,7 +3,6 @@ use std::error::Error; use utils::get_exe_dir; use config::{Config, References}; - fn execute_pandoc( input: String, references: Option, @@ -17,9 +16,9 @@ fn execute_pandoc( renderer.add_option(pandoc::PandocOption::Standalone); renderer.add_pandoc_path_hint(&get_exe_dir()); if references.is_some() { - let References { csl, bibliography } = references.unwrap(); - renderer.set_bibliography(&bibliography); - renderer.set_csl(&csl); + let mut references_data = references.unwrap(); + renderer.set_bibliography(&references_data.absolute_bibliography()); + renderer.set_csl(&references_data.absolute_csl()); } return renderer.execute(); } @@ -28,7 +27,9 @@ fn execute_pandoc( pub fn render(config: Config, input: String) -> Result { let output = execute_pandoc(input, config.references); 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() { pandoc::PandocOutput::ToBuffer(out) => Ok(out), diff --git a/src/config/mod.rs b/src/config/mod.rs index 0420e6d..133316f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,54 +2,89 @@ use serde_yaml; use serde_yaml::Value; use std::path::PathBuf; use std::collections::HashMap; -use utils::result_prefix; -use std::fs::remove_file; +use utils::{result_prefix, resolve_path, result_override}; +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 validate_types; pub mod csl; #[derive(Debug, Serialize, Deserialize, Default, Clone)] pub struct Config { - pub input: Vec, - pub output: HashMap, + input: Vec, + output: HashMap, pub title: String, + + #[serde(default = "default_verbosity")] pub verbosity: u64, + pub references: Option } #[derive(Debug, Serialize, Deserialize, Default, Clone)] pub struct References { - pub bibliography: PathBuf, - pub csl: PathBuf + bibliography: PathBuf, + csl: String, + pub inner_csl: Option } + impl Config { - fn new(raw: Value) -> Config { - return Config { - 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 absolute_output(&self, output_type: String) -> PathBuf { + return resolve_path(self.output.get(&output_type).unwrap()); } + + pub fn has_output(&self, output_type: String) -> bool { + return self.output.contains_key(&output_type); + } + + pub fn absolute_inputs(&self) -> Vec { + 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 { + 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 { - let config_str = try!(read::read()); - let config: Value = + let config_str = try!(read()); + let config: Config = 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::new(config)); -} - - -pub fn cleanup_config(config: Config) { - if config.references.is_some() { - remove_file(config.references.unwrap().csl).expect("Failed to remove file"); - } + return Ok(config); } diff --git a/src/config/read.rs b/src/config/read.rs deleted file mode 100644 index 8dd7c5f..0000000 --- a/src/config/read.rs +++ /dev/null @@ -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 { - 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 { - 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 { - let working_dir = current_dir().unwrap(); - let output_raw = conf.get("output").unwrap().as_mapping().unwrap(); - let mut output_map: HashMap = 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 { - 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()) - }); -} diff --git a/src/config/validate.rs b/src/config/validate.rs deleted file mode 100644 index 2d6d487..0000000 --- a/src/config/validate.rs +++ /dev/null @@ -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, - ] - ); -} diff --git a/src/config/validate_types.rs b/src/config/validate_types.rs deleted file mode 100644 index ce83ddd..0000000 --- a/src/config/validate_types.rs +++ /dev/null @@ -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] - ); -} diff --git a/src/main.rs b/src/main.rs index 96315a2..f498e4e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,7 @@ use utils::ok_or_exit; 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)); println!("{}", raw_html); try!(output(config, raw_html)); @@ -57,7 +57,6 @@ fn main() { "build" => { let config = get_config(args.clone()); utils::ok_or_exit(build(config.clone())); - config::cleanup_config(config); } cmd => { writeln!(io::stderr(), "Unknown command {}.", cmd).unwrap(); diff --git a/src/output/mod.rs b/src/output/mod.rs index cae7fe4..f845b15 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -3,7 +3,7 @@ use config::Config; pub mod pdf; 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)); } return Ok(()); diff --git a/src/output/pdf/mod.rs b/src/output/pdf/mod.rs index 8b60df0..4352c77 100644 --- a/src/output/pdf/mod.rs +++ b/src/output/pdf/mod.rs @@ -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> { - let output_location = &config.output["pdf"]; + let output_location = &config.absolute_output("pdf".into()); let mut pdf_app = try!(result_override(PdfApplication::new(), "Failed to create PDF Application".into())); let mut base_builder = pdf_app.builder(); diff --git a/src/utils.rs b/src/utils.rs index b11d58f..5dfe06a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use std::process::exit; use std::io::{self, Write}; use std::env::{current_exe, current_dir}; -use std::path::PathBuf; +use std::path::{PathBuf, Path}; use mktemp::Temp; @@ -43,7 +43,7 @@ pub fn get_exe_dir() -> PathBuf { .to_path_buf(); } -pub fn resolve_path(path: String) -> PathBuf { +pub fn resolve_path>(path: P) -> PathBuf { let base_dir = current_dir().unwrap(); return base_dir.join(path); }