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 config::{Config, References};
|
||||
|
||||
|
||||
fn execute_pandoc(
|
||||
input: String,
|
||||
references: Option<References>,
|
||||
|
@ -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<String, String> {
|
||||
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),
|
||||
|
|
|
@ -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<PathBuf>,
|
||||
pub output: HashMap<String, PathBuf>,
|
||||
input: Vec<PathBuf>,
|
||||
output: HashMap<String, PathBuf>,
|
||||
pub title: String,
|
||||
|
||||
#[serde(default = "default_verbosity")]
|
||||
pub verbosity: u64,
|
||||
|
||||
pub references: Option<References>
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
|
||||
pub struct References {
|
||||
pub bibliography: PathBuf,
|
||||
pub csl: PathBuf
|
||||
bibliography: PathBuf,
|
||||
csl: String,
|
||||
pub inner_csl: Option<String>
|
||||
}
|
||||
|
||||
|
||||
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<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> {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
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();
|
||||
|
|
|
@ -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(());
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<P: AsRef<Path>>(path: P) -> PathBuf {
|
||||
let base_dir = current_dir().unwrap();
|
||||
return base_dir.join(path);
|
||||
}
|
||||
|
|
Reference in a new issue