use std::io::Write;
use docspec_core::{AssetProvider, Event, EventSink, Result, StackTrackingSink};
#[cfg(feature = "blocknote-writer")]
use docspec_blocknote_writer::BlockNoteWriter;
#[cfg(feature = "html-writer")]
use docspec_html_writer::HtmlWriter;
#[cfg(feature = "oxa-writer")]
use docspec_oxa_writer::OxaWriter;
use crate::format::OutputFormat;
pub struct AnyWriter<'a, W: Write> {
inner: StackTrackingSink<AnyWriterInner<'a, W>>,
}
enum AnyWriterInner<'a, W: Write> {
#[cfg(feature = "blocknote-writer")]
BlockNote(BlockNoteWriter<'a, W>),
#[cfg(feature = "html-writer")]
Html(HtmlWriter<W>),
#[cfg(feature = "oxa-writer")]
Oxa(OxaWriter<W>),
#[cfg(not(feature = "blocknote-writer"))]
_Phantom(std::marker::PhantomData<&'a W>),
}
impl<'a, W: Write> AnyWriter<'a, W> {
#[inline]
#[must_use]
pub fn new(format: OutputFormat, writer: W) -> Self {
#[cfg(not(any(
feature = "blocknote-writer",
feature = "oxa-writer",
feature = "html-writer"
)))]
{
drop(writer);
match format {}
}
#[cfg(any(
feature = "blocknote-writer",
feature = "oxa-writer",
feature = "html-writer"
))]
{
let inner = match format {
#[cfg(feature = "blocknote-writer")]
OutputFormat::Blocknote => AnyWriterInner::BlockNote(BlockNoteWriter::new(writer)),
#[cfg(feature = "html-writer")]
OutputFormat::Html => AnyWriterInner::Html(HtmlWriter::new(writer)),
#[cfg(feature = "oxa-writer")]
OutputFormat::Oxa => AnyWriterInner::Oxa(OxaWriter::new(writer)),
};
Self {
inner: StackTrackingSink::new(inner),
}
}
}
#[inline]
#[must_use]
pub fn with_assets(format: OutputFormat, writer: W, assets: &'a dyn AssetProvider) -> Self {
#[cfg(not(any(
feature = "blocknote-writer",
feature = "oxa-writer",
feature = "html-writer"
)))]
{
drop(writer);
let _ = assets;
match format {}
}
#[cfg(any(
feature = "blocknote-writer",
feature = "oxa-writer",
feature = "html-writer"
))]
{
let inner = match format {
#[cfg(feature = "blocknote-writer")]
OutputFormat::Blocknote => {
AnyWriterInner::BlockNote(BlockNoteWriter::with_assets(writer, assets))
}
#[cfg(feature = "html-writer")]
OutputFormat::Html => {
let _ = assets;
AnyWriterInner::Html(HtmlWriter::new(writer))
}
#[cfg(feature = "oxa-writer")]
OutputFormat::Oxa => {
let _ = assets;
AnyWriterInner::Oxa(OxaWriter::new(writer))
}
};
Self {
inner: StackTrackingSink::new(inner),
}
}
}
}
impl<W: Write> EventSink for AnyWriterInner<'_, W> {
fn finish(self) -> Result<()> {
match self {
#[cfg(feature = "blocknote-writer")]
Self::BlockNote(w) => w.finish(),
#[cfg(feature = "html-writer")]
Self::Html(w) => w.finish(),
#[cfg(feature = "oxa-writer")]
Self::Oxa(w) => w.finish(),
#[cfg(not(feature = "blocknote-writer"))]
Self::_Phantom(_) => Ok(()),
}
}
fn handle_event(&mut self, event: Event) -> Result<()> {
match self {
#[cfg(feature = "blocknote-writer")]
Self::BlockNote(w) => w.handle_event(event),
#[cfg(feature = "html-writer")]
Self::Html(w) => w.handle_event(event),
#[cfg(feature = "oxa-writer")]
Self::Oxa(w) => w.handle_event(event),
#[cfg(not(feature = "blocknote-writer"))]
Self::_Phantom(_) => {
let _ = event;
Ok(())
}
}
}
}
impl<W: Write> EventSink for AnyWriter<'_, W> {
#[inline]
fn finish(self) -> Result<()> {
self.inner.finish()
}
#[inline]
fn handle_event(&mut self, event: Event) -> Result<()> {
self.inner.handle_event(event)
}
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use std::io::Write;
use docspec_core::AssetProvider;
#[cfg(any(
feature = "blocknote-writer",
feature = "html-writer",
feature = "oxa-writer"
))]
use docspec_core::{Event, EventSink as _};
#[cfg(any(
feature = "blocknote-writer",
feature = "html-writer",
feature = "oxa-writer"
))]
use super::{AnyWriter, OutputFormat};
struct NullAssets;
impl AssetProvider for NullAssets {
fn content_type(&self, _asset_id: &str) -> Option<Cow<'_, str>> {
None
}
fn stream_to(
&self,
_asset_id: &str,
_writer: &mut dyn Write,
) -> Option<std::io::Result<u64>> {
None
}
}
#[cfg(feature = "blocknote-writer")]
#[test]
fn with_assets_constructs_writer_for_blocknote() {
let assets = NullAssets;
assert!(assets.content_type("any-id").is_none());
assert!(assets.stream_to("any-id", &mut std::io::sink()).is_none());
let mut buf = Vec::new();
let mut writer = AnyWriter::with_assets(OutputFormat::Blocknote, &mut buf, &assets);
assert!(writer
.handle_event(Event::StartDocument {
id: None,
language: None,
metadata: None,
})
.is_ok());
assert!(writer.handle_event(Event::EndDocument).is_ok());
assert!(writer.finish().is_ok());
assert!(!buf.is_empty());
}
#[cfg(feature = "oxa-writer")]
#[test]
fn with_assets_constructs_writer_for_oxa() {
let assets = NullAssets;
let mut buf = Vec::new();
let mut writer = AnyWriter::with_assets(OutputFormat::Oxa, &mut buf, &assets);
assert!(writer
.handle_event(Event::StartDocument {
id: None,
language: None,
metadata: None,
})
.is_ok());
assert!(writer.handle_event(Event::EndDocument).is_ok());
assert!(writer.finish().is_ok());
assert_eq!(buf, br#"{"type":"Document","children":[]}"#);
}
#[cfg(feature = "html-writer")]
#[test]
fn with_assets_ignores_provider_for_html_writer() {
let assets = NullAssets;
let mut buf = Vec::new();
let mut writer = AnyWriter::with_assets(OutputFormat::Html, &mut buf, &assets);
assert!(writer
.handle_event(Event::StartDocument {
id: None,
language: None,
metadata: None,
})
.is_ok());
assert!(writer.handle_event(Event::EndDocument).is_ok());
assert!(writer.finish().is_ok());
assert_eq!(buf, b"<html><body></body></html>");
}
}