hellux/jotdown

Render from borrowed events?

kaj opened this issue · 3 comments

kaj commented

Hi! The jotdown format and this crate seems awesome, thanks!

I'd like to be able to create multiple html outputs from the same jotdown file, by finding different parts of the event stream and rendering them separately. Something like this:

let jd = jotdown::Parser::new(&read_to_string(path)?).collect::<Vec<_>>();

let part_a: &[&Event] = some_filter(&jd);
let html_a = String::new();
jotdown::html::push(part_a.iter(), &mut html_a);

let part_b: &[&Event] = other_filter(&jd);
let html_b = String::new();
jotdown::html::push(part_b.iter(), &mut html_b);

This fails because iterating a slice can only give an iterator of borrowed events, not owned events.

It could be fixed by using part_a.iter().cloned(), but Event currently don't implement Clone. But I don't really see a reason that formatting html should require ownership of the events, so I think it should be relatively easy to add formatting of borrowed events.

If there is agreement that this would be useful, I'm open for attempting to write a PR implementing it.

kaj commented

Thanks, cloning works fine for me (at least for now). I might attempt to make a make it possible to use references directly later, I think it should be possible to take an impl Iterator<item=impl AsRef<Event>> or something similar, but now that Render is a trait, that might be more complicated, at least until rust support for generics in traits have improved ...

Feel free to tag this issue for handling later, or just closing it as you see fit.

I did some experimentation on a branch, render_ref. The trait then looks like this:

pub trait Render {
    fn render_event<'s, W: fmt::Write>(&mut self, e: &Event<'s>, out: W) -> std::fmt::Result;
    fn render_epilogue<W: fmt::Write>(&mut self, out: W) -> std::fmt::Result;

    fn push<'s, I, W>(&mut self, mut events: I, mut out: W) -> fmt::Result
    where
        I: Iterator<Item = Event<'s>>,
        W: fmt::Write,
    {
        events.try_for_each(|e| self.render_event(&e, &mut out))?;
        self.render_epilogue(&mut out)
    }

    fn push_ref<'s, E, I, W>(&mut self, mut events: I, mut out: W) -> fmt::Result
    where
        E: AsRef<Event<'s>>,
        I: Iterator<Item = E>,
        W: fmt::Write,
    {
        events.try_for_each(|e| self.render_event(e.as_ref(), &mut out))?;
        self.render_epilogue(&mut out)
    }

    fn write<'s, I, W>(&mut self, events: I, out: W) -> io::Result<()>
    where
        I: Iterator<Item = Event<'s>>,
        W: io::Write,
    { .. }

    fn write_ref<'s, E, I, W>(&mut self, events: I, out: W) -> io::Result<()>
    where
        E: AsRef<Event<'s>>,
        I: Iterator<Item = E>,
        W: io::Write,
    { .. }
}

The renderer has to render each event separately, which slightly decreases its flexibility (although, a renderer could choose to implement push/push_ref manually also). There is a slight performance decrease, not sure why. The rendering is almost 10% slower (rendering is around 10% of parse time, so in the magnitude of 1%).

Any thoughts on this API?