Skip to content

Commit

Permalink
html: expose a range of things; allow child suppression.
Browse files Browse the repository at this point in the history
  • Loading branch information
kivikakk committed Feb 22, 2025
1 parent 81a0366 commit 7180d5d
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 19 deletions.
20 changes: 16 additions & 4 deletions examples/custom_formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@ create_formatter!(CustomFormatter, {
output.write_all(b"</i>")?;
}
},
NodeValue::Strong => |o, e| {
o.write_all(if e { b"<b>" } else { b"</b>" })?;
NodeValue::Strong => |context, entering| {
use std::io::Write;
context.write_all(if entering { b"<b>" } else { b"</b>" })?;
},
NodeValue::Image(ref nl) => |output, node, entering, suppress_children| {
assert!(node.data.borrow().sourcepos == (3, 1, 3, 18).into());
if entering {
output.write_all(nl.url.to_uppercase().as_bytes())?;
*suppress_children = true;
}
},
});

Expand All @@ -18,13 +26,17 @@ fn main() {

let options = Options::default();
let arena = Arena::new();
let doc = parse_document(&arena, "_Hello_, **world**.", &options);
let doc = parse_document(
&arena,
"_Hello_, **world**.\n\n![title](/img.png)",
&options,
);

let mut buf: Vec<u8> = vec![];
CustomFormatter::format_document(doc, &options, &mut buf).unwrap();

assert_eq!(
std::str::from_utf8(&buf).unwrap(),
"<p><i>Hello</i>, <b>world</b>.</p>\n"
"<p><i>Hello</i>, <b>world</b>.</p>\n<p>/IMG.PNG</p>\n"
);
}
61 changes: 46 additions & 15 deletions src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ pub enum RenderMode {
macro_rules! create_formatter {
// No overrides (i.e. as used to create comrak::html::HtmlFormatter).
($name:ident) => {
create_formatter!($name, {});
$crate::create_formatter!($name, {});
};

// Permit lack of trailing comma by adding one.
($name:ident, { $( $pat:pat => |$output:ident, $entering:ident| $case:tt ),* }) => {
create_formatter!($name, { $( $pat => |$output, $entering| $case ),*, });
($name:ident, { $( $pat:pat => | $( $capture:ident ),* | $case:tt ),* }) => {
$crate::create_formatter!($name, { $( $pat => | $( $capture ),* | $case ),*, });
};

($name:ident, { $( $pat:pat => |$output:ident, $entering:ident| $case:tt ),*, }) => {
($name:ident, { $( $pat:pat => | $( $capture:ident ),* | $case:tt ),*, }) => {
// I considered making this a `mod` instead, but then name resolution
// inside your pattern cases gets weird and overriding *that* to be less
// weird (with idk, `use super::*;`?) feels worse.
Expand Down Expand Up @@ -99,32 +99,61 @@ macro_rules! create_formatter {
context: &mut $crate::html::Context,
node: &'a $crate::nodes::AstNode<'a>,
entering: bool,
) -> ::std::io::Result<$crate::html::RenderMode> {
) -> ::std::io::Result<::std::option::Option<$crate::html::RenderMode>> {
match node.data.borrow().value {
$(
$pat => {
let $output: &mut dyn ::std::io::Write = context;
let $entering = entering;

let mut suppress_children = false;
$crate::formatter_captures!((context, node, entering, suppress_children), ($( $capture ),*));
$case

Ok($crate::html::RenderMode::HTML)
if suppress_children {
Ok(::std::option::Option::None)
} else {
Ok(::std::option::Option::Some($crate::html::RenderMode::HTML))
}
}
),*
_ => $crate::html::format_node_default(context, node, entering),
_ => $crate::html::format_node_default(context, node, entering).map(::std::option::Option::Some),
}
}
}
};
}

/// TODO
#[macro_export]
macro_rules! formatter_captures {
(($context:ident, $node:ident, $entering:ident, $suppress_children:ident), context, $bind:ident) => {
let $bind = $context;
};
(($context:ident, $node:ident, $entering:ident, $suppress_children:ident), output, $bind:ident) => {
let $bind: &mut dyn ::std::io::Write = $context;
};
(($context:ident, $node:ident, $entering:ident, $suppress_children:ident), entering, $bind:ident) => {
let $bind = $entering;
};
(($context:ident, $node:ident, $entering:ident, $suppress_children:ident), node, $bind:ident) => {
let $bind = $node;
};
(($context:ident, $node:ident, $entering:ident, $suppress_children:ident), suppress_children, $bind:ident) => {
let mut $bind = &mut $suppress_children;
};
(($context:ident, $node:ident, $entering:ident, $suppress_children:ident), ($capture:ident)) => {
$crate::formatter_captures!(($context, $node, $entering, $suppress_children), $capture, $capture);
};
(($context:ident, $node:ident, $entering:ident, $suppress_children:ident), ($capture:ident, $( $rest:ident ),*)) => {
$crate::formatter_captures!(($context, $node, $entering, $suppress_children), $capture, $capture);
$crate::formatter_captures!(($context, $node, $entering, $suppress_children), ($( $rest ),*));
};
}

/// TODO
pub fn format_document_with_formatter<'a, 'o, 'c: 'o>(
root: &'a AstNode<'a>,
options: &'o Options<'c>,
output: &'o mut dyn Write,
plugins: &'o Plugins<'o>,
formatter: fn(&mut Context, &'a AstNode<'a>, bool) -> io::Result<RenderMode>,
formatter: fn(&mut Context, &'a AstNode<'a>, bool) -> io::Result<Option<RenderMode>>,
) -> ::std::io::Result<()> {
// Traverse the AST iteratively using a work stack, with pre- and
// post-child-traversal phases. During pre-order traversal render the
Expand Down Expand Up @@ -159,16 +188,18 @@ pub fn format_document_with_formatter<'a, 'o, 'c: 'o>(
}
_ => (),
}
render_mode
Some(render_mode)
}
RenderMode::HTML => {
stack.push((node, RenderMode::HTML, Phase::Post));
formatter(&mut context, node, true)?
}
};

for ch in node.reverse_children() {
stack.push((ch, new_rm, Phase::Pre));
if let Some(rm) = new_rm {
for ch in node.reverse_children() {
stack.push((ch, rm, Phase::Pre));
}
}
}
Phase::Post => {
Expand Down

0 comments on commit 7180d5d

Please sign in to comment.