#![doc(html_logo_url = "https://raw.githubusercontent.com/clap-rs/clap/master/assets/clap.png")]
#![doc = include_str!("../README.md")]
#![warn(missing_docs, trivial_casts, unused_allocation, trivial_numeric_casts)]
#![forbid(unsafe_code)]
#![deny(missing_docs)]
mod render;
pub use roff;
use render::subcommand_heading;
use roff::{roman, Roff};
use std::io::Write;
/// A manpage writer
pub struct Man {
cmd: clap::Command,
title: String,
section: String,
date: String,
source: String,
manual: String,
}
/// Build a [`Man`]
impl Man {
/// Create a new manual page.
pub fn new(mut cmd: clap::Command) -> Self {
cmd.build();
let title = cmd.get_name().to_owned();
let section = "1".to_owned();
let date = "".to_owned();
let source = format!(
"{} {}",
cmd.get_name(),
cmd.get_version().unwrap_or_default()
);
let manual = "".to_owned();
Self {
cmd,
title,
section,
date,
source,
manual,
}
}
/// Override the default man page title, written in all caps
pub fn title(mut self, title: impl Into) -> Self {
self.title = title.into();
self
}
/// Override the default section this man page is placed in
///
/// Common values:
///
/// - `"1"`: User Commands
/// - `"2"`: System Calls
/// - `"3"`: C Library Functions
/// - `"4"`: Devices and Special Files
/// - `"5"`: File Formats and Conventions
/// - `"6"`: Games et. al.
/// - `"7"`: Miscellanea
/// - `"8"`: System Administration tools and Daemons
pub fn section(mut self, section: impl Into) -> Self {
self.section = section.into();
self
}
/// Override the default date for the last non-trivial change to this man page
///
/// Dates should be written in the form `YYYY-MM-DD`.
pub fn date(mut self, date: impl Into) -> Self {
self.date = date.into();
self
}
/// Override the default source your command
///
/// For those few man-pages pages in Sections 1 and 8, probably you just want to write GNU.
pub fn source(mut self, source: impl Into) -> Self {
self.source = source.into();
self
}
/// Override the default manual this page is a member of
pub fn manual(mut self, manual: impl Into) -> Self {
self.manual = manual.into();
self
}
}
/// Generate ROFF output
impl Man {
/// Render a full manual page into the writer.
///
/// If customization is needed, you can call the individual sections you want and mix them into
/// your own ROFF content.
pub fn render(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
let mut roff = Roff::default();
self._render_title(&mut roff);
self._render_name_section(&mut roff);
self._render_synopsis_section(&mut roff);
self._render_description_section(&mut roff);
if app_has_arguments(&self.cmd) {
self._render_options_section(&mut roff);
}
if app_has_subcommands(&self.cmd) {
self._render_subcommands_section(&mut roff);
}
if self.cmd.get_after_long_help().is_some() || self.cmd.get_after_help().is_some() {
self._render_extra_section(&mut roff);
}
if app_has_version(&self.cmd) {
self._render_version_section(&mut roff);
}
if self.cmd.get_author().is_some() {
self._render_authors_section(&mut roff);
}
roff.to_writer(w)
}
/// Render the title into the writer.
pub fn render_title(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
let mut roff = Roff::default();
self._render_title(&mut roff);
roff.to_writer(w)
}
fn _render_title(&self, roff: &mut Roff) {
roff.control("TH", self.title_args());
}
// Turn metadata into arguments for a .TH macro.
fn title_args(&self) -> Vec<&str> {
vec![
&self.title,
&self.section,
&self.date,
&self.source,
&self.manual,
]
}
/// Render the NAME section into the writer.
pub fn render_name_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
let mut roff = Roff::default();
self._render_name_section(&mut roff);
roff.to_writer(w)
}
fn _render_name_section(&self, roff: &mut Roff) {
roff.control("SH", ["NAME"]);
render::about(roff, &self.cmd);
}
/// Render the SYNOPSIS section into the writer.
pub fn render_synopsis_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
let mut roff = Roff::default();
self._render_synopsis_section(&mut roff);
roff.to_writer(w)
}
fn _render_synopsis_section(&self, roff: &mut Roff) {
roff.control("SH", ["SYNOPSIS"]);
render::synopsis(roff, &self.cmd);
}
/// Render the DESCRIPTION section into the writer.
pub fn render_description_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
let mut roff = Roff::default();
self._render_description_section(&mut roff);
roff.to_writer(w)
}
fn _render_description_section(&self, roff: &mut Roff) {
roff.control("SH", ["DESCRIPTION"]);
render::description(roff, &self.cmd);
}
/// Render the OPTIONS section into the writer.
pub fn render_options_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
let mut roff = Roff::default();
self._render_options_section(&mut roff);
roff.to_writer(w)
}
fn _render_options_section(&self, roff: &mut Roff) {
roff.control("SH", ["OPTIONS"]);
render::options(roff, &self.cmd);
}
/// Render the SUBCOMMANDS section into the writer.
pub fn render_subcommands_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
let mut roff = Roff::default();
self._render_subcommands_section(&mut roff);
roff.to_writer(w)
}
fn _render_subcommands_section(&self, roff: &mut Roff) {
let heading = subcommand_heading(&self.cmd);
roff.control("SH", [heading]);
render::subcommands(roff, &self.cmd, &self.section);
}
/// Render the EXTRA section into the writer.
pub fn render_extra_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
let mut roff = Roff::default();
self._render_extra_section(&mut roff);
roff.to_writer(w)
}
fn _render_extra_section(&self, roff: &mut Roff) {
roff.control("SH", ["EXTRA"]);
render::after_help(roff, &self.cmd);
}
/// Render the VERSION section into the writer.
pub fn render_version_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
let mut roff = Roff::default();
self._render_version_section(&mut roff);
roff.to_writer(w)
}
fn _render_version_section(&self, roff: &mut Roff) {
let version = roman(render::version(&self.cmd));
roff.control("SH", ["VERSION"]);
roff.text([version]);
}
/// Render the AUTHORS section into the writer.
pub fn render_authors_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
let mut roff = Roff::default();
self._render_authors_section(&mut roff);
roff.to_writer(w)
}
fn _render_authors_section(&self, roff: &mut Roff) {
let author = roman(self.cmd.get_author().unwrap_or_default());
roff.control("SH", ["AUTHORS"]);
roff.text([author]);
}
}
// Does the application have a version?
fn app_has_version(cmd: &clap::Command) -> bool {
cmd.get_version()
.or_else(|| cmd.get_long_version())
.is_some()
}
// Does the application have any command line arguments?
fn app_has_arguments(cmd: &clap::Command) -> bool {
cmd.get_arguments().any(|i| !i.is_hide_set())
}
// Does the application have any subcommands?
fn app_has_subcommands(cmd: &clap::Command) -> bool {
cmd.get_subcommands().any(|i| !i.is_hide_set())
}