pub mod config;
pub mod md;
pub mod validate;
pub use config::*;
pub use md::{parse_md, render_md};
pub use validate::{validate, Issue, Severity, ValidationReport};
use std::path::Path;
#[derive(Debug, thiserror::Error)]
pub enum CoreError {
#[error("io error reading {path}: {source}")]
Io {
path: String,
#[source]
source: std::io::Error,
},
#[error("could not parse {path} as YAML or JSON:\n yaml: {yaml}\n json: {json}")]
Parse {
path: String,
yaml: String,
json: String,
},
#[error("config is invalid:\n{0}")]
Invalid(String),
}
pub fn load(path: impl AsRef<Path>) -> Result<LoopConfig, CoreError> {
let path = path.as_ref();
let text = std::fs::read_to_string(path).map_err(|source| CoreError::Io {
path: path.display().to_string(),
source,
})?;
let origin = path.display().to_string();
if is_markdown(path) {
return md::parse_md(&text, &origin);
}
parse_str(&text, &origin)
}
pub fn is_markdown(path: &Path) -> bool {
path.extension()
.and_then(|e| e.to_str())
.map(|e| e.eq_ignore_ascii_case("md") || e.eq_ignore_ascii_case("markdown"))
.unwrap_or(false)
}
pub fn parse_str(text: &str, origin: &str) -> Result<LoopConfig, CoreError> {
let yaml_err = match serde_yaml::from_str::<LoopConfig>(text) {
Ok(cfg) => return Ok(cfg),
Err(e) => e.to_string(),
};
let json_err = match serde_json::from_str::<LoopConfig>(text) {
Ok(cfg) => return Ok(cfg),
Err(e) => e.to_string(),
};
Err(CoreError::Parse {
path: origin.to_string(),
yaml: yaml_err,
json: json_err,
})
}
pub fn load_validated(path: impl AsRef<Path>) -> Result<LoopConfig, CoreError> {
let cfg = load(path)?;
let report = validate(&cfg);
if report.has_errors() {
return Err(CoreError::Invalid(report.render()));
}
Ok(cfg)
}