use super::*;
use roff::{bold, italic, list, Roff, Troffable};
#[derive(Debug, Clone)]
pub struct Manual {
name: String,
about: Option<String>,
description: Option<String>,
authors: Vec<Author>,
flags: Vec<Flag>,
options: Vec<Opt>,
environment: Vec<Env>,
arguments: Vec<Arg>,
custom_sections: Vec<Section>,
examples: Vec<Example>,
}
impl Manual {
pub fn new(name: &str) -> Self {
Self {
name: name.into(),
about: None,
description: None,
authors: vec![],
flags: vec![],
options: vec![],
arguments: vec![],
environment: vec![],
custom_sections: vec![],
examples: vec![],
}
}
pub fn about<S: Into<String>>(mut self, about: S) -> Self {
self.about = Some(about.into());
self
}
pub fn description<S: Into<String>>(mut self, description: S) -> Self {
self.description = Some(description.into());
self
}
pub fn author(mut self, author: Author) -> Self {
self.authors.push(author);
self
}
pub fn env(mut self, env: Env) -> Self {
self.environment.push(env);
self
}
pub fn flag(mut self, flag: Flag) -> Self {
self.flags.push(flag);
self
}
pub fn option(mut self, opt: Opt) -> Self {
self.options.push(opt);
self
}
pub fn custom(mut self, custom_section: Section) -> Self {
self.custom_sections.push(custom_section);
self
}
pub fn example(mut self, example: Example) -> Self {
self.examples.push(example);
self
}
pub fn arg(mut self, arg: Arg) -> Self {
self.arguments.push(arg);
self
}
pub fn render(self) -> String {
let man_num = 1;
let mut page = Roff::new(&self.name, man_num);
page = about(page, &self.name, &self.about);
page = synopsis(
page,
&self.name,
&self.flags,
&self.options,
&self.arguments,
);
page = description(page, &self.description);
page = flags(page, &self.flags);
page = options(page, &self.options);
page = env(page, &self.environment);
for section in self.custom_sections.into_iter() {
page = custom(page, section);
}
page = exit_status(page);
page = examples(page, &self.examples);
page = authors(page, &self.authors);
page.render()
}
}
fn about(page: Roff, name: &str, desc: &Option<String>) -> Roff {
let desc = match desc {
Some(ref desc) => format!("{} - {}", name, desc),
None => name.to_owned(),
};
page.section("NAME", &[desc])
}
fn description(page: Roff, desc: &Option<String>) -> Roff {
if let Some(desc) = desc {
page.section("DESCRIPTION", &[desc.to_owned()])
} else {
page
}
}
fn synopsis(
page: Roff,
name: &str,
flags: &[Flag],
options: &[Opt],
args: &[Arg],
) -> Roff {
let flags = match flags.len() {
0 => "".into(),
_ => " [FLAGS]".into(),
};
let options = match options.len() {
0 => "".into(),
_ => " [OPTIONS]".into(),
};
let mut msg = vec![];
msg.push(bold(name));
msg.push(flags);
msg.push(options);
for arg in args {
msg.push(format!(" {}", arg.name));
}
page.section("SYNOPSIS", &msg)
}
fn authors(page: Roff, authors: &[Author]) -> Roff {
let title = match authors.len() {
0 => return page,
1 => "AUTHOR",
_ => "AUTHORS",
};
let last = authors.len() - 1;
let mut auth_values = vec![];
auth_values.push(init_list());
for (index, author) in authors.iter().enumerate() {
auth_values.push(author.name.to_owned());
if let Some(ref email) = author.email {
auth_values.push(format!(" <{}>", email))
};
if index != last {
auth_values.push(String::from("\n"));
}
}
page.section(title, &auth_values)
}
fn flags(page: Roff, flags: &[Flag]) -> Roff {
if flags.is_empty() {
return page;
}
let last = flags.len() - 1;
let mut arr: Vec<String> = vec![];
for (index, flag) in flags.iter().enumerate() {
let mut args: Vec<String> = vec![];
if let Some(ref short) = flag.short {
args.push(bold(&short));
}
if let Some(ref long) = flag.long {
if !args.is_empty() {
args.push(", ".to_string());
}
args.push(bold(&long));
}
let desc = match flag.help {
Some(ref desc) => desc.to_string(),
None => "".to_string(),
};
arr.push(list(&args, &[desc]));
if index != last {
arr.push(String::from("\n\n"));
}
}
page.section("FLAGS", &arr)
}
fn options(page: Roff, options: &[Opt]) -> Roff {
if options.is_empty() {
return page;
}
let last = options.len() - 1;
let mut arr: Vec<String> = vec![];
for (index, opt) in options.iter().enumerate() {
let mut args: Vec<String> = vec![];
if let Some(ref short) = opt.short {
args.push(bold(&short));
}
if let Some(ref long) = opt.long {
if !args.is_empty() {
args.push(", ".to_string());
}
args.push(bold(&long));
}
args.push("=".into());
args.push(italic(&opt.name));
if let Some(ref default) = opt.default {
if !args.is_empty() {
args.push(" ".to_string());
}
args.push("[".into());
args.push("default:".into());
args.push(" ".into());
args.push(italic(&default));
args.push("]".into());
}
let desc = match opt.help {
Some(ref desc) => desc.to_string(),
None => "".to_string(),
};
arr.push(list(&args, &[desc]));
if index != last {
arr.push(String::from("\n\n"));
}
}
page.section("OPTIONS", &arr)
}
fn env(page: Roff, environment: &[Env]) -> Roff {
if environment.is_empty() {
return page;
}
let last = environment.len() - 1;
let mut arr: Vec<String> = vec![];
for (index, env) in environment.iter().enumerate() {
let mut args: Vec<String> = vec![];
args.push(bold(&env.name));
if let Some(ref default) = env.default {
if !args.is_empty() {
args.push(" ".to_string());
}
args.push("[".into());
args.push("default:".into());
args.push(" ".into());
args.push(italic(&default));
args.push("]".into());
}
let desc = match env.help {
Some(ref desc) => desc.to_string(),
None => "".to_string(),
};
arr.push(list(&args, &[desc]));
if index != last {
arr.push(String::from("\n\n"));
}
}
page.section("ENVIRONMENT", &arr)
}
fn exit_status(page: Roff) -> Roff {
page.section(
"EXIT STATUS",
&[
list(&[bold("0")], &["Successful program execution.\n\n"]),
list(&[bold("1")], &["Unsuccessful program execution.\n\n"]),
list(&[bold("101")], &["The program panicked."]),
],
)
}
fn custom(page: Roff, custom_section: Section) -> Roff {
let mut paragraphs: Vec<String> = vec![];
for paragraph in custom_section.paragraphs.into_iter() {
paragraphs.push(paragraph);
paragraphs.push("\n\n".into())
}
page.section(&custom_section.name, ¶graphs)
}
fn examples(page: Roff, examples: &[Example]) -> Roff {
if examples.is_empty() {
return page;
};
let mut arr = vec![];
for example in examples {
let text = example.text.unwrap_or("");
let mut full_command = String::from(example.prompt);
if let Some(command) = example.command {
full_command.push_str(" ");
full_command.push_str(command);
};
let output = match example.output {
Some(output) => {
let mut full_output = String::from("\n.br\n");
full_output.push_str(output);
full_output.push_str("\n");
full_output
}
None => String::from("\n"),
};
let example = list(&[text], &[bold(full_command.as_str()), output]);
arr.push(example);
}
page.section("examples", &arr)
}
fn init_list() -> String {
String::from(".P\n.RS 2\n.nf\n")
}