diff --git a/examples/custom_formatter_user.rs b/examples/custom_formatter_user.rs new file mode 100644 index 00000000..7d7872ac --- /dev/null +++ b/examples/custom_formatter_user.rs @@ -0,0 +1,47 @@ +use comrak::html::ChildRendering; +use comrak::{create_formatter, nodes::NodeValue}; +use std::io::Write; + +create_formatter!(CustomFormatter, { + NodeValue::Emph => |context, entering| { + context.user += 1; + if entering { + context.write_all(b"")?; + } else { + context.write_all(b"")?; + } + }, + NodeValue::Strong => |context, entering| { + context.user += 1; + context.write_all(if entering { b"" } else { b"" })?; + }, + NodeValue::Image(ref nl) => |context, node, entering| { + assert!(node.data.borrow().sourcepos == (3, 1, 3, 18).into()); + if entering { + context.write_all(nl.url.to_uppercase().as_bytes())?; + } + return Ok(ChildRendering::Skip); + }, +}); + +fn main() { + use comrak::{parse_document, Arena, Options}; + + let options = Options::default(); + let arena = Arena::new(); + let doc = parse_document( + &arena, + "_Hello_, **world**.\n\n![title](/img.png)", + &options, + ); + + let mut buf: Vec = vec![]; + let converted_count = CustomFormatter::format_document(doc, &options, &mut buf, 0).unwrap(); + + assert_eq!( + std::str::from_utf8(&buf).unwrap(), + "

Hello, world.

\n

/IMG.PNG

\n" + ); + + assert_eq!(converted_count, 4); +} diff --git a/src/html.rs b/src/html.rs index b254e7b2..bb0bd396 100644 --- a/src/html.rs +++ b/src/html.rs @@ -36,6 +36,7 @@ pub fn format_document<'a>( output, &Plugins::default(), format_node_default, + (), ) } @@ -46,7 +47,7 @@ pub fn format_document_with_plugins<'a>( output: &mut dyn Write, plugins: &Plugins, ) -> io::Result<()> { - format_document_with_formatter(root, options, output, plugins, format_node_default) + format_document_with_formatter(root, options, output, plugins, format_node_default, ()) } /// Returned by the [`format_document_with_formatter`] callback to indicate @@ -87,11 +88,17 @@ pub enum ChildRendering { /// override to change this behaviour, in some or all cases. These values are /// only noted when `entering` a node. /// +/// If you supply a type parameter after the name of your formatter, it will be +/// taken as an additional argument on the generated `format_document` method, +/// is available on the [`Context`] as the `user` property, and becomes the +/// return value of `format_document`. +/// /// ``` /// # use comrak::{create_formatter, parse_document, Arena, Options, nodes::NodeValue, html::ChildRendering}; /// # use std::io::Write; -/// create_formatter!(CustomFormatter, { +/// create_formatter!(CustomFormatter, { /// NodeValue::Emph => |context, entering| { +/// context.user += 1; /// if entering { /// context.write_all(b"")?; /// } else { @@ -99,7 +106,7 @@ pub enum ChildRendering { /// } /// }, /// NodeValue::Strong => |context, entering| { -/// use std::io::Write; +/// context.user += 1; /// context.write_all(if entering { b"" } else { b"" })?; /// }, /// NodeValue::Image(ref nl) => |context, node, entering| { @@ -120,12 +127,14 @@ pub enum ChildRendering { /// ); /// /// let mut buf: Vec = vec![]; -/// CustomFormatter::format_document(doc, &options, &mut buf).unwrap(); +/// let converted_count = CustomFormatter::format_document(doc, &options, &mut buf, 0).unwrap(); /// /// assert_eq!( /// std::str::from_utf8(&buf).unwrap(), /// "

Hello, world.

\n

/IMG.PNG

\n" /// ); +/// +/// assert_eq!(converted_count, 4); /// ``` #[macro_export] macro_rules! create_formatter { @@ -134,10 +143,23 @@ macro_rules! create_formatter { $crate::create_formatter!($name, { $( $pat => | $( $capture ),* | $case ),*, }); }; + ($name:ident<$type:ty>, { $( $pat:pat => | $( $capture:ident ),* | $case:tt ),* }) => { + $crate::create_formatter!($name<$type>, { $( $pat => | $( $capture ),* | $case ),*, }); + }; + ($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. + $crate::create_formatter!($name<()>, { $( $pat => | $( $capture ),* | $case ),*, }); + }; + + // TODO: is there a nice way to deduplicate the below two clauses? When a + // type isn't given, we default to `()`; in turn, we specialise the macro + // when `()` is the type and supply the `()` value on the user's behalf. + // This preserves the API from before the user type was added, and is just + // neater/cleaner besides. + // + // If you are reading this comment, you might know of a nice way to do this! + // I'd rather not resort to proc macros! TIA! + ($name:ident<()>, { $( $pat:pat => | $( $capture:ident ),* | $case:tt ),*, }) => { #[allow(missing_copy_implementations)] #[allow(missing_debug_implementations)] /// Created by [`comrak::create_formatter!`][crate::create_formatter]. @@ -157,6 +179,7 @@ macro_rules! create_formatter { output, &$crate::Plugins::default(), Self::formatter, + () ) } @@ -174,11 +197,77 @@ macro_rules! create_formatter { output, plugins, Self::formatter, + () ) } fn formatter<'a>( - context: &mut $crate::html::Context, + context: &mut $crate::html::Context<()>, + node: &'a $crate::nodes::AstNode<'a>, + entering: bool, + ) -> ::std::io::Result<$crate::html::ChildRendering> { + match node.data.borrow().value { + $( + $pat => { + $crate::formatter_captures!((context, node, entering), ($( $capture ),*)); + $case + // Don't warn on unconditional return in user code. + #[allow(unreachable_code)] + ::std::result::Result::Ok($crate::html::ChildRendering::HTML) + } + ),* + _ => $crate::html::format_node_default(context, node, entering), + } + } + } + }; + + ($name:ident<$type:ty>, { $( $pat:pat => | $( $capture:ident ),* | $case:tt ),*, }) => { + #[allow(missing_copy_implementations)] + #[allow(missing_debug_implementations)] + /// Created by [`comrak::create_formatter!`][crate::create_formatter]. + pub struct $name; + + impl $name { + /// Formats an AST as HTML, modified by the given options. + #[inline] + pub fn format_document<'a>( + root: &'a $crate::nodes::AstNode<'a>, + options: &$crate::Options, + output: &mut dyn ::std::io::Write, + user: $type, + ) -> ::std::io::Result<$type> { + $crate::html::format_document_with_formatter( + root, + options, + output, + &$crate::Plugins::default(), + Self::formatter, + user + ) + } + + /// Formats an AST as HTML, modified by the given options. Accepts custom plugins. + #[inline] + pub fn format_document_with_plugins<'a, 'o, 'c: 'o>( + root: &'a $crate::nodes::AstNode<'a>, + options: &'o $crate::Options<'c>, + output: &'o mut dyn ::std::io::Write, + plugins: &'o $crate::Plugins<'o>, + user: $type, + ) -> ::std::io::Result<$type> { + $crate::html::format_document_with_formatter( + root, + options, + output, + plugins, + Self::formatter, + user + ) + } + + fn formatter<'a>( + context: &mut $crate::html::Context<$type>, node: &'a $crate::nodes::AstNode<'a>, entering: bool, ) -> ::std::io::Result<$crate::html::ChildRendering> { @@ -232,24 +321,25 @@ macro_rules! formatter_captures { /// boolean indicating whether the node is being entered into or exited. The /// returned [`ChildRendering`] is used to inform whether and how the node's /// children are recursed into automatically. -pub fn format_document_with_formatter<'a, 'o, 'c: 'o>( +pub fn format_document_with_formatter<'a, 'o, 'c: 'o, T>( root: &'a AstNode<'a>, options: &'o Options<'c>, output: &'o mut dyn Write, plugins: &'o Plugins<'o>, formatter: fn( - context: &mut Context, + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result, -) -> ::std::io::Result<()> { + user: T, +) -> ::std::io::Result { // Traverse the AST iteratively using a work stack, with pre- and // post-child-traversal phases. During pre-order traversal render the // opening tags, then push the node back onto the stack for the // post-order traversal phase, then push the children in reverse order // onto the stack and begin rendering first child. - let mut context = Context::new(output, options, plugins); + let mut context = Context::new(output, options, plugins, user); enum Phase { Pre, @@ -301,17 +391,15 @@ pub fn format_document_with_formatter<'a, 'o, 'c: 'o>( } } - context.finish()?; - - Ok(()) + context.finish() } /// Default node formatting function, used by [`format_document`], /// [`format_document_with_plugins`] and as the fallback for any node types not /// handled in custom formatters created by [`create_formatter!`]. #[inline] -pub fn format_node_default<'a>( - context: &mut Context, +pub fn format_node_default<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -374,7 +462,7 @@ pub fn format_node_default<'a>( /// This function renders anything iff `context.options.render.sourcepos` is /// true, and includes a leading space if so, so you can use it unconditionally /// immediately before writing a closing `>` in your opening HTML tag. -pub fn render_sourcepos<'a>(context: &mut Context, node: &'a AstNode<'a>) -> io::Result<()> { +pub fn render_sourcepos<'a, T>(context: &mut Context, node: &'a AstNode<'a>) -> io::Result<()> { if context.options.render.sourcepos { let ast = node.data.borrow(); if ast.sourcepos.start.line > 0 { @@ -384,8 +472,8 @@ pub fn render_sourcepos<'a>(context: &mut Context, node: &'a AstNode<'a>) -> io: Ok(()) } -fn render_block_quote<'a>( - context: &mut Context, +fn render_block_quote<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -401,8 +489,8 @@ fn render_block_quote<'a>( Ok(ChildRendering::HTML) } -fn render_code<'a>( - context: &mut Context, +fn render_code<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -424,8 +512,8 @@ fn render_code<'a>( Ok(ChildRendering::HTML) } -fn render_code_block<'a>( - context: &mut Context, +fn render_code_block<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -508,16 +596,16 @@ fn render_code_block<'a>( Ok(ChildRendering::HTML) } -fn render_document<'a>( - _context: &mut Context, +fn render_document<'a, T>( + _context: &mut Context, _node: &'a AstNode<'a>, _entering: bool, ) -> io::Result { Ok(ChildRendering::HTML) } -fn render_emph<'a>( - context: &mut Context, +fn render_emph<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -535,8 +623,8 @@ fn render_emph<'a>( Ok(ChildRendering::HTML) } -fn render_heading<'a>( - context: &mut Context, +fn render_heading<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -594,8 +682,8 @@ fn render_heading<'a>( Ok(ChildRendering::HTML) } -fn render_html_block<'a>( - context: &mut Context, +fn render_html_block<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -622,8 +710,8 @@ fn render_html_block<'a>( Ok(ChildRendering::HTML) } -fn render_html_inline<'a>( - context: &mut Context, +fn render_html_inline<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -649,8 +737,8 @@ fn render_html_inline<'a>( Ok(ChildRendering::HTML) } -fn render_image<'a>( - context: &mut Context, +fn render_image<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -697,8 +785,8 @@ fn render_image<'a>( Ok(ChildRendering::HTML) } -fn render_item<'a>( - context: &mut Context, +fn render_item<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -714,8 +802,8 @@ fn render_item<'a>( Ok(ChildRendering::HTML) } -fn render_line_break<'a>( - context: &mut Context, +fn render_line_break<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -731,8 +819,8 @@ fn render_line_break<'a>( Ok(ChildRendering::HTML) } -fn render_link<'a>( - context: &mut Context, +fn render_link<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -777,8 +865,8 @@ fn render_link<'a>( Ok(ChildRendering::HTML) } -fn render_list<'a>( - context: &mut Context, +fn render_list<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -819,8 +907,8 @@ fn render_list<'a>( Ok(ChildRendering::HTML) } -fn render_paragraph<'a>( - context: &mut Context, +fn render_paragraph<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -861,8 +949,8 @@ fn render_paragraph<'a>( Ok(ChildRendering::HTML) } -fn render_soft_break<'a>( - context: &mut Context, +fn render_soft_break<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -882,8 +970,8 @@ fn render_soft_break<'a>( Ok(ChildRendering::HTML) } -fn render_strong<'a>( - context: &mut Context, +fn render_strong<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -907,8 +995,8 @@ fn render_strong<'a>( Ok(ChildRendering::HTML) } -fn render_text<'a>( - context: &mut Context, +fn render_text<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -924,8 +1012,8 @@ fn render_text<'a>( Ok(ChildRendering::HTML) } -fn render_thematic_break<'a>( - context: &mut Context, +fn render_thematic_break<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -941,8 +1029,8 @@ fn render_thematic_break<'a>( // GFM -fn render_footnote_definition<'a>( - context: &mut Context, +fn render_footnote_definition<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -972,8 +1060,8 @@ fn render_footnote_definition<'a>( Ok(ChildRendering::HTML) } -fn render_footnote_reference<'a>( - context: &mut Context, +fn render_footnote_reference<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -1002,8 +1090,8 @@ fn render_footnote_reference<'a>( Ok(ChildRendering::HTML) } -fn render_strikethrough<'a>( - context: &mut Context, +fn render_strikethrough<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -1021,8 +1109,8 @@ fn render_strikethrough<'a>( Ok(ChildRendering::HTML) } -fn render_table<'a>( - context: &mut Context, +fn render_table<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -1047,8 +1135,8 @@ fn render_table<'a>( Ok(ChildRendering::HTML) } -fn render_table_cell<'a>( - context: &mut Context, +fn render_table_cell<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -1104,8 +1192,8 @@ fn render_table_cell<'a>( Ok(ChildRendering::HTML) } -fn render_table_row<'a>( - context: &mut Context, +fn render_table_row<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -1137,8 +1225,8 @@ fn render_table_row<'a>( Ok(ChildRendering::HTML) } -fn render_task_item<'a>( - context: &mut Context, +fn render_task_item<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -1171,8 +1259,8 @@ fn render_task_item<'a>( // Extensions -fn render_alert<'a>( - context: &mut Context, +fn render_alert<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -1203,8 +1291,8 @@ fn render_alert<'a>( Ok(ChildRendering::HTML) } -fn render_description_details<'a>( - context: &mut Context, +fn render_description_details<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -1219,16 +1307,16 @@ fn render_description_details<'a>( Ok(ChildRendering::HTML) } -fn render_description_item<'a>( - _context: &mut Context, +fn render_description_item<'a, T>( + _context: &mut Context, _node: &'a AstNode<'a>, _entering: bool, ) -> io::Result { Ok(ChildRendering::HTML) } -fn render_description_list<'a>( - context: &mut Context, +fn render_description_list<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -1244,8 +1332,8 @@ fn render_description_list<'a>( Ok(ChildRendering::HTML) } -fn render_description_term<'a>( - context: &mut Context, +fn render_description_term<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -1260,8 +1348,8 @@ fn render_description_term<'a>( Ok(ChildRendering::HTML) } -fn render_escaped<'a>( - context: &mut Context, +fn render_escaped<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -1281,8 +1369,8 @@ fn render_escaped<'a>( Ok(ChildRendering::HTML) } -fn render_escaped_tag<'a>( - context: &mut Context, +fn render_escaped_tag<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, _entering: bool, ) -> io::Result { @@ -1296,8 +1384,8 @@ fn render_escaped_tag<'a>( Ok(ChildRendering::HTML) } -fn render_frontmatter<'a>( - _context: &mut Context, +fn render_frontmatter<'a, T>( + _context: &mut Context, _node: &'a AstNode<'a>, _entering: bool, ) -> io::Result { @@ -1306,8 +1394,8 @@ fn render_frontmatter<'a>( /// Renders a math dollar inline, `$...$` and `$$...$$` using `` to be /// similar to other renderers. -pub fn render_math<'a>( - context: &mut Context, +pub fn render_math<'a, T>( + context: &mut Context, node: &'a AstNode<'a>, entering: bool, ) -> io::Result { @@ -1344,8 +1432,8 @@ pub fn render_math<'a>( } /// Renders a math code block, ```` ```math ```` using `
`.
-pub fn render_math_code_block<'a>(
-    context: &mut Context,
+pub fn render_math_code_block<'a, T>(
+    context: &mut Context,
     node: &'a AstNode<'a>,
     literal: &String,
 ) -> io::Result {
@@ -1380,8 +1468,8 @@ pub fn render_math_code_block<'a>(
     Ok(ChildRendering::HTML)
 }
 
-fn render_multiline_block_quote<'a>(
-    context: &mut Context,
+fn render_multiline_block_quote<'a, T>(
+    context: &mut Context,
     node: &'a AstNode<'a>,
     entering: bool,
 ) -> io::Result {
@@ -1398,8 +1486,8 @@ fn render_multiline_block_quote<'a>(
     Ok(ChildRendering::HTML)
 }
 
-fn render_raw<'a>(
-    context: &mut Context,
+fn render_raw<'a, T>(
+    context: &mut Context,
     node: &'a AstNode<'a>,
     entering: bool,
 ) -> io::Result {
@@ -1416,8 +1504,8 @@ fn render_raw<'a>(
 }
 
 #[cfg(feature = "shortcodes")]
-fn render_short_code<'a>(
-    context: &mut Context,
+fn render_short_code<'a, T>(
+    context: &mut Context,
     node: &'a AstNode<'a>,
     entering: bool,
 ) -> io::Result {
@@ -1433,8 +1521,8 @@ fn render_short_code<'a>(
     Ok(ChildRendering::HTML)
 }
 
-fn render_spoiler_text<'a>(
-    context: &mut Context,
+fn render_spoiler_text<'a, T>(
+    context: &mut Context,
     node: &'a AstNode<'a>,
     entering: bool,
 ) -> io::Result {
@@ -1452,8 +1540,8 @@ fn render_spoiler_text<'a>(
     Ok(ChildRendering::HTML)
 }
 
-fn render_subscript<'a>(
-    context: &mut Context,
+fn render_subscript<'a, T>(
+    context: &mut Context,
     node: &'a AstNode<'a>,
     entering: bool,
 ) -> io::Result {
@@ -1471,8 +1559,8 @@ fn render_subscript<'a>(
     Ok(ChildRendering::HTML)
 }
 
-fn render_superscript<'a>(
-    context: &mut Context,
+fn render_superscript<'a, T>(
+    context: &mut Context,
     node: &'a AstNode<'a>,
     entering: bool,
 ) -> io::Result {
@@ -1490,8 +1578,8 @@ fn render_superscript<'a>(
     Ok(ChildRendering::HTML)
 }
 
-fn render_underline<'a>(
-    context: &mut Context,
+fn render_underline<'a, T>(
+    context: &mut Context,
     node: &'a AstNode<'a>,
     entering: bool,
 ) -> io::Result {
@@ -1509,8 +1597,8 @@ fn render_underline<'a>(
     Ok(ChildRendering::HTML)
 }
 
-fn render_wiki_link<'a>(
-    context: &mut Context,
+fn render_wiki_link<'a, T>(
+    context: &mut Context,
     node: &'a AstNode<'a>,
     entering: bool,
 ) -> io::Result {
@@ -1561,7 +1649,10 @@ pub fn collect_text<'a>(node: &'a AstNode<'a>, output: &mut Vec) {
     }
 }
 
-fn put_footnote_backref(context: &mut Context, nfd: &NodeFootnoteDefinition) -> io::Result {
+fn put_footnote_backref(
+    context: &mut Context,
+    nfd: &NodeFootnoteDefinition,
+) -> io::Result {
     if context.written_footnote_ix >= context.footnote_ix {
         return Ok(false);
     }
diff --git a/src/html/context.rs b/src/html/context.rs
index 33065a97..990f61ad 100644
--- a/src/html/context.rs
+++ b/src/html/context.rs
@@ -7,7 +7,7 @@ use std::io::{self, Write};
 /// Context struct given to formatter functions as taken by
 /// [`html::format_document_with_formatter`].  Output can be appended to through
 /// this struct's [`Write`] interface.
-pub struct Context<'o, 'c> {
+pub struct Context<'o, 'c, T = ()> {
     output: &'o mut dyn Write,
     last_was_lf: Cell,
 
@@ -17,16 +17,19 @@ pub struct Context<'o, 'c> {
     pub plugins: &'o Plugins<'o>,
     /// [`Anchorizer`] instance used in this render.
     pub anchorizer: Anchorizer,
+    /// Any user data used by the [`Context`].
+    pub user: T,
 
     pub(super) footnote_ix: u32,
     pub(super) written_footnote_ix: u32,
 }
 
-impl<'o, 'c> Context<'o, 'c> {
+impl<'o, 'c, T> Context<'o, 'c, T> {
     pub(super) fn new(
         output: &'o mut dyn Write,
         options: &'o Options<'c>,
         plugins: &'o Plugins<'o>,
+        user: T,
     ) -> Self {
         Context {
             output,
@@ -34,16 +37,17 @@ impl<'o, 'c> Context<'o, 'c> {
             options,
             plugins,
             anchorizer: Anchorizer::new(),
+            user,
             footnote_ix: 0,
             written_footnote_ix: 0,
         }
     }
 
-    pub(super) fn finish(&mut self) -> io::Result<()> {
+    pub(super) fn finish(mut self) -> io::Result {
         if self.footnote_ix > 0 {
             self.write_all(b"\n\n")?;
         }
-        Ok(())
+        Ok(self.user)
     }
 
     /// If the last byte written to ts [`Write`] interface was **not** a U+000A
@@ -68,7 +72,7 @@ impl<'o, 'c> Context<'o, 'c> {
     }
 }
 
-impl<'o, 'c> Write for Context<'o, 'c> {
+impl<'o, 'c, T> Write for Context<'o, 'c, T> {
     fn flush(&mut self) -> io::Result<()> {
         self.output.flush()
     }
@@ -82,7 +86,7 @@ impl<'o, 'c> Write for Context<'o, 'c> {
     }
 }
 
-impl<'o, 'c> std::fmt::Debug for Context<'o, 'c> {
+impl<'o, 'c, T> std::fmt::Debug for Context<'o, 'c, T> {
     fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
         formatter.write_str("")
     }