Skip to content

Commit 25efcf8

Browse files
committed
feat(clap-complete): Introduce fallible generator
It may happen that writing completion fails due to whichever reason. Currently clap-complete panics without letting calling code to react on that error (i.e. ignore). Introduce fallible version of the `generate` function by mostly only getting rid of except and instead exposing Result. For backwards compatibility make default trait implementation of `try_generate` function to call `generate` not to break users defining their own `generate`. Issue: #5993
1 parent e2188d9 commit 25efcf8

File tree

8 files changed

+95
-18
lines changed

8 files changed

+95
-18
lines changed

clap_complete/src/aot/generator/mod.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub trait Generator {
2121
/// # Examples
2222
///
2323
/// ```
24-
/// # use std::io::Write;
24+
/// # use std::io::{Error, Write};
2525
/// # use clap::Command;
2626
/// use clap_complete::Generator;
2727
///
@@ -32,6 +32,7 @@ pub trait Generator {
3232
/// format!("{name}.fish")
3333
/// }
3434
/// # fn generate(&self, cmd: &Command, buf: &mut dyn Write) {}
35+
/// # fn try_generate(&self, cmd: &Command, buf: &mut dyn Write) -> Result<(), Error> {Ok(())}
3536
/// }
3637
/// ```
3738
fn file_name(&self, name: &str) -> String;
@@ -48,7 +49,7 @@ pub trait Generator {
4849
/// as if it is printed using [`std::println`].
4950
///
5051
/// ```
51-
/// use std::{io::Write, fmt::write};
52+
/// use std::{io::{Error, Write}, fmt::write};
5253
/// use clap::Command;
5354
/// use clap_complete::Generator;
5455
///
@@ -61,9 +62,46 @@ pub trait Generator {
6162
/// fn generate(&self, cmd: &Command, buf: &mut dyn Write) {
6263
/// write!(buf, "{cmd}").unwrap();
6364
/// }
65+
/// # fn try_generate(&self, cmd: &Command, buf: &mut dyn Write) -> Result<(), Error> {
66+
/// # write!(buf, "{cmd}")
67+
/// # }
6468
/// }
6569
/// ```
6670
fn generate(&self, cmd: &Command, buf: &mut dyn Write);
71+
72+
///
73+
/// Fallible version to generate output out of [`clap::Command`].
74+
///
75+
/// # Examples
76+
///
77+
/// The following example generator displays the [`clap::Command`]
78+
/// as if it is printed using [`std::println`].
79+
///
80+
/// ```
81+
/// use std::{io::{Error, Write}, fmt::write};
82+
/// use clap::Command;
83+
/// use clap_complete::Generator;
84+
///
85+
/// pub struct ClapDebug;
86+
///
87+
/// impl Generator for ClapDebug {
88+
/// # fn file_name(&self, name: &str) -> String {
89+
/// # name.into()
90+
/// # }
91+
/// # fn generate(&self, cmd: &Command, buf: &mut dyn Write) {
92+
/// # self.try_generate(cmd, buf).expect("failed to write completion file");
93+
/// # }
94+
/// fn try_generate(&self, cmd: &Command, buf: &mut dyn Write) -> Result<(), Error> {
95+
/// write!(buf, "{cmd}")
96+
/// }
97+
/// }
98+
/// ```
99+
fn try_generate(&self, cmd: &Command, buf: &mut dyn Write) -> Result<(), Error> {
100+
// NOTE(gtema): It is done this way to introduce new function but not break downstream
101+
// users. Before cutting major version this should be turned around with [`generate`]
102+
// calling [`try_generate`].
103+
Ok(self.generate(cmd, buf))
104+
}
67105
}
68106

69107
/// Generate a completions file for a specified shell at compile-time.

clap_complete/src/aot/shells/bash.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use std::{fmt::Write as _, io::Write};
1+
use std::{
2+
fmt::Write as _,
3+
io::{Error, Write},
4+
};
25

36
use clap::{Arg, Command, ValueHint};
47

@@ -14,6 +17,11 @@ impl Generator for Bash {
1417
}
1518

1619
fn generate(&self, cmd: &Command, buf: &mut dyn Write) {
20+
self.try_generate(cmd, buf)
21+
.expect("failed to write completion file");
22+
}
23+
24+
fn try_generate(&self, cmd: &Command, buf: &mut dyn Write) -> Result<(), Error> {
1725
let bin_name = cmd
1826
.get_bin_name()
1927
.expect("crate::generate should have set the bin_name");
@@ -75,7 +83,7 @@ fi
7583
name_opts_details = option_details_for_path(cmd, bin_name),
7684
subcmds = all_subcommands(cmd, &fn_name),
7785
subcmd_details = subcommand_details(cmd)
78-
).expect("failed to write completion file");
86+
)
7987
}
8088
}
8189

clap_complete/src/aot/shells/elvish.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::io::Write;
1+
use std::io::{Error, Write};
22

33
use clap::builder::StyledStr;
44
use clap::Command;
@@ -16,6 +16,11 @@ impl Generator for Elvish {
1616
}
1717

1818
fn generate(&self, cmd: &Command, buf: &mut dyn Write) {
19+
self.try_generate(cmd, buf)
20+
.expect("failed to write completion file");
21+
}
22+
23+
fn try_generate(&self, cmd: &Command, buf: &mut dyn Write) -> Result<(), Error> {
1924
let bin_name = cmd
2025
.get_bin_name()
2126
.expect("crate::generate should have set the bin_name");
@@ -48,7 +53,6 @@ set edit:completion:arg-completer[{bin_name}] = {{|@words|
4853
}}
4954
"#,
5055
)
51-
.expect("failed to write completion file");
5256
}
5357
}
5458

clap_complete/src/aot/shells/fish.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::io::Write;
1+
use std::io::{Error, Write};
22

33
use clap::{builder, Arg, Command, ValueHint};
44

@@ -16,6 +16,11 @@ impl Generator for Fish {
1616
}
1717

1818
fn generate(&self, cmd: &Command, buf: &mut dyn Write) {
19+
self.try_generate(cmd, buf)
20+
.expect("failed to write completion file");
21+
}
22+
23+
fn try_generate(&self, cmd: &Command, buf: &mut dyn Write) -> Result<(), Error> {
1924
let bin_name = cmd
2025
.get_bin_name()
2126
.expect("crate::generate should have set the bin_name");
@@ -42,7 +47,7 @@ impl Generator for Fish {
4247
needs_fn_name,
4348
using_fn_name,
4449
);
45-
write!(buf, "{buffer}").expect("failed to write completion file");
50+
write!(buf, "{buffer}")
4651
}
4752
}
4853

clap_complete/src/aot/shells/powershell.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::io::Write;
1+
use std::io::{Error, Write};
22

33
use clap::builder::StyledStr;
44
use clap::{Arg, Command};
@@ -16,6 +16,11 @@ impl Generator for PowerShell {
1616
}
1717

1818
fn generate(&self, cmd: &Command, buf: &mut dyn Write) {
19+
self.try_generate(cmd, buf)
20+
.expect("failed to write completion file");
21+
}
22+
23+
fn try_generate(&self, cmd: &Command, buf: &mut dyn Write) -> Result<(), Error> {
1924
let bin_name = cmd
2025
.get_bin_name()
2126
.expect("crate::generate should have set the bin_name");
@@ -53,7 +58,6 @@ Register-ArgumentCompleter -Native -CommandName '{bin_name}' -ScriptBlock {{
5358
}}
5459
"#
5560
)
56-
.expect("failed to write completion file");
5761
}
5862
}
5963

clap_complete/src/aot/shells/shell.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::fmt::Display;
2+
use std::io::Error;
23
use std::path::Path;
34
use std::str::FromStr;
45

@@ -81,12 +82,17 @@ impl Generator for Shell {
8182
}
8283

8384
fn generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::Write) {
85+
self.try_generate(cmd, buf)
86+
.expect("failed to write completion file");
87+
}
88+
89+
fn try_generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::Write) -> Result<(), Error> {
8490
match self {
85-
Shell::Bash => shells::Bash.generate(cmd, buf),
86-
Shell::Elvish => shells::Elvish.generate(cmd, buf),
87-
Shell::Fish => shells::Fish.generate(cmd, buf),
88-
Shell::PowerShell => shells::PowerShell.generate(cmd, buf),
89-
Shell::Zsh => shells::Zsh.generate(cmd, buf),
91+
Shell::Bash => shells::Bash.try_generate(cmd, buf),
92+
Shell::Elvish => shells::Elvish.try_generate(cmd, buf),
93+
Shell::Fish => shells::Fish.try_generate(cmd, buf),
94+
Shell::PowerShell => shells::PowerShell.try_generate(cmd, buf),
95+
Shell::Zsh => shells::Zsh.try_generate(cmd, buf),
9096
}
9197
}
9298
}

clap_complete/src/aot/shells/zsh.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::io::Write;
1+
use std::io::{Error, Write};
22

33
use clap::{Arg, ArgAction, Command, ValueHint};
44

@@ -15,6 +15,11 @@ impl Generator for Zsh {
1515
}
1616

1717
fn generate(&self, cmd: &Command, buf: &mut dyn Write) {
18+
self.try_generate(cmd, buf)
19+
.expect("failed to write completion file");
20+
}
21+
22+
fn try_generate(&self, cmd: &Command, buf: &mut dyn Write) -> Result<(), Error> {
1823
let bin_name = cmd
1924
.get_bin_name()
2025
.expect("crate::generate should have set the bin_name");
@@ -53,7 +58,6 @@ fi
5358
subcommands = get_subcommands_of(cmd),
5459
subcommand_details = subcommand_details(cmd)
5560
)
56-
.expect("failed to write completion file");
5761
}
5862
}
5963

clap_complete_nushell/src/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ impl Generator for Nushell {
3636
}
3737

3838
fn generate(&self, cmd: &Command, buf: &mut dyn std::io::Write) {
39+
self.try_generate(cmd, buf)
40+
.expect("failed to write completion file");
41+
}
42+
43+
fn try_generate(
44+
&self,
45+
cmd: &Command,
46+
buf: &mut dyn std::io::Write,
47+
) -> Result<(), std::io::Error> {
3948
let mut completions = String::new();
4049

4150
completions.push_str("module completions {\n\n");
@@ -50,7 +59,6 @@ impl Generator for Nushell {
5059
completions.push_str("export use completions *\n");
5160

5261
buf.write_all(completions.as_bytes())
53-
.expect("Failed to write to generated file");
5462
}
5563
}
5664

0 commit comments

Comments
 (0)