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