1#![doc(html_logo_url = "https://raw.githubusercontent.com/clap-rs/clap/master/assets/clap.png")]
2#![doc = include_str!("../README.md")]
3#![warn(missing_docs, trivial_casts, unused_allocation, trivial_numeric_casts)]
4#![forbid(unsafe_code)]
5#![deny(missing_docs)]
6
7mod render;
8
9pub use roff;
10
11use render::subcommand_heading;
12use roff::{roman, Roff};
13use std::io::Write;
14
15/// A manpage writer
16pub struct Man {
17    cmd: clap::Command,
18    title: String,
19    section: String,
20    date: String,
21    source: String,
22    manual: String,
23}
24
25/// Build a [`Man`]
26impl Man {
27    /// Create a new manual page.
28    pub fn new(mut cmd: clap::Command) -> Self {
29        cmd.build();
30        let title = cmd.get_name().to_owned();
31        let section = "1".to_owned();
32        let date = "".to_owned();
33        let source = format!(
34            "{} {}",
35            cmd.get_name(),
36            cmd.get_version().unwrap_or_default()
37        );
38        let manual = "".to_owned();
39        Self {
40            cmd,
41            title,
42            section,
43            date,
44            source,
45            manual,
46        }
47    }
48
49    /// Override the default man page title, written in all caps
50    pub fn title(mut self, title: impl Into<String>) -> Self {
51        self.title = title.into();
52        self
53    }
54
55    /// Override the default section this man page is placed in
56    ///
57    /// Common values:
58    ///
59    /// - `"1"`: User Commands
60    /// - `"2"`: System Calls
61    /// - `"3"`: C Library Functions
62    /// - `"4"`: Devices and Special Files
63    /// - `"5"`: File Formats and Conventions
64    /// - `"6"`: Games et. al.
65    /// - `"7"`: Miscellanea
66    /// - `"8"`: System Administration tools and Daemons
67    pub fn section(mut self, section: impl Into<String>) -> Self {
68        self.section = section.into();
69        self
70    }
71
72    /// Override the default date for the last non-trivial change to this man page
73    ///
74    /// Dates should be written in the form `YYYY-MM-DD`.
75    pub fn date(mut self, date: impl Into<String>) -> Self {
76        self.date = date.into();
77        self
78    }
79
80    /// Override the default source your command
81    ///
82    /// For those few man-pages pages in Sections 1 and 8, probably you just want to write GNU.
83    pub fn source(mut self, source: impl Into<String>) -> Self {
84        self.source = source.into();
85        self
86    }
87
88    /// Override the default manual this page is a member of
89    pub fn manual(mut self, manual: impl Into<String>) -> Self {
90        self.manual = manual.into();
91        self
92    }
93}
94
95/// Generate ROFF output
96impl Man {
97    /// Render a full manual page into the writer.
98    ///
99    /// If customization is needed, you can call the individual sections you want and mix them into
100    /// your own ROFF content.
101    pub fn render(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
102        let mut roff = Roff::default();
103        self._render_title(&mut roff);
104        self._render_name_section(&mut roff);
105        self._render_synopsis_section(&mut roff);
106        self._render_description_section(&mut roff);
107
108        if app_has_arguments(&self.cmd) {
109            self._render_options_section(&mut roff);
110        }
111
112        if app_has_subcommands(&self.cmd) {
113            self._render_subcommands_section(&mut roff);
114        }
115
116        if self.cmd.get_after_long_help().is_some() || self.cmd.get_after_help().is_some() {
117            self._render_extra_section(&mut roff);
118        }
119
120        if app_has_version(&self.cmd) {
121            self._render_version_section(&mut roff);
122        }
123
124        if self.cmd.get_author().is_some() {
125            self._render_authors_section(&mut roff);
126        }
127
128        roff.to_writer(w)
129    }
130
131    /// Render the title into the writer.
132    pub fn render_title(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
133        let mut roff = Roff::default();
134        self._render_title(&mut roff);
135        roff.to_writer(w)
136    }
137
138    fn _render_title(&self, roff: &mut Roff) {
139        roff.control("TH", self.title_args());
140    }
141
142    // Turn metadata into arguments for a .TH macro.
143    fn title_args(&self) -> Vec<&str> {
144        vec![
145            &self.title,
146            &self.section,
147            &self.date,
148            &self.source,
149            &self.manual,
150        ]
151    }
152
153    /// Render the NAME section into the writer.
154    pub fn render_name_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
155        let mut roff = Roff::default();
156        self._render_name_section(&mut roff);
157        roff.to_writer(w)
158    }
159
160    fn _render_name_section(&self, roff: &mut Roff) {
161        roff.control("SH", ["NAME"]);
162        render::about(roff, &self.cmd);
163    }
164
165    /// Render the SYNOPSIS section into the writer.
166    pub fn render_synopsis_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
167        let mut roff = Roff::default();
168        self._render_synopsis_section(&mut roff);
169        roff.to_writer(w)
170    }
171
172    fn _render_synopsis_section(&self, roff: &mut Roff) {
173        roff.control("SH", ["SYNOPSIS"]);
174        render::synopsis(roff, &self.cmd);
175    }
176
177    /// Render the DESCRIPTION section into the writer.
178    pub fn render_description_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
179        let mut roff = Roff::default();
180        self._render_description_section(&mut roff);
181        roff.to_writer(w)
182    }
183
184    fn _render_description_section(&self, roff: &mut Roff) {
185        roff.control("SH", ["DESCRIPTION"]);
186        render::description(roff, &self.cmd);
187    }
188
189    /// Render the OPTIONS section into the writer.
190    pub fn render_options_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
191        let mut roff = Roff::default();
192        self._render_options_section(&mut roff);
193        roff.to_writer(w)
194    }
195
196    fn _render_options_section(&self, roff: &mut Roff) {
197        roff.control("SH", ["OPTIONS"]);
198        render::options(roff, &self.cmd);
199    }
200
201    /// Render the SUBCOMMANDS section into the writer.
202    pub fn render_subcommands_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
203        let mut roff = Roff::default();
204        self._render_subcommands_section(&mut roff);
205        roff.to_writer(w)
206    }
207
208    fn _render_subcommands_section(&self, roff: &mut Roff) {
209        let heading = subcommand_heading(&self.cmd);
210        roff.control("SH", [heading]);
211        render::subcommands(roff, &self.cmd, &self.section);
212    }
213
214    /// Render the EXTRA section into the writer.
215    pub fn render_extra_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
216        let mut roff = Roff::default();
217        self._render_extra_section(&mut roff);
218        roff.to_writer(w)
219    }
220
221    fn _render_extra_section(&self, roff: &mut Roff) {
222        roff.control("SH", ["EXTRA"]);
223        render::after_help(roff, &self.cmd);
224    }
225
226    /// Render the VERSION section into the writer.
227    pub fn render_version_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
228        let mut roff = Roff::default();
229        self._render_version_section(&mut roff);
230        roff.to_writer(w)
231    }
232
233    fn _render_version_section(&self, roff: &mut Roff) {
234        let version = roman(render::version(&self.cmd));
235        roff.control("SH", ["VERSION"]);
236        roff.text([version]);
237    }
238
239    /// Render the AUTHORS section into the writer.
240    pub fn render_authors_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
241        let mut roff = Roff::default();
242        self._render_authors_section(&mut roff);
243        roff.to_writer(w)
244    }
245
246    fn _render_authors_section(&self, roff: &mut Roff) {
247        let author = roman(self.cmd.get_author().unwrap_or_default());
248        roff.control("SH", ["AUTHORS"]);
249        roff.text([author]);
250    }
251}
252
253// Does the application have a version?
254fn app_has_version(cmd: &clap::Command) -> bool {
255    cmd.get_version()
256        .or_else(|| cmd.get_long_version())
257        .is_some()
258}
259
260// Does the application have any command line arguments?
261fn app_has_arguments(cmd: &clap::Command) -> bool {
262    cmd.get_arguments().any(|i| !i.is_hide_set())
263}
264
265// Does the application have any subcommands?
266fn app_has_subcommands(cmd: &clap::Command) -> bool {
267    cmd.get_subcommands().any(|i| !i.is_hide_set())
268}
269