Переглянути джерело

meli/args: add --gzipped flag to man subcommand

Add --gzipped flag to print a manpage without decompressing it, for
piping the output to a file. Because why not.

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
Manos Pitsidianakis 1 рік тому
батько
коміт
814af0e94d
3 змінених файлів з 104 додано та 22 видалено
  1. 24 7
      meli/src/args.rs
  2. 23 15
      meli/src/manpages.rs
  3. 57 0
      meli/tests/test_cli_subcommands.rs

+ 24 - 7
meli/src/args.rs

@@ -91,17 +91,18 @@ pub enum SubCommand {
 
 #[derive(Debug, StructOpt)]
 pub struct ManOpt {
+    /// If set, output text in stdout instead of spawning `$PAGER`.
+    #[cfg(feature = "cli-docs")]
+    #[cfg_attr(feature = "cli-docs", structopt(long = "no-raw", alias = "no-raw"))]
+    pub no_raw: bool,
+    /// If set, output compressed gzip manpage in binary form in stdout.
+    #[cfg(feature = "cli-docs")]
+    #[cfg_attr(feature = "cli-docs", structopt(long = "gzipped"))]
+    pub gzipped: bool,
     #[cfg(feature = "cli-docs")]
     #[cfg_attr(feature = "cli-docs", structopt(default_value = "meli", possible_values=manpages::POSSIBLE_VALUES, value_name="PAGE", parse(try_from_str = manpages::parse_manpage)))]
     /// Name of manual page.
     pub page: manpages::ManPages,
-    /// If true, output text in stdout instead of spawning `$PAGER`.
-    #[cfg(feature = "cli-docs")]
-    #[cfg_attr(
-        feature = "cli-docs",
-        structopt(long = "no-raw", alias = "no-raw", value_name = "bool")
-    )]
-    pub no_raw: Option<Option<bool>>,
 }
 
 #[derive(Debug, StructOpt)]
@@ -161,6 +162,22 @@ impl Opt {
             SubCommand::Man(ManOpt {
                 page,
                 no_raw,
+                gzipped: true,
+            }) => {
+                use std::io::Write;
+
+                ret_err!(std::io::stdout().write_all(if no_raw {
+                    page.text_gz()
+                } else {
+                    page.mdoc_gz()
+                }));
+                Ok(())
+            }
+            #[cfg(feature = "cli-docs")]
+            SubCommand::Man(ManOpt {
+                page,
+                no_raw,
+                gzipped: false,
             }) => {
                 subcommands::man(page, false).and_then(|s| subcommands::pager(s, no_raw))
             }

+ 23 - 15
meli/src/manpages.rs

@@ -84,6 +84,19 @@ impl std::fmt::Display for ManPages {
 }
 
 impl ManPages {
+    const MANPAGES: [&'static [u8]; 4] = [
+        include_bytes!(concat!(env!("OUT_DIR"), "/meli.txt.gz")),
+        include_bytes!(concat!(env!("OUT_DIR"), "/meli.conf.txt.gz")),
+        include_bytes!(concat!(env!("OUT_DIR"), "/meli-themes.txt.gz")),
+        include_bytes!(concat!(env!("OUT_DIR"), "/meli.7.txt.gz")),
+    ];
+    const MANPAGES_MDOC: [&'static [u8]; 4] = [
+        include_bytes!(concat!(env!("OUT_DIR"), "/meli.mdoc.gz")),
+        include_bytes!(concat!(env!("OUT_DIR"), "/meli.conf.mdoc.gz")),
+        include_bytes!(concat!(env!("OUT_DIR"), "/meli-themes.mdoc.gz")),
+        include_bytes!(concat!(env!("OUT_DIR"), "/meli.7.mdoc.gz")),
+    ];
+
     pub fn install(destination: Option<PathBuf>) -> Result<PathBuf> {
         fn path_valid(p: &Path, tries: &mut Vec<PathBuf>) -> bool {
             tries.push(p.into());
@@ -145,24 +158,19 @@ impl ManPages {
         Ok(path)
     }
 
-    pub fn read(self, source: bool) -> Result<String> {
-        const MANPAGES: [&[u8]; 4] = [
-            include_bytes!(concat!(env!("OUT_DIR"), "/meli.txt.gz")),
-            include_bytes!(concat!(env!("OUT_DIR"), "/meli.conf.txt.gz")),
-            include_bytes!(concat!(env!("OUT_DIR"), "/meli-themes.txt.gz")),
-            include_bytes!(concat!(env!("OUT_DIR"), "/meli.7.txt.gz")),
-        ];
-        const MANPAGES_MDOC: [&[u8]; 4] = [
-            include_bytes!(concat!(env!("OUT_DIR"), "/meli.mdoc.gz")),
-            include_bytes!(concat!(env!("OUT_DIR"), "/meli.conf.mdoc.gz")),
-            include_bytes!(concat!(env!("OUT_DIR"), "/meli-themes.mdoc.gz")),
-            include_bytes!(concat!(env!("OUT_DIR"), "/meli.7.mdoc.gz")),
-        ];
+    pub fn mdoc_gz(self) -> &'static [u8] {
+        Self::MANPAGES_MDOC[self as usize]
+    }
 
+    pub fn text_gz(self) -> &'static [u8] {
+        Self::MANPAGES[self as usize]
+    }
+
+    pub fn read(self, source: bool) -> Result<String> {
         let mut gz = GzDecoder::new(if source {
-            MANPAGES_MDOC[self as usize]
+            self.mdoc_gz()
         } else {
-            MANPAGES[self as usize]
+            self.text_gz()
         });
         let mut v = String::with_capacity(
             str::parse::<usize>(unsafe {

+ 57 - 0
meli/tests/test_cli_subcommands.rs

@@ -114,11 +114,68 @@ fn test_cli_subcommands() {
         }
     }
 
+    fn test_subcommand_man() {
+        for (man, title) in [
+            ("meli.1", "MELI(1)"),
+            ("meli.conf.5", "MELI.CONF(5)"),
+            ("meli-themes.5", "MELI-THEMES(5)"),
+            ("meli.7", "MELI(7)"),
+        ] {
+            for gzipped in [true, false] {
+                for no_raw in [true, false] {
+                    let mut cmd = Command::cargo_bin("meli").unwrap();
+                    let args = match (no_raw, gzipped) {
+                        (true, true) => &["man", "--no-raw", "--gzipped", man][..],
+                        (true, false) => &["man", "--no-raw", man],
+                        (false, false) => &["man", man],
+                        (false, true) => &["man", "--gzipped", man],
+                    };
+                    let output = cmd.args(args).output().unwrap().assert();
+                    output.code(0).stdout(predicate::function(|x: &[u8]| {
+                        use std::io::Read;
+
+                        use flate2::bufread::GzDecoder;
+
+                        let mut gz = GzDecoder::new(x);
+                        let content = if gzipped {
+                            let size = gz.header().unwrap().comment().unwrap();
+
+                            let mut v = String::with_capacity(
+                                str::parse::<usize>(
+                                    std::str::from_utf8(size)
+                                        .expect("was not compressed with size comment header"),
+                                )
+                                .expect("was not compressed with size comment header"),
+                            );
+                            gz.read_to_string(&mut v)
+                                .expect("expected gzipped output but could not decode it.");
+                            v
+                        } else {
+                            assert_eq!(gz.header(), None);
+                            let mut v = String::with_capacity(0);
+                            gz.read_to_string(&mut v).unwrap_err();
+                            String::from_utf8(x.to_vec()).expect("invalid utf-8 content")
+                        };
+                        if !no_raw && gzipped {
+                            assert!(content.contains(man));
+                        } else {
+                            assert!(content.contains('\u{8}'));
+                            assert!(content.contains(title));
+                        }
+
+                        true
+                    }));
+                }
+            }
+        }
+    }
+
     version();
     help();
     test_subcommand_succeeds("help");
     test_subcommand_succeeds("compiled-with");
     test_subcommand_succeeds("man");
+    test_subcommand_man();
 
     let tmp_dir = TempDir::new().unwrap();