Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible Value help #3503

Merged
merged 8 commits into from Mar 2, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Expand Up @@ -28,7 +28,7 @@ jobs:
name: Test
strategy:
matrix:
build: [linux, windows, mac, minimal, default]
build: [linux, windows, mac, minimal, default, next]
include:
- build: linux
os: ubuntu-latest
Expand All @@ -50,6 +50,10 @@ jobs:
os: ubuntu-latest
rust: "stable"
features: "default"
- build: next
os: ubuntu-latest
rust: "stable"
features: "next"
continue-on-error: ${{ matrix.rust != 'stable' }}
runs-on: ${{ matrix.os }}
steps:
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/rust-next.yml
Expand Up @@ -7,7 +7,7 @@ jobs:
name: Test
strategy:
matrix:
build: [stable, linux, windows, mac, nightly, minimal, default]
build: [stable, linux, windows, mac, nightly, minimal, default, next]
include:
- build: stable
os: ubuntu-latest
Expand Down Expand Up @@ -37,6 +37,10 @@ jobs:
os: ubuntu-latest
rust: "stable"
features: "default"
- build: next
os: ubuntu-latest
rust: "stable"
features: "next"
continue-on-error: ${{ matrix.rust != 'stable' }}
runs-on: ${{ matrix.os }}
steps:
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -81,6 +81,7 @@ unicode = ["textwrap/unicode-width", "unicase"] # Support for unicode character
unstable-replace = []
unstable-multicall = []
unstable-grouped = []
unstable-v4 = []

[lib]
bench = false
Expand Down
1 change: 1 addition & 0 deletions Makefile
Expand Up @@ -15,6 +15,7 @@ _FEATURES_minimal = --no-default-features --features "std"
_FEATURES_default =
_FEATURES_wasm = --features "derive cargo env unicode yaml regex unstable-replace unstable-multicall unstable-grouped"
_FEATURES_full = --features "derive cargo env unicode yaml regex unstable-replace unstable-multicall unstable-grouped wrap_help"
_FEATURES_next = ${_FEATURES_full} --features unstable-v4
ModProg marked this conversation as resolved.
Show resolved Hide resolved
_FEATURES_debug = ${_FEATURES_full} --features debug
_FEATURES_release = ${_FEATURES_full} --release

Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -153,6 +153,7 @@ Why use the procedural [Builder API](https://github.com/clap-rs/clap/blob/v3.1.1
* **unstable-replace**: Enable [`Command::replace`](https://github.com/clap-rs/clap/issues/2836)
* **unstable-multicall**: Enable [`Command::multicall`](https://github.com/clap-rs/clap/issues/2861)
* **unstable-grouped**: Enable [`ArgMatches::grouped_values_of`](https://github.com/clap-rs/clap/issues/2924)
* **unstable-v4**: Show help messages for possible values in long help [#3312](https://github.com/clap-rs/clap/issues/3312)
ModProg marked this conversation as resolved.
Show resolved Hide resolved

## Sponsors

Expand Down
5 changes: 5 additions & 0 deletions src/build/possible_value.rs
Expand Up @@ -161,6 +161,11 @@ impl<'help> PossibleValue<'help> {
self.hide
}

/// Report if PossibleValue is not hidden and has a help message
pub fn should_show_help(&self) -> bool {
!self.hide && self.help.is_some()
}

/// Get the name if argument value is not hidden, `None` otherwise
pub fn get_visible_name(&self) -> Option<&'help str> {
if self.hide {
Expand Down
116 changes: 98 additions & 18 deletions src/output/help.rs
Expand Up @@ -11,6 +11,7 @@ use std::{
use crate::{
build::{display_arg_val, Arg, Command},
output::{fmt::Colorizer, Usage},
PossibleValue,
};

// Third party
Expand Down Expand Up @@ -385,7 +386,7 @@ impl<'help, 'cmd, 'writer> Help<'help, 'cmd, 'writer> {
/// Writes argument's help to the wrapped stream.
fn help(
&mut self,
is_not_positional: bool,
arg: Option<&Arg<'help>>,
about: &str,
spec_vals: &str,
next_line_help: bool,
Expand All @@ -401,7 +402,7 @@ impl<'help, 'cmd, 'writer> Help<'help, 'cmd, 'writer> {
longest + 12
};

let too_long = spaces + display_width(about) + display_width(spec_vals) >= self.term_w;
let too_long = spaces + display_width(&help) >= self.term_w;

// Is help on next line, if so then indent
if next_line_help {
Expand All @@ -423,17 +424,96 @@ impl<'help, 'cmd, 'writer> Help<'help, 'cmd, 'writer> {
if let Some(part) = help.lines().next() {
self.none(part)?;
}

// indent of help
let spaces = if next_line_help {
TAB_WIDTH * 3
} else if let Some(true) = arg.map(|a| a.is_positional()) {
longest + TAB_WIDTH * 2
} else {
longest + TAB_WIDTH * 3
};

for part in help.lines().skip(1) {
self.none("\n")?;
if next_line_help {
self.none(format!("{}{}{}", TAB, TAB, TAB))?;
} else if is_not_positional {
self.spaces(longest + 12)?;
} else {
self.spaces(longest + 8)?;
}
self.spaces(spaces)?;
self.none(part)?;
}

#[cfg(feature = "unstable-v4")]
if let Some(arg) = arg {
if self.use_long
&& arg
.possible_vals
.iter()
.any(PossibleValue::should_show_help)
{
ModProg marked this conversation as resolved.
Show resolved Hide resolved
debug!("Help::help: Found possible vals...{:?}", arg.possible_vals);
if !help.is_empty() {
self.none("\n\n")?;
self.spaces(spaces)?;
}
self.none("Possible values:")?;
let longest = arg
.possible_vals
.iter()
.filter_map(|f| f.get_visible_name().map(display_width))
.max()
.expect("Only called with possible value");
let help_longest = arg
.possible_vals
.iter()
.filter_map(|f| f.get_help().map(display_width))
.max()
.expect("Only called with possible value with help");
// should new line
let taken = longest + spaces + 2; // 2 = "- "
let possible_value_new_line = self.term_w >= taken
&& (taken as f32 / self.term_w as f32) > 0.60
&& help_longest + 2 // 2 = ": "
> (self.term_w - taken);

let spaces = spaces + TAB_WIDTH;
let spaces_help = if possible_value_new_line {
spaces + TAB_WIDTH
} else {
spaces + longest + 4 // 2 = "- " + ": "
};

for pv in arg.possible_vals.iter().filter(|pv| !pv.is_hide_set()) {
self.none("\n")?;
self.spaces(spaces)?;
self.none("- ")?;
self.good(pv.get_name())?;
if let Some(help) = pv.get_help() {
debug!("Help::help: Possible Value help");

if possible_value_new_line {
self.none(":\n")?;
self.spaces(spaces_help)?;
} else {
self.none(": ")?;
}

let avail_chars = if self.term_w > spaces_help {
self.term_w - spaces_help
} else {
usize::MAX
};

let help = text_wrapper(help, avail_chars);
let mut help = help.lines();

self.none(help.next().unwrap_or_default())?;
for part in help {
self.none("\n")?;
self.spaces(spaces_help)?;
self.none(part)?;
}
}
}
}
}
Ok(())
}

Expand All @@ -456,13 +536,7 @@ impl<'help, 'cmd, 'writer> Help<'help, 'cmd, 'writer> {
arg.help.or(arg.long_help).unwrap_or("")
};

self.help(
!arg.is_positional(),
about,
spec_vals,
next_line_help,
longest,
)?;
self.help(Some(arg), about, spec_vals, next_line_help, longest)?;
Ok(())
}

Expand Down Expand Up @@ -572,7 +646,12 @@ impl<'help, 'cmd, 'writer> Help<'help, 'cmd, 'writer> {
}
}

if !a.is_hide_possible_values_set() && !a.possible_vals.is_empty() {
if !(a.is_hide_possible_values_set()
|| a.possible_vals.is_empty()
|| cfg!(feature = "unstable-v4")
&& self.use_long
&& a.possible_vals.iter().any(PossibleValue::should_show_help))
{
debug!(
"Help::spec_vals: Found possible vals...{:?}",
a.possible_vals
Expand Down Expand Up @@ -670,7 +749,7 @@ impl<'help, 'cmd, 'writer> Help<'help, 'cmd, 'writer> {
.unwrap_or("");

self.subcmd(sc_str, next_line_help, longest)?;
self.help(false, about, spec_vals, next_line_help, longest)
self.help(None, about, spec_vals, next_line_help, longest)
epage marked this conversation as resolved.
Show resolved Hide resolved
}

fn sc_spec_vals(&self, a: &Command) -> String {
Expand Down Expand Up @@ -1011,6 +1090,7 @@ pub(crate) fn dimensions() -> Option<(usize, usize)> {
}

const TAB: &str = " ";
const TAB_WIDTH: usize = 4;

pub(crate) enum HelpWriter<'writer> {
Normal(&'writer mut dyn Write),
Expand Down
8 changes: 6 additions & 2 deletions src/parse/parser.rs
Expand Up @@ -8,7 +8,6 @@ use std::{
use os_str_bytes::RawOsStr;

// Internal
use crate::build::AppSettings as AS;
use crate::build::{Arg, Command};
use crate::error::Error as ClapError;
use crate::error::Result as ClapResult;
Expand All @@ -18,6 +17,7 @@ use crate::parse::features::suggestions;
use crate::parse::{ArgMatcher, SubCommand};
use crate::parse::{Validator, ValueSource};
use crate::util::{color::ColorChoice, Id};
use crate::{build::AppSettings as AS, PossibleValue};
use crate::{INTERNAL_ERROR_MSG, INVALID_UTF8};

pub(crate) struct Parser<'help, 'cmd> {
Expand Down Expand Up @@ -786,7 +786,11 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
// specified by the user is sent through. If hide_short_help is not included,
// then items specified with hidden_short_help will also be hidden.
let should_long = |v: &Arg| {
v.long_help.is_some() || v.is_hide_long_help_set() || v.is_hide_short_help_set()
v.long_help.is_some()
|| v.is_hide_long_help_set()
|| v.is_hide_short_help_set()
|| cfg!(feature = "unstable-v4")
&& v.possible_vals.iter().any(PossibleValue::should_show_help)
};

// Subcommands aren't checked because we prefer short help for them, deferring to
Expand Down