dmos 0.7.0

Djot HTML renderer with advanced features
Documentation
use core::fmt;
use std::fmt::Write;

use jotdown::{Container, Event, Render};

/// Implementation of [`jotdown::Render`] that only writes a document's plain-text title.
///
/// The title is assumed to be the first top-level heading, stripped from any
/// formatting. This can be useful, for example to write the title of a document
/// to an HTML `<title>` element.
#[derive(Default)]
pub struct TitleRenderer {
    in_title: bool,
    title: String,
    checked_section: bool,
    done: bool,
}

impl<'s> Render<'s> for TitleRenderer {
    fn push_events<I, W>(&mut self, mut events: I, mut out: W) -> std::fmt::Result
    where
        I: Iterator<Item = Event<'s>>,
        W: Write,
    {
        events.try_for_each(|e| self.push_event(e, &mut out))?;
        if !self.done {
            Err(fmt::Error)
        } else {
            Ok(())
        }
    }

    fn push_event<W>(&mut self, event: Event<'_>, out: W) -> std::fmt::Result
    where
        W: std::fmt::Write,
    {
        self.render_event(event, out)
    }
}

impl TitleRenderer {
    fn render_event<W>(&mut self, e: Event<'_>, mut out: W) -> std::fmt::Result
    where
        W: std::fmt::Write,
    {
        match e {
            Event::Start(Container::Section { id: _ }, attr) => {
                if !self.checked_section {
                    self.checked_section = true;
                    if let Some(t) = attr.get_value("dmos:title") {
                        out.write_str(&t.to_string())?;
                        out.write_char('\n')?;
                        self.done = true;
                    }
                }
            }
            Event::Start(
                Container::Heading {
                    level,
                    has_section: _,
                    id: _,
                },
                _,
            ) => {
                if !self.done && level == 1 {
                    self.in_title = true;
                }
            }
            Event::End(Container::Heading {
                level: _,
                has_section: _,
                id: _,
            }) => {
                if !self.done && self.in_title {
                    out.write_str(&self.title)?;
                    out.write_char('\n')?;
                    self.done = true;
                }
            }
            Event::Str(s) => {
                if !self.done && self.in_title {
                    self.title.push_str(&s);
                }
            }
            Event::Blankline => (),
            Event::Start(Container::Document, _) => (),
            _ => {
                if !self.done && !self.in_title {
                    // there is something before the title, abort
                    return Err(fmt::Error);
                }
            }
        }
        Ok(())
    }
}