Let serde handle config magic for us!

This commit is contained in:
Jake Howard 2017-11-21 22:12:46 +00:00
parent a70bd4af7f
commit 51d82ff192
Signed by: jake
GPG key ID: 57AFB45680EDD477
9 changed files with 74 additions and 302 deletions

View file

@ -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),

View file

@ -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");
}
} }

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

View file

@ -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,
]
);
}

View file

@ -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]
);
}

View file

@ -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();

View file

@ -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(());

View file

@ -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();

View file

@ -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);
} }