diff --git a/src/error.rs b/src/error.rs index 5cce282..0c5cea4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,36 +1,72 @@ -use std::{io::{Error as IOError, ErrorKind}, path::StripPrefixError}; +use std::{ + fmt::Display, + io::{Error as IOError, ErrorKind}, + path::StripPrefixError, +}; use glob::PatternError; #[derive(Debug)] -pub struct Error { +pub struct Error<'a> { message: String, + source: Option<&'a dyn std::error::Error>, } -impl From for Error { - fn from(value: String) -> Self { - Self { message: value } +impl<'a> Display for Error<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &self.message)?; + + if let Some(source) = self.source { + write!(f, " ({})", &source)?; + } + + Ok(()) } } -impl From for Error { +impl<'a> std::error::Error for Error<'a> { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.source + } +} + +impl<'a> From for Error<'a> { + fn from(value: String) -> Self { + Self { + message: value, + source: None, + } + } +} + +impl<'a> From for Error<'a> { fn from(value: PatternError) -> Self { Self { message: value.to_string(), + source: Some(&value), } } } -impl From for Error { +impl<'a> From for Error<'a> { fn from(value: StripPrefixError) -> Self { Self { message: value.to_string(), + source: Some(&value), } } } -impl From for IOError { +impl<'a> From> for IOError { fn from(value: Error) -> Self { IOError::new(ErrorKind::Other, value.message) } } +impl<'a> From<&dyn std::error::Error> for Error<'a> { + fn from(value: &dyn std::error::Error) -> Self { + Self { + message: value.to_string(), + source: Some(&value), + } + } +} diff --git a/src/main.rs b/src/main.rs index 8e39c2a..dd49f06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,19 @@ -pub mod error; -pub mod context; pub mod config; +pub mod context; +pub mod error; mod file_path; -use clap::Parser as Clap; use config::Config; use context::Context; +use error::Error; use file_path::FilePath; -use crate::error::Error; + use roxy_markdown_parser::MarkdownParser; use roxy_markdown_tera_rewriter::{MarkdownTeraPreformatter, MarkdownTeraRewriter}; use roxy_syntect::SyntectParser; use roxy_tera_parser::{TeraParser, TeraParserOptions}; + +use clap::Parser as Clap; use std::{ fs::{self, File}, io::{BufReader, Read}, @@ -48,71 +50,94 @@ fn get_files + std::fmt::Debug>(path: &P) -> Result, Ok(files) } -fn main() -> Result<(), Error> { - let opts = Options::parse(); - - let file_path = FilePath::new(&opts.input, &opts.output); - - let config_path = file_path.input.with_file_name("config.toml"); - let config = fs::read_to_string(&config_path).map_or_else( +fn load_config(path: &Path) -> Config { + fs::read_to_string(path).map_or_else( |_| Config::default(), |f| { toml::from_str::(f.as_str()) .unwrap() .merge(Config::default()) }, - ); + ) +} + +fn context_from_meta_files<'a, T: AsRef>( + files: &Vec<&PathBuf>, + file_path: &'a FilePath, +) -> Result> { + let mut context = Context::new(); + + for path in files { + let mut buf = Vec::new(); + + let mut file = File::open(path).map(BufReader::new)?; + file.read_to_end(&mut buf)?; + let mut str = String::from_utf8(buf)?; + let toml: Table = toml::from_str(&mut str)?; + + context.insert(&file_path.strip_root(path)?, &tera::to_value(toml)?); + } + + Ok(context) +} + +fn create_parser<'a, T: AsRef>( + file: &Path, + file_path: &FilePath, + context: &'a Context, + theme: &str, +) -> Result, Error<'a>> { + let mut parser = Parser::new(); + let mut preformatter = MarkdownTeraPreformatter::new(); + parser.push(&mut preformatter); + + let mut syntect = SyntectParser::new(theme); + parser.push(&mut syntect); + + let mut md = MarkdownParser::new(); + parser.push(&mut md); + + let mut rewriter = MarkdownTeraRewriter::new(); + parser.push(&mut rewriter); + + let file_name = file.with_extension("html"); + let output_path = file_path.to_output(&file_name)?; + + if let Ok(path) = &file_path.strip_root(&file_name) { + if let Some(current_context) = context.get(path) { + context.insert(&"this", ¤t_context.clone()); + } + } + + let mut tera = tera::Tera::default(); + let mut html = TeraParser::new(&mut tera, TeraParserOptions::default()); + html.add_context(context.to_inner()); + Ok(parser) +} + +fn main() -> Result<(), Box> { + let opts = Options::parse(); + + let file_path = FilePath::new(&opts.input, &opts.output); + + let config_path = file_path.input.with_file_name("config.toml"); + let config = load_config(&config_path); let files = get_files(&file_path.input)?; let (meta, files): (Vec<&PathBuf>, Vec<&PathBuf>) = files.iter().partition(|f| f.extension().unwrap() == "toml"); - let mut context = Context::new(); - - for path in meta { - let mut buf = Vec::new(); - - let mut file = File::open(path).map(BufReader::new).unwrap(); - file.read_to_end(&mut buf).unwrap(); - let mut str = String::from_utf8(buf).unwrap(); - let toml: Table = toml::from_str(&mut str).unwrap(); - - context.insert( - &file_path.strip_root(path).unwrap(), - &tera::to_value(toml).unwrap(), - ); - } + let mut context = context_from_meta_files(&meta, &file_path)?; let theme = config.theme.unwrap_or(DEFAULT_THEME.to_string()); for file in files { - let mut parser = Parser::new(); - let mut preformatter = MarkdownTeraPreformatter::new(); - parser.push(&mut preformatter); - - let mut syntect = SyntectParser::new(&theme.as_str()); - parser.push(&mut syntect); - - let mut md = MarkdownParser::new(); - parser.push(&mut md); - - let mut rewriter = MarkdownTeraRewriter::new(); - parser.push(&mut rewriter); + let mut parser = create_parser(file, &file_path, &context, &theme)?; let file_name = file.with_extension("html"); let output_path = file_path.to_output(&file_name)?; - if let Ok(path) = &file_path.strip_root(&file_name) { - if let Some(current_context) = context.get(path) { - context.insert(&"this", ¤t_context.clone()); - } - } - - let mut tera = tera::Tera::default(); - let mut html = TeraParser::new(&mut tera, TeraParserOptions::default()); - html.add_context(context.to_inner()); - parser.push(&mut html); - Roxy::process_file(&file, &output_path, &mut parser).unwrap(); } + Ok(()) }