diff --git a/crates/org-cli/Cargo.toml b/crates/org-cli/Cargo.toml index 100563f..fa75b80 100644 --- a/crates/org-cli/Cargo.toml +++ b/crates/org-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "org-rust" -version = "0.1.16" +version = "0.1.17" description = "CLI tool for converting Org-Mode documents to other formats" keywords = ["org-mode", "parser"] categories = ["command-line-utilities"] @@ -18,8 +18,8 @@ rust-version.workspace = true clap = { version = "4.3.11", features = ["derive"] } lazy_format = "2.0.0" regex = "1.9.5" -org-exporter = { version = "0.1.6", path = "../org-exporter", package = "org-rust-exporter" } -org-parser = { version = "0.1.4", path = "../org-parser", package = "org-rust-parser" } +org-exporter = { version = "0.1.8", path = "../org-exporter", package = "org-rust-exporter" } +org-parser = { version = "0.1.5", path = "../org-parser", package = "org-rust-parser" } serde = { version = "1.0.196", features=["derive"]} toml = "0.8.8" anyhow = "1.0.82" @@ -30,8 +30,8 @@ clap = { version = "4.3.11", features=["derive"]} clap_complete = "4.3.2" clap_mangen = "0.2.14" serde = { version = "1.0.196", features=["derive"]} -org-exporter = { version = "0.1.2", path = "../org-exporter", package = "org-rust-exporter" } -org-parser = { version = "0.1.2", path = "../org-parser", package = "org-rust-parser" } +org-exporter = { version = "0.1.8", path = "../org-exporter", package = "org-rust-exporter" } +org-parser = { version = "0.1.5", path = "../org-parser", package = "org-rust-parser" } # [[bin]] diff --git a/crates/org-cli/src/cli.rs b/crates/org-cli/src/cli.rs index f2341d1..47ef818 100644 --- a/crates/org-cli/src/cli.rs +++ b/crates/org-cli/src/cli.rs @@ -51,7 +51,7 @@ impl Backend { parsed: &org_parser::Parser, buf: &mut String, conf: ConfigOptions - ) -> Result<(), core::fmt::Error> { + ) -> Result<(), Vec> { match self { Backend::Html => org_exporter::Html::export_tree(parsed, buf, conf), Backend::Org => org_exporter::Org::export_tree(parsed, buf, conf), diff --git a/crates/org-cli/src/main.rs b/crates/org-cli/src/main.rs index df62ca2..d318a52 100644 --- a/crates/org-cli/src/main.rs +++ b/crates/org-cli/src/main.rs @@ -161,7 +161,14 @@ fn run() -> anyhow::Result<()> { } let conf = ConfigOptions::new(Some(file_path.to_path_buf())); - backend.export(&parser_output, &mut exported_content, conf)?; + if let Err(err_vec) = backend.export(&parser_output, &mut exported_content, conf) { + let mut build_str = String::new(); + for e in err_vec { + build_str.push_str(&e.to_string()); + build_str.push_str("\n"); + } + Err(CliError::new().with_cause(&build_str))? + } // handle a template (if needed) if let Some(template_path) = parser_output.keywords.get("template_path") { diff --git a/crates/org-cli/src/template.rs b/crates/org-cli/src/template.rs index 43fd025..9836513 100644 --- a/crates/org-cli/src/template.rs +++ b/crates/org-cli/src/template.rs @@ -243,10 +243,10 @@ impl<'a, 'template> Template<'a, 'template> { } } -#[cfg(test)] -mod tests { - use super::*; +// #[cfg(test)] +// mod tests { +// use super::*; - #[test] - fn bad_template() {} -} +// #[test] +// fn bad_template() {} +// } diff --git a/crates/org-cli/src/utils.rs b/crates/org-cli/src/utils.rs index e5a2042..21c5336 100644 --- a/crates/org-cli/src/utils.rs +++ b/crates/org-cli/src/utils.rs @@ -8,7 +8,7 @@ use crate::types::CliError; // a fs::canonicalize that doesnt care for existince. used for error handling // yanked straight from: // https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61 -pub fn normalize_path(path: &Path) -> PathBuf { +pub fn _normalize_path(path: &Path) -> PathBuf { let mut components = path.components().peekable(); let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { components.next(); diff --git a/crates/org-exporter/Cargo.toml b/crates/org-exporter/Cargo.toml index 18fa06b..d51a246 100644 --- a/crates/org-exporter/Cargo.toml +++ b/crates/org-exporter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "org-rust-exporter" -version = "0.1.7" +version = "0.1.8" description = "exporter for org mode documents parsed with `org-rust-parser`" homepage.workspace = true @@ -14,8 +14,9 @@ rust-version.workspace = true [dependencies] latex2mathml = "0.2.3" memchr = "2.5.0" -org-parser = { version = "0.1.3", path = "../org-parser", package = "org-rust-parser" } +org-parser = { version = "0.1.5", path = "../org-parser", package = "org-rust-parser" } phf = {version = "0.11.1", features = ["macros"]} +thiserror = "1.0.63" [dev-dependencies] pretty_assertions = "1.3.0" diff --git a/crates/org-exporter/src/html.rs b/crates/org-exporter/src/html.rs index dd5125a..894b7a1 100644 --- a/crates/org-exporter/src/html.rs +++ b/crates/org-exporter/src/html.rs @@ -5,7 +5,7 @@ use core::fmt; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; -use std::fmt::{Result, Write}; +use std::fmt::Write; use latex2mathml::{latex_to_mathml, DisplayStyle}; use memchr::memchr3_iter; @@ -15,10 +15,16 @@ use org_parser::{parse_macro_call, parse_org, Expr, Node, NodeID, Parser}; use crate::include::include_handle; use crate::org_macros::macro_handle; -use crate::types::{ConfigOptions, Exporter, ExporterInner}; +use crate::types::{ConfigOptions, Exporter, ExporterInner, LogicErrorKind}; use crate::utils::{process_toc, Options, TocItem}; +use crate::ExportError; use phf::phf_set; +macro_rules! w { + ($dst:expr, $($arg:tt)*) => { + $dst.write_fmt(format_args!($($arg)*)).expect("writing to buffer during export failed") + }; +} // file types we can wrap an `img` around static IMAGE_TYPES: phf::Set<&str> = phf_set! { "jpeg", @@ -69,6 +75,7 @@ pub struct Html<'buf> { footnotes: Vec, footnote_ids: HashMap, conf: ConfigOptions, + errors: Vec, } /// Wrapper around strings that need to be properly HTML escaped. @@ -83,7 +90,7 @@ impl<'a, S: AsRef> fmt::Display for HtmlEscape { // we can iterate over bytes since it's not possible for // an ascii character to appear in the codepoint of another larger char // if we see an ascii, then it's guaranteed to be valid - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut prev_pos = 0; // there are other characters we could escape, but memchr caps out at 3 // the really important one is `<`, and then also probably & @@ -113,7 +120,7 @@ impl<'a, S: AsRef> fmt::Display for HtmlEscape { } impl<'buf> Exporter<'buf> for Html<'buf> { - fn export(input: &str, conf: ConfigOptions) -> core::result::Result { + fn export(input: &str, conf: ConfigOptions) -> core::result::Result> { let mut buf = String::new(); Html::export_buf(input, &mut buf, conf)?; Ok(buf) @@ -123,7 +130,7 @@ impl<'buf> Exporter<'buf> for Html<'buf> { input: &'inp str, buf: &'buf mut T, conf: ConfigOptions, - ) -> Result { + ) -> core::result::Result<(), Vec> { let parsed: Parser<'_> = parse_org(input); Html::export_tree(&parsed, buf, conf) } @@ -132,35 +139,50 @@ impl<'buf> Exporter<'buf> for Html<'buf> { parsed: &Parser, buf: &'buf mut T, conf: ConfigOptions, - ) -> fmt::Result { + ) -> core::result::Result<(), Vec> { let mut obj = Html { buf, nox: HashSet::new(), footnotes: Vec::new(), footnote_ids: HashMap::new(), conf, + errors: Vec::new(), }; if let Ok(opts) = Options::handle_opts(parsed) { if let Ok(tocs) = process_toc(parsed, &opts) { - write!( - obj, - r#""#); } fn toc_rec<'a, T: fmt::Write + ExporterInner<'a>>( @@ -168,27 +190,27 @@ fn toc_rec<'a, T: fmt::Write + ExporterInner<'a>>( writer: &mut T, parent: &TocItem, curr_level: u8, -) -> Result { - write!(writer, "
  • ")?; +) { + w!(writer, "
  • "); if curr_level < parent.level { - write!(writer, "
      ")?; - toc_rec(&parser, writer, parent, curr_level + 1)?; - write!(writer, "
    ")?; + w!(writer, "
      "); + toc_rec(&parser, writer, parent, curr_level + 1); + w!(writer, "
    "); } else { - write!(writer, r#""#, parent.target)?; + w!(writer, r#""#, parent.target); for id in parent.name { - writer.export_rec(id, parser)?; + writer.export_rec(id, parser); } - write!(writer, "")?; + w!(writer, ""); if !parent.children.is_empty() { - write!(writer, "
      ")?; + w!(writer, "
        "); for child in &parent.children { - toc_rec(&parser, writer, child, curr_level + 1)?; + toc_rec(&parser, writer, child, curr_level + 1); } - write!(writer, "
      ")?; + w!(writer, "
    "); } } - write!(writer, "
  • ") + w!(writer, ""); } impl<'buf> ExporterInner<'buf> for Html<'buf> { @@ -196,7 +218,7 @@ impl<'buf> ExporterInner<'buf> for Html<'buf> { input: &'inp str, buf: &'buf mut T, conf: ConfigOptions, - ) -> Result { + ) -> core::result::Result<(), Vec> { let parsed = parse_macro_call(input); let mut obj = Html { buf, @@ -204,41 +226,47 @@ impl<'buf> ExporterInner<'buf> for Html<'buf> { footnotes: Vec::new(), footnote_ids: HashMap::new(), conf, + errors: Vec::new(), }; - obj.export_rec(&parsed.pool.root_id(), &parsed) + obj.export_rec(&parsed.pool.root_id(), &parsed); + if obj.errors().is_empty() { + Ok(()) + } else { + Err(obj.errors) + } } - fn export_rec(&mut self, node_id: &NodeID, parser: &Parser) -> Result { + fn export_rec(&mut self, node_id: &NodeID, parser: &Parser) { // avoid parsing this node if self.nox.contains(node_id) { - return Ok(()); + return; } let node = &parser.pool[*node_id]; match &node.obj { Expr::Root(inner) => { for id in inner { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } } Expr::Heading(inner) => { let heading_number: u8 = inner.heading_level.into(); - write!(self, "")?; + w!(self, ""); if let Some(title) = &inner.title { for id in &title.1 { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } } - writeln!(self, "")?; + w!(self, "\n"); if let Some(children) = &inner.children { for id in children { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } } } @@ -250,31 +278,31 @@ impl<'buf> ExporterInner<'buf> for Html<'buf> { contents, } => { if parameters.get("exports").is_some_and(|&x| x == "none") { - return Ok(()); + return; } - write!(self, "")?; + w!(self, "\n"); for id in contents { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - writeln!(self, "")?; + w!(self, "\n"); } Block::Quote { parameters, contents, } => { if parameters.get("exports").is_some_and(|&x| x == "none") { - return Ok(()); + return; } - write!(self, "")?; + w!(self, "\n"); for id in contents { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - writeln!(self, "")?; + w!(self, "\n"); } Block::Special { parameters, @@ -282,26 +310,26 @@ impl<'buf> ExporterInner<'buf> for Html<'buf> { name, } => { if parameters.get("exports").is_some_and(|&x| x == "none") { - return Ok(()); + return; } // html5 names are directly converted into tags if HTML5_TYPES.contains(name) { - write!(self, "<{name}")?; - self.prop(node)?; - writeln!(self, ">")?; + w!(self, "<{name}"); + self.prop(node); + w!(self, ">\n"); for id in contents { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - write!(self, "")?; + w!(self, ""); } else { - write!(self, "")?; + w!(self, "\n"); for id in contents { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - writeln!(self, "")?; + w!(self, "\n"); } } @@ -311,21 +339,21 @@ impl<'buf> ExporterInner<'buf> for Html<'buf> { contents, } => { if parameters.get("exports").is_some_and(|&x| x == "none") { - return Ok(()); + return; } - writeln!(self, "")?; + w!(self, "\n"); } Block::Example { parameters, contents, } => { if parameters.get("exports").is_some_and(|&x| x == "none") { - return Ok(()); + return; } - write!(self, "\n{}", HtmlEscape(contents))?; + w!(self, "\n{}\n", HtmlEscape(contents)); } Block::Export { backend, @@ -333,10 +361,10 @@ impl<'buf> ExporterInner<'buf> for Html<'buf> { contents, } => { if parameters.get("exports").is_some_and(|&x| x == "none") { - return Ok(()); + return; } if backend.is_some_and(|x| x == Html::backend_name()) { - writeln!(self, "{contents}")?; + w!(self, "{contents}\n"); } } Block::Src { @@ -345,29 +373,29 @@ impl<'buf> ExporterInner<'buf> for Html<'buf> { contents, } => { if parameters.get("exports").is_some_and(|&x| x == "none") { - return Ok(()); + return; } - write!(self, "
    ")?;
    -                        write!(self, "");
    +                        w!(self, "\n{}
    ", HtmlEscape(contents))?; + self.prop(node); + w!(self, ">\n{}\n", HtmlEscape(contents)); } Block::Verse { parameters, contents, } => { if parameters.get("exports").is_some_and(|&x| x == "none") { - return Ok(()); + return; } // FIXME: apparently verse blocks contain objects... - write!(self, "\n{}

    ", HtmlEscape(contents))?; + w!(self, "\n{}

    \n", HtmlEscape(contents)); } } } @@ -398,15 +426,15 @@ impl<'buf> ExporterInner<'buf> for Html<'buf> { } PathReg::File(a) => format!("{a}"), }; - write!(self, r#""#, HtmlEscape(&path_link))?; + w!(self, r#""#, HtmlEscape(&path_link)); if let Some(children) = &inner.description { for id in children { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } } else { - write!(self, "{}", HtmlEscape(inner.path.to_str(parser.source)))?; + w!(self, "{}", HtmlEscape(inner.path.to_str(parser.source))); } - write!(self, "")?; + w!(self, ""); } Expr::Paragraph(inner) => { @@ -427,14 +455,14 @@ impl<'buf> ExporterInner<'buf> for Html<'buf> { let ending_tag = link_source.split('.').last(); if let Some(extension) = ending_tag { if IMAGE_TYPES.contains(extension) { - write!(self, "
    \n\n = @@ -443,158 +471,174 @@ impl<'buf> ExporterInner<'buf> for Html<'buf> { } else { link_source.into() }; - write!(self, "{}", HtmlEscape(alt_text))?; + w!(self, "{}", HtmlEscape(alt_text)); } - write!(self, "\">\n
    ")?; - return Ok(()); + w!(self, "\">\n"); + return; } } } } - write!(self, "")?; + w!(self, ""); for id in &inner.0 { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - writeln!(self, "

    ")?; + w!(self, "

    \n"); } Expr::Italic(inner) => { - write!(self, "")?; + w!(self, ""); for id in &inner.0 { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - write!(self, "")?; + w!(self, ""); } Expr::Bold(inner) => { - write!(self, "")?; + w!(self, ""); for id in &inner.0 { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - write!(self, "")?; + w!(self, ""); } Expr::StrikeThrough(inner) => { - write!(self, "")?; + w!(self, ""); for id in &inner.0 { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - write!(self, "")?; + w!(self, ""); } Expr::Underline(inner) => { - write!(self, "")?; + w!(self, ""); for id in &inner.0 { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - write!(self, "")?; - // write!(self, "")?; + w!(self, ""); + // w!(self, "")?; // for id in &inner.0 { - // self.export_rec(id, parser)?; + // self.export_rec(id, parser); // } - // write!(self, "")?; + // w!(self, "")?; } Expr::BlankLine => { - // write!(self, "\n")?; + // w!(self, "\n")?; } Expr::SoftBreak => { - write!(self, " ")?; + w!(self, " "); } Expr::LineBreak => { - writeln!(self, "\n
    ")?; + w!(self, "\n
    \n"); } Expr::HorizontalRule => { - writeln!(self, "\n
    ")?; + w!(self, "\n
    \n"); } Expr::Plain(inner) => { - write!(self, "{}", HtmlEscape(inner))?; + w!(self, "{}", HtmlEscape(inner)); } Expr::Verbatim(inner) => { - write!(self, "{}", HtmlEscape(inner.0))?; + w!(self, "{}", HtmlEscape(inner.0)); } Expr::Code(inner) => { - write!(self, "{}", HtmlEscape(inner.0))?; + w!(self, "{}", HtmlEscape(inner.0)); } Expr::Comment(inner) => { - write!(self, "", inner.0)?; + w!(self, "", inner.0); } Expr::InlineSrc(inner) => { - write!( + w!( self, "{}", inner.lang, HtmlEscape(inner.body) - )?; + ); // if let Some(args) = inner.headers { - // write!(self, "[{args}]")?; + // w!(self, "[{args}]")?; // } - // write!(self, "{{{}}}", inner.body)?; + // w!(self, "{{{}}}", inner.body)?; } Expr::Keyword(inner) => { if inner.key.to_ascii_lowercase() == "include" { - // FIXME: proper error handling - write!(self, r#"
    ")?; - include_handle(inner.val, self).unwrap(); - write!(self, "
    ")?; + w!(self, r#"
    "); + + if let Err(e) = include_handle(inner.val, self) { + self.errors().push(ExportError::LogicError { + span: node.start..node.end, + source: LogicErrorKind::Include(e), + }); + return; + } + + // .map_err(|e| ExportError::LogicError { + // span: node.start..node.end, + // source: LogicErrorKind::Include(e), + // })?; + w!(self, "
    "); } } Expr::LatexEnv(inner) => { - let ret = latex_to_mathml( - &format!( - r"\begin{{{0}}} + let formatted = &format!( + r"\begin{{{0}}} {1} \end{{{0}}} ", - inner.name, inner.contents - ), - DisplayStyle::Block, - ) - .unwrap(); - writeln!(self, "{ret}")?; + inner.name, inner.contents + ); + let ret = latex_to_mathml(&formatted, DisplayStyle::Block); + // TODO/FIXME: this should be an error + w!( + self, + "{}\n", + if let Ok(val) = &ret { val } else { formatted } + ); } Expr::LatexFragment(inner) => match inner { LatexFragment::Command { name, contents } => { let mut pot_cont = String::new(); - write!(pot_cont, r#"{name}"#)?; + w!(pot_cont, r#"{name}"#); if let Some(command_cont) = contents { - write!(pot_cont, "{{{command_cont}}}")?; + w!(pot_cont, "{{{command_cont}}}"); } - write!( + // TODO/FIXME: this should be an error + w!( self, "{}", &latex_to_mathml(&pot_cont, DisplayStyle::Inline).unwrap(), - )?; + ); } LatexFragment::Display(inner) => { - writeln!( + // TODO/FIXME: this should be an error + w!( self, - "{}", + "{}\n", &latex_to_mathml(inner, DisplayStyle::Block).unwrap() - )?; + ); } LatexFragment::Inline(inner) => { - write!( + // TODO/FIXME: this should be an error + w!( self, "{}", &latex_to_mathml(inner, DisplayStyle::Inline).unwrap() - )?; + ); } }, Expr::Item(inner) => { if let Some(tag) = inner.tag { - write!(self, "
    {}
    ", HtmlEscape(tag))?; - write!(self, "
    ")?; + w!(self, "
    {}
    ", HtmlEscape(tag)); + w!(self, "
    "); for id in &inner.children { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - write!(self, "
    ")?; + w!(self, ""); } else { - write!(self, " ExporterInner<'buf> for Html<'buf> { CheckBox::Intermediate => "trans", CheckBox::Off => "off", CheckBox::On => "on", - })?; + }); } - write!(self, ">")?; + w!(self, ">"); for id in &inner.children { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - writeln!(self, "")?; + w!(self, "\n"); } } Expr::PlainList(inner) => { - let tag = match inner.kind { - ListKind::Unordered => "ul", + let (tag, desc) = match inner.kind { + ListKind::Unordered => ("ul", ""), ListKind::Ordered(counter_kind) => match counter_kind { org_parser::element::CounterKind::Letter(c) => { if c.is_ascii_uppercase() { - r#"ol type="A""# + ("ol", r#" type="A""#) } else { - r#"ol type="a""# + ("ol", r#" type="a""#) } } - org_parser::element::CounterKind::Number(_) => r#"ol type="1""#, + org_parser::element::CounterKind::Number(_) => ("ol", r#" type="1""#), }, - ListKind::Descriptive => "dd", + ListKind::Descriptive => ("dd", ""), }; - write!(self, "<{tag}")?; - self.prop(node)?; - writeln!(self, ">")?; + w!(self, "<{tag}{desc}"); + self.prop(node); + w!(self, ">\n"); for id in &inner.children { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - writeln!(self, "")?; + w!(self, "\n"); } Expr::PlainLink(inner) => { - write!( + w!( self, "{0}:{1}", - inner.protocol, inner.path - )?; + inner.protocol, + inner.path + ); } Expr::Entity(inner) => { - write!(self, "{}", inner.mapped_item)?; + w!(self, "{}", inner.mapped_item); } Expr::Table(inner) => { - write!(self, "")?; + w!(self, "\n"); for id in &inner.children { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - writeln!(self, "")?; + w!(self, "\n"); } Expr::TableRow(inner) => { match inner { TableRow::Rule => { /*skip*/ } TableRow::Standard(stands) => { - writeln!(self, "")?; + w!(self, "\n"); for id in stands.iter() { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - writeln!(self, "")?; + w!(self, "\n"); } } } Expr::TableCell(inner) => { - write!(self, "")?; + w!(self, ""); for id in &inner.0 { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } - writeln!(self, "")?; + w!(self, "\n"); } Expr::Emoji(inner) => { - write!(self, "{}", inner.mapped_item)?; + w!(self, "{}", inner.mapped_item); } Expr::Superscript(inner) => { - write!(self, "")?; + w!(self, ""); match &inner.0 { PlainOrRec::Plain(inner) => { - write!(self, "{inner}")?; + w!(self, "{inner}"); } PlainOrRec::Rec(inner) => { for id in inner { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } } } - write!(self, "")?; + w!(self, ""); } Expr::Subscript(inner) => { - write!(self, "")?; + w!(self, ""); match &inner.0 { PlainOrRec::Plain(inner) => { - write!(self, "{inner}")?; + w!(self, "{inner}"); } PlainOrRec::Rec(inner) => { for id in inner { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } } } - write!(self, "")?; + w!(self, ""); } Expr::Target(inner) => { - write!(self, "")?; - write!( + w!(self, ""); + w!( self, "{}", parser.pool[*node_id].id_target.as_ref().unwrap(), // must exist HtmlEscape(inner.0) - )?; + ); } Expr::Macro(macro_call) => { - if let Ok(macro_contents) = macro_handle(parser, macro_call, self.config_opts()) { - match macro_contents { - Cow::Owned(p) => { - Html::export_macro_buf(&p, self, self.config_opts().clone())?; - } - Cow::Borrowed(r) => { - write!(self, "{}", HtmlEscape(r))?; + let macro_contents = match macro_handle(parser, macro_call, self.config_opts()) { + Ok(contents) => contents, + Err(e) => { + self.errors().push(ExportError::LogicError { + span: node.start..node.end, + source: LogicErrorKind::Macro(e), + }); + return; + } + }; + + match macro_contents { + Cow::Owned(p) => { + if let Err(mut err_vec) = + Html::export_macro_buf(&p, self, self.config_opts().clone()) + { + self.errors().append(&mut err_vec); + // TODO alert for errors handled within macro } } + Cow::Borrowed(r) => { + w!(self, "{}", HtmlEscape(r)); + } } } Expr::Drawer(inner) => { for id in &inner.children { - self.export_rec(id, parser)?; + self.export_rec(id, parser); } } Expr::ExportSnippet(inner) => { if inner.backend == Html::backend_name() { - write!(self, "{}", inner.contents)?; + w!(self, "{}", inner.contents); } } Expr::Affiliated(inner) => match inner { Affiliated::Name(_id) => {} Affiliated::Caption(id, contents) => { if let Some(caption_id) = id { - writeln!(self, "
    ")?; - self.export_rec(caption_id, parser)?; - writeln!(self, "
    ")?; - self.export_rec(contents, parser)?; - writeln!(self, "
    ")?; - writeln!(self, "
    ")?; + w!(self, "
    \n"); + self.export_rec(caption_id, parser); + w!(self, "
    \n"); + self.export_rec(contents, parser); + w!(self, "
    \n"); + w!(self, "
    \n"); self.nox.insert(*caption_id); } } @@ -792,17 +851,16 @@ impl<'buf> ExporterInner<'buf> for Html<'buf> { format!("{index}") }; - write!( + w!( self, r##" {1} "##, - fn_id, index, - )?; + fn_id, + index, + ); } } - - Ok(()) } fn backend_name() -> &'static str { @@ -812,38 +870,39 @@ impl<'buf> ExporterInner<'buf> for Html<'buf> { fn config_opts(&self) -> &ConfigOptions { &self.conf } + fn errors(&mut self) -> &mut Vec { + &mut self.errors + } } // Writers for generic attributes impl<'buf> Html<'buf> { /// Adds a property - fn prop(&mut self, node: &Node) -> Result { + fn prop(&mut self, node: &Node) { // if the target needs an id if let Some(tag_contents) = node.id_target.as_ref() { - write!(self, r#" id="{tag_contents}""#)?; + w!(self, r#" id="{tag_contents}""#); } // attach any keys that need to be placed if let Some(attrs) = node.attrs.get(Html::backend_name()) { for (key, val) in attrs { - self.attr(key, val)?; + self.attr(key, val); } } - - Ok(()) } - fn class(&mut self, name: &str) -> Result { - write!(self, r#" class="{name}""#) + fn class(&mut self, name: &str) { + w!(self, r#" class="{name}""#); } - fn attr(&mut self, key: &str, val: &str) -> Result { - write!(self, r#" {}="{}""#, key, HtmlEscape(val)) + fn attr(&mut self, key: &str, val: &str) { + w!(self, r#" {}="{}""#, key, HtmlEscape(val)); } - fn exp_footnotes(&mut self, parser: &Parser) -> Result { + fn exp_footnotes(&mut self, parser: &Parser) { if self.footnotes.is_empty() { - return Ok(()); + return; } // get last heading, and check if its title is Footnotes, @@ -859,7 +918,7 @@ impl<'buf> Html<'buf> { false }); - writeln!( + w!( self, r#"
    @@ -867,14 +926,23 @@ impl<'buf> Html<'buf> { .footdef p {{ display:inline; }} - "# - )?; + +"# + ); if heading_query.is_none() { - writeln!(self, r#"

    Footnotes

    "#)?; + w!( + self, + r#"

    Footnotes

    +"# + ); } - writeln!(self, r#"
    "#)?; + w!( + self, + r#"
    +"# + ); // FIXME // lifetime shenanigans making me do this.. can't figure em out @@ -883,7 +951,7 @@ impl<'buf> Html<'buf> { let man = self.footnotes.clone(); for (mut pos, def_id) in man.iter().enumerate() { pos += 1; - write!( + w!( self, r##" @@ -892,30 +960,30 @@ impl<'buf> Html<'buf> { {pos} "## - )?; + ); match &parser.pool[*def_id].obj { Expr::FootnoteDef(fn_def) => { for child_id in &fn_def.children { - self.export_rec(child_id, parser)?; + self.export_rec(child_id, parser); } } Expr::FootnoteRef(fn_ref) => { if let Some(children) = fn_ref.children.as_ref() { for child_id in children { - self.export_rec(child_id, parser)?; + self.export_rec(child_id, parser); } } } _ => (), } - write!(self, r#"
    "#)?; + w!(self, r#"
    "#); } - write!(self, "\n
    \n") + w!(self, "\n \n"); } } impl fmt::Write for Html<'_> { - fn write_str(&mut self, s: &str) -> Result { + fn write_str(&mut self, s: &str) -> fmt::Result { self.buf.write_str(s) } } @@ -925,60 +993,51 @@ mod tests { use super::*; use pretty_assertions::assert_eq; - fn html_export(input: &str) -> core::result::Result { - Html::export(input, ConfigOptions::default()) + fn html_export(input: &str) -> String { + Html::export(input, ConfigOptions::default()).unwrap() } #[test] - fn combined_macros() -> fmt::Result { + fn combined_macros() { let a = html_export( r"#+macro: poem hiii $1 $2 text {{{poem(cool,three)}}} ", - )?; + ); assert_eq!( a, - r"

    -hiii cool three text -

    + r"

    hiii cool three text

    " ); - - Ok(()) } #[test] - fn keyword_macro() -> Result { + fn keyword_macro() { let a = html_export( r" #+title: hiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii {{{keyword(title)}}} ", - )?; + ); assert_eq!( a, - r"

    -hiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii -

    + r"

    hiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii

    ", ); - Ok(()) } #[test] - fn line_break() -> Result { + fn line_break() { let a = html_export( r" abc\\ ", - )?; + ); assert_eq!( a, - r"

    -abc + r"

    abc
    -

    ", ); @@ -986,34 +1045,31 @@ abc let n = html_export( r" abc\\ q ", - )?; + ); assert_eq!( n, - r"

    -abc\\ q -

    + r"

    abc\\ q

    ", ); - Ok(()) } #[test] - fn horizontal_rule() -> Result { + fn horizontal_rule() { let a = html_export( r"----- ", - )?; + ); let b = html_export( r" ----- ", - )?; + ); let c = html_export( r" ------------------------- ", - )?; + ); assert_eq!(a, b); assert_eq!(b, c); @@ -1022,21 +1078,17 @@ abc\\ q let nb = html_export( r" ---- ", - )?; + ); assert_eq!( nb, - r"

    ----- -

    + r"

    ----

    ", ); - - Ok(()) } #[test] - fn correct_cache() -> Result { + fn correct_cache() { let a = html_export( r" - one @@ -1046,65 +1098,56 @@ abc\\ q abc &+ 10\\ \end{align} ", - )?; + ); println!("{a}"); - - Ok(()) } #[test] - fn html_unicode() -> Result { + fn html_unicode() { let a = html_export( r"a é😳 ", - )?; + ); assert_eq!( a, - r"

    -a é😳 -

    + r"

    a é😳

    " ); - - Ok(()) } #[test] - fn list_counter_set() -> Result { + fn list_counter_set() { let a = html_export( r" 1. [@4] wordsss?? ", - )?; + ); assert_eq!( a, - r#"
      -
    1. -wordsss?? -

      + r#"
        +
      1. wordsss??

      "#, ); - Ok(()) } #[test] - fn anon_footnote() -> Result { + fn anon_footnote() { let a = html_export( r" -hi [fn:next:coolio] +hi [fn:next:coolio] yeah [fn:next] ", - )?; + ); // just codifying what the output is here, not supposed to be set in stone assert_eq!( a, - r##"

      -hi + r##"

      hi 1 - -

      + yeah + 1 +