use crate::DocumentationWriter;
use std::borrow::Cow;
use std::io::Write;
use std::{io, mem};
macro_rules! setter {
($(#[$doc:meta])* $var:ident, $with:ident, $set:ident, $typ:ty) => {
$(#[$doc])*
#[doc=concat!("[", stringify!($set), "](Self::", stringify!($set), ")")]
#[inline]
pub fn $with(mut self, $var: $typ) -> Self {
self.$var = $var;
self
}
$(#[$doc])*
#[doc=concat!("[", stringify!($with), "](Self::", stringify!($with), ")")]
#[inline]
pub fn set_section(&mut self, $var: $typ) {
self.$var = $var;
}
};
($var:ident, $with:ident, $set:ident) => {
setter!($var, $with, $set, Cow<'static, str>);
};
}
pub struct ManWriter<W: Write> {
writer: W,
section: &'static str,
title: Cow<'static, str>,
subtitle: Cow<'static, str>,
license: Cow<'static, str>,
wrote_header: bool,
see_also: Vec<(String, String)>,
}
impl<W: Write> ManWriter<W> {
pub fn new(w: W) -> Self {
Self {
writer: w,
section: "",
title: "".into(),
subtitle: "".into(),
license: "".into(),
wrote_header: false,
see_also: vec![],
}
}
setter!(
section,
with_section,
set_section,
&'static str
);
}
impl<W: Write> DocumentationWriter for ManWriter<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".SH \"SYNOPSIS\"\n")?;
let mut args_iter = usage.splitn(2, ' ');
if let Some(command) = args_iter.next() {
self.emphasis(command)?;
if let Some(args) = args_iter.next() {
self.write_escaped_para(args)?;
self.write_raw(b"\n")?;
}
}
Ok(())
}
fn start_description(&mut self) -> Result<(), Self::Error> {
self.write_header_once()?;
self.write_raw(b".SH \"DESCRIPTION\"\n")
}
fn start_section(&mut self, name: &str) -> Result<(), Self::Error> {
self.write_header_once()?;
self.write_raw(b".SH ")?;
self.write_escaped_str(name.to_uppercase())?;
self.write_raw(b"\n")
}
fn plain(&mut self, s: &str) -> Result<(), Self::Error> {
self.write_escaped_para(s.trim())?;
self.write_raw(b"\n")
}
fn paragraph_break(&mut self) -> Result<(), Self::Error> {
self.write_raw(b".P\n")
}
fn emphasis(&mut self, text: &str) -> Result<(), Self::Error> {
self.write_raw(b".I ")?;
self.write_escaped_para(text)?;
self.write_raw(b"\n")
}
fn strong(&mut self, text: &str) -> Result<(), Self::Error> {
self.write_raw(b".B ")?;
self.write_escaped_para(text)?;
self.write_raw(b"\n")
}
fn link(&mut self, text: &str, to: &str) -> Result<(), Self::Error> {
self.see_also.push((text.to_owned(), to.to_owned()));
if let Some(man) = to.strip_prefix("man:") {
if text != "" {
self.plain(text)?;
self.write_raw(b" (")?;
}
self.write_raw(b".BR ")?;
self.write_escaped_para(man.replace("(", " ("))?;
if text != "" {
self.write_raw(b" )")?;
}
self.write_raw(b"\n")?;
} else if let Some(mail) = to.strip_prefix("mailto:") {
self.write_raw(b".MT ")?;
self.write_escaped_para(mail)?;
self.write_raw(b"\n")?;
if text != "" {
self.write_escaped_para(text)?;
} else {
self.write_escaped_para(mail)?;
}
self.write_raw(b"\n.ME\n")?;
} else {
self.write_raw(b".UR ")?;
self.write_escaped_para(to)?;
self.write_raw(b"\n")?;
if text != "" {
self.write_escaped_para(text)?;
} else {
self.write_escaped_para(to)?;
}
self.write_raw(b"\n.UE\n")?;
}
Ok(())
}
fn start_options(&mut self) -> Result<(), Self::Error> {
self.write_header_once()?;
self.write_raw(b".SH \"OPTIONS\"\n")
}
fn option(&mut self, name: &str, default: &str) -> Result<(), Self::Error> {
self.write_raw(br#".IP "\fI"#)?;
self.write_escaped_para(name)?;
if default != "" {
self.write_raw(b"=")?;
self.write_escaped_para(default)?;
}
self.write_raw(b"\\fP\" 10\n")
}
fn start_environment(&mut self) -> Result<(), Self::Error> {
self.write_header_once()?;
self.write_raw(b".SH \"ENVIRONMENT\"\n")
}
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".SH ")?;
self.write_escaped_str(name)?;
self.write_raw(b"\n")
}
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.see_also.is_empty() {
self.write_raw(b".SH \"SEE ALSO\"\n")?;
let see_also = mem::take(&mut self.see_also);
for also in see_also {
self.link(&also.0, &also.1)?;
}
}
if self.license != "" {
self.write_raw(b"\n.SH \"COPYRIGHT\"")?;
let license = mem::take(&mut self.license);
let mut lines = license.split('\n').peekable();
while let Some(line) = lines.next() {
self.write_raw(b"\n")?;
self.write_escaped_para(line)?;
match lines.peek() {
Some(&"") => {
self.write_raw(b"\n.P")?;
lines.next();
}
Some(_) => self.write_raw(b"\n.br")?,
None => {
lines.next();
}
}
}
}
Ok(())
}
}
impl<W: Write> ManWriter<W> {
fn write_header_once(&mut self) -> Result<(), io::Error> {
if self.wrote_header {
return Ok(());
}
self.wrote_header = true;
if self.title != "" {
let title = mem::take(&mut self.title);
self.write_raw(b".TH ")?;
self.write_escaped_str(title.to_uppercase())?;
if self.section != "" {
self.write_raw(b" ")?;
self.write_escaped_str(self.section)?;
} else {
self.write_raw(b" \"1\"")?;
}
self.write_raw(b"\n")?;
self.write_raw(b".SH \"NAME\"\n")?;
self.write_escaped_para(title)?;
if self.subtitle != "" {
self.write_raw(b"\n\\(em ")?;
let subtitle = mem::take(&mut self.subtitle);
self.write_escaped_para(subtitle)?;
}
self.write_raw(b"\n")?;
}
Ok(())
}
fn write_raw(&mut self, s: &[u8]) -> Result<(), io::Error> {
self.writer.write_all(s)
}
fn write_escaped_para<'a>(&mut self, s: impl Into<Cow<'a, str>>) -> Result<(), io::Error> {
let s = s.into();
if s.starts_with(&['.', '\''][..]) {
self.write_raw(b"\\&")?;
}
self.writer
.write_all(s.replace('"', "\\(dq").replace('\\', "\\e").as_bytes())
}
fn write_escaped_str<'a>(&mut self, s: impl Into<Cow<'a, str>>) -> Result<(), io::Error> {
self.write_raw(b"\"")?;
self.write_escaped_para(s.into())?;
self.write_raw(b"\"")
}
}