use std::{borrow::Cow, fmt::Write, ops::Range};
use mdbook_markdown::pulldown_cmark::{Event, Options};
use pulldown_cmark_to_cmark::{Error, cmark};
use tap::Pipe;
use tracing::{debug, trace, trace_span};
use crate::error::ExpectFmt;
pub struct PatchStream<'a, S> {
source: &'a str,
stream: S,
range: Option<Range<usize>>,
patch: Option<String>,
}
impl<'a, 'b, E, S> Iterator for PatchStream<'a, S>
where
E: Iterator<Item = Event<'b>>,
S: Iterator<Item = Spanned<E>>,
{
type Item = Result<Cow<'a, str>, Error>;
fn next(&mut self) -> Option<Self::Item> {
let range = self.range.clone()?;
if let Some(patch) = self.patch.take() {
trace!("- {range:?} {:?}", &self.source[range.clone()]);
trace!("+ {range:?} {patch:?}");
return Some(Ok(Cow::Owned(patch)));
}
let Some((events, span)) = self.stream.next() else {
let range = range.end..;
trace!(" {range:?}");
trace!(" EOF");
self.range = None;
return Some(Ok(Cow::Borrowed(&self.source[range])));
};
if range.start > span.start {
debug!("span {span:?} is before already yielded span {range:?}");
return Some(Err(Error::FormatFailed(Default::default())));
}
let patch = match trace_span!("chunk", ?span)
.in_scope(|| String::new().pipe(|mut out| cmark(events, &mut out).and(Ok(out))))
{
Err(error) => return Some(Err(error)),
Ok(patch) => patch,
};
self.range = Some(span.clone());
self.patch = Some(patch);
let range = range.end..span.start;
trace!(" {range:?}");
Some(Ok(Cow::Borrowed(&self.source[range])))
}
}
impl<'a, S> PatchStream<'a, S> {
pub fn new(source: &'a str, stream: S) -> Self {
Self {
source,
stream,
range: Some(0..0),
patch: None,
}
}
}
impl<'a, S> PatchStream<'a, S>
where
Self: Iterator<Item = Result<Cow<'a, str>, Error>>,
{
pub fn into_string(self) -> Result<String, Error> {
let mut out = String::new();
for chunk in self {
write!(out, "{}", chunk?).expect_fmt();
}
Ok(out)
}
}
pub const fn default_markdown_options() -> Options {
Options::empty()
.union(Options::ENABLE_TABLES)
.union(Options::ENABLE_FOOTNOTES)
.union(Options::ENABLE_STRIKETHROUGH)
.union(Options::ENABLE_TASKLISTS)
.union(Options::ENABLE_HEADING_ATTRIBUTES)
}
pub type Spanned<T> = (T, Range<usize>);