Repository: mitsuhiko/minijinja (language: rust)

Issue:
Add completions command to the CLI
I've seen most CLI apps offer a completions command, eg. `starship completions fish`, which is very convenient.


Discussion / hints:

Fix patch:
diff --git a/minijinja-cli/Cargo.toml b/minijinja-cli/Cargo.toml
index 564bb807..2233b70f 100644
--- a/minijinja-cli/Cargo.toml
+++ b/minijinja-cli/Cargo.toml
@@ -19,6 +19,7 @@ cbor = ["ciborium"]
 datetime = ["minijinja-contrib/datetime", "minijinja-contrib/timezone"]
 json5 = ["serde_json5"]
 repl = ["rustyline"]
+completions = ["clap_complete", "clap_complete_nushell", "clap_complete_fig"]
 
 [dependencies]
 anyhow = "1.0.74"
@@ -47,6 +48,9 @@ serde_qs = { version = "0.12.0", optional = true }
 serde_yaml = { version = "0.9.25", optional = true }
 tempfile = "3.9.0"
 toml = { version = "0.7.6", optional = true }
+clap_complete = { version = "4", optional = true }
+clap_complete_fig = { version = "4", optional = true }
+clap_complete_nushell = { version = "4", optional = true }
 
 [build-dependencies]
 clap = { version = "4.3.21", default-features = false, features = [
@@ -56,7 +60,7 @@ clap = { version = "4.3.21", default-features = false, features = [
     "usage",
     "error-context",
 ] }
-clap_complete = "4"
-clap_complete_fig = "4"
-clap_complete_nushell = "4"
 clap_mangen = "0.2"
+clap_complete = { version = "4", optional = true }
+clap_complete_fig = { version = "4", optional = true }
+clap_complete_nushell = { version = "4", optional = true }
diff --git a/minijinja-cli/README.md b/minijinja-cli/README.md
index e1dc9e11..371c5371 100644
--- a/minijinja-cli/README.md
+++ b/minijinja-cli/README.md
@@ -71,6 +71,10 @@ can be set to stdin at once.
     prints internals of the template.  Possible options are `tokens` to see the output
     of the tokenizer, `ast` to see the AST after parsing, and `instructions` to inspect
     the compiled bytecode.
+- `-o`, `--output` `<FILENAME>`:
+    writes the output to a filename rather than stdout.
+- `--generate-completion` `<SHELL>`:
+    generate the completions for the given shell.
 - `--version`:
     prints the version.
 - `--help`:
@@ -143,6 +147,11 @@ selected when the defaults are turned off:
 * `json5`: enables JSON5 support (instead of JSON)
 * `querystring`: enables querystring support
 * `datetime`: enables the date and time filters and `now()` function
+* `completions`: enables the generation of completions
+
+Additionally if the `ASSET_OUT_DIR` environment variable is set during
+compilation manpage (and optionally completions) are generated into that
+folder.
 
 ## Sponsor
 
diff --git a/minijinja-cli/build.rs b/minijinja-cli/build.rs
index 46dbe149..d1429dc1 100644
--- a/minijinja-cli/build.rs
+++ b/minijinja-cli/build.rs
@@ -1,10 +1,5 @@
 use std::fs::create_dir_all;
 
-use clap::ValueEnum;
-use clap_complete::Shell;
-use clap_complete_fig::Fig;
-use clap_complete_nushell::Nushell;
-
 pub mod cli {
     include!("src/cli.rs");
 }
@@ -17,6 +12,7 @@ fn main() -> std::io::Result<()> {
         None => return Ok(()),
     };
 
+    #[allow(unused_mut)]
     let mut cli = cli::make_command();
 
     let man = clap_mangen::Man::new(cli.clone());
@@ -27,14 +23,29 @@ fn main() -> std::io::Result<()> {
     create_dir_all(&man_out_dir)?;
     std::fs::write(man_out_dir.join("minijinja-cli.1"), man_buffer)?;
 
-    let completions_out_dir = out_dir.as_path().join("completions");
-    create_dir_all(&completions_out_dir)?;
-
-    for shell in Shell::value_variants() {
-        clap_complete::generate_to(*shell, &mut cli, "minijinja-cli", &completions_out_dir)?;
+    #[cfg(feature = "completions")]
+    {
+        use clap::ValueEnum;
+
+        let completions_out_dir = out_dir.as_path().join("completions");
+        create_dir_all(&completions_out_dir)?;
+
+        for shell in clap_complete::Shell::value_variants() {
+            clap_complete::generate_to(*shell, &mut cli, "minijinja-cli", &completions_out_dir)?;
+        }
+        clap_complete::generate_to(
+            clap_complete_nushell::Nushell,
+            &mut cli,
+            "minijinja-cli",
+            &completions_out_dir,
+        )?;
+        clap_complete::generate_to(
+            clap_complete_fig::Fig,
+            &mut cli,
+            "minijinja-cli",
+            &completions_out_dir,
+        )?;
     }
-    clap_complete::generate_to(Nushell, &mut cli, "minijinja-cli", &completions_out_dir)?;
-    clap_complete::generate_to(Fig, &mut cli, "minijinja-cli", &completions_out_dir)?;
 
     Ok(())
 }
diff --git a/minijinja-cli/src/cli.rs b/minijinja-cli/src/cli.rs
index b7ff7cd4..418394cb 100644
--- a/minijinja-cli/src/cli.rs
+++ b/minijinja-cli/src/cli.rs
@@ -38,6 +38,17 @@ pub(super) fn make_command() -> Command {
             #[cfg(feature = "repl")]
             arg!(--repl "starts the repl with the given data")
                 .conflicts_with_all(["expr", "template"]),
+            #[cfg(feature = "completions")]
+            arg!(--"generate-completion" <SHELL> "generate a completion script for the given shell")
+                .value_parser([
+                    "bash",
+                    "elvish",
+                    "fig",
+                    "fish",
+                    "nushell",
+                    "powershell",
+                    "zsh",
+                ]),
             arg!(-o --output <FILENAME> "path tot he output file")
                 .default_value("-")
                 .value_parser(value_parser!(PathBuf)),
diff --git a/minijinja-cli/src/main.rs b/minijinja-cli/src/main.rs
index 07124836..234e435b 100644
--- a/minijinja-cli/src/main.rs
+++ b/minijinja-cli/src/main.rs
@@ -226,9 +226,43 @@ fn create_env(
     env
 }
 
+#[cfg(feature = "completions")]
+fn generate_completions(shell: &str) -> Result<i32, Error> {
+    macro_rules! gen {
+        ($shell:expr) => {
+            clap_complete::generate(
+                $shell,
+                &mut cli::make_command(),
+                "minijinja-cli",
+                &mut std::io::stdout(),
+            )
+        };
+    }
+
+    match shell {
+        "bash" => gen!(clap_complete::Shell::Bash),
+        "zsh" => gen!(clap_complete::Shell::Zsh),
+        "elvish" => gen!(clap_complete::Shell::Elvish),
+        "fish" => gen!(clap_complete::Shell::Fish),
+        "powershell" => gen!(clap_complete::Shell::PowerShell),
+        "nushell" => gen!(clap_complete_nushell::Nushell),
+        "fig" => gen!(clap_complete_fig::Fig),
+        _ => unreachable!(),
+    };
+
+    Ok(0)
+}
+
 fn execute() -> Result<i32, Error> {
     let matches = cli::make_command().get_matches();
 
+    #[cfg(feature = "completions")]
+    {
+        if let Some(shell) = matches.get_one::<String>("generate-completion") {
+            return generate_completions(shell);
+        }
+    }
+
     let format = matches.get_one::<String>("format").unwrap();
     let (base, stdin_used) = if let Some(data) = matches.get_one::<PathBuf>("data") {
         load_data(format, data)?


Test patch: