mirror of
https://github.com/clap-rs/clap
synced 2025-03-04 23:37:32 +00:00
commit
ba582e77b0
8 changed files with 204 additions and 283 deletions
|
@ -116,27 +116,33 @@ impl<'help> App<'help> {
|
|||
/// # ;
|
||||
/// ```
|
||||
pub fn new<S: Into<String>>(name: S) -> Self {
|
||||
let name = name.into();
|
||||
|
||||
App {
|
||||
id: Id::from(&*name),
|
||||
name,
|
||||
..Default::default()
|
||||
/// The actual implementation of `new`, non-generic to save code size.
|
||||
///
|
||||
/// If we don't do this rustc will unnecessarily generate multiple versions
|
||||
/// of this code.
|
||||
fn new_inner<'help>(name: String) -> App<'help> {
|
||||
App {
|
||||
id: Id::from(&*name),
|
||||
name,
|
||||
..Default::default()
|
||||
}
|
||||
.arg(
|
||||
Arg::new("help")
|
||||
.long("help")
|
||||
.help("Print help information")
|
||||
.global(true)
|
||||
.generated(),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("version")
|
||||
.long("version")
|
||||
.help("Print version information")
|
||||
.global(true)
|
||||
.generated(),
|
||||
)
|
||||
}
|
||||
.arg(
|
||||
Arg::new("help")
|
||||
.long("help")
|
||||
.help("Print help information")
|
||||
.global(true)
|
||||
.generated(),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("version")
|
||||
.long("version")
|
||||
.help("Print version information")
|
||||
.global(true)
|
||||
.generated(),
|
||||
)
|
||||
|
||||
new_inner(name.into())
|
||||
}
|
||||
|
||||
/// Adds an [argument] to the list of valid possibilities.
|
||||
|
@ -2214,7 +2220,7 @@ impl<'help> App<'help> {
|
|||
}
|
||||
|
||||
/// Should we color the output?
|
||||
#[inline]
|
||||
#[inline(never)]
|
||||
pub fn get_color(&self) -> ColorChoice {
|
||||
debug!("App::color: Color setting...");
|
||||
|
||||
|
@ -3015,7 +3021,7 @@ impl<'help> App<'help> {
|
|||
debug!("No");
|
||||
let bin_name = format!(
|
||||
"{}{}{}",
|
||||
self.bin_name.as_ref().unwrap_or(&self.name.clone()),
|
||||
self.bin_name.as_ref().unwrap_or(&self.name),
|
||||
if self.bin_name.is_some() { " " } else { "" },
|
||||
&*sc.name
|
||||
);
|
||||
|
@ -3043,11 +3049,9 @@ impl<'help> App<'help> {
|
|||
debug!("App::_render_version");
|
||||
|
||||
let ver = if use_long {
|
||||
self.long_version
|
||||
.unwrap_or_else(|| self.version.unwrap_or(""))
|
||||
self.long_version.or(self.version).unwrap_or("")
|
||||
} else {
|
||||
self.version
|
||||
.unwrap_or_else(|| self.long_version.unwrap_or(""))
|
||||
self.version.or(self.long_version).unwrap_or("")
|
||||
};
|
||||
if let Some(bn) = self.bin_name.as_ref() {
|
||||
if bn.contains(' ') {
|
||||
|
|
|
@ -17,7 +17,7 @@ use std::{
|
|||
error::Error,
|
||||
ffi::OsStr,
|
||||
fmt::{self, Display, Formatter},
|
||||
iter, str,
|
||||
str,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
#[cfg(feature = "env")]
|
||||
|
@ -4335,7 +4335,7 @@ impl<'help> Arg<'help> {
|
|||
/// [`Arg::exclusive(true)`]: Arg::exclusive()
|
||||
#[must_use]
|
||||
pub fn conflicts_with_all(mut self, names: &[&str]) -> Self {
|
||||
self.blacklist.extend(names.iter().map(Id::from));
|
||||
self.blacklist.extend(names.iter().copied().map(Id::from));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -4943,12 +4943,12 @@ impl<'help> Arg<'help> {
|
|||
// Used for positionals when printing
|
||||
pub(crate) fn name_no_brackets(&self) -> Cow<str> {
|
||||
debug!("Arg::name_no_brackets:{}", self.name);
|
||||
let mut delim = String::new();
|
||||
delim.push(if self.is_set(ArgSettings::RequireDelimiter) {
|
||||
let delim = if self.is_set(ArgSettings::RequireDelimiter) {
|
||||
self.val_delim.expect(INTERNAL_ERROR_MSG)
|
||||
} else {
|
||||
' '
|
||||
});
|
||||
}
|
||||
.to_string();
|
||||
if !self.val_names.is_empty() {
|
||||
debug!("Arg::name_no_brackets: val_names={:#?}", self.val_names);
|
||||
|
||||
|
@ -5029,13 +5029,13 @@ impl<'help> Display for Arg<'help> {
|
|||
} else {
|
||||
" "
|
||||
};
|
||||
write!(f, "{}", sep)?;
|
||||
f.write_str(sep)?;
|
||||
}
|
||||
if self.is_set(ArgSettings::TakesValue) || self.is_positional() {
|
||||
display_arg_val(self, |s, _| write!(f, "{}", s))?;
|
||||
display_arg_val(self, |s, _| f.write_str(s))?;
|
||||
}
|
||||
if need_closing_bracket {
|
||||
write!(f, "]")?;
|
||||
f.write_str("]")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -5123,7 +5123,8 @@ where
|
|||
arg.val_delim.expect(INTERNAL_ERROR_MSG)
|
||||
} else {
|
||||
' '
|
||||
};
|
||||
}
|
||||
.to_string();
|
||||
if !arg.val_names.is_empty() {
|
||||
// If have val_name.
|
||||
match (arg.val_names.len(), arg.num_vals) {
|
||||
|
@ -5131,11 +5132,10 @@ where
|
|||
// If single value name with multiple num_of_vals, display all
|
||||
// the values with the single value name.
|
||||
let arg_name = format!("<{}>", arg.val_names.get(0).unwrap());
|
||||
let mut it = iter::repeat(arg_name).take(num_vals).peekable();
|
||||
while let Some(arg_name) = it.next() {
|
||||
for n in 1..=num_vals {
|
||||
write(&arg_name, true)?;
|
||||
if it.peek().is_some() {
|
||||
write(&delim.to_string(), false)?;
|
||||
if n != num_vals {
|
||||
write(&delim, false)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5145,7 +5145,7 @@ where
|
|||
while let Some(val) = it.next() {
|
||||
write(&format!("<{}>", val), true)?;
|
||||
if it.peek().is_some() {
|
||||
write(&delim.to_string(), false)?;
|
||||
write(&delim, false)?;
|
||||
}
|
||||
}
|
||||
if (num_val_names == 1 && mult_val) || (arg.is_positional() && mult_occ) {
|
||||
|
@ -5156,11 +5156,10 @@ where
|
|||
} else if let Some(num_vals) = arg.num_vals {
|
||||
// If number_of_values is specified, display the value multiple times.
|
||||
let arg_name = format!("<{}>", arg.name);
|
||||
let mut it = iter::repeat(&arg_name).take(num_vals).peekable();
|
||||
while let Some(arg_name) = it.next() {
|
||||
write(arg_name, true)?;
|
||||
if it.peek().is_some() {
|
||||
write(&delim.to_string(), false)?;
|
||||
for n in 1..=num_vals {
|
||||
write(&arg_name, true)?;
|
||||
if n != num_vals {
|
||||
write(&delim, false)?;
|
||||
}
|
||||
}
|
||||
} else if arg.is_positional() {
|
||||
|
@ -5190,12 +5189,12 @@ mod test {
|
|||
let mut f = Arg::new("flg").multiple_occurrences(true);
|
||||
f.long = Some("flag");
|
||||
|
||||
assert_eq!(&*format!("{}", f), "--flag");
|
||||
assert_eq!(f.to_string(), "--flag");
|
||||
|
||||
let mut f2 = Arg::new("flg");
|
||||
f2.short = Some('f');
|
||||
|
||||
assert_eq!(&*format!("{}", f2), "-f");
|
||||
assert_eq!(f2.to_string(), "-f");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -5204,7 +5203,7 @@ mod test {
|
|||
f.long = Some("flag");
|
||||
f.aliases = vec![("als", true)];
|
||||
|
||||
assert_eq!(&*format!("{}", f), "--flag")
|
||||
assert_eq!(f.to_string(), "--flag")
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -5217,7 +5216,7 @@ mod test {
|
|||
("f3", true),
|
||||
("f4", true),
|
||||
];
|
||||
assert_eq!(&*format!("{}", f), "-f");
|
||||
assert_eq!(f.to_string(), "-f");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -5226,7 +5225,7 @@ mod test {
|
|||
f.short = Some('a');
|
||||
f.short_aliases = vec![('b', true)];
|
||||
|
||||
assert_eq!(&*format!("{}", f), "-a")
|
||||
assert_eq!(f.to_string(), "-a")
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -5234,7 +5233,7 @@ mod test {
|
|||
let mut f = Arg::new("flg");
|
||||
f.short = Some('a');
|
||||
f.short_aliases = vec![('b', false), ('c', true), ('d', true), ('e', true)];
|
||||
assert_eq!(&*format!("{}", f), "-a");
|
||||
assert_eq!(f.to_string(), "-a");
|
||||
}
|
||||
|
||||
// Options
|
||||
|
@ -5246,7 +5245,7 @@ mod test {
|
|||
.takes_value(true)
|
||||
.multiple_occurrences(true);
|
||||
|
||||
assert_eq!(&*format!("{}", o), "--option <opt>");
|
||||
assert_eq!(o.to_string(), "--option <opt>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -5256,14 +5255,14 @@ mod test {
|
|||
.takes_value(true)
|
||||
.multiple_values(true);
|
||||
|
||||
assert_eq!(&*format!("{}", o), "--option <opt>...");
|
||||
assert_eq!(o.to_string(), "--option <opt>...");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn option_display2() {
|
||||
let o2 = Arg::new("opt").short('o').value_names(&["file", "name"]);
|
||||
|
||||
assert_eq!(&*format!("{}", o2), "-o <file> <name>");
|
||||
assert_eq!(o2.to_string(), "-o <file> <name>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -5274,7 +5273,7 @@ mod test {
|
|||
.multiple_values(true)
|
||||
.value_names(&["file", "name"]);
|
||||
|
||||
assert_eq!(&*format!("{}", o2), "-o <file> <name>");
|
||||
assert_eq!(o2.to_string(), "-o <file> <name>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -5284,7 +5283,7 @@ mod test {
|
|||
.long("option")
|
||||
.visible_alias("als");
|
||||
|
||||
assert_eq!(&*format!("{}", o), "--option <opt>");
|
||||
assert_eq!(o.to_string(), "--option <opt>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -5295,7 +5294,7 @@ mod test {
|
|||
.visible_aliases(&["als2", "als3", "als4"])
|
||||
.alias("als_not_visible");
|
||||
|
||||
assert_eq!(&*format!("{}", o), "--option <opt>");
|
||||
assert_eq!(o.to_string(), "--option <opt>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -5305,7 +5304,7 @@ mod test {
|
|||
.short('a')
|
||||
.visible_short_alias('b');
|
||||
|
||||
assert_eq!(&*format!("{}", o), "-a <opt>");
|
||||
assert_eq!(o.to_string(), "-a <opt>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -5316,7 +5315,7 @@ mod test {
|
|||
.visible_short_aliases(&['b', 'c', 'd'])
|
||||
.short_alias('e');
|
||||
|
||||
assert_eq!(&*format!("{}", o), "-a <opt>");
|
||||
assert_eq!(o.to_string(), "-a <opt>");
|
||||
}
|
||||
|
||||
// Positionals
|
||||
|
@ -5328,7 +5327,7 @@ mod test {
|
|||
.takes_value(true)
|
||||
.multiple_values(true);
|
||||
|
||||
assert_eq!(&*format!("{}", p), "<pos>...");
|
||||
assert_eq!(p.to_string(), "<pos>...");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -5338,21 +5337,21 @@ mod test {
|
|||
.takes_value(true)
|
||||
.multiple_occurrences(true);
|
||||
|
||||
assert_eq!(&*format!("{}", p), "<pos>...");
|
||||
assert_eq!(p.to_string(), "<pos>...");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn positional_display_required() {
|
||||
let p2 = Arg::new("pos").index(1).required(true);
|
||||
|
||||
assert_eq!(&*format!("{}", p2), "<pos>");
|
||||
assert_eq!(p2.to_string(), "<pos>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn positional_display_val_names() {
|
||||
let p2 = Arg::new("pos").index(1).value_names(&["file1", "file2"]);
|
||||
|
||||
assert_eq!(&*format!("{}", p2), "<file1> <file2>");
|
||||
assert_eq!(p2.to_string(), "<file1> <file2>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -5362,6 +5361,6 @@ mod test {
|
|||
.required(true)
|
||||
.value_names(&["file1", "file2"]);
|
||||
|
||||
assert_eq!(&*format!("{}", p2), "<file1> <file2>");
|
||||
assert_eq!(p2.to_string(), "<file1> <file2>");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ impl<'help> ArgGroup<'help> {
|
|||
#[must_use]
|
||||
pub fn name<S: Into<&'help str>>(mut self, n: S) -> Self {
|
||||
self.name = n.into();
|
||||
self.id = Id::from(&self.name);
|
||||
self.id = Id::from(self.name);
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
cmp,
|
||||
fmt::Write as _,
|
||||
io::{self, Write},
|
||||
usize,
|
||||
};
|
||||
|
@ -139,6 +140,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
write_method!(self, msg, none)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn spaces(&mut self, n: usize) -> io::Result<()> {
|
||||
// A string with 64 consecutive spaces.
|
||||
const SHORT_SPACE: &str =
|
||||
|
@ -157,7 +159,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
let mut longest = 2;
|
||||
let mut arg_v = Vec::with_capacity(10);
|
||||
|
||||
for arg in args
|
||||
for &arg in args
|
||||
.iter()
|
||||
.filter(|arg| should_show_arg(self.use_long, *arg))
|
||||
{
|
||||
|
@ -184,7 +186,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
let mut ord_v = Vec::new();
|
||||
|
||||
// Determine the longest
|
||||
for arg in args.iter().filter(|arg| {
|
||||
for &arg in args.iter().filter(|arg| {
|
||||
// If it's NextLineHelp we don't care to compute how long it is because it may be
|
||||
// NextLineHelp on purpose simply *because* it's so long and would throw off all other
|
||||
// args alignment
|
||||
|
@ -256,7 +258,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
self.none(TAB)?;
|
||||
|
||||
if let Some(s) = arg.short {
|
||||
self.good(&format!("-{}", s))
|
||||
self.good(format!("-{}", s))
|
||||
} else if !arg.is_positional() {
|
||||
self.none(TAB)
|
||||
} else {
|
||||
|
@ -271,7 +273,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
if arg.short.is_some() {
|
||||
self.none(", ")?;
|
||||
}
|
||||
self.good(&format!("--{}", long))?;
|
||||
self.good(format!("--{}", long))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -408,7 +410,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
|
||||
// Is help on next line, if so then indent
|
||||
if next_line_help {
|
||||
self.none(&format!("\n{}{}{}", TAB, TAB, TAB))?;
|
||||
self.none(format!("\n{}{}{}", TAB, TAB, TAB))?;
|
||||
}
|
||||
|
||||
debug!("Help::help: Too long...");
|
||||
|
@ -429,7 +431,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
for part in help.lines().skip(1) {
|
||||
self.none("\n")?;
|
||||
if next_line_help {
|
||||
self.none(&format!("{}{}{}", TAB, TAB, TAB))?;
|
||||
self.none(format!("{}{}{}", TAB, TAB, TAB))?;
|
||||
} else if is_not_positional {
|
||||
self.spaces(longest + 12)?;
|
||||
} else {
|
||||
|
@ -454,9 +456,9 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
self.align_to_about(arg, next_line_help, longest)?;
|
||||
|
||||
let about = if self.use_long {
|
||||
arg.long_help.unwrap_or_else(|| arg.help.unwrap_or(""))
|
||||
arg.long_help.or(arg.help).unwrap_or("")
|
||||
} else {
|
||||
arg.help.unwrap_or_else(|| arg.long_help.unwrap_or(""))
|
||||
arg.help.or(arg.long_help).unwrap_or("")
|
||||
};
|
||||
|
||||
self.help(
|
||||
|
@ -667,7 +669,7 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
|
||||
let spec_vals = &self.sc_spec_vals(app);
|
||||
|
||||
let about = app.about.unwrap_or_else(|| app.long_about.unwrap_or(""));
|
||||
let about = app.about.or(app.long_about).unwrap_or("");
|
||||
|
||||
self.subcmd(sc_str, next_line_help, longest)?;
|
||||
self.help(false, about, spec_vals, next_line_help, longest)
|
||||
|
@ -793,8 +795,8 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
if !first {
|
||||
self.none("\n\n")?;
|
||||
}
|
||||
self.warning(&*format!("{}:\n", heading))?;
|
||||
self.write_args(&*args)?;
|
||||
self.warning(format!("{}:\n", heading))?;
|
||||
self.write_args(&args)?;
|
||||
first = false
|
||||
}
|
||||
}
|
||||
|
@ -837,19 +839,15 @@ impl<'help, 'app, 'parser, 'writer> Help<'help, 'app, 'parser, 'writer> {
|
|||
.filter(|subcommand| should_show_subcommand(subcommand))
|
||||
{
|
||||
let mut sc_str = String::new();
|
||||
sc_str.push_str(
|
||||
&subcommand
|
||||
.short_flag
|
||||
.map_or(String::new(), |c| format!("-{}, ", c)),
|
||||
);
|
||||
sc_str.push_str(
|
||||
&subcommand
|
||||
.long_flag
|
||||
.map_or(String::new(), |c| format!("--{}, ", c)),
|
||||
);
|
||||
if let Some(short) = subcommand.short_flag {
|
||||
write!(sc_str, "-{}", short).unwrap();
|
||||
}
|
||||
if let Some(long) = subcommand.long_flag {
|
||||
write!(sc_str, "--{}", long).unwrap();
|
||||
}
|
||||
sc_str.push_str(&subcommand.name);
|
||||
longest = longest.max(display_width(&sc_str));
|
||||
ord_v.push((subcommand.get_display_order(), sc_str, subcommand.clone()));
|
||||
ord_v.push((subcommand.get_display_order(), sc_str, subcommand));
|
||||
}
|
||||
ord_v.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1)));
|
||||
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
// std
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use indexmap::IndexSet;
|
||||
|
||||
// Internal
|
||||
|
@ -52,12 +49,13 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
.app
|
||||
.usage
|
||||
.as_ref()
|
||||
.unwrap_or_else(|| self.p.app.bin_name.as_ref().unwrap_or(&self.p.app.name));
|
||||
.or_else(|| self.p.app.bin_name.as_ref())
|
||||
.unwrap_or(&self.p.app.name);
|
||||
usage.push_str(&*name);
|
||||
let req_string = if incl_reqs {
|
||||
self.get_required_usage_from(&[], None, false)
|
||||
.iter()
|
||||
.fold(String::new(), |a, s| a + &format!(" {}", s)[..])
|
||||
.fold(String::new(), |a, s| a + " " + s)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
@ -181,7 +179,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
let r_string = self
|
||||
.get_required_usage_from(used, None, true)
|
||||
.iter()
|
||||
.fold(String::new(), |acc, s| acc + &format!(" {}", s)[..]);
|
||||
.fold(String::new(), |acc, s| acc + " " + s);
|
||||
|
||||
usage.push_str(
|
||||
&self
|
||||
|
@ -189,7 +187,8 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
.app
|
||||
.usage
|
||||
.as_ref()
|
||||
.unwrap_or_else(|| self.p.app.bin_name.as_ref().unwrap_or(&self.p.app.name))[..],
|
||||
.or_else(|| self.p.app.bin_name.as_ref())
|
||||
.unwrap_or(&self.p.app.name)[..],
|
||||
);
|
||||
usage.push_str(&*r_string);
|
||||
if self.p.is_set(AS::SubcommandRequired) {
|
||||
|
@ -442,7 +441,7 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
}
|
||||
ret_val.extend_from_slice(&g_vec);
|
||||
|
||||
let pmap = unrolled_reqs
|
||||
let mut pvec = unrolled_reqs
|
||||
.iter()
|
||||
.chain(incls.iter())
|
||||
.filter(|a| self.p.app.get_positionals().any(|p| &&p.id == a))
|
||||
|
@ -451,9 +450,10 @@ impl<'help, 'app, 'parser> Usage<'help, 'app, 'parser> {
|
|||
.filter(|&pos| incl_last || !pos.is_set(ArgSettings::Last))
|
||||
.filter(|pos| !args_in_groups.contains(&pos.id))
|
||||
.map(|pos| (pos.index.unwrap(), pos))
|
||||
.collect::<BTreeMap<usize, &Arg>>(); // sort by index
|
||||
.collect::<Vec<(usize, &Arg)>>();
|
||||
pvec.sort_by_key(|(ind, _)| *ind); // sort by index
|
||||
|
||||
for p in pmap.values() {
|
||||
for (_, p) in pvec {
|
||||
debug!("Usage::get_required_usage_from:iter:{:?}", p.id);
|
||||
if !args_in_groups.contains(&p.id) {
|
||||
ret_val.push(p.to_string());
|
||||
|
|
|
@ -63,13 +63,14 @@ impl ArgMatcher {
|
|||
// --global-arg where the value is `other`, however the occurs will be 0.
|
||||
let to_update = if let Some(parent_ma) = vals_map.get(global_arg) {
|
||||
if parent_ma.occurs > 0 && ma.occurs == 0 {
|
||||
parent_ma.clone()
|
||||
parent_ma
|
||||
} else {
|
||||
ma.clone()
|
||||
ma
|
||||
}
|
||||
} else {
|
||||
ma.clone()
|
||||
};
|
||||
ma
|
||||
}
|
||||
.clone();
|
||||
vals_map.insert(global_arg.clone(), to_update);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -538,6 +538,21 @@ impl Error {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub(crate) fn for_app(
|
||||
app: &App,
|
||||
colorizer: Colorizer,
|
||||
kind: ErrorKind,
|
||||
info: Vec<String>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
colorizer,
|
||||
kind,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
)
|
||||
.set_info(info)
|
||||
}
|
||||
|
||||
pub(crate) fn set_info(mut self, info: Vec<String>) -> Self {
|
||||
self.info = info;
|
||||
self
|
||||
|
@ -561,24 +576,20 @@ impl Error {
|
|||
c.warning(arg);
|
||||
c.none("' cannot be used with");
|
||||
|
||||
let mut info = vec![];
|
||||
match others.len() {
|
||||
0 => {
|
||||
c.none(" one or more of the other specified arguments");
|
||||
}
|
||||
1 => {
|
||||
let v = &others[0];
|
||||
c.none(" '");
|
||||
c.warning(v.clone());
|
||||
c.warning(&*others[0]);
|
||||
c.none("'");
|
||||
info.push(v.clone());
|
||||
}
|
||||
_ => {
|
||||
c.none(":");
|
||||
for v in others {
|
||||
for v in &others {
|
||||
c.none("\n ");
|
||||
c.warning(v.to_string());
|
||||
info.push(v.to_string());
|
||||
c.warning(&**v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -586,12 +597,7 @@ impl Error {
|
|||
put_usage(&mut c, usage);
|
||||
try_help(app, &mut c);
|
||||
|
||||
Self::new(
|
||||
c,
|
||||
ErrorKind::ArgumentConflict,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
)
|
||||
.set_info(info)
|
||||
Self::for_app(app, c, ErrorKind::ArgumentConflict, others)
|
||||
}
|
||||
|
||||
pub(crate) fn empty_value(app: &App, arg: &Arg, usage: String) -> Self {
|
||||
|
@ -599,58 +605,45 @@ impl Error {
|
|||
let arg = arg.to_string();
|
||||
|
||||
start_error(&mut c, "The argument '");
|
||||
c.warning(arg.clone());
|
||||
c.warning(&*arg);
|
||||
c.none("' requires a value but none was supplied");
|
||||
put_usage(&mut c, usage);
|
||||
try_help(app, &mut c);
|
||||
|
||||
Self::new(
|
||||
c,
|
||||
ErrorKind::EmptyValue,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
)
|
||||
.set_info(vec![arg])
|
||||
Self::for_app(app, c, ErrorKind::EmptyValue, vec![arg])
|
||||
}
|
||||
|
||||
pub(crate) fn no_equals(app: &App, arg: String, usage: String) -> Self {
|
||||
let mut c = Colorizer::new(true, app.get_color());
|
||||
|
||||
start_error(&mut c, "Equal sign is needed when assigning values to '");
|
||||
c.warning(&arg);
|
||||
c.warning(&*arg);
|
||||
c.none("'.");
|
||||
|
||||
put_usage(&mut c, usage);
|
||||
try_help(app, &mut c);
|
||||
|
||||
Self::new(
|
||||
c,
|
||||
ErrorKind::NoEquals,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
)
|
||||
.set_info(vec![arg])
|
||||
Self::for_app(app, c, ErrorKind::NoEquals, vec![arg])
|
||||
}
|
||||
|
||||
pub(crate) fn invalid_value<G>(
|
||||
pub(crate) fn invalid_value(
|
||||
app: &App,
|
||||
bad_val: String,
|
||||
good_vals: &[G],
|
||||
good_vals: &[&str],
|
||||
arg: &Arg,
|
||||
usage: String,
|
||||
) -> Self
|
||||
where
|
||||
G: AsRef<str> + Display,
|
||||
{
|
||||
) -> Self {
|
||||
let mut c = Colorizer::new(true, app.get_color());
|
||||
let suffix = suggestions::did_you_mean(&bad_val, good_vals.iter()).pop();
|
||||
let arg = arg.to_string();
|
||||
|
||||
let mut sorted: Vec<String> = good_vals
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.map(|v| {
|
||||
.map(|&v| {
|
||||
if v.contains(char::is_whitespace) {
|
||||
format!("{:?}", v)
|
||||
} else {
|
||||
v
|
||||
v.to_owned()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
@ -659,7 +652,7 @@ impl Error {
|
|||
start_error(&mut c, "");
|
||||
c.warning(format!("{:?}", bad_val));
|
||||
c.none(" isn't a valid value for '");
|
||||
c.warning(arg.to_string());
|
||||
c.warning(&*arg);
|
||||
c.none("'\n\t[possible values: ");
|
||||
|
||||
if let Some((last, elements)) = sorted.split_last() {
|
||||
|
@ -682,15 +675,10 @@ impl Error {
|
|||
put_usage(&mut c, usage);
|
||||
try_help(app, &mut c);
|
||||
|
||||
let mut info = vec![arg.to_string(), bad_val];
|
||||
let mut info = vec![arg, bad_val];
|
||||
info.extend(sorted);
|
||||
|
||||
Self::new(
|
||||
c,
|
||||
ErrorKind::InvalidValue,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
)
|
||||
.set_info(info)
|
||||
Self::for_app(app, c, ErrorKind::InvalidValue, info)
|
||||
}
|
||||
|
||||
pub(crate) fn invalid_subcommand(
|
||||
|
@ -703,7 +691,7 @@ impl Error {
|
|||
let mut c = Colorizer::new(true, app.get_color());
|
||||
|
||||
start_error(&mut c, "The subcommand '");
|
||||
c.warning(subcmd.clone());
|
||||
c.warning(&*subcmd);
|
||||
c.none("' wasn't recognized\n\n\tDid you mean ");
|
||||
c.good(did_you_mean);
|
||||
c.none("");
|
||||
|
@ -716,30 +704,20 @@ impl Error {
|
|||
put_usage(&mut c, usage);
|
||||
try_help(app, &mut c);
|
||||
|
||||
Self::new(
|
||||
c,
|
||||
ErrorKind::InvalidSubcommand,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
)
|
||||
.set_info(vec![subcmd])
|
||||
Self::for_app(app, c, ErrorKind::InvalidSubcommand, vec![subcmd])
|
||||
}
|
||||
|
||||
pub(crate) fn unrecognized_subcommand(app: &App, subcmd: String, name: String) -> Self {
|
||||
let mut c = Colorizer::new(true, app.get_color());
|
||||
|
||||
start_error(&mut c, " The subcommand '");
|
||||
c.warning(subcmd.clone());
|
||||
c.warning(&*subcmd);
|
||||
c.none("' wasn't recognized\n\n");
|
||||
c.warning("USAGE:");
|
||||
c.none(format!("\n {} <subcommands>", name));
|
||||
try_help(app, &mut c);
|
||||
|
||||
Self::new(
|
||||
c,
|
||||
ErrorKind::UnrecognizedSubcommand,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
)
|
||||
.set_info(vec![subcmd])
|
||||
Self::for_app(app, c, ErrorKind::UnrecognizedSubcommand, vec![subcmd])
|
||||
}
|
||||
|
||||
pub(crate) fn missing_required_argument(
|
||||
|
@ -754,22 +732,15 @@ impl Error {
|
|||
"The following required arguments were not provided:",
|
||||
);
|
||||
|
||||
let mut info = vec![];
|
||||
for v in required {
|
||||
for v in &required {
|
||||
c.none("\n ");
|
||||
c.good(v.to_string());
|
||||
info.push(v.to_string());
|
||||
c.good(&**v);
|
||||
}
|
||||
|
||||
put_usage(&mut c, usage);
|
||||
try_help(app, &mut c);
|
||||
|
||||
Self::new(
|
||||
c,
|
||||
ErrorKind::MissingRequiredArgument,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
)
|
||||
.set_info(info)
|
||||
Self::for_app(app, c, ErrorKind::MissingRequiredArgument, required)
|
||||
}
|
||||
|
||||
pub(crate) fn missing_subcommand(app: &App, name: String, usage: String) -> Self {
|
||||
|
@ -781,11 +752,7 @@ impl Error {
|
|||
put_usage(&mut c, usage);
|
||||
try_help(app, &mut c);
|
||||
|
||||
Self::new(
|
||||
c,
|
||||
ErrorKind::MissingSubcommand,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
)
|
||||
Self::for_app(app, c, ErrorKind::MissingSubcommand, vec![])
|
||||
}
|
||||
|
||||
pub(crate) fn invalid_utf8(app: &App, usage: String) -> Self {
|
||||
|
@ -798,11 +765,7 @@ impl Error {
|
|||
put_usage(&mut c, usage);
|
||||
try_help(app, &mut c);
|
||||
|
||||
Self::new(
|
||||
c,
|
||||
ErrorKind::InvalidUtf8,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
)
|
||||
Self::for_app(app, c, ErrorKind::InvalidUtf8, vec![])
|
||||
}
|
||||
|
||||
pub(crate) fn too_many_occurrences(
|
||||
|
@ -813,47 +776,41 @@ impl Error {
|
|||
usage: String,
|
||||
) -> Self {
|
||||
let mut c = Colorizer::new(true, app.get_color());
|
||||
let verb = Error::singular_or_plural(curr_occurs);
|
||||
let were_provided = Error::singular_or_plural(curr_occurs);
|
||||
let arg = arg.to_string();
|
||||
let max_occurs = max_occurs.to_string();
|
||||
let curr_occurs = curr_occurs.to_string();
|
||||
|
||||
start_error(&mut c, "The argument '");
|
||||
c.warning(arg.to_string());
|
||||
c.warning(&*arg);
|
||||
c.none("' allows at most ");
|
||||
c.warning(max_occurs.to_string());
|
||||
c.warning(&*max_occurs);
|
||||
c.none(" occurrences, but ");
|
||||
c.warning(curr_occurs.to_string());
|
||||
c.none(format!(" {} provided", verb));
|
||||
c.warning(&*curr_occurs);
|
||||
c.none(were_provided);
|
||||
put_usage(&mut c, usage);
|
||||
try_help(app, &mut c);
|
||||
|
||||
Self::new(
|
||||
Self::for_app(
|
||||
app,
|
||||
c,
|
||||
ErrorKind::TooManyOccurrences,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
vec![arg, curr_occurs, max_occurs],
|
||||
)
|
||||
.set_info(vec![
|
||||
arg.to_string(),
|
||||
curr_occurs.to_string(),
|
||||
max_occurs.to_string(),
|
||||
])
|
||||
}
|
||||
|
||||
pub(crate) fn too_many_values(app: &App, val: String, arg: String, usage: String) -> Self {
|
||||
let mut c = Colorizer::new(true, app.get_color());
|
||||
|
||||
start_error(&mut c, "The value '");
|
||||
c.warning(val.clone());
|
||||
c.warning(&*val);
|
||||
c.none("' was provided to '");
|
||||
c.warning(&arg);
|
||||
c.warning(&*arg);
|
||||
c.none("' but it wasn't expecting any more values");
|
||||
put_usage(&mut c, usage);
|
||||
try_help(app, &mut c);
|
||||
|
||||
Self::new(
|
||||
c,
|
||||
ErrorKind::TooManyValues,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
)
|
||||
.set_info(vec![arg, val])
|
||||
Self::for_app(app, c, ErrorKind::TooManyValues, vec![arg, val])
|
||||
}
|
||||
|
||||
pub(crate) fn too_few_values(
|
||||
|
@ -864,28 +821,27 @@ impl Error {
|
|||
usage: String,
|
||||
) -> Self {
|
||||
let mut c = Colorizer::new(true, app.get_color());
|
||||
let verb = Error::singular_or_plural(curr_vals);
|
||||
let were_provided = Error::singular_or_plural(curr_vals);
|
||||
let arg = arg.to_string();
|
||||
let min_vals = min_vals.to_string();
|
||||
let curr_vals = curr_vals.to_string();
|
||||
|
||||
start_error(&mut c, "The argument '");
|
||||
c.warning(arg.to_string());
|
||||
c.warning(&*arg);
|
||||
c.none("' requires at least ");
|
||||
c.warning(min_vals.to_string());
|
||||
c.warning(&*min_vals);
|
||||
c.none(" values, but only ");
|
||||
c.warning(curr_vals.to_string());
|
||||
c.none(format!(" {} provided", verb));
|
||||
c.warning(&*curr_vals);
|
||||
c.none(were_provided);
|
||||
put_usage(&mut c, usage);
|
||||
try_help(app, &mut c);
|
||||
|
||||
Self::new(
|
||||
Self::for_app(
|
||||
app,
|
||||
c,
|
||||
ErrorKind::TooFewValues,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
vec![arg, curr_vals, min_vals],
|
||||
)
|
||||
.set_info(vec![
|
||||
arg.to_string(),
|
||||
curr_vals.to_string(),
|
||||
min_vals.to_string(),
|
||||
])
|
||||
}
|
||||
|
||||
pub(crate) fn value_validation(
|
||||
|
@ -939,7 +895,7 @@ impl Error {
|
|||
start_error(&mut c, "Invalid value");
|
||||
|
||||
c.none(" for '");
|
||||
c.warning(arg.clone());
|
||||
c.warning(&*arg);
|
||||
c.none("'");
|
||||
|
||||
c.none(format!(": {}", err));
|
||||
|
@ -957,28 +913,27 @@ impl Error {
|
|||
usage: String,
|
||||
) -> Self {
|
||||
let mut c = Colorizer::new(true, app.get_color());
|
||||
let verb = Error::singular_or_plural(curr_vals);
|
||||
let were_provided = Error::singular_or_plural(curr_vals);
|
||||
let arg = arg.to_string();
|
||||
let num_vals = num_vals.to_string();
|
||||
let curr_vals = curr_vals.to_string();
|
||||
|
||||
start_error(&mut c, "The argument '");
|
||||
c.warning(arg.to_string());
|
||||
c.warning(&*arg);
|
||||
c.none("' requires ");
|
||||
c.warning(num_vals.to_string());
|
||||
c.warning(&*num_vals);
|
||||
c.none(" values, but ");
|
||||
c.warning(curr_vals.to_string());
|
||||
c.none(format!(" {} provided", verb));
|
||||
c.warning(&*curr_vals);
|
||||
c.none(were_provided);
|
||||
put_usage(&mut c, usage);
|
||||
try_help(app, &mut c);
|
||||
|
||||
Self::new(
|
||||
Self::for_app(
|
||||
app,
|
||||
c,
|
||||
ErrorKind::WrongNumberOfValues,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
vec![arg, curr_vals, num_vals],
|
||||
)
|
||||
.set_info(vec![
|
||||
arg.to_string(),
|
||||
curr_vals.to_string(),
|
||||
num_vals.to_string(),
|
||||
])
|
||||
}
|
||||
|
||||
pub(crate) fn unexpected_multiple_usage(app: &App, arg: &Arg, usage: String) -> Self {
|
||||
|
@ -986,17 +941,12 @@ impl Error {
|
|||
let arg = arg.to_string();
|
||||
|
||||
start_error(&mut c, "The argument '");
|
||||
c.warning(arg.clone());
|
||||
c.warning(&*arg);
|
||||
c.none("' was provided more than once, but cannot be used multiple times");
|
||||
put_usage(&mut c, usage);
|
||||
try_help(app, &mut c);
|
||||
|
||||
Self::new(
|
||||
c,
|
||||
ErrorKind::UnexpectedMultipleUsage,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
)
|
||||
.set_info(vec![arg])
|
||||
Self::for_app(app, c, ErrorKind::UnexpectedMultipleUsage, vec![arg])
|
||||
}
|
||||
|
||||
pub(crate) fn unknown_argument(
|
||||
|
@ -1008,7 +958,7 @@ impl Error {
|
|||
let mut c = Colorizer::new(true, app.get_color());
|
||||
|
||||
start_error(&mut c, "Found argument '");
|
||||
c.warning(arg.clone());
|
||||
c.warning(&*arg);
|
||||
c.none("' which wasn't expected, or isn't valid in this context");
|
||||
|
||||
if let Some((flag, subcmd)) = did_you_mean {
|
||||
|
@ -1040,19 +990,14 @@ impl Error {
|
|||
put_usage(&mut c, usage);
|
||||
try_help(app, &mut c);
|
||||
|
||||
Self::new(
|
||||
c,
|
||||
ErrorKind::UnknownArgument,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
)
|
||||
.set_info(vec![arg])
|
||||
Self::for_app(app, c, ErrorKind::UnknownArgument, vec![arg])
|
||||
}
|
||||
|
||||
pub(crate) fn unnecessary_double_dash(app: &App, arg: String, usage: String) -> Self {
|
||||
let mut c = Colorizer::new(true, app.get_color());
|
||||
|
||||
start_error(&mut c, "Found argument '");
|
||||
c.warning(arg.clone());
|
||||
c.warning(&*arg);
|
||||
c.none("' which wasn't expected, or isn't valid in this context");
|
||||
|
||||
c.none(format!(
|
||||
|
@ -1062,30 +1007,25 @@ impl Error {
|
|||
put_usage(&mut c, usage);
|
||||
try_help(app, &mut c);
|
||||
|
||||
Self::new(
|
||||
c,
|
||||
ErrorKind::UnknownArgument,
|
||||
app.settings.is_set(AppSettings::WaitOnError),
|
||||
)
|
||||
.set_info(vec![arg])
|
||||
Self::for_app(app, c, ErrorKind::UnknownArgument, vec![arg])
|
||||
}
|
||||
|
||||
pub(crate) fn argument_not_found_auto(arg: String) -> Self {
|
||||
let mut c = Colorizer::new(true, ColorChoice::Never);
|
||||
|
||||
start_error(&mut c, "The argument '");
|
||||
c.warning(arg.clone());
|
||||
c.warning(&*arg);
|
||||
c.none("' wasn't found\n");
|
||||
|
||||
Self::new(c, ErrorKind::ArgumentNotFound, false).set_info(vec![arg])
|
||||
}
|
||||
|
||||
/// Returns the singular or plural form on the verb to be based on the argument's value.
|
||||
fn singular_or_plural(n: usize) -> String {
|
||||
fn singular_or_plural(n: usize) -> &'static str {
|
||||
if n > 1 {
|
||||
String::from("were")
|
||||
" were provided"
|
||||
} else {
|
||||
String::from("was")
|
||||
" was provided"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
ffi::{OsStr, OsString},
|
||||
fmt::Write as _,
|
||||
};
|
||||
|
||||
// Third Party
|
||||
|
@ -475,8 +476,8 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
}
|
||||
|
||||
matcher.subcommand(SubCommand {
|
||||
name: sc_name.clone(),
|
||||
id: sc_name.into(),
|
||||
id: Id::from(&*sc_name),
|
||||
name: sc_name,
|
||||
matches: sc_m.into_inner(),
|
||||
});
|
||||
|
||||
|
@ -735,11 +736,11 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
let mut sc_names = sc.name.clone();
|
||||
let mut flag_subcmd = false;
|
||||
if let Some(l) = sc.long_flag {
|
||||
sc_names.push_str(&format!(", --{}", l));
|
||||
write!(sc_names, ", --{}", l).unwrap();
|
||||
flag_subcmd = true;
|
||||
}
|
||||
if let Some(s) = sc.short_flag {
|
||||
sc_names.push_str(&format!(", -{}", s));
|
||||
write!(sc_names, ", -{}", s).unwrap();
|
||||
flag_subcmd = true;
|
||||
}
|
||||
|
||||
|
@ -1189,26 +1190,12 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
);
|
||||
if !(trailing_values && self.is_set(AS::DontDelimitTrailingValues)) {
|
||||
if let Some(delim) = arg.val_delim {
|
||||
let arg_split = val.split(delim);
|
||||
let vals = if let Some(t) = arg.terminator {
|
||||
let mut vals = vec![];
|
||||
for val in arg_split {
|
||||
if t == val {
|
||||
break;
|
||||
}
|
||||
vals.push(val);
|
||||
}
|
||||
vals
|
||||
} else {
|
||||
arg_split.collect()
|
||||
};
|
||||
self.add_multiple_vals_to_arg(
|
||||
arg,
|
||||
vals.into_iter().map(|x| x.to_os_str().into_owned()),
|
||||
matcher,
|
||||
ty,
|
||||
append,
|
||||
);
|
||||
let terminator = arg.terminator.map(OsStr::new);
|
||||
let vals = val
|
||||
.split(delim)
|
||||
.map(|x| x.to_os_str().into_owned())
|
||||
.take_while(|val| Some(val.as_os_str()) != terminator);
|
||||
self.add_multiple_vals_to_arg(arg, vals, matcher, ty, append);
|
||||
// If there was a delimiter used or we must use the delimiter to
|
||||
// separate the values or no more vals is needed, we're not
|
||||
// looking for more values.
|
||||
|
@ -1594,11 +1581,7 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
|
||||
match Help::new(HelpWriter::Buffer(&mut c), self, use_long).write_help() {
|
||||
Err(e) => e.into(),
|
||||
_ => ClapError::new(
|
||||
c,
|
||||
ErrorKind::DisplayHelp,
|
||||
self.app.settings.is_set(AS::WaitOnError),
|
||||
),
|
||||
_ => ClapError::for_app(self.app, c, ErrorKind::DisplayHelp, vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1608,11 +1591,7 @@ impl<'help, 'app> Parser<'help, 'app> {
|
|||
let msg = self.app._render_version(use_long);
|
||||
let mut c = Colorizer::new(false, self.color_help());
|
||||
c.none(msg);
|
||||
ClapError::new(
|
||||
c,
|
||||
ErrorKind::DisplayVersion,
|
||||
self.app.settings.is_set(AS::WaitOnError),
|
||||
)
|
||||
ClapError::for_app(self.app, c, ErrorKind::DisplayVersion, vec![])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue