#[cfg(feature = "gfm")]
use crate::ast::TableAlignment;
use crate::ast::{CodeBlockType, CustomNode, HeadingType, ListItem, Node};
use crate::error::{WriteError, WriteResult};
use crate::options::WriterOptions;
use crate::writer::runtime::diagnostics::{Diagnostic, DiagnosticSink, NullSink};
use crate::writer::runtime::proxy::{BlockWriterProxy, InlineWriterProxy};
use crate::writer::runtime::visitor::{walk_node, NodeHandler};
use ecow::EcoString;
use log;
use std::fmt;
use super::format::FormatPolicy;
use super::utils::node_contains_newline;
pub struct CommonMarkWriter {
pub options: WriterOptions,
pub buffer: EcoString,
format: FormatPolicy,
diagnostics: Box<dyn DiagnosticSink + 'static>,
}
impl fmt::Debug for CommonMarkWriter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CommonMarkWriter")
.field("options", &self.options)
.field("buffer", &self.buffer)
.field("format", &self.format)
.finish()
}
}
impl CommonMarkWriter {
pub fn new() -> Self {
Self::with_options(WriterOptions::default())
}
pub fn with_options(options: WriterOptions) -> Self {
Self {
options,
buffer: EcoString::new(),
format: FormatPolicy,
diagnostics: Box::new(NullSink),
}
}
pub fn with_diagnostic_sink(mut self, sink: Box<dyn DiagnosticSink + 'static>) -> Self {
self.diagnostics = sink;
self
}
pub fn set_diagnostic_sink(&mut self, sink: Box<dyn DiagnosticSink + 'static>) {
self.diagnostics = sink;
}
pub fn diagnostic_sink(&mut self) -> &mut dyn DiagnosticSink {
self.diagnostics.as_mut()
}
pub(crate) fn is_strict_mode(&self) -> bool {
self.options.strict
}
pub fn write(&mut self, node: &Node) -> WriteResult<()> {
walk_node(self, node)
}
pub(crate) fn write_custom_node(&mut self, node: &dyn CustomNode) -> WriteResult<()> {
if node.is_block() {
let mut proxy = BlockWriterProxy::new(self);
node.write_block(&mut proxy)
} else {
let mut proxy = InlineWriterProxy::new(self);
node.write_inline(&mut proxy)
}
}
pub(crate) fn check_no_newline(&mut self, node: &Node, context: &str) -> WriteResult<()> {
if node_contains_newline(node) {
if self.is_strict_mode() {
return Err(WriteError::NewlineInInlineElement(
context.to_string().into(),
));
} else {
self.emit_warning(format!(
"Newline character found in inline element '{context}', but non-strict mode allows it (output may be affected)."
));
}
}
Ok(())
}
pub(crate) fn capture_with_buffer<F>(&mut self, f: F) -> WriteResult<EcoString>
where
F: FnOnce(&mut Self) -> WriteResult<()>,
{
let mut outer = EcoString::new();
std::mem::swap(&mut self.buffer, &mut outer);
let write_result = f(self);
let captured = std::mem::take(&mut self.buffer);
std::mem::swap(&mut self.buffer, &mut outer);
write_result?;
Ok(captured)
}
pub fn into_string(self) -> EcoString {
self.buffer
}
pub fn write_str(&mut self, s: &str) -> WriteResult<()> {
self.buffer.push_str(s);
Ok(())
}
pub fn write_char(&mut self, c: char) -> WriteResult<()> {
self.buffer.push(c);
Ok(())
}
pub(crate) fn ensure_trailing_newline(&mut self) -> WriteResult<()> {
self.format.ensure_trailing_newline(&mut self.buffer)
}
pub(crate) fn ensure_blank_line(&mut self) -> WriteResult<()> {
self.format.ensure_blank_line(&mut self.buffer)
}
pub(crate) fn prepare_block_sequence(
&mut self,
previous_was_block: bool,
next_is_block: bool,
) -> WriteResult<()> {
self.format
.prepare_block_sequence(&mut self.buffer, previous_was_block, next_is_block)
}
pub(crate) fn emit_warning<S: Into<EcoString>>(&mut self, message: S) {
let message = message.into();
self.diagnostics.emit(Diagnostic::warning(message.clone()));
log::warn!("{message}");
}
pub(crate) fn emit_info<S: Into<EcoString>>(&mut self, message: S) {
let message = message.into();
self.diagnostics.emit(Diagnostic::info(message.clone()));
log::info!("{message}");
}
}
impl NodeHandler for CommonMarkWriter {
type Error = WriteError;
fn document(&mut self, children: &[Node]) -> WriteResult<()> {
self.write_document(children)
}
fn paragraph(&mut self, content: &[Node]) -> WriteResult<()> {
self.write_paragraph(content)
}
fn text(&mut self, text: &EcoString) -> WriteResult<()> {
self.write_text_content(text)
}
fn emphasis(&mut self, content: &[Node]) -> WriteResult<()> {
self.write_emphasis(content)
}
fn strong(&mut self, content: &[Node]) -> WriteResult<()> {
self.write_strong(content)
}
fn thematic_break(&mut self) -> WriteResult<()> {
self.write_thematic_break()
}
fn heading(
&mut self,
level: u8,
content: &[Node],
heading_type: &HeadingType,
) -> WriteResult<()> {
self.write_heading(level, content, heading_type)
}
fn inline_code(&mut self, code: &EcoString) -> WriteResult<()> {
self.write_code_content(code)
}
fn code_block(
&mut self,
language: &Option<EcoString>,
content: &EcoString,
block_type: &CodeBlockType,
) -> WriteResult<()> {
self.write_code_block(language, content, block_type)
}
fn html_block(&mut self, content: &EcoString) -> WriteResult<()> {
self.write_html_block(content)
}
fn html_element(&mut self, element: &crate::ast::HtmlElement) -> WriteResult<()> {
self.write_html_element(element)
}
fn block_quote(&mut self, content: &[Node]) -> WriteResult<()> {
self.write_blockquote(content)
}
fn unordered_list(&mut self, items: &[ListItem]) -> WriteResult<()> {
self.write_unordered_list(items)
}
fn ordered_list(&mut self, start: u32, items: &[ListItem]) -> WriteResult<()> {
self.write_ordered_list(start, items)
}
#[cfg(feature = "gfm")]
fn table(
&mut self,
headers: &[Node],
alignments: &[TableAlignment],
rows: &[Vec<Node>],
) -> WriteResult<()> {
self.write_table_with_alignment(headers, alignments, rows)
}
#[cfg(not(feature = "gfm"))]
fn table(&mut self, headers: &[Node], rows: &[Vec<Node>]) -> WriteResult<()> {
self.write_table(headers, rows)
}
fn link(
&mut self,
url: &EcoString,
title: &Option<EcoString>,
content: &[Node],
) -> WriteResult<()> {
self.write_link(url, title, content)
}
fn image(
&mut self,
url: &EcoString,
title: &Option<EcoString>,
alt: &[Node],
) -> WriteResult<()> {
self.write_image(url, title, alt)
}
fn soft_break(&mut self) -> WriteResult<()> {
self.write_soft_break()
}
fn hard_break(&mut self) -> WriteResult<()> {
self.write_hard_break()
}
fn autolink(&mut self, url: &EcoString, is_email: bool) -> WriteResult<()> {
self.write_autolink(url, is_email)
}
#[cfg(feature = "gfm")]
fn extended_autolink(&mut self, url: &EcoString) -> WriteResult<()> {
self.write_extended_autolink(url)
}
fn link_reference_definition(
&mut self,
label: &EcoString,
destination: &EcoString,
title: &Option<EcoString>,
) -> WriteResult<()> {
self.write_link_reference_definition(label, destination, title)
}
fn reference_link(&mut self, label: &EcoString, content: &[Node]) -> WriteResult<()> {
self.write_reference_link(label, content)
}
#[cfg(feature = "gfm")]
fn strikethrough(&mut self, content: &[Node]) -> WriteResult<()> {
self.write_strikethrough(content)
}
fn custom(&mut self, node: &dyn CustomNode) -> WriteResult<()> {
self.write_custom_node(node)
}
fn unsupported(&mut self, node: &Node) -> WriteResult<()> {
self.emit_warning(format!(
"Unsupported node type encountered and skipped: {node:?}"
));
Ok(())
}
}
impl Default for CommonMarkWriter {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for Node {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut writer = CommonMarkWriter::new();
match writer.write(self) {
Ok(_) => write!(f, "{}", writer.into_string()),
Err(e) => write!(f, "Error writing Node: {e}"),
}
}
}