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
7 mod render;
8
9 pub use roff;
10
11 use render::subcommand_heading;
12 use roff::{roman, Roff};
13 use std::io::Write;
14
15 /// A manpage writer
16 pub 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`]
26 impl Man {
27 /// Create a new manual page.
new(mut cmd: clap::Command) -> Self28 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
title(mut self, title: impl Into<String>) -> Self50 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
section(mut self, section: impl Into<String>) -> Self67 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`.
date(mut self, date: impl Into<String>) -> Self75 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.
source(mut self, source: impl Into<String>) -> Self83 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
manual(mut self, manual: impl Into<String>) -> Self89 pub fn manual(mut self, manual: impl Into<String>) -> Self {
90 self.manual = manual.into();
91 self
92 }
93 }
94
95 /// Generate ROFF output
96 impl 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.
render(&self, w: &mut dyn Write) -> Result<(), std::io::Error>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.
render_title(&self, w: &mut dyn Write) -> Result<(), std::io::Error>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
_render_title(&self, roff: &mut Roff)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.
title_args(&self) -> Vec<&str>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.
render_name_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error>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
_render_name_section(&self, roff: &mut Roff)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.
render_synopsis_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error>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
_render_synopsis_section(&self, roff: &mut Roff)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.
render_description_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error>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
_render_description_section(&self, roff: &mut Roff)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.
render_options_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error>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
_render_options_section(&self, roff: &mut Roff)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.
render_subcommands_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error>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
_render_subcommands_section(&self, roff: &mut Roff)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.
render_extra_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error>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
_render_extra_section(&self, roff: &mut Roff)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.
render_version_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error>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
_render_version_section(&self, roff: &mut Roff)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.
render_authors_section(&self, w: &mut dyn Write) -> Result<(), std::io::Error>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
_render_authors_section(&self, roff: &mut Roff)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?
app_has_version(cmd: &clap::Command) -> bool254 fn 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?
app_has_arguments(cmd: &clap::Command) -> bool261 fn 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?
app_has_subcommands(cmd: &clap::Command) -> bool266 fn app_has_subcommands(cmd: &clap::Command) -> bool {
267 cmd.get_subcommands().any(|i| !i.is_hide_set())
268 }
269