-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf: avoid retrieving possible_values unless used
In some sophisticated situations, these may be expensive to calculate. One example might be a '--branch' option accepting any single Git branch that exists on the remote -- in such a case, the remote would need to be queried for all possible_values. The cost is ultimately unavoidable at runtime since this validation has to happen eventually, but there's no need to pay it when generating help text if `is_hide_possible_values_set`. To keep '-h' fast, avoid collecting `possible_values` during '-h' unless we're actually going to use the values in display. This is trivially based on the short-circuiting logic at [1], which at least supports the idea that actually consuming the iterator is not generally-guaranteed behavior when `hide_possible_values` is set. [1]: clap_builder/src/builder/command.rs:long_help_exists_
- Loading branch information
1 parent
d092896
commit a819532
Showing
4 changed files
with
204 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
#![cfg(feature = "string")] | ||
|
||
use std::sync::{Arc, Mutex}; | ||
|
||
use clap::{Arg, Command}; | ||
use clap_builder::builder::{PossibleValue, PossibleValuesParser, TypedValueParser}; | ||
|
||
#[cfg(feature = "error-context")] | ||
use super::utils; | ||
|
||
#[derive(Clone)] | ||
struct ExpensiveValues { | ||
iterated: Arc<Mutex<bool>>, | ||
} | ||
|
||
impl ExpensiveValues { | ||
pub fn new() -> Self { | ||
ExpensiveValues { | ||
iterated: Arc::new(Mutex::new(false)), | ||
} | ||
} | ||
} | ||
|
||
impl IntoIterator for ExpensiveValues { | ||
type Item = String; | ||
|
||
type IntoIter = ExpensiveValuesIntoIterator; | ||
|
||
fn into_iter(self) -> Self::IntoIter { | ||
ExpensiveValuesIntoIterator { me: self, index: 0 } | ||
} | ||
} | ||
|
||
struct ExpensiveValuesIntoIterator { | ||
me: ExpensiveValues, | ||
index: usize, | ||
} | ||
|
||
impl Iterator for ExpensiveValuesIntoIterator { | ||
type Item = String; | ||
fn next(&mut self) -> Option<String> { | ||
let mut guard = self | ||
.me | ||
.iterated | ||
.lock() | ||
.expect("not working across multiple threads"); | ||
|
||
*guard = true; | ||
self.index += 1; | ||
|
||
if self.index < 3 { | ||
Some(format!("expensive-value-{}", self.index)) | ||
} else { | ||
None | ||
} | ||
} | ||
} | ||
|
||
impl TypedValueParser for ExpensiveValues { | ||
type Value = String; | ||
|
||
fn parse_ref( | ||
&self, | ||
_cmd: &clap_builder::Command, | ||
_arg: Option<&clap_builder::Arg>, | ||
_value: &std::ffi::OsStr, | ||
) -> Result<Self::Value, clap_builder::Error> { | ||
todo!() | ||
} | ||
|
||
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> { | ||
Some(Box::new(self.clone().into_iter().map(PossibleValue::from))) | ||
} | ||
} | ||
|
||
#[test] | ||
fn no_iterate_when_hidden() { | ||
static PV_EXPECTED: &str = "\ | ||
Usage: clap-test [some-cheap-option] [some-expensive-option] | ||
Arguments: | ||
[some-cheap-option] cheap [possible values: some, cheap, values] | ||
[some-expensive-option] expensive | ||
Options: | ||
-h, --help Print help | ||
"; | ||
let expensive = ExpensiveValues::new(); | ||
utils::assert_output( | ||
Command::new("test") | ||
.arg( | ||
Arg::new("some-cheap-option") | ||
.help("cheap") | ||
.value_parser(PossibleValuesParser::new(["some", "cheap", "values"])), | ||
) | ||
.arg( | ||
Arg::new("some-expensive-option") | ||
.help("expensive") | ||
.hide_possible_values(true) | ||
.value_parser(expensive.clone()), | ||
), | ||
"clap-test -h", | ||
PV_EXPECTED, | ||
false, | ||
); | ||
assert_eq!(*expensive.iterated.lock().unwrap(), false); | ||
} | ||
|
||
#[test] | ||
fn iterate_when_displayed() { | ||
static PV_EXPECTED: &str = "\ | ||
Usage: clap-test [some-cheap-option] [some-expensive-option] | ||
Arguments: | ||
[some-cheap-option] cheap [possible values: some, cheap, values] | ||
[some-expensive-option] expensive [possible values: expensive-value-1, expensive-value-2] | ||
Options: | ||
-h, --help Print help | ||
"; | ||
let expensive = ExpensiveValues::new(); | ||
utils::assert_output( | ||
Command::new("test") | ||
.arg( | ||
Arg::new("some-cheap-option") | ||
.help("cheap") | ||
.value_parser(PossibleValuesParser::new(["some", "cheap", "values"])), | ||
) | ||
.arg( | ||
Arg::new("some-expensive-option") | ||
.help("expensive") | ||
.hide_possible_values(false) | ||
.value_parser(expensive.clone()), | ||
), | ||
"clap-test -h", | ||
PV_EXPECTED, | ||
false, | ||
); | ||
assert_eq!(*expensive.iterated.lock().unwrap(), true); | ||
} |