use crate::{
buffer::{extract_sections, manpage::escape::Apostrophes, Block, HelpItems, Style, Token},
Doc, OptionParser, Parser,
};
mod escape;
mod monoid;
mod roff;
use roff::{Font, Roff};
#[derive(Debug, Clone, Copy)]
pub enum Section<'a> {
General,
SystemCall,
LibraryFunction,
SpecialFile,
FileFormat,
Game,
Misc,
Sysadmin,
Custom(&'a str),
}
impl Section<'_> {
fn as_str(&self) -> &str {
match self {
Section::General => "1",
Section::SystemCall => "2",
Section::LibraryFunction => "3",
Section::SpecialFile => "4",
Section::FileFormat => "5",
Section::Game => "6",
Section::Misc => "7",
Section::Sysadmin => "8",
Section::Custom(s) => s,
}
}
}
impl<T> OptionParser<T> {
pub fn render_manpage(
&self,
app: impl AsRef<str>,
section: Section,
last_update_date: Option<&str>,
vendor: Option<&str>,
application_title: Option<&str>,
) -> String {
let mut sections = Vec::new();
let root = self.inner.meta();
let mut path = vec![app.as_ref().to_string()];
extract_sections(&root, &self.info, &mut path, &mut sections);
let mut buf = Doc::default();
if sections.len() > 1 {
buf.token(Token::BlockStart(Block::Block));
buf.token(Token::BlockStart(Block::Header));
buf.text("SYNOPSIS");
buf.token(Token::BlockEnd(Block::Header));
buf.token(Token::BlockEnd(Block::Block));
buf.token(Token::BlockStart(Block::Meta));
for section in §ions {
for p in §ion.path {
buf.literal(p);
buf.text(" ");
}
buf.write_meta(section.meta, true);
buf.text("\n");
}
buf.token(Token::BlockEnd(Block::Meta));
}
for section in §ions {
if sections.len() > 1 {
buf.token(Token::BlockStart(Block::Header));
buf.write_path(§ion.path);
buf.token(Token::BlockEnd(Block::Header));
}
if let Some(descr) = §ion.info.descr {
buf.token(Token::BlockStart(Block::Header));
buf.text("NAME");
buf.token(Token::BlockEnd(Block::Header));
buf.text(app.as_ref());
buf.text(" - ");
buf.doc(descr);
}
buf.token(Token::BlockStart(Block::Header));
buf.text("SYNOPSIS");
buf.token(Token::BlockEnd(Block::Header));
buf.write_path(§ion.path);
buf.write_meta(section.meta, true);
if let Some(t) = §ion.info.header {
buf.token(Token::BlockStart(Block::Block));
buf.doc(t);
buf.token(Token::BlockEnd(Block::Block));
}
let mut items = HelpItems::default();
items.append_meta(section.meta);
let help_meta = section.info.meta();
items.append_meta(&help_meta);
buf.write_help_item_groups(items, false);
if let Some(footer) = §ion.info.footer {
buf.token(Token::BlockStart(Block::Block));
buf.doc(footer);
buf.token(Token::BlockEnd(Block::Block));
}
}
let mut manpage = Roff::new();
manpage.control(
"TH",
[
app.as_ref(),
section.as_str(),
last_update_date.unwrap_or("-"),
vendor.unwrap_or("-"),
application_title.unwrap_or(""),
]
.iter()
.copied(),
);
buf.render_roff(manpage)
}
}
impl From<Style> for Font {
fn from(value: Style) -> Self {
match value {
Style::Text => Font::Roman,
Style::Emphasis | Style::Literal => Font::Bold,
Style::Metavar | Style::Invalid => Font::Italic,
}
}
}
impl Doc {
pub(crate) fn render_roff(&self, mut roff: Roff) -> String {
let mut capture = (String::new(), false);
let mut byte_pos = 0;
for token in self.tokens.iter().copied() {
match token {
Token::Text { bytes, style } => {
let input = &self.payload[byte_pos..byte_pos + bytes];
byte_pos += bytes;
if capture.1 {
capture.0.push_str(input);
continue;
} else {
if style == Style::Emphasis {
roff.control0("SS");
}
roff.text(&[(Font::from(style), input)]);
}
}
Token::BlockStart(block) => {
match block {
Block::Header | Block::Section2 | Block::Section3 => {
capture.1 = true;
}
Block::ItemTerm => {
roff.control0("TP").strip_newlines(true);
}
Block::ItemBody | Block::DefinitionList | Block::InlineBlock => {}
Block::Block => {
roff.control0("PP");
}
Block::Meta => {
roff.control0("nf");
}
Block::TermRef => todo!(),
}
}
Token::BlockEnd(block) => {
match block {
Block::Header => {
capture.1 = false;
roff.control("SH", [capture.0.to_uppercase()]);
capture.0.clear();
}
Block::Section2 | Block::Section3 => {
capture.1 = false;
roff.control("SS", [capture.0.to_uppercase()]);
capture.0.clear();
}
Block::ItemTerm => {
roff.roff_linebreak().strip_newlines(false);
}
Block::ItemBody => {
roff.control0("PP").strip_newlines(false);
}
Block::DefinitionList | Block::Block | Block::InlineBlock => {}
Block::Meta => {
roff.control0("fi");
}
Block::TermRef => todo!(),
}
}
}
}
roff.render(Apostrophes::Handle)
}
}