use crate::DocumentationWriter;
use std::borrow::Cow;
use std::io::Write;
use std::{io, mem};
pub struct MarkdownWriter<W: Write> {
writer: W,
title: Cow<'static, str>,
subtitle: Cow<'static, str>,
license: Cow<'static, str>,
wrote_header: bool,
}
impl<W: Write> MarkdownWriter<W> {
pub fn new(w: W) -> Self {
Self {
writer: w,
title: "".into(),
subtitle: "".into(),
license: "".into(),
wrote_header: false,
}
}
}
impl<W: Write> DocumentationWriter for MarkdownWriter<W> {
type Error = io::Error;
fn set_title(&mut self, title: Cow<'static, str>) {
self.title = title;
}
fn set_subtitle(&mut self, subtitle: Cow<'static, str>) {
self.subtitle = subtitle;
}
fn set_license(&mut self, license: Cow<'static, str>) {
self.license = license;
}
fn usage(&mut self, usage: &str) -> Result<(), Self::Error> {
self.write_header_once()?;
self.write_raw(b"usage: ")?;
self.write_code_literal(usage)?;
self.write_raw(b"\n")?;
Ok(())
}
fn start_description(&mut self) -> Result<(), Self::Error> {
self.write_header_once()?;
self.write_raw(b"\n\n")?;
Ok(())
}
fn start_section(&mut self, name: &str) -> Result<(), Self::Error> {
self.write_header_once()?;
self.write_raw(b"\n\n## ")?;
self.write_escaped(name)?;
self.write_raw(b"\n")?;
Ok(())
}
fn plain(&mut self, s: &str) -> Result<(), Self::Error> {
self.write_escaped(s.trim())?;
self.write_raw(b"\n")
}
fn paragraph_break(&mut self) -> Result<(), Self::Error> {
self.write_raw(b"\n\n")?;
Ok(())
}
fn emphasis(&mut self, text: &str) -> Result<(), Self::Error> {
self.write_raw(b"*")?;
self.write_escaped(text)?;
self.write_raw(b"*\n")?;
Ok(())
}
fn strong(&mut self, text: &str) -> Result<(), Self::Error> {
self.write_raw(b"**")?;
self.write_escaped(text)?;
self.write_raw(b"**\n")?;
Ok(())
}
fn link(&mut self, text: &str, to: &str) -> Result<(), Self::Error> {
if let Some(man) = to.strip_prefix("man:") {
let paren_index = man.find('(').unwrap_or(man.len());
self.write_raw(b"**")?;
self.write_escaped(&man[..paren_index])?;
self.write_raw(b"**")?;
self.write_escaped(&man[paren_index..])?;
return Ok(());
}
if text == "" {
self.write_raw(b"<")?;
self.write_raw(
to.replace(" ", "%20")
.replace("(", "%28")
.replace(")", "%29")
.replace("<", "%3c")
.replace(">", "%3e")
.as_bytes(),
)?;
self.write_raw(b">")?;
} else {
self.write_raw(b"[")?;
self.write_escaped(text)?;
self.write_raw(b"](")?;
self.write_raw(
to.replace(" ", "%20")
.replace("(", "%28")
.replace(")", "%29")
.as_bytes(),
)?;
self.write_raw(b")")?;
}
Ok(())
}
fn start_options(&mut self) -> Result<(), Self::Error> {
self.write_header_once()?;
self.write_raw(b"\n\n## Options\n")?;
Ok(())
}
fn option(&mut self, name: &str, default: &str) -> Result<(), Self::Error> {
self.write_raw(b"- ")?;
if default == "" {
self.write_code_literal(name)?;
} else {
self.write_code_literal(format!("{}={}", name, default))?;
}
self.write_raw(b": ")?;
Ok(())
}
fn start_environment(&mut self) -> Result<(), Self::Error> {
self.write_header_once()?;
self.write_raw(b"\n\n## Environment\n")?;
Ok(())
}
fn variable(&mut self, name: &str, default: &str) -> Result<(), Self::Error> {
self.option(name, default)
}
fn start_enum(&mut self, name: &str) -> Result<(), Self::Error> {
self.write_header_once()?;
self.write_raw(b"\n\n## ")?;
self.write_escaped(name)?;
self.write_raw(b"\n")?;
Ok(())
}
fn variant(&mut self, name: &str) -> Result<(), Self::Error> {
self.option(name, "")
}
fn finish(mut self) -> Result<(), Self::Error> {
self.write_header_once()?;
if self.license != "" {
self.write_raw(b"\n\n## License\n")?;
let license = mem::take(&mut self.license);
self.write_escaped(license.replace("\n", " \n"))?;
}
Ok(())
}
}
impl<W: Write> MarkdownWriter<W> {
fn write_header_once(&mut self) -> Result<(), io::Error> {
if self.wrote_header {
return Ok(());
}
self.wrote_header = true;
if !self.title.is_empty() {
self.write_raw(b"# ")?;
let title = mem::take(&mut self.title);
self.write_escaped(title)?;
if !self.subtitle.is_empty() {
self.write_raw(b" – ")?;
let subtitle = mem::take(&mut self.subtitle);
self.write_escaped(subtitle)?;
}
}
self.write_raw(b"\n\n")?;
Ok(())
}
fn write_raw(&mut self, s: &[u8]) -> Result<(), io::Error> {
self.writer.write_all(s)
}
fn write_escaped<'a>(&mut self, s: impl Into<Cow<'a, str>>) -> Result<(), io::Error> {
self.writer.write_all(
s.into()
.replace('*', "\\*")
.replace('_', "\\_")
.replace('<', "\\<")
.replace('[', "\\[")
.replace('`', "\\`")
.replace('#', "\\#")
.replace('&', "\\&")
.replace('.', "\\.")
.as_bytes(),
)
}
fn write_code_literal<'a>(&mut self, s: impl Into<Cow<'a, str>>) -> Result<(), io::Error> {
let s = s.into();
let mut backticks = "`".to_owned();
loop {
if !s.contains(&backticks) {
self.write_raw(backticks.as_bytes())?;
self.write_raw(s.as_bytes())?;
self.write_raw(backticks.as_bytes())?;
break;
} else {
backticks.push('`');
}
}
Ok(())
}
}