mirror of
https://github.com/clap-rs/clap
synced 2024-11-10 06:44:16 +00:00
Merge pull request #4114 from epage/wrap
feat(help): Open the door for user styling in the future
This commit is contained in:
commit
f2e5b0670a
32 changed files with 1293 additions and 832 deletions
68
Cargo.lock
generated
68
Cargo.lock
generated
|
@ -113,7 +113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"textwrap 0.11.0",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
|
@ -133,11 +133,12 @@ dependencies = [
|
|||
"static_assertions",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"terminal_size 0.2.1",
|
||||
"textwrap 0.15.0",
|
||||
"terminal_size",
|
||||
"trybuild",
|
||||
"trycmd",
|
||||
"unic-emoji-char",
|
||||
"unicase",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -929,16 +930,6 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.2.1"
|
||||
|
@ -958,16 +949,6 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
dependencies = [
|
||||
"terminal_size 0.1.17",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
|
@ -1031,6 +1012,47 @@ dependencies = [
|
|||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-char-property"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
|
||||
dependencies = [
|
||||
"unic-char-range",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-char-range"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
|
||||
|
||||
[[package]]
|
||||
name = "unic-common"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
|
||||
|
||||
[[package]]
|
||||
name = "unic-emoji-char"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d"
|
||||
dependencies = [
|
||||
"unic-char-property",
|
||||
"unic-char-range",
|
||||
"unic-ucd-version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-ucd-version"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
|
||||
dependencies = [
|
||||
"unic-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.6.0"
|
||||
|
|
|
@ -71,9 +71,9 @@ suggestions = ["dep:strsim"]
|
|||
deprecated = ["clap_derive?/deprecated"] # Guided experience to prepare for next breaking release (at different stages of development, this may become default)
|
||||
derive = ["clap_derive"]
|
||||
cargo = [] # Disable if you're not using Cargo, enables Cargo-env-var-dependent macros
|
||||
wrap_help = ["dep:terminal_size", "textwrap/terminal_size"]
|
||||
wrap_help = ["dep:terminal_size"]
|
||||
env = [] # Use environment variables during arg parsing
|
||||
unicode = ["textwrap/unicode-width", "dep:unicase"] # Support for unicode characters in arguments and help messages
|
||||
unicode = ["dep:unicode-width", "dep:unicase"] # Support for unicode characters in arguments and help messages
|
||||
perf = [] # Optimize for runtime performance
|
||||
|
||||
# In-work features
|
||||
|
@ -88,13 +88,13 @@ bench = false
|
|||
clap_derive = { path = "./clap_derive", version = "=4.0.0-alpha.0", optional = true }
|
||||
clap_lex = { path = "./clap_lex", version = "0.2.2" }
|
||||
bitflags = "1.2"
|
||||
textwrap = { version = "0.15.0", default-features = false, features = [] }
|
||||
unicase = { version = "2.6", optional = true }
|
||||
strsim = { version = "0.10", optional = true }
|
||||
atty = { version = "0.2", optional = true }
|
||||
termcolor = { version = "1.1.1", optional = true }
|
||||
terminal_size = { version = "0.2.1", optional = true }
|
||||
backtrace = { version = "0.3", optional = true }
|
||||
unicode-width = { version = "0.1.9", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0.18"
|
||||
|
@ -105,6 +105,7 @@ humantime = "2"
|
|||
snapbox = "0.2"
|
||||
shlex = "1.1.0"
|
||||
static_assertions = "1.1.0"
|
||||
unic-emoji-char = "0.9.0"
|
||||
|
||||
[[example]]
|
||||
name = "demo"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::io::Write;
|
||||
|
||||
use clap::builder::StyledStr;
|
||||
use clap::*;
|
||||
|
||||
use crate::generator::{utils, Generator};
|
||||
|
@ -59,9 +60,9 @@ fn escape_string(string: &str) -> String {
|
|||
string.replace('\'', "''")
|
||||
}
|
||||
|
||||
fn get_tooltip<T: ToString>(help: Option<&str>, data: T) -> String {
|
||||
fn get_tooltip<T: ToString>(help: Option<&StyledStr>, data: T) -> String {
|
||||
match help {
|
||||
Some(help) => escape_string(help),
|
||||
Some(help) => escape_string(&help.to_string()),
|
||||
_ => data.to_string(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ fn gen_fish_inner(
|
|||
}
|
||||
|
||||
if let Some(data) = option.get_help() {
|
||||
template.push_str(format!(" -d '{}'", escape_string(data)).as_str());
|
||||
template.push_str(format!(" -d '{}'", escape_string(&data.to_string())).as_str());
|
||||
}
|
||||
|
||||
template.push_str(value_completion(option).as_str());
|
||||
|
@ -118,7 +118,7 @@ fn gen_fish_inner(
|
|||
}
|
||||
|
||||
if let Some(data) = flag.get_help() {
|
||||
template.push_str(format!(" -d '{}'", escape_string(data)).as_str());
|
||||
template.push_str(format!(" -d '{}'", escape_string(&data.to_string())).as_str());
|
||||
}
|
||||
|
||||
buffer.push_str(template.as_str());
|
||||
|
@ -132,7 +132,7 @@ fn gen_fish_inner(
|
|||
template.push_str(format!(" -a \"{}\"", &subcommand.get_name()).as_str());
|
||||
|
||||
if let Some(data) = subcommand.get_about() {
|
||||
template.push_str(format!(" -d '{}'", escape_string(data)).as_str())
|
||||
template.push_str(format!(" -d '{}'", escape_string(&data.to_string())).as_str())
|
||||
}
|
||||
|
||||
buffer.push_str(template.as_str());
|
||||
|
@ -164,7 +164,7 @@ fn value_completion(option: &Arg) -> String {
|
|||
Some(format!(
|
||||
"{}\t{}",
|
||||
escape_string(value.get_name()).as_str(),
|
||||
escape_string(value.get_help().unwrap_or_default()).as_str()
|
||||
escape_string(&value.get_help().unwrap_or_default().to_string())
|
||||
))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::io::Write;
|
||||
|
||||
use clap::builder::StyledStr;
|
||||
use clap::*;
|
||||
|
||||
use crate::generator::{utils, Generator};
|
||||
|
@ -64,9 +65,9 @@ fn escape_string(string: &str) -> String {
|
|||
string.replace('\'', "''")
|
||||
}
|
||||
|
||||
fn get_tooltip<T: ToString>(help: Option<&str>, data: T) -> String {
|
||||
fn get_tooltip<T: ToString>(help: Option<&StyledStr>, data: T) -> String {
|
||||
match help {
|
||||
Some(help) => escape_string(help),
|
||||
Some(help) => escape_string(&help.to_string()),
|
||||
_ => data.to_string(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -153,7 +153,7 @@ fn subcommands_of(p: &Command) -> String {
|
|||
let text = format!(
|
||||
"'{name}:{help}' \\",
|
||||
name = name,
|
||||
help = escape_help(subcommand.get_about().unwrap_or_default())
|
||||
help = escape_help(&subcommand.get_about().unwrap_or_default().to_string())
|
||||
);
|
||||
|
||||
if !text.is_empty() {
|
||||
|
@ -372,7 +372,8 @@ fn value_completion(arg: &Arg) -> Option<String> {
|
|||
Some(format!(
|
||||
r#"{name}\:"{tooltip}""#,
|
||||
name = escape_value(value.get_name()),
|
||||
tooltip = value.get_help().map(escape_help).unwrap_or_default()
|
||||
tooltip =
|
||||
escape_help(&value.get_help().unwrap_or_default().to_string()),
|
||||
))
|
||||
}
|
||||
})
|
||||
|
@ -445,7 +446,7 @@ fn write_opts_of(p: &Command, p_global: Option<&Command>) -> String {
|
|||
for o in p.get_opts() {
|
||||
debug!("write_opts_of:iter: o={}", o.get_id());
|
||||
|
||||
let help = o.get_help().map(escape_help).unwrap_or_default();
|
||||
let help = escape_help(&o.get_help().unwrap_or_default().to_string());
|
||||
let conflicts = arg_conflicts(p, o, p_global);
|
||||
|
||||
let multiple = "*";
|
||||
|
@ -541,7 +542,7 @@ fn write_flags_of(p: &Command, p_global: Option<&Command>) -> String {
|
|||
for f in utils::flags(p) {
|
||||
debug!("write_flags_of:iter: f={}", f.get_id());
|
||||
|
||||
let help = f.get_help().map(escape_help).unwrap_or_default();
|
||||
let help = escape_help(&f.get_help().unwrap_or_default().to_string());
|
||||
let conflicts = arg_conflicts(p, &f, p_global);
|
||||
|
||||
let multiple = "*";
|
||||
|
@ -634,7 +635,8 @@ fn write_positionals_of(p: &Command) -> String {
|
|||
name = arg.get_id(),
|
||||
help = arg
|
||||
.get_help()
|
||||
.map_or("".to_owned(), |v| " -- ".to_owned() + v)
|
||||
.map(|s| s.to_string())
|
||||
.map_or("".to_owned(), |v| " -- ".to_owned() + &v)
|
||||
.replace('[', "\\[")
|
||||
.replace(']', "\\]")
|
||||
.replace('\'', "'\\''")
|
||||
|
|
|
@ -39,7 +39,7 @@ _my-app() {
|
|||
return 0
|
||||
;;
|
||||
my__app__help)
|
||||
opts="-c [<SUBCOMMAND>...]"
|
||||
opts="-c [SUBCOMMAND]..."
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
|
|
|
@ -39,7 +39,7 @@ _my-app() {
|
|||
return 0
|
||||
;;
|
||||
my__app__help)
|
||||
opts="[<SUBCOMMAND>...]"
|
||||
opts="[SUBCOMMAND]..."
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
|
|
|
@ -138,7 +138,7 @@ _my-app() {
|
|||
return 0
|
||||
;;
|
||||
my__app__help)
|
||||
opts="[<SUBCOMMAND>...]"
|
||||
opts="[SUBCOMMAND]..."
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
|
|
|
@ -48,7 +48,7 @@ _my-app() {
|
|||
return 0
|
||||
;;
|
||||
my__app__help)
|
||||
opts="[<SUBCOMMAND>...]"
|
||||
opts="[SUBCOMMAND]..."
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
|
|
|
@ -45,7 +45,7 @@ _my-app() {
|
|||
return 0
|
||||
;;
|
||||
my__app__help)
|
||||
opts="[<SUBCOMMAND>...]"
|
||||
opts="[SUBCOMMAND]..."
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
|
@ -73,7 +73,7 @@ _my-app() {
|
|||
return 0
|
||||
;;
|
||||
my__app__some_cmd__help)
|
||||
opts="[<SUBCOMMAND>...]"
|
||||
opts="[SUBCOMMAND]..."
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
|
|
|
@ -28,7 +28,7 @@ impl Generator for Fig {
|
|||
write!(
|
||||
&mut buffer,
|
||||
" description: \"{}\",\n",
|
||||
escape_string(cmd.get_about().unwrap_or_default())
|
||||
escape_string(&cmd.get_about().unwrap_or_default().to_string())
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -101,7 +101,7 @@ fn gen_fig_inner(
|
|||
buffer,
|
||||
"{:indent$}description: \"{}\",\n",
|
||||
"",
|
||||
escape_string(data),
|
||||
escape_string(&data.to_string()),
|
||||
indent = indent + 4
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -205,7 +205,7 @@ fn gen_options(cmd: &Command, indent: usize) -> String {
|
|||
&mut buffer,
|
||||
"{:indent$}description: \"{}\",\n",
|
||||
"",
|
||||
escape_string(data),
|
||||
escape_string(&data.to_string()),
|
||||
indent = indent + 4
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -314,7 +314,7 @@ fn gen_options(cmd: &Command, indent: usize) -> String {
|
|||
&mut buffer,
|
||||
"{:indent$}description: \"{}\",\n",
|
||||
"",
|
||||
escape_string(data).as_str(),
|
||||
escape_string(&data.to_string()).as_str(),
|
||||
indent = indent + 4
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -426,7 +426,7 @@ fn gen_args(arg: &Arg, indent: usize) -> String {
|
|||
&mut buffer,
|
||||
"{:indent$}description: \"{}\",\n",
|
||||
"",
|
||||
escape_string(help),
|
||||
escape_string(&help.to_string()),
|
||||
indent = indent + 6
|
||||
)
|
||||
.unwrap();
|
||||
|
|
|
@ -17,7 +17,7 @@ pub(crate) fn about(roff: &mut Roff, cmd: &clap::Command) {
|
|||
|
||||
pub(crate) fn description(roff: &mut Roff, cmd: &clap::Command) {
|
||||
if let Some(about) = cmd.get_long_about().or_else(|| cmd.get_about()) {
|
||||
for line in about.lines() {
|
||||
for line in about.to_string().lines() {
|
||||
if line.trim().is_empty() {
|
||||
roff.control("PP", []);
|
||||
} else {
|
||||
|
@ -110,7 +110,7 @@ pub(crate) fn options(roff: &mut Roff, cmd: &clap::Command) {
|
|||
let mut arg_help_written = false;
|
||||
if let Some(help) = opt.get_long_help().or_else(|| opt.get_help()) {
|
||||
arg_help_written = true;
|
||||
body.push(roman(help));
|
||||
body.push(roman(help.to_string()));
|
||||
}
|
||||
|
||||
roff.control("TP", []);
|
||||
|
@ -224,7 +224,7 @@ pub(crate) fn subcommands(roff: &mut Roff, cmd: &clap::Command, section: &str) {
|
|||
roff.text([roman(&name)]);
|
||||
|
||||
if let Some(about) = sub.get_about().or_else(|| sub.get_long_about()) {
|
||||
for line in about.lines() {
|
||||
for line in about.to_string().lines() {
|
||||
roff.text([roman(line)]);
|
||||
}
|
||||
}
|
||||
|
@ -242,7 +242,7 @@ pub(crate) fn version(cmd: &clap::Command) -> String {
|
|||
|
||||
pub(crate) fn after_help(roff: &mut Roff, cmd: &clap::Command) {
|
||||
if let Some(about) = cmd.get_after_long_help().or_else(|| cmd.get_after_help()) {
|
||||
for line in about.lines() {
|
||||
for line in about.to_string().lines() {
|
||||
roff.text([roman(line)]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ use std::env;
|
|||
#[cfg(feature = "env")]
|
||||
use std::ffi::OsString;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cmp::{Ord, Ordering},
|
||||
fmt::{self, Display, Formatter},
|
||||
str,
|
||||
|
@ -17,6 +16,7 @@ use crate::builder::IntoResettable;
|
|||
use crate::builder::OsStr;
|
||||
use crate::builder::PossibleValue;
|
||||
use crate::builder::Str;
|
||||
use crate::builder::StyledStr;
|
||||
use crate::builder::ValueRange;
|
||||
use crate::ArgAction;
|
||||
use crate::Id;
|
||||
|
@ -54,8 +54,8 @@ use crate::INTERNAL_ERROR_MSG;
|
|||
#[derive(Default, Clone)]
|
||||
pub struct Arg {
|
||||
pub(crate) id: Id,
|
||||
pub(crate) help: Option<Str>,
|
||||
pub(crate) long_help: Option<Str>,
|
||||
pub(crate) help: Option<StyledStr>,
|
||||
pub(crate) long_help: Option<StyledStr>,
|
||||
pub(crate) action: Option<ArgAction>,
|
||||
pub(crate) value_parser: Option<super::ValueParser>,
|
||||
pub(crate) blacklist: Vec<Id>,
|
||||
|
@ -1900,7 +1900,7 @@ impl Arg {
|
|||
/// [`Arg::long_help`]: Arg::long_help()
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn help(mut self, h: impl IntoResettable<Str>) -> Self {
|
||||
pub fn help(mut self, h: impl IntoResettable<StyledStr>) -> Self {
|
||||
self.help = h.into_resettable().into_option();
|
||||
self
|
||||
}
|
||||
|
@ -1962,7 +1962,7 @@ impl Arg {
|
|||
/// [`Arg::help`]: Arg::help()
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn long_help(mut self, h: impl IntoResettable<Str>) -> Self {
|
||||
pub fn long_help(mut self, h: impl IntoResettable<StyledStr>) -> Self {
|
||||
self.long_help = h.into_resettable().into_option();
|
||||
self
|
||||
}
|
||||
|
@ -3539,8 +3539,8 @@ impl Arg {
|
|||
|
||||
/// Get the help specified for this argument, if any
|
||||
#[inline]
|
||||
pub fn get_help(&self) -> Option<&str> {
|
||||
self.help.as_deref()
|
||||
pub fn get_help(&self) -> Option<&StyledStr> {
|
||||
self.help.as_ref()
|
||||
}
|
||||
|
||||
/// Get the long help specified for this argument, if any
|
||||
|
@ -3550,12 +3550,12 @@ impl Arg {
|
|||
/// ```rust
|
||||
/// # use clap::Arg;
|
||||
/// let arg = Arg::new("foo").long_help("long help");
|
||||
/// assert_eq!(Some("long help"), arg.get_long_help());
|
||||
/// assert_eq!(Some("long help".to_owned()), arg.get_long_help().map(|s| s.to_string()));
|
||||
/// ```
|
||||
///
|
||||
#[inline]
|
||||
pub fn get_long_help(&self) -> Option<&str> {
|
||||
self.long_help.as_deref()
|
||||
pub fn get_long_help(&self) -> Option<&StyledStr> {
|
||||
self.long_help.as_ref()
|
||||
}
|
||||
|
||||
/// Get the help heading specified for this argument, if any
|
||||
|
@ -3950,29 +3950,108 @@ impl Arg {
|
|||
}
|
||||
|
||||
// Used for positionals when printing
|
||||
pub(crate) fn name_no_brackets(&self) -> Cow<str> {
|
||||
pub(crate) fn name_no_brackets(&self) -> String {
|
||||
debug!("Arg::name_no_brackets:{}", self.get_id());
|
||||
let delim = " ";
|
||||
if !self.val_names.is_empty() {
|
||||
debug!("Arg::name_no_brackets: val_names={:#?}", self.val_names);
|
||||
|
||||
if self.val_names.len() > 1 {
|
||||
Cow::Owned(
|
||||
self.val_names
|
||||
.iter()
|
||||
.map(|n| format!("<{}>", n))
|
||||
.collect::<Vec<_>>()
|
||||
.join(delim),
|
||||
)
|
||||
self.val_names
|
||||
.iter()
|
||||
.map(|n| format!("<{}>", n))
|
||||
.collect::<Vec<_>>()
|
||||
.join(delim)
|
||||
} else {
|
||||
Cow::Borrowed(self.val_names.first().expect(INTERNAL_ERROR_MSG))
|
||||
self.val_names
|
||||
.first()
|
||||
.expect(INTERNAL_ERROR_MSG)
|
||||
.as_str()
|
||||
.to_owned()
|
||||
}
|
||||
} else {
|
||||
debug!("Arg::name_no_brackets: just name");
|
||||
Cow::Borrowed(self.get_id().as_str())
|
||||
self.get_id().as_str().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn stylize_arg_suffix(&self) -> StyledStr {
|
||||
let mut styled = StyledStr::new();
|
||||
|
||||
let mut need_closing_bracket = false;
|
||||
if self.is_takes_value_set() && !self.is_positional() {
|
||||
let is_optional_val = self.get_min_vals() == 0;
|
||||
let sep = if self.is_require_equals_set() {
|
||||
if is_optional_val {
|
||||
need_closing_bracket = true;
|
||||
"[="
|
||||
} else {
|
||||
"="
|
||||
}
|
||||
} else if is_optional_val {
|
||||
need_closing_bracket = true;
|
||||
" ["
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
styled.good(sep);
|
||||
}
|
||||
if self.is_takes_value_set() || self.is_positional() {
|
||||
let arg_val = self.render_arg_val();
|
||||
styled.good(arg_val);
|
||||
} else if matches!(*self.get_action(), ArgAction::Count) {
|
||||
styled.good("...");
|
||||
}
|
||||
if need_closing_bracket {
|
||||
styled.none("]");
|
||||
}
|
||||
|
||||
styled
|
||||
}
|
||||
|
||||
/// Write the values such as <name1> <name2>
|
||||
fn render_arg_val(&self) -> String {
|
||||
let mut rendered = String::new();
|
||||
|
||||
let num_vals = self.get_num_args().expect(INTERNAL_ERROR_MSG);
|
||||
|
||||
let mut val_names = if self.val_names.is_empty() {
|
||||
vec![self.id.as_internal_str().to_owned()]
|
||||
} else {
|
||||
self.val_names.clone()
|
||||
};
|
||||
if val_names.len() == 1 {
|
||||
let min = num_vals.min_values().max(1);
|
||||
let val_name = val_names.pop().unwrap();
|
||||
val_names = vec![val_name; min];
|
||||
}
|
||||
|
||||
debug_assert!(self.is_takes_value_set());
|
||||
for (n, val_name) in val_names.iter().enumerate() {
|
||||
let arg_name = if self.is_positional() && num_vals.min_values() == 0 {
|
||||
format!("[{}]", val_name)
|
||||
} else {
|
||||
format!("<{}>", val_name)
|
||||
};
|
||||
|
||||
if n != 0 {
|
||||
rendered.push(' ');
|
||||
}
|
||||
rendered.push_str(&arg_name);
|
||||
}
|
||||
|
||||
let mut extra_values = false;
|
||||
extra_values |= val_names.len() < num_vals.max_values();
|
||||
if self.is_positional() && matches!(*self.get_action(), ArgAction::Append) {
|
||||
extra_values = true;
|
||||
}
|
||||
if extra_values {
|
||||
rendered.push_str("...");
|
||||
}
|
||||
|
||||
rendered
|
||||
}
|
||||
|
||||
/// Either multiple values or occurrences
|
||||
pub(crate) fn is_multiple(&self) -> bool {
|
||||
self.is_multiple_values_set() || matches!(*self.get_action(), ArgAction::Append)
|
||||
|
@ -4017,41 +4096,7 @@ impl Display for Arg {
|
|||
} else if let Some(s) = self.get_short() {
|
||||
write!(f, "-{}", s)?;
|
||||
}
|
||||
let mut need_closing_bracket = false;
|
||||
let is_optional_val = self.get_min_vals() == 0;
|
||||
if self.is_positional() {
|
||||
if is_optional_val {
|
||||
let sep = "[";
|
||||
need_closing_bracket = true;
|
||||
f.write_str(sep)?;
|
||||
}
|
||||
} else if self.is_takes_value_set() {
|
||||
let sep = if self.is_require_equals_set() {
|
||||
if is_optional_val {
|
||||
need_closing_bracket = true;
|
||||
"[="
|
||||
} else {
|
||||
"="
|
||||
}
|
||||
} else if is_optional_val {
|
||||
need_closing_bracket = true;
|
||||
" ["
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
f.write_str(sep)?;
|
||||
}
|
||||
if self.is_takes_value_set() || self.is_positional() {
|
||||
let arg_val = render_arg_val(self);
|
||||
f.write_str(&arg_val)?;
|
||||
} else if matches!(*self.get_action(), ArgAction::Count) {
|
||||
f.write_str("...")?;
|
||||
}
|
||||
if need_closing_bracket {
|
||||
f.write_str("]")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
self.stylize_arg_suffix().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4098,53 +4143,6 @@ impl fmt::Debug for Arg {
|
|||
}
|
||||
}
|
||||
|
||||
/// Write the values such as <name1> <name2>
|
||||
pub(crate) fn render_arg_val(arg: &Arg) -> String {
|
||||
let mut rendered = String::new();
|
||||
|
||||
let delim = " ";
|
||||
|
||||
let val_names = if arg.val_names.is_empty() {
|
||||
vec![arg.id.as_internal_str().to_owned()]
|
||||
} else {
|
||||
arg.val_names.clone()
|
||||
};
|
||||
|
||||
let mut extra_values = false;
|
||||
debug_assert!(arg.is_takes_value_set());
|
||||
let num_vals = arg.get_num_args().expect(INTERNAL_ERROR_MSG);
|
||||
if val_names.len() == 1 {
|
||||
let arg_name = format!("<{}>", val_names[0]);
|
||||
let min = num_vals.min_values().max(1);
|
||||
for n in 1..=min {
|
||||
if n != 1 {
|
||||
rendered.push_str(delim);
|
||||
}
|
||||
rendered.push_str(&arg_name);
|
||||
}
|
||||
extra_values |= min < num_vals.max_values();
|
||||
} else {
|
||||
debug_assert!(1 < val_names.len());
|
||||
for (n, val_name) in val_names.iter().enumerate() {
|
||||
let arg_name = format!("<{}>", val_name);
|
||||
if n != 0 {
|
||||
rendered.push_str(delim);
|
||||
}
|
||||
rendered.push_str(&arg_name);
|
||||
}
|
||||
extra_values |= val_names.len() < num_vals.max_values();
|
||||
}
|
||||
if arg.is_positional() && matches!(*arg.get_action(), ArgAction::Append) {
|
||||
extra_values = true;
|
||||
}
|
||||
|
||||
if extra_values {
|
||||
rendered.push_str("...");
|
||||
}
|
||||
|
||||
rendered
|
||||
}
|
||||
|
||||
// Flags
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
@ -4380,7 +4378,7 @@ mod test {
|
|||
let mut p = Arg::new("pos").index(1).num_args(0..);
|
||||
p._build();
|
||||
|
||||
assert_eq!(p.to_string(), "[<pos>...]");
|
||||
assert_eq!(p.to_string(), "[pos]...");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -4399,7 +4397,7 @@ mod test {
|
|||
.action(ArgAction::Set);
|
||||
p._build();
|
||||
|
||||
assert_eq!(p.to_string(), "[<pos>]");
|
||||
assert_eq!(p.to_string(), "[pos]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -75,22 +75,22 @@ pub struct Command {
|
|||
author: Option<Str>,
|
||||
version: Option<Str>,
|
||||
long_version: Option<Str>,
|
||||
about: Option<Str>,
|
||||
long_about: Option<Str>,
|
||||
before_help: Option<Str>,
|
||||
before_long_help: Option<Str>,
|
||||
after_help: Option<Str>,
|
||||
after_long_help: Option<Str>,
|
||||
about: Option<StyledStr>,
|
||||
long_about: Option<StyledStr>,
|
||||
before_help: Option<StyledStr>,
|
||||
before_long_help: Option<StyledStr>,
|
||||
after_help: Option<StyledStr>,
|
||||
after_long_help: Option<StyledStr>,
|
||||
aliases: Vec<(Str, bool)>, // (name, visible)
|
||||
short_flag_aliases: Vec<(char, bool)>, // (name, visible)
|
||||
long_flag_aliases: Vec<(Str, bool)>, // (name, visible)
|
||||
usage_str: Option<Str>,
|
||||
usage_str: Option<StyledStr>,
|
||||
usage_name: Option<String>,
|
||||
help_str: Option<Str>,
|
||||
help_str: Option<StyledStr>,
|
||||
disp_ord: Option<usize>,
|
||||
term_w: Option<usize>,
|
||||
max_w: Option<usize>,
|
||||
template: Option<Str>,
|
||||
template: Option<StyledStr>,
|
||||
settings: AppFlags,
|
||||
g_settings: AppFlags,
|
||||
args: MKeyMap,
|
||||
|
@ -1442,7 +1442,7 @@ impl Command {
|
|||
/// # ;
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn about(mut self, about: impl IntoResettable<Str>) -> Self {
|
||||
pub fn about(mut self, about: impl IntoResettable<StyledStr>) -> Self {
|
||||
self.about = about.into_resettable().into_option();
|
||||
self
|
||||
}
|
||||
|
@ -1467,7 +1467,7 @@ impl Command {
|
|||
/// ```
|
||||
/// [`Command::about`]: Command::about()
|
||||
#[must_use]
|
||||
pub fn long_about(mut self, long_about: impl IntoResettable<Str>) -> Self {
|
||||
pub fn long_about(mut self, long_about: impl IntoResettable<StyledStr>) -> Self {
|
||||
self.long_about = long_about.into_resettable().into_option();
|
||||
self
|
||||
}
|
||||
|
@ -1489,7 +1489,7 @@ impl Command {
|
|||
/// ```
|
||||
///
|
||||
#[must_use]
|
||||
pub fn after_help(mut self, help: impl Into<Str>) -> Self {
|
||||
pub fn after_help(mut self, help: impl Into<StyledStr>) -> Self {
|
||||
self.after_help = Some(help.into());
|
||||
self
|
||||
}
|
||||
|
@ -1511,7 +1511,7 @@ impl Command {
|
|||
/// # ;
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn after_long_help(mut self, help: impl Into<Str>) -> Self {
|
||||
pub fn after_long_help(mut self, help: impl Into<StyledStr>) -> Self {
|
||||
self.after_long_help = Some(help.into());
|
||||
self
|
||||
}
|
||||
|
@ -1531,7 +1531,7 @@ impl Command {
|
|||
/// # ;
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn before_help(mut self, help: impl Into<Str>) -> Self {
|
||||
pub fn before_help(mut self, help: impl Into<StyledStr>) -> Self {
|
||||
self.before_help = Some(help.into());
|
||||
self
|
||||
}
|
||||
|
@ -1551,7 +1551,7 @@ impl Command {
|
|||
/// # ;
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn before_long_help(mut self, help: impl Into<Str>) -> Self {
|
||||
pub fn before_long_help(mut self, help: impl Into<StyledStr>) -> Self {
|
||||
self.before_long_help = Some(help.into());
|
||||
self
|
||||
}
|
||||
|
@ -1645,7 +1645,7 @@ impl Command {
|
|||
///
|
||||
/// [`ArgMatches::usage`]: ArgMatches::usage()
|
||||
#[must_use]
|
||||
pub fn override_usage(mut self, usage: impl Into<Str>) -> Self {
|
||||
pub fn override_usage(mut self, usage: impl Into<StyledStr>) -> Self {
|
||||
self.usage_str = Some(usage.into());
|
||||
self
|
||||
}
|
||||
|
@ -1682,7 +1682,7 @@ impl Command {
|
|||
/// # ;
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn override_help(mut self, help: impl Into<Str>) -> Self {
|
||||
pub fn override_help(mut self, help: impl Into<StyledStr>) -> Self {
|
||||
self.help_str = Some(help.into());
|
||||
self
|
||||
}
|
||||
|
@ -1732,7 +1732,7 @@ impl Command {
|
|||
/// [`Command::before_help`]: Command::before_help()
|
||||
/// [`Command::before_long_help`]: Command::before_long_help()
|
||||
#[must_use]
|
||||
pub fn help_template(mut self, s: impl Into<Str>) -> Self {
|
||||
pub fn help_template(mut self, s: impl Into<StyledStr>) -> Self {
|
||||
self.template = Some(s.into());
|
||||
self
|
||||
}
|
||||
|
@ -3242,16 +3242,16 @@ impl Command {
|
|||
///
|
||||
/// [`Command::about`]: Command::about()
|
||||
#[inline]
|
||||
pub fn get_about(&self) -> Option<&str> {
|
||||
self.about.as_deref()
|
||||
pub fn get_about(&self) -> Option<&StyledStr> {
|
||||
self.about.as_ref()
|
||||
}
|
||||
|
||||
/// Get the help message specified via [`Command::long_about`].
|
||||
///
|
||||
/// [`Command::long_about`]: Command::long_about()
|
||||
#[inline]
|
||||
pub fn get_long_about(&self) -> Option<&str> {
|
||||
self.long_about.as_deref()
|
||||
pub fn get_long_about(&self) -> Option<&StyledStr> {
|
||||
self.long_about.as_ref()
|
||||
}
|
||||
|
||||
/// Get the custom section heading specified via [`Command::next_help_heading`].
|
||||
|
@ -3364,26 +3364,26 @@ impl Command {
|
|||
|
||||
/// Returns the help heading for listing subcommands.
|
||||
#[inline]
|
||||
pub fn get_before_help(&self) -> Option<&str> {
|
||||
self.before_help.as_deref()
|
||||
pub fn get_before_help(&self) -> Option<&StyledStr> {
|
||||
self.before_help.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the help heading for listing subcommands.
|
||||
#[inline]
|
||||
pub fn get_before_long_help(&self) -> Option<&str> {
|
||||
self.before_long_help.as_deref()
|
||||
pub fn get_before_long_help(&self) -> Option<&StyledStr> {
|
||||
self.before_long_help.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the help heading for listing subcommands.
|
||||
#[inline]
|
||||
pub fn get_after_help(&self) -> Option<&str> {
|
||||
self.after_help.as_deref()
|
||||
pub fn get_after_help(&self) -> Option<&StyledStr> {
|
||||
self.after_help.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the help heading for listing subcommands.
|
||||
#[inline]
|
||||
pub fn get_after_long_help(&self) -> Option<&str> {
|
||||
self.after_long_help.as_deref()
|
||||
pub fn get_after_long_help(&self) -> Option<&StyledStr> {
|
||||
self.after_long_help.as_ref()
|
||||
}
|
||||
|
||||
/// Find subcommand such that its name or one of aliases equals `name`.
|
||||
|
@ -3667,16 +3667,16 @@ impl Command {
|
|||
|
||||
// Internally used only
|
||||
impl Command {
|
||||
pub(crate) fn get_override_usage(&self) -> Option<&str> {
|
||||
self.usage_str.as_deref()
|
||||
pub(crate) fn get_override_usage(&self) -> Option<&StyledStr> {
|
||||
self.usage_str.as_ref()
|
||||
}
|
||||
|
||||
pub(crate) fn get_override_help(&self) -> Option<&str> {
|
||||
self.help_str.as_deref()
|
||||
pub(crate) fn get_override_help(&self) -> Option<&StyledStr> {
|
||||
self.help_str.as_ref()
|
||||
}
|
||||
|
||||
pub(crate) fn get_help_template(&self) -> Option<&str> {
|
||||
self.template.as_deref()
|
||||
pub(crate) fn get_help_template(&self) -> Option<&StyledStr> {
|
||||
self.template.as_ref()
|
||||
}
|
||||
|
||||
pub(crate) fn get_term_width(&self) -> Option<usize> {
|
||||
|
@ -4190,7 +4190,7 @@ impl Command {
|
|||
.map(|x| {
|
||||
if x.is_positional() {
|
||||
// Print val_name for positional arguments. e.g. <file_name>
|
||||
x.name_no_brackets().to_string()
|
||||
x.name_no_brackets()
|
||||
} else {
|
||||
// Print usage string for flags arguments, e.g. <--help>
|
||||
x.to_string()
|
||||
|
|
|
@ -339,13 +339,13 @@ pub(crate) fn assert_app(cmd: &Command) {
|
|||
|
||||
if let Some(help_template) = cmd.get_help_template() {
|
||||
assert!(
|
||||
!help_template.contains("{flags}"),
|
||||
!help_template.to_string().contains("{flags}"),
|
||||
"Command {}: {}",
|
||||
cmd.get_name(),
|
||||
"`{flags}` template variable was removed in clap3, they are now included in `{options}`",
|
||||
);
|
||||
assert!(
|
||||
!help_template.contains("{unified}"),
|
||||
!help_template.to_string().contains("{unified}"),
|
||||
"Command {}: {}",
|
||||
cmd.get_name(),
|
||||
"`{unified}` template variable was removed in clap3, use `{options}` instead"
|
||||
|
|
|
@ -57,5 +57,4 @@ pub use value_parser::_AnonymousValueParser;
|
|||
#[allow(unused_imports)]
|
||||
pub(crate) use self::str::Inner as StrInner;
|
||||
pub(crate) use action::CountType;
|
||||
pub(crate) use arg::render_arg_val;
|
||||
pub(crate) use arg_settings::{ArgFlags, ArgSettings};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::{borrow::Cow, iter};
|
||||
|
||||
use crate::builder::Str;
|
||||
use crate::builder::StyledStr;
|
||||
use crate::util::eq_ignore_case;
|
||||
|
||||
/// A possible value of an argument.
|
||||
|
@ -30,7 +31,7 @@ use crate::util::eq_ignore_case;
|
|||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct PossibleValue {
|
||||
name: Str,
|
||||
help: Option<Str>,
|
||||
help: Option<StyledStr>,
|
||||
aliases: Vec<Str>, // (name, visible)
|
||||
hide: bool,
|
||||
}
|
||||
|
@ -75,7 +76,7 @@ impl PossibleValue {
|
|||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn help(mut self, help: impl Into<Str>) -> Self {
|
||||
pub fn help(mut self, help: impl Into<StyledStr>) -> Self {
|
||||
self.help = Some(help.into());
|
||||
self
|
||||
}
|
||||
|
@ -144,16 +145,16 @@ impl PossibleValue {
|
|||
|
||||
/// Get the help specified for this argument, if any
|
||||
#[inline]
|
||||
pub fn get_help(&self) -> Option<&str> {
|
||||
self.help.as_deref()
|
||||
pub fn get_help(&self) -> Option<&StyledStr> {
|
||||
self.help.as_ref()
|
||||
}
|
||||
|
||||
/// Get the help specified for this argument, if any and the argument
|
||||
/// value is not hidden
|
||||
#[inline]
|
||||
pub(crate) fn get_visible_help(&self) -> Option<&str> {
|
||||
pub(crate) fn get_visible_help(&self) -> Option<&StyledStr> {
|
||||
if !self.hide {
|
||||
self.help.as_deref()
|
||||
self.get_help()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
use crate::builder::OsStr;
|
||||
use crate::builder::Str;
|
||||
use crate::builder::StyledStr;
|
||||
|
||||
/// Clearable builder value
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
|
@ -43,6 +44,15 @@ pub trait IntoResettable<T> {
|
|||
fn into_resettable(self) -> Resettable<T>;
|
||||
}
|
||||
|
||||
impl IntoResettable<StyledStr> for Option<&'static str> {
|
||||
fn into_resettable(self) -> Resettable<StyledStr> {
|
||||
match self {
|
||||
Some(s) => Resettable::Value(s.into()),
|
||||
None => Resettable::Reset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResettable<OsStr> for Option<&'static str> {
|
||||
fn into_resettable(self) -> Resettable<OsStr> {
|
||||
match self {
|
||||
|
@ -67,6 +77,12 @@ impl<T> IntoResettable<T> for Resettable<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<I: Into<StyledStr>> IntoResettable<StyledStr> for I {
|
||||
fn into_resettable(self) -> Resettable<StyledStr> {
|
||||
Resettable::Value(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Into<OsStr>> IntoResettable<OsStr> for I {
|
||||
fn into_resettable(self) -> Resettable<OsStr> {
|
||||
Resettable::Value(self.into())
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
use crate::output::display_width;
|
||||
use crate::output::textwrap;
|
||||
|
||||
/// Terminal-styling container
|
||||
#[derive(Clone, Default, Debug, PartialEq, Eq)]
|
||||
pub struct StyledStr {
|
||||
|
@ -11,28 +14,102 @@ impl StyledStr {
|
|||
}
|
||||
|
||||
pub(crate) fn good(&mut self, msg: impl Into<String>) {
|
||||
self.stylize(Some(Style::Good), msg.into());
|
||||
self.stylize_(Some(Style::Good), msg.into());
|
||||
}
|
||||
|
||||
pub(crate) fn warning(&mut self, msg: impl Into<String>) {
|
||||
self.stylize(Some(Style::Warning), msg.into());
|
||||
self.stylize_(Some(Style::Warning), msg.into());
|
||||
}
|
||||
|
||||
pub(crate) fn error(&mut self, msg: impl Into<String>) {
|
||||
self.stylize(Some(Style::Error), msg.into());
|
||||
self.stylize_(Some(Style::Error), msg.into());
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn hint(&mut self, msg: impl Into<String>) {
|
||||
self.stylize(Some(Style::Hint), msg.into());
|
||||
self.stylize_(Some(Style::Hint), msg.into());
|
||||
}
|
||||
|
||||
pub(crate) fn none(&mut self, msg: impl Into<String>) {
|
||||
self.stylize(None, msg.into());
|
||||
self.stylize_(None, msg.into());
|
||||
}
|
||||
|
||||
fn stylize(&mut self, style: Option<Style>, msg: String) {
|
||||
self.pieces.push((style, msg));
|
||||
pub(crate) fn stylize(&mut self, style: impl Into<Option<Style>>, msg: impl Into<String>) {
|
||||
self.stylize_(style.into(), msg.into());
|
||||
}
|
||||
|
||||
pub(crate) fn replace_newline(&mut self) {
|
||||
for (_, content) in &mut self.pieces {
|
||||
*content = content.replace("{n}", "\n");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn indent(&mut self, initial: &str, trailing: &str) {
|
||||
if let Some((_, first)) = self.pieces.first_mut() {
|
||||
first.insert_str(0, initial);
|
||||
}
|
||||
let mut line_sep = "\n".to_owned();
|
||||
line_sep.push_str(trailing);
|
||||
for (_, content) in &mut self.pieces {
|
||||
*content = content.replace('\n', &line_sep);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn wrap(&mut self, hard_width: usize) {
|
||||
let mut wrapper = textwrap::wrap_algorithms::LineWrapper::new(hard_width);
|
||||
for (_, content) in &mut self.pieces {
|
||||
let mut total = Vec::new();
|
||||
for (i, line) in content.split_inclusive('\n').enumerate() {
|
||||
if 0 < i {
|
||||
// start of a section does not imply newline
|
||||
wrapper.reset();
|
||||
}
|
||||
let line =
|
||||
textwrap::word_separators::find_words_ascii_space(line).collect::<Vec<_>>();
|
||||
total.extend(wrapper.wrap(line));
|
||||
}
|
||||
let total = total.join("");
|
||||
*content = total;
|
||||
}
|
||||
if let Some((_, last)) = self.pieces.last_mut() {
|
||||
*last = last.trim_end().to_owned();
|
||||
}
|
||||
}
|
||||
|
||||
fn stylize_(&mut self, style: Option<Style>, msg: String) {
|
||||
if !msg.is_empty() {
|
||||
self.pieces.push((style, msg));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub(crate) fn display_width(&self) -> usize {
|
||||
let mut width = 0;
|
||||
for (_, c) in &self.pieces {
|
||||
width += display_width(c);
|
||||
}
|
||||
width
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.pieces.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = (Option<Style>, &str)> {
|
||||
self.pieces.iter().map(|(s, c)| (*s, c.as_str()))
|
||||
}
|
||||
|
||||
pub(crate) fn into_iter(self) -> impl Iterator<Item = (Option<Style>, String)> {
|
||||
self.pieces.into_iter()
|
||||
}
|
||||
|
||||
pub(crate) fn extend(
|
||||
&mut self,
|
||||
other: impl IntoIterator<Item = (impl Into<Option<Style>>, impl Into<String>)>,
|
||||
) {
|
||||
for (style, content) in other {
|
||||
self.stylize(style.into(), content.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
|
@ -68,6 +145,13 @@ impl StyledStr {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for &'_ StyledStr {
|
||||
fn default() -> Self {
|
||||
static DEFAULT: StyledStr = StyledStr::new();
|
||||
&DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::string::String> for StyledStr {
|
||||
fn from(name: std::string::String) -> Self {
|
||||
let mut styled = StyledStr::new();
|
||||
|
|
1095
src/output/help.rs
1095
src/output/help.rs
File diff suppressed because it is too large
Load diff
|
@ -2,6 +2,9 @@ mod help;
|
|||
mod usage;
|
||||
|
||||
pub(crate) mod fmt;
|
||||
pub(crate) mod textwrap;
|
||||
|
||||
pub(crate) use self::help::Help;
|
||||
pub(crate) use self::textwrap::core::display_width;
|
||||
pub(crate) use self::textwrap::wrap;
|
||||
pub(crate) use self::usage::Usage;
|
||||
|
|
145
src/output/textwrap/core.rs
Normal file
145
src/output/textwrap/core.rs
Normal file
|
@ -0,0 +1,145 @@
|
|||
/// Compute the display width of `text`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// **Note:** When the `unicode` Cargo feature is disabled, all characters are presumed to take up
|
||||
/// 1 width. With the feature enabled, function will correctly deal with [combining characters] in
|
||||
/// their decomposed form (see [Unicode equivalence]).
|
||||
///
|
||||
/// An example of a decomposed character is “é”, which can be decomposed into: “e” followed by a
|
||||
/// combining acute accent: “◌́”. Without the `unicode` Cargo feature, every `char` has a width of
|
||||
/// 1. This includes the combining accent:
|
||||
///
|
||||
/// ## Emojis and CJK Characters
|
||||
///
|
||||
/// Characters such as emojis and [CJK characters] used in the
|
||||
/// Chinese, Japanese, and Korean languages are seen as double-width,
|
||||
/// even if the `unicode-width` feature is disabled:
|
||||
///
|
||||
/// # Limitations
|
||||
///
|
||||
/// The displayed width of a string cannot always be computed from the
|
||||
/// string alone. This is because the width depends on the rendering
|
||||
/// engine used. This is particularly visible with [emoji modifier
|
||||
/// sequences] where a base emoji is modified with, e.g., skin tone or
|
||||
/// hair color modifiers. It is up to the rendering engine to detect
|
||||
/// this and to produce a suitable emoji.
|
||||
///
|
||||
/// A simple example is “❤️”, which consists of “❤” (U+2764: Black
|
||||
/// Heart Symbol) followed by U+FE0F (Variation Selector-16). By
|
||||
/// itself, “❤” is a black heart, but if you follow it with the
|
||||
/// variant selector, you may get a wider red heart.
|
||||
///
|
||||
/// A more complex example would be “👨🦰” which should depict a man
|
||||
/// with red hair. Here the computed width is too large — and the
|
||||
/// width differs depending on the use of the `unicode-width` feature:
|
||||
///
|
||||
/// This happens because the grapheme consists of three code points:
|
||||
/// “👨” (U+1F468: Man), Zero Width Joiner (U+200D), and “🦰”
|
||||
/// (U+1F9B0: Red Hair). You can see them above in the test. With
|
||||
/// `unicode-width` enabled, the ZWJ is correctly seen as having zero
|
||||
/// width, without it is counted as a double-width character.
|
||||
///
|
||||
/// ## Terminal Support
|
||||
///
|
||||
/// Modern browsers typically do a great job at combining characters
|
||||
/// as shown above, but terminals often struggle more. As an example,
|
||||
/// Gnome Terminal version 3.38.1, shows “❤️” as a big red heart, but
|
||||
/// shows "👨🦰" as “👨🦰”.
|
||||
///
|
||||
/// [combining characters]: https://en.wikipedia.org/wiki/Combining_character
|
||||
/// [Unicode equivalence]: https://en.wikipedia.org/wiki/Unicode_equivalence
|
||||
/// [CJK characters]: https://en.wikipedia.org/wiki/CJK_characters
|
||||
/// [emoji modifier sequences]: https://unicode.org/emoji/charts/full-emoji-modifiers.html
|
||||
#[inline(never)]
|
||||
pub(crate) fn display_width(text: &str) -> usize {
|
||||
let mut width = 0;
|
||||
for ch in text.chars() {
|
||||
width += ch_width(ch);
|
||||
}
|
||||
width
|
||||
}
|
||||
|
||||
#[cfg(feature = "unicode")]
|
||||
fn ch_width(ch: char) -> usize {
|
||||
unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unicode"))]
|
||||
fn ch_width(_: char) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[cfg(feature = "unicode")]
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
#[test]
|
||||
fn emojis_have_correct_width() {
|
||||
use unic_emoji_char::is_emoji;
|
||||
|
||||
// Emojis in the Basic Latin (ASCII) and Latin-1 Supplement
|
||||
// blocks all have a width of 1 column. This includes
|
||||
// characters such as '#' and '©'.
|
||||
for ch in '\u{1}'..'\u{FF}' {
|
||||
if is_emoji(ch) {
|
||||
let desc = format!("{:?} U+{:04X}", ch, ch as u32);
|
||||
|
||||
#[cfg(feature = "unicode")]
|
||||
assert_eq!(ch.width().unwrap(), 1, "char: {}", desc);
|
||||
|
||||
#[cfg(not(feature = "unicode"))]
|
||||
assert_eq!(ch_width(ch), 1, "char: {}", desc);
|
||||
}
|
||||
}
|
||||
|
||||
// Emojis in the remaining blocks of the Basic Multilingual
|
||||
// Plane (BMP), in the Supplementary Multilingual Plane (SMP),
|
||||
// and in the Supplementary Ideographic Plane (SIP), are all 1
|
||||
// or 2 columns wide when unicode-width is used, and always 2
|
||||
// columns wide otherwise. This includes all of our favorite
|
||||
// emojis such as 😊.
|
||||
for ch in '\u{FF}'..'\u{2FFFF}' {
|
||||
if is_emoji(ch) {
|
||||
let desc = format!("{:?} U+{:04X}", ch, ch as u32);
|
||||
|
||||
#[cfg(feature = "unicode")]
|
||||
assert!(ch.width().unwrap() <= 2, "char: {}", desc);
|
||||
|
||||
#[cfg(not(feature = "unicode"))]
|
||||
assert_eq!(ch_width(ch), 1, "char: {}", desc);
|
||||
}
|
||||
}
|
||||
|
||||
// The remaining planes contain almost no assigned code points
|
||||
// and thus also no emojis.
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unicode")]
|
||||
fn display_width_works() {
|
||||
assert_eq!("Café Plain".len(), 11); // “é” is two bytes
|
||||
assert_eq!(display_width("Café Plain"), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unicode")]
|
||||
fn display_width_narrow_emojis() {
|
||||
assert_eq!(display_width("⁉"), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unicode")]
|
||||
fn display_width_narrow_emojis_variant_selector() {
|
||||
assert_eq!(display_width("⁉\u{fe0f}"), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unicode")]
|
||||
fn display_width_emojis() {
|
||||
assert_eq!(display_width("😂😭🥺🤣✨😍🙏🥰😊🔥"), 20);
|
||||
}
|
||||
}
|
113
src/output/textwrap/mod.rs
Normal file
113
src/output/textwrap/mod.rs
Normal file
|
@ -0,0 +1,113 @@
|
|||
//! Fork of `textwrap` crate
|
||||
//!
|
||||
//! Benefits of forking:
|
||||
//! - Pull in only what we need rather than relying on the compiler to remove what we don't need
|
||||
//! - `LineWrapper` is able to incrementally wrap which will help with `StyledStr
|
||||
|
||||
pub(crate) mod core;
|
||||
pub(crate) mod word_separators;
|
||||
pub(crate) mod wrap_algorithms;
|
||||
|
||||
pub(crate) fn wrap(content: &str, hard_width: usize) -> String {
|
||||
let mut wrapper = wrap_algorithms::LineWrapper::new(hard_width);
|
||||
let mut total = Vec::new();
|
||||
for line in content.split_inclusive('\n') {
|
||||
wrapper.reset();
|
||||
let line = word_separators::find_words_ascii_space(line).collect::<Vec<_>>();
|
||||
total.extend(wrapper.wrap(line));
|
||||
}
|
||||
total.join("")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
/// Compatibility shim to keep textwrap's tests
|
||||
fn wrap(content: &str, hard_width: usize) -> Vec<String> {
|
||||
super::wrap(content, hard_width)
|
||||
.trim_end()
|
||||
.split('\n')
|
||||
.map(|s| s.to_owned())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_wrap() {
|
||||
assert_eq!(wrap("foo", 10), vec!["foo"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrap_simple() {
|
||||
assert_eq!(wrap("foo bar baz", 5), vec!["foo", "bar", "baz"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_be_or_not() {
|
||||
assert_eq!(
|
||||
wrap("To be, or not to be, that is the question.", 10),
|
||||
vec!["To be, or", "not to be,", "that is", "the", "question."]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_words_on_first_line() {
|
||||
assert_eq!(wrap("foo bar baz", 10), vec!["foo bar", "baz"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_word() {
|
||||
assert_eq!(wrap("foo", 0), vec!["foo"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_words() {
|
||||
assert_eq!(wrap("foo bar", 0), vec!["foo", "bar"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_width() {
|
||||
assert_eq!(wrap("foo bar", usize::MAX), vec!["foo bar"]);
|
||||
|
||||
let text = "Hello there! This is some English text. \
|
||||
It should not be wrapped given the extents below.";
|
||||
assert_eq!(wrap(text, usize::MAX), vec![text]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leading_whitespace() {
|
||||
assert_eq!(wrap(" foo bar", 6), vec![" foo", "bar"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leading_whitespace_empty_first_line() {
|
||||
// If there is no space for the first word, the first line
|
||||
// will be empty. This is because the string is split into
|
||||
// words like [" ", "foobar ", "baz"], which puts "foobar " on
|
||||
// the second line. We never output trailing whitespace
|
||||
assert_eq!(wrap(" foobar baz", 6), vec!["", "foobar", "baz"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_whitespace() {
|
||||
// Whitespace is only significant inside a line. After a line
|
||||
// gets too long and is broken, the first word starts in
|
||||
// column zero and is not indented.
|
||||
assert_eq!(wrap("foo bar baz ", 5), vec!["foo", "bar", "baz"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_99() {
|
||||
// We did not reset the in_whitespace flag correctly and did
|
||||
// not handle single-character words after a line break.
|
||||
assert_eq!(
|
||||
wrap("aaabbbccc x yyyzzzwww", 9),
|
||||
vec!["aaabbbccc", "x", "yyyzzzwww"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_129() {
|
||||
// The dash is an em-dash which takes up four bytes. We used
|
||||
// to panic since we tried to index into the character.
|
||||
assert_eq!(wrap("x – x", 1), vec!["x", "–", "x"]);
|
||||
}
|
||||
}
|
91
src/output/textwrap/word_separators.rs
Normal file
91
src/output/textwrap/word_separators.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
pub(crate) fn find_words_ascii_space(line: &str) -> impl Iterator<Item = &'_ str> + '_ {
|
||||
let mut start = 0;
|
||||
let mut in_whitespace = false;
|
||||
let mut char_indices = line.char_indices();
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
for (idx, ch) in char_indices.by_ref() {
|
||||
if in_whitespace && ch != ' ' {
|
||||
let word = &line[start..idx];
|
||||
start = idx;
|
||||
in_whitespace = ch == ' ';
|
||||
return Some(word);
|
||||
}
|
||||
|
||||
in_whitespace = ch == ' ';
|
||||
}
|
||||
|
||||
if start < line.len() {
|
||||
let word = &line[start..];
|
||||
start = line.len();
|
||||
return Some(word);
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! test_find_words {
|
||||
($ascii_name:ident,
|
||||
$([ $line:expr, $ascii_words:expr ]),+) => {
|
||||
#[test]
|
||||
fn $ascii_name() {
|
||||
$(
|
||||
let expected_words: Vec<&str> = $ascii_words.to_vec();
|
||||
let actual_words = find_words_ascii_space($line)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual_words, expected_words, "Line: {:?}", $line);
|
||||
)+
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_find_words!(ascii_space_empty, ["", []]);
|
||||
|
||||
test_find_words!(ascii_single_word, ["foo", ["foo"]]);
|
||||
|
||||
test_find_words!(ascii_two_words, ["foo bar", ["foo ", "bar"]]);
|
||||
|
||||
test_find_words!(
|
||||
ascii_multiple_words,
|
||||
["foo bar", ["foo ", "bar"]],
|
||||
["x y z", ["x ", "y ", "z"]]
|
||||
);
|
||||
|
||||
test_find_words!(ascii_only_whitespace, [" ", [" "]], [" ", [" "]]);
|
||||
|
||||
test_find_words!(
|
||||
ascii_inter_word_whitespace,
|
||||
["foo bar", ["foo ", "bar"]]
|
||||
);
|
||||
|
||||
test_find_words!(ascii_trailing_whitespace, ["foo ", ["foo "]]);
|
||||
|
||||
test_find_words!(ascii_leading_whitespace, [" foo", [" ", "foo"]]);
|
||||
|
||||
test_find_words!(
|
||||
ascii_multi_column_char,
|
||||
["\u{1f920}", ["\u{1f920}"]] // cowboy emoji 🤠
|
||||
);
|
||||
|
||||
test_find_words!(
|
||||
ascii_hyphens,
|
||||
["foo-bar", ["foo-bar"]],
|
||||
["foo- bar", ["foo- ", "bar"]],
|
||||
["foo - bar", ["foo ", "- ", "bar"]],
|
||||
["foo -bar", ["foo ", "-bar"]]
|
||||
);
|
||||
|
||||
test_find_words!(ascii_newline, ["foo\nbar", ["foo\nbar"]]);
|
||||
|
||||
test_find_words!(ascii_tab, ["foo\tbar", ["foo\tbar"]]);
|
||||
|
||||
test_find_words!(
|
||||
ascii_non_breaking_space,
|
||||
["foo\u{00A0}bar", ["foo\u{00A0}bar"]]
|
||||
);
|
||||
}
|
44
src/output/textwrap/wrap_algorithms.rs
Normal file
44
src/output/textwrap/wrap_algorithms.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use super::core::display_width;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct LineWrapper {
|
||||
line_width: usize,
|
||||
hard_width: usize,
|
||||
}
|
||||
|
||||
impl LineWrapper {
|
||||
pub(crate) fn new(hard_width: usize) -> Self {
|
||||
Self {
|
||||
line_width: 0,
|
||||
hard_width,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn reset(&mut self) {
|
||||
self.line_width = 0;
|
||||
}
|
||||
|
||||
pub(crate) fn wrap<'w>(&mut self, mut words: Vec<&'w str>) -> Vec<&'w str> {
|
||||
let mut i = 0;
|
||||
while i < words.len() {
|
||||
let word = &words[i];
|
||||
let trimmed = word.trim_end();
|
||||
let word_width = display_width(trimmed);
|
||||
let trimmed_delta = word.len() - trimmed.len();
|
||||
if i != 0 && self.hard_width < self.line_width + word_width {
|
||||
if 0 < i {
|
||||
let last = i - 1;
|
||||
let trimmed = words[last].trim_end();
|
||||
words[last] = trimmed;
|
||||
}
|
||||
words.insert(i, "\n");
|
||||
i += 1;
|
||||
self.reset();
|
||||
}
|
||||
self.line_width += word_width + trimmed_delta;
|
||||
|
||||
i += 1;
|
||||
}
|
||||
words
|
||||
}
|
||||
}
|
|
@ -40,7 +40,7 @@ impl<'cmd> Usage<'cmd> {
|
|||
pub(crate) fn create_usage_no_title(&self, used: &[Id]) -> String {
|
||||
debug!("Usage::create_usage_no_title");
|
||||
if let Some(u) = self.cmd.get_override_usage() {
|
||||
u.to_owned()
|
||||
u.to_string()
|
||||
} else if used.is_empty() {
|
||||
self.create_help_usage(true)
|
||||
} else {
|
||||
|
|
|
@ -2065,7 +2065,7 @@ USAGE:
|
|||
myapp help [SUBCOMMAND]...
|
||||
|
||||
ARGS:
|
||||
<SUBCOMMAND>... The subcommand whose help message to display
|
||||
[SUBCOMMAND]... The subcommand whose help message to display
|
||||
";
|
||||
|
||||
let cmd = Command::new("myapp")
|
||||
|
@ -2083,7 +2083,7 @@ USAGE:
|
|||
myapp subcmd help [SUBCOMMAND]...
|
||||
|
||||
ARGS:
|
||||
<SUBCOMMAND>... The subcommand whose help message to display
|
||||
[SUBCOMMAND]... The subcommand whose help message to display
|
||||
";
|
||||
|
||||
let cmd = Command::new("myapp")
|
||||
|
@ -2569,7 +2569,7 @@ USAGE:
|
|||
example help [SUBCOMMAND]...
|
||||
|
||||
ARGS:
|
||||
<SUBCOMMAND>... The subcommand whose help message to display
|
||||
[SUBCOMMAND]... The subcommand whose help message to display
|
||||
",
|
||||
false,
|
||||
);
|
||||
|
@ -2612,7 +2612,7 @@ USAGE:
|
|||
example help [SUBCOMMAND]...
|
||||
|
||||
ARGS:
|
||||
<SUBCOMMAND>... The subcommand whose help message to display
|
||||
[SUBCOMMAND]... The subcommand whose help message to display
|
||||
",
|
||||
false,
|
||||
);
|
||||
|
|
|
@ -450,7 +450,11 @@ fn mut_subcommand_with_alias_resolve() {
|
|||
let mut cmd =
|
||||
Command::new("foo").subcommand(Command::new("bar").alias("baz").about("test subcmd"));
|
||||
assert_eq!(
|
||||
cmd.find_subcommand("baz").unwrap().get_about().unwrap(),
|
||||
cmd.find_subcommand("baz")
|
||||
.unwrap()
|
||||
.get_about()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"test subcmd"
|
||||
);
|
||||
|
||||
|
@ -459,7 +463,11 @@ fn mut_subcommand_with_alias_resolve() {
|
|||
|
||||
cmd = cmd.mut_subcommand(&*true_name, |subcmd| subcmd.about("modified about"));
|
||||
assert_eq!(
|
||||
cmd.find_subcommand("baz").unwrap().get_about().unwrap(),
|
||||
cmd.find_subcommand("baz")
|
||||
.unwrap()
|
||||
.get_about()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"modified about"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -233,7 +233,10 @@ fn doc_comment_about_handles_both_abouts() {
|
|||
}
|
||||
|
||||
let cmd = Opts::command();
|
||||
assert_eq!(cmd.get_about(), Some("Opts doc comment summary"));
|
||||
assert_eq!(
|
||||
cmd.get_about().map(|s| s.to_string()),
|
||||
Some("Opts doc comment summary".to_owned())
|
||||
);
|
||||
// clap will fallback to `about` on `None`. The main care about is not providing a `Sub` doc
|
||||
// comment.
|
||||
assert_eq!(cmd.get_long_about(), None);
|
||||
|
|
|
@ -94,7 +94,11 @@ fn inferred_help() {
|
|||
let mut cmd = Opt::command();
|
||||
cmd.build();
|
||||
let arg = cmd.get_arguments().find(|a| a.get_id() == "help").unwrap();
|
||||
assert_eq!(arg.get_help(), Some("Foo"), "Incorrect help");
|
||||
assert_eq!(
|
||||
arg.get_help().map(|s| s.to_string()),
|
||||
Some("Foo".to_owned()),
|
||||
"Incorrect help"
|
||||
);
|
||||
assert!(matches!(arg.get_action(), clap::ArgAction::Help));
|
||||
}
|
||||
|
||||
|
@ -114,7 +118,11 @@ fn inferred_version() {
|
|||
.get_arguments()
|
||||
.find(|a| a.get_id() == "version")
|
||||
.unwrap();
|
||||
assert_eq!(arg.get_help(), Some("Foo"), "Incorrect help");
|
||||
assert_eq!(
|
||||
arg.get_help().map(|s| s.to_string()),
|
||||
Some("Foo".to_owned()),
|
||||
"Incorrect help"
|
||||
);
|
||||
assert!(matches!(arg.get_action(), clap::ArgAction::Version));
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::SetTrue));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(!arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), None);
|
||||
assert_eq!(arg.get_help().map(|s| s.to_string()), None);
|
||||
|
||||
let arg = clap::arg!(foo: -'b');
|
||||
assert_eq!(arg.get_id(), "foo");
|
||||
|
@ -54,7 +54,7 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::SetTrue));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(!arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), None);
|
||||
assert_eq!(arg.get_help().map(|s| s.to_string()), None);
|
||||
|
||||
let arg = clap::arg!(foo: -b ...);
|
||||
assert_eq!(arg.get_id(), "foo");
|
||||
|
@ -62,7 +62,7 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::Count));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(!arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), None);
|
||||
assert_eq!(arg.get_help().map(|s| s.to_string()), None);
|
||||
|
||||
let arg = clap::arg!(foo: -b "How to use it");
|
||||
assert_eq!(arg.get_id(), "foo");
|
||||
|
@ -70,7 +70,10 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::SetTrue));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(!arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), Some("How to use it"));
|
||||
assert_eq!(
|
||||
arg.get_help().map(|s| s.to_string()),
|
||||
Some("How to use it".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -82,7 +85,7 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::SetTrue));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(!arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), None);
|
||||
assert_eq!(arg.get_help().map(|s| s.to_string()), None);
|
||||
|
||||
let arg = clap::arg!(foo: -'b' --hello);
|
||||
assert_eq!(arg.get_id(), "foo");
|
||||
|
@ -91,7 +94,7 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::SetTrue));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(!arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), None);
|
||||
assert_eq!(arg.get_help().map(|s| s.to_string()), None);
|
||||
|
||||
let arg = clap::arg!(foo: -b --hello ...);
|
||||
assert_eq!(arg.get_id(), "foo");
|
||||
|
@ -100,7 +103,7 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::Count));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(!arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), None);
|
||||
assert_eq!(arg.get_help().map(|s| s.to_string()), None);
|
||||
|
||||
let arg = clap::arg!(foo: -b --hello "How to use it");
|
||||
assert_eq!(arg.get_id(), "foo");
|
||||
|
@ -109,7 +112,10 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::SetTrue));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(!arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), Some("How to use it"));
|
||||
assert_eq!(
|
||||
arg.get_help().map(|s| s.to_string()),
|
||||
Some("How to use it".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -186,7 +192,7 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::Set));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), None);
|
||||
assert_eq!(arg.get_help().map(|s| s.to_string()), None);
|
||||
|
||||
let arg = clap::arg!(foo: -'b' <NUM>);
|
||||
assert_eq!(arg.get_id(), "foo");
|
||||
|
@ -194,7 +200,7 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::Set));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), None);
|
||||
assert_eq!(arg.get_help().map(|s| s.to_string()), None);
|
||||
|
||||
let arg = clap::arg!(foo: -b <NUM> ...);
|
||||
assert_eq!(arg.get_id(), "foo");
|
||||
|
@ -202,7 +208,7 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::Append));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), None);
|
||||
assert_eq!(arg.get_help().map(|s| s.to_string()), None);
|
||||
|
||||
let arg = clap::arg!(foo: -b <NUM> "How to use it");
|
||||
assert_eq!(arg.get_id(), "foo");
|
||||
|
@ -210,7 +216,10 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::Set));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), Some("How to use it"));
|
||||
assert_eq!(
|
||||
arg.get_help().map(|s| s.to_string()),
|
||||
Some("How to use it".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -221,7 +230,7 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::Set));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), None);
|
||||
assert_eq!(arg.get_help().map(|s| s.to_string()), None);
|
||||
|
||||
let arg = clap::arg!([NUM]);
|
||||
assert_eq!(arg.get_id(), "NUM");
|
||||
|
@ -229,7 +238,7 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::Set));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(!arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), None);
|
||||
assert_eq!(arg.get_help().map(|s| s.to_string()), None);
|
||||
|
||||
let arg = clap::arg!(<NUM>);
|
||||
assert_eq!(arg.get_id(), "NUM");
|
||||
|
@ -237,7 +246,7 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::Set));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), None);
|
||||
assert_eq!(arg.get_help().map(|s| s.to_string()), None);
|
||||
|
||||
let arg = clap::arg!(foo: <NUM>);
|
||||
assert_eq!(arg.get_id(), "foo");
|
||||
|
@ -245,7 +254,7 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::Set));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), None);
|
||||
assert_eq!(arg.get_help().map(|s| s.to_string()), None);
|
||||
|
||||
let arg = clap::arg!(<NUM> ...);
|
||||
assert_eq!(arg.get_id(), "NUM");
|
||||
|
@ -253,7 +262,7 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::Append));
|
||||
assert_eq!(arg.get_num_args(), Some((1..).into()));
|
||||
assert!(arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), None);
|
||||
assert_eq!(arg.get_help().map(|s| s.to_string()), None);
|
||||
|
||||
let arg = clap::arg!(<NUM> "How to use it");
|
||||
assert_eq!(arg.get_id(), "NUM");
|
||||
|
@ -261,7 +270,10 @@ mod arg {
|
|||
assert!(matches!(arg.get_action(), clap::ArgAction::Set));
|
||||
assert_eq!(arg.get_num_args(), None);
|
||||
assert!(arg.is_required_set());
|
||||
assert_eq!(arg.get_help(), Some("How to use it"));
|
||||
assert_eq!(
|
||||
arg.get_help().map(|s| s.to_string()),
|
||||
Some("How to use it".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in a new issue