Skip to content

Commit 0f60239

Browse files
authored
Merge pull request #6057 from GilShoshan94/master
feat(help): Allow styling for inline context
2 parents 0e535e5 + 83d4206 commit 0f60239

File tree

8 files changed

+274
-46
lines changed

8 files changed

+274
-46
lines changed

clap_builder/src/builder/styling.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ pub struct Styles {
2828
placeholder: Style,
2929
valid: Style,
3030
invalid: Style,
31+
context: Style,
32+
context_value: Option<Style>,
3133
}
3234

3335
impl Styles {
@@ -41,6 +43,8 @@ impl Styles {
4143
placeholder: Style::new(),
4244
valid: Style::new(),
4345
invalid: Style::new(),
46+
context: Style::new(),
47+
context_value: None,
4448
}
4549
}
4650

@@ -58,6 +62,8 @@ impl Styles {
5862
placeholder: Style::new(),
5963
valid: Style::new().fg_color(Some(Color::Ansi(AnsiColor::Green))),
6064
invalid: Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow))),
65+
context: Style::new(),
66+
context_value: None,
6167
}
6268
}
6369
#[cfg(not(feature = "color"))]
@@ -114,6 +120,24 @@ impl Styles {
114120
self.invalid = style;
115121
self
116122
}
123+
124+
/// Highlight all specified contexts, e.g. `[default: false]`
125+
///
126+
/// To specialize the style of the value within the context, see [`Styles::context_value`]
127+
#[inline]
128+
pub const fn context(mut self, style: Style) -> Self {
129+
self.context = style;
130+
self
131+
}
132+
133+
/// Highlight values within all of the context, e.g. the `false` in `[default: false]`
134+
///
135+
/// If not explicitly set, falls back to `context`'s style.
136+
#[inline]
137+
pub const fn context_value(mut self, style: Style) -> Self {
138+
self.context_value = Some(style);
139+
self
140+
}
117141
}
118142

119143
/// Reflection
@@ -159,6 +183,25 @@ impl Styles {
159183
pub const fn get_invalid(&self) -> &Style {
160184
&self.invalid
161185
}
186+
187+
/// Highlight all specified contexts, e.g. `[default: false]`
188+
///
189+
/// To specialize the style of the value within the context, see [`Styles::context_value`]
190+
#[inline(always)]
191+
pub const fn get_context(&self) -> &Style {
192+
&self.context
193+
}
194+
195+
/// Highlight values within all of the context, e.g. the `false` in `[default: false]`
196+
///
197+
/// If not explicitly set, falls back to `context`'s style.
198+
#[inline(always)]
199+
pub const fn get_context_value(&self) -> &Style {
200+
match &self.context_value {
201+
Some(s) => s,
202+
None => &self.context,
203+
}
204+
}
162205
}
163206

164207
impl super::AppExt for Styles {}

clap_builder/src/output/help_template.rs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,10 @@ impl HelpTemplate<'_, '_> {
739739

740740
fn spec_vals(&self, a: &Arg) -> String {
741741
debug!("HelpTemplate::spec_vals: a={a}");
742+
let ctx = &self.styles.get_context();
743+
let ctx_val = &self.styles.get_context_value();
744+
let val_sep = format!("{ctx}, {ctx:#}"); // context values styled separator
745+
742746
let mut spec_vals = Vec::new();
743747
#[cfg(feature = "env")]
744748
if let Some(ref env) = a.env {
@@ -758,7 +762,11 @@ impl HelpTemplate<'_, '_> {
758762
} else {
759763
Default::default()
760764
};
761-
let env_info = format!("[env: {}{}]", env.0.to_string_lossy(), env_val);
765+
let env_info = format!(
766+
"{ctx}[env: {ctx:#}{ctx_val}{}{}{ctx_val:#}{ctx}]{ctx:#}",
767+
env.0.to_string_lossy(),
768+
env_val
769+
);
762770
spec_vals.push(env_info);
763771
}
764772
}
@@ -768,21 +776,23 @@ impl HelpTemplate<'_, '_> {
768776
a.default_vals
769777
);
770778

771-
let pvs = a
779+
let dvs = a
772780
.default_vals
773781
.iter()
774-
.map(|pvs| pvs.to_string_lossy())
775-
.map(|pvs| {
776-
if pvs.contains(char::is_whitespace) {
777-
Cow::from(format!("{pvs:?}"))
782+
.map(|dv| dv.to_string_lossy())
783+
.map(|dv| {
784+
if dv.contains(char::is_whitespace) {
785+
Cow::from(format!("{dv:?}"))
778786
} else {
779-
pvs
787+
dv
780788
}
781789
})
782790
.collect::<Vec<_>>()
783791
.join(" ");
784792

785-
spec_vals.push(format!("[default: {pvs}]"));
793+
spec_vals.push(format!(
794+
"{ctx}[default: {ctx:#}{ctx_val}{dvs}{ctx_val:#}{ctx}]{ctx:#}"
795+
));
786796
}
787797

788798
let mut als = Vec::new();
@@ -791,7 +801,7 @@ impl HelpTemplate<'_, '_> {
791801
.short_aliases
792802
.iter()
793803
.filter(|&als| als.1) // visible
794-
.map(|als| format!("-{}", als.0)); // name
804+
.map(|als| format!("{ctx_val}-{}{ctx_val:#}", als.0)); // name
795805
debug!(
796806
"HelpTemplate::spec_vals: Found short aliases...{:?}",
797807
a.short_aliases
@@ -802,12 +812,13 @@ impl HelpTemplate<'_, '_> {
802812
.aliases
803813
.iter()
804814
.filter(|&als| als.1) // visible
805-
.map(|als| format!("--{}", als.0)); // name
815+
.map(|als| format!("{ctx_val}--{}{ctx_val:#}", als.0)); // name
806816
debug!("HelpTemplate::spec_vals: Found aliases...{:?}", a.aliases);
807817
als.extend(long_als);
808818

809819
if !als.is_empty() {
810-
spec_vals.push(format!("[aliases: {}]", als.join(", ")));
820+
let als = als.join(&val_sep);
821+
spec_vals.push(format!("{ctx}[aliases: {ctx:#}{als}{ctx}]{ctx:#}"));
811822
}
812823

813824
if !a.is_hide_possible_values_set() && !self.use_long_pv(a) {
@@ -818,10 +829,11 @@ impl HelpTemplate<'_, '_> {
818829
let pvs = possible_vals
819830
.iter()
820831
.filter_map(PossibleValue::get_visible_quoted_name)
832+
.map(|pv| format!("{ctx_val}{pv}{ctx_val:#}"))
821833
.collect::<Vec<_>>()
822-
.join(", ");
834+
.join(&val_sep);
823835

824-
spec_vals.push(format!("[possible values: {pvs}]"));
836+
spec_vals.push(format!("{ctx}[possible values: {ctx:#}{pvs}{ctx}]{ctx:#}"));
825837
}
826838
}
827839
let connector = if self.use_long { "\n" } else { " " };
@@ -984,15 +996,20 @@ impl HelpTemplate<'_, '_> {
984996

985997
fn sc_spec_vals(&self, a: &Command) -> String {
986998
debug!("HelpTemplate::sc_spec_vals: a={}", a.get_name());
999+
let ctx = &self.styles.get_context();
1000+
let ctx_val = &self.styles.get_context_value();
1001+
let val_sep = format!("{ctx}, {ctx:#}"); // context values styled separator
9871002
let mut spec_vals = vec![];
9881003

9891004
let mut short_als = a
9901005
.get_visible_short_flag_aliases()
991-
.map(|a| format!("-{a}"))
1006+
.map(|a| format!("{ctx_val}-{a}{ctx_val:#}"))
9921007
.collect::<Vec<_>>();
993-
let als = a.get_visible_aliases().map(|s| s.to_string());
1008+
let als = a
1009+
.get_visible_aliases()
1010+
.map(|s| format!("{ctx_val}{s}{ctx_val:#}"));
9941011
short_als.extend(als);
995-
let all_als = short_als.join(", ");
1012+
let all_als = short_als.join(&val_sep);
9961013
if !all_als.is_empty() {
9971014
debug!(
9981015
"HelpTemplate::spec_vals: Found aliases...{:?}",
@@ -1002,7 +1019,7 @@ impl HelpTemplate<'_, '_> {
10021019
"HelpTemplate::spec_vals: Found short flag aliases...{:?}",
10031020
a.get_all_short_flag_aliases().collect::<Vec<_>>()
10041021
);
1005-
spec_vals.push(format!("[aliases: {all_als}]"));
1022+
spec_vals.push(format!("{ctx}[aliases: {ctx:#}{all_als}{ctx}]{ctx:#}"));
10061023
}
10071024

10081025
spec_vals.join(" ")

src/bin/stdio-fixture.rs

Lines changed: 90 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,105 @@
1+
use clap::{builder::PossibleValue, Arg, ArgAction, Command};
2+
13
fn main() {
24
#[allow(unused_mut)]
3-
let mut cmd = clap::Command::new("stdio-fixture")
5+
let mut cmd = Command::new("stdio-fixture")
46
.version("1.0")
57
.long_version("1.0 - a2132c")
68
.arg_required_else_help(true)
7-
.subcommand(clap::Command::new("more"))
9+
.subcommand(Command::new("more"))
10+
.subcommand(
11+
Command::new("test")
12+
.visible_alias("do-stuff")
13+
.long_about("Subcommand with one visible alias"),
14+
)
15+
.subcommand(
16+
Command::new("test_2")
17+
.visible_aliases(["do-other-stuff", "tests"])
18+
.about("several visible aliases")
19+
.long_about("Subcommand with multiple visible aliases"),
20+
)
21+
.subcommand(
22+
Command::new("test_3")
23+
.long_flag("test")
24+
.about("several visible long flag aliases")
25+
.visible_long_flag_aliases(["testing", "testall", "test_all"]),
26+
)
27+
.subcommand(
28+
Command::new("test_4")
29+
.short_flag('t')
30+
.about("several visible short flag aliases")
31+
.visible_short_flag_aliases(['q', 'w']),
32+
)
33+
.subcommand(
34+
Command::new("test_5")
35+
.short_flag('e')
36+
.long_flag("test-hdr")
37+
.about("all kinds of visible aliases")
38+
.visible_aliases(["tests_4k"])
39+
.visible_long_flag_aliases(["thetests", "t4k"])
40+
.visible_short_flag_aliases(['r', 'y']),
41+
)
842
.arg(
9-
clap::Arg::new("verbose")
43+
Arg::new("verbose")
1044
.long("verbose")
1145
.help("log")
12-
.action(clap::ArgAction::SetTrue)
46+
.action(ArgAction::SetTrue)
1347
.long_help("more log"),
48+
)
49+
.arg(
50+
Arg::new("config")
51+
.action(ArgAction::Set)
52+
.help("Speed configuration")
53+
.short('c')
54+
.long("config")
55+
.value_name("MODE")
56+
.value_parser([
57+
PossibleValue::new("fast"),
58+
PossibleValue::new("slow").help("slower than fast"),
59+
PossibleValue::new("secret speed").hide(true),
60+
])
61+
.default_value("fast"),
62+
)
63+
.arg(
64+
Arg::new("name")
65+
.action(ArgAction::Set)
66+
.help("App name")
67+
.long_help("Set the instance app name")
68+
.value_name("NAME")
69+
.visible_alias("app-name")
70+
.default_value("clap"),
71+
)
72+
.arg(
73+
Arg::new("fruits")
74+
.short('f')
75+
.visible_short_alias('b')
76+
.action(ArgAction::Append)
77+
.value_name("FRUITS")
78+
.help("List of fruits")
79+
.default_values(["apple", "banane", "orange"]),
1480
);
81+
#[cfg(feature = "env")]
82+
{
83+
cmd = cmd.arg(
84+
Arg::new("env_arg")
85+
.help("Read from env var when arg is not present.")
86+
.value_name("ENV")
87+
.env("ENV_ARG"),
88+
);
89+
}
1590
#[cfg(feature = "color")]
1691
{
17-
use clap::builder::styling;
18-
const STYLES: styling::Styles = styling::Styles::styled()
19-
.header(styling::AnsiColor::Green.on_default().bold())
20-
.usage(styling::AnsiColor::Green.on_default().bold())
21-
.literal(styling::AnsiColor::Blue.on_default().bold())
22-
.placeholder(styling::AnsiColor::Cyan.on_default());
92+
use clap::builder::styling::{AnsiColor, Styles};
93+
const STYLES: Styles = Styles::styled()
94+
.header(AnsiColor::Green.on_default().bold())
95+
.error(AnsiColor::Red.on_default().bold())
96+
.usage(AnsiColor::Green.on_default().bold().underline())
97+
.literal(AnsiColor::Blue.on_default().bold())
98+
.placeholder(AnsiColor::Cyan.on_default())
99+
.valid(AnsiColor::Green.on_default())
100+
.invalid(AnsiColor::Magenta.on_default().bold())
101+
.context(AnsiColor::Yellow.on_default().dimmed())
102+
.context_value(AnsiColor::Yellow.on_default().italic());
23103
cmd = cmd.styles(STYLES);
24104
}
25105
cmd.get_matches();

tests/ui/arg_required_else_help_stderr.toml

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,25 @@ args = []
33
status.code = 2
44
stdout = ""
55
stderr = """
6-
Usage: stdio-fixture[EXE] [OPTIONS] [COMMAND]
6+
Usage: stdio-fixture[EXE] [OPTIONS] [NAME] [ENV] [COMMAND]
77
88
Commands:
9-
more
10-
help Print this message or the help of the given subcommand(s)
9+
more
10+
test Subcommand with one visible alias [aliases: do-stuff]
11+
test_2 several visible aliases [aliases: do-other-stuff, tests]
12+
test_3, --test several visible long flag aliases
13+
test_4, -t several visible short flag aliases [aliases: -q, -w]
14+
test_5, -e, --test-hdr all kinds of visible aliases [aliases: -r, -y, tests_4k]
15+
help Print this message or the help of the given subcommand(s)
16+
17+
Arguments:
18+
[NAME] App name [default: clap] [aliases: --app-name]
19+
[ENV] Read from env var when arg is not present. [env: ENV_ARG=]
1120
1221
Options:
13-
--verbose log
14-
-h, --help Print help (see more with '--help')
15-
-V, --version Print version
22+
--verbose log
23+
-c, --config <MODE> Speed configuration [default: fast] [possible values: fast, slow]
24+
-f <FRUITS> List of fruits [default: apple banane orange] [aliases: -b]
25+
-h, --help Print help (see more with '--help')
26+
-V, --version Print version
1627
"""

tests/ui/error_stderr.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ stdout = ""
55
stderr = """
66
error: unexpected argument '--unknown-argument' found
77
8-
Usage: stdio-fixture[EXE] [OPTIONS] [COMMAND]
8+
tip: to pass '--unknown-argument' as a value, use '-- --unknown-argument'
9+
10+
Usage: stdio-fixture[EXE] [OPTIONS] [NAME] [ENV] [COMMAND]
911
1012
For more information, try '--help'.
1113
"""

tests/ui/h_flag_stdout.toml

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,26 @@ bin.name = "stdio-fixture"
22
args = ["-h"]
33
status.code = 0
44
stdout = """
5-
Usage: stdio-fixture[EXE] [OPTIONS] [COMMAND]
5+
Usage: stdio-fixture[EXE] [OPTIONS] [NAME] [ENV] [COMMAND]
66
77
Commands:
8-
more
9-
help Print this message or the help of the given subcommand(s)
8+
more
9+
test Subcommand with one visible alias [aliases: do-stuff]
10+
test_2 several visible aliases [aliases: do-other-stuff, tests]
11+
test_3, --test several visible long flag aliases
12+
test_4, -t several visible short flag aliases [aliases: -q, -w]
13+
test_5, -e, --test-hdr all kinds of visible aliases [aliases: -r, -y, tests_4k]
14+
help Print this message or the help of the given subcommand(s)
15+
16+
Arguments:
17+
[NAME] App name [default: clap] [aliases: --app-name]
18+
[ENV] Read from env var when arg is not present. [env: ENV_ARG=]
1019
1120
Options:
12-
--verbose log
13-
-h, --help Print help (see more with '--help')
14-
-V, --version Print version
21+
--verbose log
22+
-c, --config <MODE> Speed configuration [default: fast] [possible values: fast, slow]
23+
-f <FRUITS> List of fruits [default: apple banane orange] [aliases: -b]
24+
-h, --help Print help (see more with '--help')
25+
-V, --version Print version
1526
"""
1627
stderr = ""

0 commit comments

Comments
 (0)