Repository: clap-rs/clap
Pull request #3366 (2022-01-28 22:23:10)

fix(man)!: Clean up Meta API 
`Meta` wasn't really serving a distinct purpose in the API, so rolled it
into `Man` now that it lazily renders.


Patch:
diff --git a/clap_man/examples/man-builder.rs b/clap_man/examples/man-builder.rs
deleted file mode 100644
index a9466463..00000000
--- a/clap_man/examples/man-builder.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-use clap::{arg, App};
-use clap_man::{Man, Meta};
-
-// Run this example as `cargo run --example man-builder | man -l -`.
-
-fn main() -> Result<(), std::io::Error> {
-    let app = App::new("myapp")
-        .version("1.0")
-        .author("Kevin K. <kbknapp@gmail.com>")
-        .about("Does awesome things")
-        .subcommand_help_heading("Commands")
-        .arg(
-            arg!(-c --config <FILE> "Sets a custom config file")
-                .long_help("Some more text about how to set a custom config file")
-                .required(false)
-                .takes_value(true),
-        )
-        .arg(arg!([output] "Sets an optional output file").index(1))
-        .arg(arg!(-d --debug ... "Turn debugging information on"))
-        .subcommand(
-            App::new("test")
-                .about("does testing things")
-                .arg(arg!(-l --list "Lists test values")),
-        )
-        .after_help(
-            "\
-For more information about the config file syntax, look up the INI format.
-To see the debug information, visit our website on github.com",
-        );
-
-    let meta = Meta::from_clap("1", "GNU", &app);
-    let man = Man::new(meta, app);
-    man.render(&mut std::io::stdout())
-}
diff --git a/clap_man/examples/man.rs b/clap_man/examples/man.rs
index 779f6455..6d8a2a23 100644
--- a/clap_man/examples/man.rs
+++ b/clap_man/examples/man.rs
@@ -1,5 +1,5 @@
 use clap::{arg, App};
-use clap_man::generate_manpage;
+use clap_man::Man;
 use std::io;
 
 // Run this example as `cargo run --example man | man -l -`.
@@ -36,5 +36,5 @@ And a few newlines.",
                 .arg(arg!(-l --list "Lists test values")),
         );
 
-    generate_manpage(app, &mut io::stdout())
+    Man::new(app).render(&mut io::stdout())
 }
diff --git a/clap_man/src/lib.rs b/clap_man/src/lib.rs
index 158bfd36..b9303e40 100644
--- a/clap_man/src/lib.rs
+++ b/clap_man/src/lib.rs
@@ -12,15 +12,9 @@ use render::subcommand_heading;
 use roff::{roman, Roff};
 use std::io::Write;
 
-/// Generate a manual page and write it out.
-pub fn generate_manpage(app: clap::App<'_>, buf: &mut dyn Write) -> Result<(), std::io::Error> {
-    let meta = Meta::from_clap("1", "", &app);
-    let man = Man::new(meta, app);
-    man.render(buf)
-}
-
-/// Metadata about a manual page.
-pub struct Meta {
+/// A manual page as constructed from a clap::App.
+pub struct Man<'a> {
+    app: clap::App<'a>,
     title: String,
     section: String,
     date: String,
@@ -28,51 +22,63 @@ pub struct Meta {
     manual: String,
 }
 
-impl Meta {
-    /// Create metadata from a clap::App.
-    pub fn from_clap(section: &str, manual: &str, app: &clap::App) -> Self {
+impl<'a> Man<'a> {
+    /// Create a new manual page.
+    pub fn new(mut app: clap::App<'a>) -> Self {
+        app._build_all();
+        let title = app.get_name().to_owned();
+        let section = "1".to_owned();
+        let date = "".to_owned();
+        let source = format!(
+            "{} {}",
+            app.get_name(),
+            app.get_version().unwrap_or_default()
+        );
+        let manual = "".to_owned();
         Self {
-            title: app.get_name().to_string(),
-            section: section.to_string(),
-            date: "".to_string(), // FIXME
-            source: format!(
-                "{} {}",
-                app.get_name(),
-                app.get_version().unwrap_or_default()
-            ),
-            manual: manual.to_string(),
+            app,
+            title,
+            section,
+            date,
+            source,
+            manual,
         }
     }
 
-    // Turn metadata into arguments for a .TH macro.
-    fn to_args(&self) -> Vec<&str> {
-        vec![
-            &self.title,
-            &self.section,
-            &self.date,
-            &self.source,
-            &self.manual,
-        ]
+    /// Override the default title
+    pub fn title(mut self, title: impl Into<String>) -> Self {
+        self.title = title.into();
+        self
     }
-}
 
-/// A manual page as constructed from a clap::App.
-pub struct Man<'a> {
-    meta: Meta,
-    app: clap::App<'a>,
-}
+    /// Override the default section
+    pub fn section(mut self, section: impl Into<String>) -> Self {
+        self.section = section.into();
+        self
+    }
 
-impl<'a> Man<'a> {
-    /// Create a new manual page.
-    pub fn new(meta: Meta, mut app: clap::App<'a>) -> Self {
-        app._build_all();
-        Self { meta, app }
+    /// Override the default date
+    pub fn date(mut self, date: impl Into<String>) -> Self {
+        self.date = date.into();
+        self
+    }
+
+    /// Override the default source
+    pub fn source(mut self, source: impl Into<String>) -> Self {
+        self.source = source.into();
+        self
+    }
+
+    /// Override the default manual
+    pub fn manual(mut self, manual: impl Into<String>) -> Self {
+        self.manual = manual.into();
+        self
     }
 
     /// Render a manual page into writer.
     pub fn render(&self, w: &mut dyn Write) -> Result<(), std::io::Error> {
         let mut roff = Roff::default();
-        roff.control("TH", self.meta.to_args());
+        self._render_title(&mut roff);
         self._render_name_section(&mut roff);
         self._render_synopsis_section(&mut roff);
         self._render_description_section(&mut roff);
@@ -100,6 +106,28 @@ impl<'a> Man<'a> {
         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();
@@ -158,7 +186,7 @@ impl<'a> Man<'a> {
     fn _render_subcommands_section(&self, roff: &mut Roff) {
         let heading = subcommand_heading(&self.app);
         roff.control("SH", [heading.as_str()]);
-        render::subcommands(roff, &self.app, &self.meta.section);
+        render::subcommands(roff, &self.app, &self.section);
     }
 
     /// Render the EXTRA section into the writer.


Test patch:
diff --git a/clap_man/tests/generate.rs b/clap_man/tests/generate.rs
index 4733873d..aa98192b 100644
--- a/clap_man/tests/generate.rs
+++ b/clap_man/tests/generate.rs
@@ -1,5 +1,5 @@
 use clap::{arg, App};
-use clap_man::generate_manpage;
+use clap_man::Man;
 use std::io;
 
 #[test]
@@ -25,5 +25,5 @@ fn render_manpage() {
                 .arg(arg!(-l --list "Lists test values")),
         );
 
-    generate_manpage(app, &mut io::sink()).unwrap();
+    Man::new(app).render(&mut io::sink()).unwrap();
 }