diff --git a/src/roxy.rs b/src/roxy.rs index 840c29f..55d360f 100644 --- a/src/roxy.rs +++ b/src/roxy.rs @@ -1,102 +1,196 @@ -pub trait Parse: std::fmt::Debug { - fn parse(&self, src: &[u8], dst: Vec) -> std::io::Result<()>; +use std::{ + fs::{self, File}, + io::{BufRead, BufReader, Read, Write}, + path::Path, +}; - fn as_dyn(&self) -> &dyn Parse - where - Self: Sized, - { - self +pub trait Parse { + fn parse(&mut self, path: &str, src: &[u8], dst: &mut Vec) -> std::io::Result<()>; +} + +pub trait AndThenParser

{ + fn and_then(&mut self, parser: P) -> &Self; +} + +pub struct Parser { + steps: Vec>, +} + +impl Parser { + pub fn new() -> Self { + Parser { steps: Vec::new() } + } + + pub fn push(&mut self, parser: P) { + self.steps.push(Box::new(parser)); } } -pub trait AndThenParser<'a, P> { - fn and_then(&mut self, parser: &'a P) -> &Self; +impl Parse for Parser { + fn parse(&mut self, path: &str, src: &[u8], dst: &mut Vec) -> std::io::Result<()> { + let mut buf_1 = Vec::from(src); + let mut buf_2 = Vec::from(dst.as_slice()); + + self.steps + .iter_mut() + .try_fold((&mut buf_1, &mut buf_2), |(src, mut dst), p| { + dst.clear(); + p.parse(path, src.as_slice(), &mut dst).map(|()| (dst, src)) + }) + .map(|(a, _)| dst.write_all(a))? + } } #[derive(Debug)] -pub struct Markdown<'a> { - inner: Option<&'a dyn Parse>, -} +pub struct Markdown; -impl<'a> Markdown<'a> { +impl Markdown { pub fn new() -> Self { - Self { inner: None } + Self } } -impl<'a, P: Parse> AndThenParser<'a, P> for Markdown<'a> { - fn and_then(&mut self, parser: &'a P) -> &Self { - self.inner = Some(parser.as_dyn()); - self - } -} - -impl<'a> Parse for Markdown<'a> { - fn parse(&self, src: &[u8], dst: Vec) -> std::io::Result<()> { - self.inner.as_ref().map(|p| p.parse(src, dst)); - Ok(()) - } -} - -impl<'a> Iterator for Markdown<'a> { - type Item = &'a dyn Parse; - - fn next(&mut self) -> Option { - self.inner +impl Parse for Markdown { + fn parse(&mut self, _path: &str, src: &[u8], dst: &mut Vec) -> std::io::Result<()> { + let src = String::from_utf8_lossy(src).to_string(); + let parser = pulldown_cmark::Parser::new(src.as_str()); + pulldown_cmark::html::write_html(dst, parser) } } #[derive(Debug, Default)] -pub struct Html<'a> { - inner: Option<&'a dyn Parse>, +pub struct Html { pub tera: tera::Tera, context: tera::Context, } -impl<'a> Html<'a> { +impl Html { pub fn new(tera: tera::Tera, context: tera::Context) -> Self { - Self { - inner: None, - tera, - context, - } + Self { tera, context } } } -impl<'a, P: Parse> AndThenParser<'a, P> for Html<'a> { - fn and_then(&mut self, parser: &'a P) -> &Self { - self.inner = Some(parser.as_dyn()); - self +impl Parse for Html { + fn parse(&mut self, path: &str, src: &[u8], dst: &mut Vec) -> std::io::Result<()> { + // TODO: This error is a hack + let err = |_| std::io::Error::new(std::io::ErrorKind::InvalidData, "fail"); + let template = String::from_utf8_lossy(src).to_string(); + + self.tera + .add_raw_template(path, template.as_str()) + .map_err(err)?; + + self.tera.render_to(path, &self.context, dst).map_err(err) } } -impl<'a> Parse for Html<'a> { - fn parse(&self, src: &[u8], dst: Vec) -> std::io::Result<()> { - self.inner.as_ref().map(|p| p.parse(src, dst)); - Ok(()) +pub struct Asset<'a, R> { + path: &'a str, + data: R, +} + +impl<'a, R> Asset<'a, R> { + pub fn new(path: &'a str, data: R) -> Self { + Asset { path, data } } } -impl<'a> Iterator for Html<'a> { - type Item = &'a dyn Parse; +impl<'a> TryFrom<&'a str> for Asset<'a, BufReader> { + type Error = std::io::Error; - fn next(&mut self) -> Option { - self.inner + fn try_from(value: &'a str) -> Result { + File::open(value) + .map(BufReader::new) + .map(|data| Self::new(value, data)) + } +} + +impl<'a, R: Read> Read for Asset<'a, R> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.data.read(buf) + } +} + +impl<'a, R: BufRead> BufRead for Asset<'a, R> { + fn fill_buf(&mut self) -> std::io::Result<&[u8]> { + self.data.fill_buf() + } + + fn consume(&mut self, amt: usize) { + self.data.consume(amt) } } pub struct Roxy; -impl Roxy {} +impl Roxy { + pub fn load(path: &str) -> std::io::Result>> { + path.try_into() + } + + fn read_asset( + asset: &mut Asset>, + parser: &mut Parser, + ) -> std::io::Result> { + let mut src = Vec::new(); + let mut dst = Vec::new(); + + asset.data.read_to_end(&mut src)?; + + parser.parse(asset.path, &src, &mut dst)?; + + Ok(dst) + } + + fn path_to_str>(path: &P) -> std::io::Result<&str> { + path.as_ref() + .to_str() + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "{path} is invalid")) + } + + pub fn parse>(path: &P, parser: &mut Parser) -> std::io::Result> { + Self::path_to_str(path) + .and_then(Self::load) + .and_then(|mut a| Self::read_asset(&mut a, parser)) + } + + fn mkdir_and_open>(path: &P) -> std::io::Result { + let path = path.as_ref(); + fs::create_dir_all(path.with_file_name(""))?; + File::create(path) + } + + pub fn process_file>( + input: &P, + output: &P, + parser: &mut Parser, + ) -> std::io::Result<()> { + let buf = Self::parse(input, parser)?; + Self::mkdir_and_open(&output).and_then(|mut f| f.write_all(&buf)) + } +} #[cfg(test)] mod tests { - use crate::roxy::{Html, Markdown, AndThenParser}; + use crate::roxy::{Html, Markdown, Parser}; + + use super::Parse; #[test] - fn iterate_parsers() { - let parser = Markdown::new().and_then(&Html::default()); + fn md_and_html() { + let mut parsers = Parser::new(); + parsers.push(Markdown::new()); + let mut ctx = tera::Context::new(); - println!("{parser:?}"); + ctx.insert("test", "fox"); + parsers.push(Html::new(tera::Tera::default(), ctx)); + + let mut buf = Vec::new(); + + parsers + .parse("test.html", b"# {{ test }} :3", &mut buf) + .unwrap(); + + assert_eq!(String::from_utf8_lossy(buf.as_slice()), "

fox :3

\n"); } }