WIP. Big reformat

This commit is contained in:
Alena Yuryeva 2018-07-24 19:48:24 +03:00
parent 7e6d403009
commit e1fb98a0c1
38 changed files with 1182 additions and 759 deletions

View file

@ -8,15 +8,17 @@ use clap::{App, Arg};
use test::Bencher; use test::Bencher;
macro_rules! create_app { macro_rules! create_app {
() => ({ () => {{
App::new("claptests") App::new("claptests")
.version("0.1") .version("0.1")
.about("tests clap library") .about("tests clap library")
.author("Kevin K. <kbknapp@gmail.com>") .author("Kevin K. <kbknapp@gmail.com>")
.args_from_usage("-f --flag 'tests flags' .args_from_usage(
"-f --flag 'tests flags'
-o --option=[opt] 'tests options' -o --option=[opt] 'tests options'
[positional] 'tests positional'") [positional] 'tests positional'",
}) )
}};
} }
#[bench] #[bench]

View file

@ -12,9 +12,9 @@ extern crate test;
use clap::{App, AppSettings, Arg, ArgSettings}; use clap::{App, AppSettings, Arg, ArgSettings};
use test::Bencher;
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Cursor; use std::io::Cursor;
use test::Bencher;
#[bench] #[bench]
fn build_app_short(b: &mut Bencher) { b.iter(|| app_short()); } fn build_app_short(b: &mut Bencher) { b.iter(|| app_short()); }
@ -410,306 +410,451 @@ macro_rules! doc {
doc!($map, $name, $short, $short) doc!($map, $name, $short, $short)
}; };
($map:expr, $name:expr, $short:expr, $long:expr) => { ($map:expr, $name:expr, $short:expr, $long:expr) => {
$map.insert($name, Usage { $map.insert(
short: $short, $name,
long: concat!($long, "\n "), Usage {
}); short: $short,
long: concat!($long, "\n "),
},
);
}; };
} }
lazy_static! { lazy_static! {
static ref USAGES: HashMap<&'static str, Usage> = { static ref USAGES: HashMap<&'static str, Usage> = {
let mut h = HashMap::new(); let mut h = HashMap::new();
doc!(h, "help-short", doc!(
"Show short help output.", h,
"Show short help output. Use --help to show more details."); "help-short",
doc!(h, "help", "Show short help output.",
"Show verbose help output.", "Show short help output. Use --help to show more details."
"When given, more details about flags are provided."); );
doc!(h, "version", doc!(
"Prints version information."); h,
"help",
"Show verbose help output.",
"When given, more details about flags are provided."
);
doc!(h, "version", "Prints version information.");
doc!(h, "pattern", doc!(
"A regular expression used for searching.", h,
"A regular expression used for searching. Multiple patterns \ "pattern",
may be given. To match a pattern beginning with a -, use [-]."); "A regular expression used for searching.",
doc!(h, "regexp", "A regular expression used for searching. Multiple patterns \
"A regular expression used for searching.", may be given. To match a pattern beginning with a -, use [-]."
"A regular expression used for searching. Multiple patterns \ );
may be given. To match a pattern beginning with a -, use [-]."); doc!(
doc!(h, "path", h,
"A file or directory to search.", "regexp",
"A file or directory to search. Directories are searched \ "A regular expression used for searching.",
recursively."); "A regular expression used for searching. Multiple patterns \
doc!(h, "files", may be given. To match a pattern beginning with a -, use [-]."
"Print each file that would be searched.", );
"Print each file that would be searched without actually \ doc!(
performing the search. This is useful to determine whether a \ h,
particular file is being searched or not."); "path",
doc!(h, "type-list", "A file or directory to search.",
"Show all supported file types.", "A file or directory to search. Directories are searched \
"Show all supported file types and their corresponding globs."); recursively."
);
doc!(
h,
"files",
"Print each file that would be searched.",
"Print each file that would be searched without actually \
performing the search. This is useful to determine whether a \
particular file is being searched or not."
);
doc!(
h,
"type-list",
"Show all supported file types.",
"Show all supported file types and their corresponding globs."
);
doc!(h, "text", doc!(h, "text", "Search binary files as if they were text.");
"Search binary files as if they were text."); doc!(h, "count", "Only show count of matches for each file.");
doc!(h, "count", doc!(
"Only show count of matches for each file."); h,
doc!(h, "color", "color",
"When to use color. [default: auto]", "When to use color. [default: auto]",
"When to use color in the output. The possible values are \ "When to use color in the output. The possible values are \
never, auto, always or ansi. The default is auto. When always \ never, auto, always or ansi. The default is auto. When always \
is used, coloring is attempted based on your environment. When \ is used, coloring is attempted based on your environment. When \
ansi used, coloring is forcefully done using ANSI escape color \ ansi used, coloring is forcefully done using ANSI escape color \
codes."); codes."
doc!(h, "colors", );
"Configure color settings and styles.", doc!(
"This flag specifies color settings for use in the output. \ h,
This flag may be provided multiple times. Settings are applied \ "colors",
iteratively. Colors are limited to one of eight choices: \ "Configure color settings and styles.",
red, blue, green, cyan, magenta, yellow, white and black. \ "This flag specifies color settings for use in the output. \
Styles are limited to nobold, bold, nointense or intense.\n\n\ This flag may be provided multiple times. Settings are applied \
The format of the flag is {type}:{attribute}:{value}. {type} \ iteratively. Colors are limited to one of eight choices: \
should be one of path, line or match. {attribute} can be fg, bg \ red, blue, green, cyan, magenta, yellow, white and black. \
or style. {value} is either a color (for fg and bg) or a text \ Styles are limited to nobold, bold, nointense or intense.\n\n\
style. A special format, {type}:none, will clear all color \ The format of the flag is {type}:{attribute}:{value}. {type} \
settings for {type}.\n\nFor example, the following command will \ should be one of path, line or match. {attribute} can be fg, bg \
change the match color to magenta and the background color for \ or style. {value} is either a color (for fg and bg) or a text \
line numbers to yellow:\n\n\ style. A special format, {type}:none, will clear all color \
rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo."); settings for {type}.\n\nFor example, the following command will \
doc!(h, "fixed-strings", change the match color to magenta and the background color for \
"Treat the pattern as a literal string.", line numbers to yellow:\n\n\
"Treat the pattern as a literal string instead of a regular \ rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo."
expression. When this flag is used, special regular expression \ );
meta characters such as (){}*+. do not need to be escaped."); doc!(
doc!(h, "glob", h,
"Include or exclude files/directories.", "fixed-strings",
"Include or exclude files/directories for searching that \ "Treat the pattern as a literal string.",
match the given glob. This always overrides any other \ "Treat the pattern as a literal string instead of a regular \
ignore logic. Multiple glob flags may be used. Globbing \ expression. When this flag is used, special regular expression \
rules match .gitignore globs. Precede a glob with a ! \ meta characters such as (){}*+. do not need to be escaped."
to exclude it."); );
doc!(h, "ignore-case", doc!(
"Case insensitive search.", h,
"Case insensitive search. This is overridden by \ "glob",
--case-sensitive."); "Include or exclude files/directories.",
doc!(h, "line-number", "Include or exclude files/directories for searching that \
"Show line numbers.", match the given glob. This always overrides any other \
"Show line numbers (1-based). This is enabled by default when \ ignore logic. Multiple glob flags may be used. Globbing \
searching in a tty."); rules match .gitignore globs. Precede a glob with a ! \
doc!(h, "no-line-number", to exclude it."
"Suppress line numbers.", );
"Suppress line numbers. This is enabled by default when NOT \ doc!(
searching in a tty."); h,
doc!(h, "quiet", "ignore-case",
"Do not print anything to stdout.", "Case insensitive search.",
"Do not print anything to stdout. If a match is found in a file, \ "Case insensitive search. This is overridden by \
stop searching. This is useful when ripgrep is used only for \ --case-sensitive."
its exit code."); );
doc!(h, "type", doc!(
"Only search files matching TYPE.", h,
"Only search files matching TYPE. Multiple type flags may be \ "line-number",
provided. Use the --type-list flag to list all available \ "Show line numbers.",
types."); "Show line numbers (1-based). This is enabled by default when \
doc!(h, "type-not", searching in a tty."
"Do not search files matching TYPE.", );
"Do not search files matching TYPE. Multiple type-not flags may \ doc!(
be provided. Use the --type-list flag to list all available \ h,
types."); "no-line-number",
doc!(h, "unrestricted", "Suppress line numbers.",
"Reduce the level of \"smart\" searching.", "Suppress line numbers. This is enabled by default when NOT \
"Reduce the level of \"smart\" searching. A single -u \ searching in a tty."
won't respect .gitignore (etc.) files. Two -u flags will \ );
additionally search hidden files and directories. Three \ doc!(
-u flags will additionally search binary files. -uu is \ h,
roughly equivalent to grep -r and -uuu is roughly \ "quiet",
equivalent to grep -a -r."); "Do not print anything to stdout.",
doc!(h, "invert-match", "Do not print anything to stdout. If a match is found in a file, \
"Invert matching.", stop searching. This is useful when ripgrep is used only for \
"Invert matching. Show lines that don't match given patterns."); its exit code."
doc!(h, "word-regexp", );
"Only show matches surrounded by word boundaries.", doc!(
"Only show matches surrounded by word boundaries. This is \ h,
equivalent to putting \\b before and after all of the search \ "type",
patterns."); "Only search files matching TYPE.",
"Only search files matching TYPE. Multiple type flags may be \
provided. Use the --type-list flag to list all available \
types."
);
doc!(
h,
"type-not",
"Do not search files matching TYPE.",
"Do not search files matching TYPE. Multiple type-not flags may \
be provided. Use the --type-list flag to list all available \
types."
);
doc!(
h,
"unrestricted",
"Reduce the level of \"smart\" searching.",
"Reduce the level of \"smart\" searching. A single -u \
won't respect .gitignore (etc.) files. Two -u flags will \
additionally search hidden files and directories. Three \
-u flags will additionally search binary files. -uu is \
roughly equivalent to grep -r and -uuu is roughly \
equivalent to grep -a -r."
);
doc!(
h,
"invert-match",
"Invert matching.",
"Invert matching. Show lines that don't match given patterns."
);
doc!(
h,
"word-regexp",
"Only show matches surrounded by word boundaries.",
"Only show matches surrounded by word boundaries. This is \
equivalent to putting \\b before and after all of the search \
patterns."
);
doc!(h, "after-context", doc!(h, "after-context", "Show NUM lines after each match.");
"Show NUM lines after each match."); doc!(h, "before-context", "Show NUM lines before each match.");
doc!(h, "before-context", doc!(h, "context", "Show NUM lines before and after each match.");
"Show NUM lines before each match."); doc!(
doc!(h, "context", h,
"Show NUM lines before and after each match."); "column",
doc!(h, "column", "Show column numbers",
"Show column numbers", "Show column numbers (1-based). This only shows the column \
"Show column numbers (1-based). This only shows the column \ numbers for the first match on each line. This does not try \
numbers for the first match on each line. This does not try \ to account for Unicode. One byte is equal to one column. This \
to account for Unicode. One byte is equal to one column. This \ implies --line-number."
implies --line-number."); );
doc!(h, "context-separator", doc!(
"Set the context separator string. [default: --]", h,
"The string used to separate non-contiguous context lines in the \ "context-separator",
output. Escape sequences like \\x7F or \\t may be used. The \ "Set the context separator string. [default: --]",
default value is --."); "The string used to separate non-contiguous context lines in the \
doc!(h, "debug", output. Escape sequences like \\x7F or \\t may be used. The \
"Show debug messages.", default value is --."
"Show debug messages. Please use this when filing a bug report."); );
doc!(h, "file", doc!(
"Search for patterns from the given file.", h,
"Search for patterns from the given file, with one pattern per \ "debug",
line. When this flag is used or multiple times or in \ "Show debug messages.",
combination with the -e/--regexp flag, then all patterns \ "Show debug messages. Please use this when filing a bug report."
provided are searched. Empty pattern lines will match all input \ );
lines, and the newline is not counted as part of the pattern."); doc!(
doc!(h, "files-with-matches", h,
"Only show the path of each file with at least one match."); "file",
doc!(h, "files-without-match", "Search for patterns from the given file.",
"Only show the path of each file that contains zero matches."); "Search for patterns from the given file, with one pattern per \
doc!(h, "with-filename", line. When this flag is used or multiple times or in \
"Show file name for each match.", combination with the -e/--regexp flag, then all patterns \
"Prefix each match with the file name that contains it. This is \ provided are searched. Empty pattern lines will match all input \
the default when more than one file is searched."); lines, and the newline is not counted as part of the pattern."
doc!(h, "no-filename", );
"Never show the file name for a match.", doc!(
"Never show the file name for a match. This is the default when \ h,
one file is searched."); "files-with-matches",
doc!(h, "heading", "Only show the path of each file with at least one match."
"Show matches grouped by each file.", );
"This shows the file name above clusters of matches from each \ doc!(
file instead of showing the file name for every match. This is \ h,
the default mode at a tty."); "files-without-match",
doc!(h, "no-heading", "Only show the path of each file that contains zero matches."
"Don't group matches by each file.", );
"Don't group matches by each file. If -H/--with-filename is \ doc!(
enabled, then file names will be shown for every line matched. \ h,
This is the default mode when not at a tty."); "with-filename",
doc!(h, "hidden", "Show file name for each match.",
"Search hidden files and directories.", "Prefix each match with the file name that contains it. This is \
"Search hidden files and directories. By default, hidden files \ the default when more than one file is searched."
and directories are skipped."); );
doc!(h, "ignore-file", doc!(
"Specify additional ignore files.", h,
"Specify additional ignore files for filtering file paths. \ "no-filename",
Ignore files should be in the gitignore format and are matched \ "Never show the file name for a match.",
relative to the current working directory. These ignore files \ "Never show the file name for a match. This is the default when \
have lower precedence than all other ignore files. When \ one file is searched."
specifying multiple ignore files, earlier files have lower \ );
precedence than later files."); doc!(
doc!(h, "follow", h,
"Follow symbolic links."); "heading",
doc!(h, "max-count", "Show matches grouped by each file.",
"Limit the number of matches.", "This shows the file name above clusters of matches from each \
"Limit the number of matching lines per file searched to NUM."); file instead of showing the file name for every match. This is \
doc!(h, "maxdepth", the default mode at a tty."
"Descend at most NUM directories.", );
"Limit the depth of directory traversal to NUM levels beyond \ doc!(
the paths given. A value of zero only searches the \ h,
starting-points themselves.\n\nFor example, \ "no-heading",
'rg --maxdepth 0 dir/' is a no-op because dir/ will not be \ "Don't group matches by each file.",
descended into. 'rg --maxdepth 1 dir/' will search only the \ "Don't group matches by each file. If -H/--with-filename is \
direct children of dir/."); enabled, then file names will be shown for every line matched. \
doc!(h, "mmap", This is the default mode when not at a tty."
"Searching using memory maps when possible.", );
"Search using memory maps when possible. This is enabled by \ doc!(
default when ripgrep thinks it will be faster. Note that memory \ h,
map searching doesn't currently support all options, so if an \ "hidden",
incompatible option (e.g., --context) is given with --mmap, \ "Search hidden files and directories.",
then memory maps will not be used."); "Search hidden files and directories. By default, hidden files \
doc!(h, "no-messages", and directories are skipped."
"Suppress all error messages.", );
"Suppress all error messages. This is equivalent to redirecting \ doc!(
stderr to /dev/null."); h,
doc!(h, "no-mmap", "ignore-file",
"Never use memory maps.", "Specify additional ignore files.",
"Never use memory maps, even when they might be faster."); "Specify additional ignore files for filtering file paths. \
doc!(h, "no-ignore", Ignore files should be in the gitignore format and are matched \
"Don't respect ignore files.", relative to the current working directory. These ignore files \
"Don't respect ignore files (.gitignore, .ignore, etc.). This \ have lower precedence than all other ignore files. When \
implies --no-ignore-parent and --no-ignore-vcs."); specifying multiple ignore files, earlier files have lower \
doc!(h, "no-ignore-parent", precedence than later files."
"Don't respect ignore files in parent directories.", );
"Don't respect ignore files (.gitignore, .ignore, etc.) in \ doc!(h, "follow", "Follow symbolic links.");
parent directories."); doc!(
doc!(h, "no-ignore-vcs", h,
"Don't respect VCS ignore files", "max-count",
"Don't respect version control ignore files (.gitignore, etc.). \ "Limit the number of matches.",
This implies --no-ignore-parent. Note that .ignore files will \ "Limit the number of matching lines per file searched to NUM."
continue to be respected."); );
doc!(h, "null", doc!(
"Print NUL byte after file names", h,
"Whenever a file name is printed, follow it with a NUL byte. \ "maxdepth",
This includes printing file names before matches, and when \ "Descend at most NUM directories.",
printing a list of matching files such as with --count, \ "Limit the depth of directory traversal to NUM levels beyond \
--files-with-matches and --files. This option is useful for use \ the paths given. A value of zero only searches the \
with xargs."); starting-points themselves.\n\nFor example, \
doc!(h, "path-separator", 'rg --maxdepth 0 dir/' is a no-op because dir/ will not be \
"Path separator to use when printing file paths.", descended into. 'rg --maxdepth 1 dir/' will search only the \
"The path separator to use when printing file paths. This \ direct children of dir/."
defaults to your platform's path separator, which is / on Unix \ );
and \\ on Windows. This flag is intended for overriding the \ doc!(
default when the environment demands it (e.g., cygwin). A path \ h,
separator is limited to a single byte."); "mmap",
doc!(h, "pretty", "Searching using memory maps when possible.",
"Alias for --color always --heading -n."); "Search using memory maps when possible. This is enabled by \
doc!(h, "replace", default when ripgrep thinks it will be faster. Note that memory \
"Replace matches with string given.", map searching doesn't currently support all options, so if an \
"Replace every match with the string given when printing \ incompatible option (e.g., --context) is given with --mmap, \
results. Neither this flag nor any other flag will modify your \ then memory maps will not be used."
files.\n\nCapture group indices (e.g., $5) and names \ );
(e.g., $foo) are supported in the replacement string.\n\n\ doc!(
Note that the replacement by default replaces each match, and \ h,
NOT the entire line. To replace the entire line, you should \ "no-messages",
match the entire line."); "Suppress all error messages.",
doc!(h, "case-sensitive", "Suppress all error messages. This is equivalent to redirecting \
"Search case sensitively.", stderr to /dev/null."
"Search case sensitively. This overrides -i/--ignore-case and \ );
-S/--smart-case."); doc!(
doc!(h, "smart-case", h,
"Smart case search.", "no-mmap",
"Searches case insensitively if the pattern is all lowercase. \ "Never use memory maps.",
Search case sensitively otherwise. This is overridden by \ "Never use memory maps, even when they might be faster."
either -s/--case-sensitive or -i/--ignore-case."); );
doc!(h, "sort-files", doc!(
"Sort results by file path. Implies --threads=1.", h,
"Sort results by file path. Note that this currently \ "no-ignore",
disables all parallelism and runs search in a single thread."); "Don't respect ignore files.",
doc!(h, "threads", "Don't respect ignore files (.gitignore, .ignore, etc.). This \
"The approximate number of threads to use.", implies --no-ignore-parent and --no-ignore-vcs."
"The approximate number of threads to use. A value of 0 (which \ );
is the default) causes ripgrep to choose the thread count \ doc!(
using heuristics."); h,
doc!(h, "vimgrep", "no-ignore-parent",
"Show results in vim compatible format.", "Don't respect ignore files in parent directories.",
"Show results with every match on its own line, including \ "Don't respect ignore files (.gitignore, .ignore, etc.) in \
line numbers and column numbers. With this option, a line with \ parent directories."
more than one match will be printed more than once."); );
doc!(
h,
"no-ignore-vcs",
"Don't respect VCS ignore files",
"Don't respect version control ignore files (.gitignore, etc.). \
This implies --no-ignore-parent. Note that .ignore files will \
continue to be respected."
);
doc!(
h,
"null",
"Print NUL byte after file names",
"Whenever a file name is printed, follow it with a NUL byte. \
This includes printing file names before matches, and when \
printing a list of matching files such as with --count, \
--files-with-matches and --files. This option is useful for use \
with xargs."
);
doc!(
h,
"path-separator",
"Path separator to use when printing file paths.",
"The path separator to use when printing file paths. This \
defaults to your platform's path separator, which is / on Unix \
and \\ on Windows. This flag is intended for overriding the \
default when the environment demands it (e.g., cygwin). A path \
separator is limited to a single byte."
);
doc!(h, "pretty", "Alias for --color always --heading -n.");
doc!(
h,
"replace",
"Replace matches with string given.",
"Replace every match with the string given when printing \
results. Neither this flag nor any other flag will modify your \
files.\n\nCapture group indices (e.g., $5) and names \
(e.g., $foo) are supported in the replacement string.\n\n\
Note that the replacement by default replaces each match, and \
NOT the entire line. To replace the entire line, you should \
match the entire line."
);
doc!(
h,
"case-sensitive",
"Search case sensitively.",
"Search case sensitively. This overrides -i/--ignore-case and \
-S/--smart-case."
);
doc!(
h,
"smart-case",
"Smart case search.",
"Searches case insensitively if the pattern is all lowercase. \
Search case sensitively otherwise. This is overridden by \
either -s/--case-sensitive or -i/--ignore-case."
);
doc!(
h,
"sort-files",
"Sort results by file path. Implies --threads=1.",
"Sort results by file path. Note that this currently \
disables all parallelism and runs search in a single thread."
);
doc!(
h,
"threads",
"The approximate number of threads to use.",
"The approximate number of threads to use. A value of 0 (which \
is the default) causes ripgrep to choose the thread count \
using heuristics."
);
doc!(
h,
"vimgrep",
"Show results in vim compatible format.",
"Show results with every match on its own line, including \
line numbers and column numbers. With this option, a line with \
more than one match will be printed more than once."
);
doc!(h, "type-add", doc!(
"Add a new glob for a file type.", h,
"Add a new glob for a particular file type. Only one glob can be \ "type-add",
added at a time. Multiple --type-add flags can be provided. \ "Add a new glob for a file type.",
Unless --type-clear is used, globs are added to any existing \ "Add a new glob for a particular file type. Only one glob can be \
globs defined inside of ripgrep.\n\nNote that this MUST be \ added at a time. Multiple --type-add flags can be provided. \
passed to every invocation of ripgrep. Type settings are NOT \ Unless --type-clear is used, globs are added to any existing \
persisted.\n\nExample: \ globs defined inside of ripgrep.\n\nNote that this MUST be \
rg --type-add 'foo:*.foo' -tfoo PATTERN.\n\n\ passed to every invocation of ripgrep. Type settings are NOT \
--type-add can also be used to include rules from other types \ persisted.\n\nExample: \
with the special include directive. The include directive \ rg --type-add 'foo:*.foo' -tfoo PATTERN.\n\n\
permits specifying one or more other type names (separated by a \ --type-add can also be used to include rules from other types \
comma) that have been defined and its rules will automatically \ with the special include directive. The include directive \
be imported into the type specified. For example, to create a \ permits specifying one or more other type names (separated by a \
type called src that matches C++, Python and Markdown files, one \ comma) that have been defined and its rules will automatically \
can use:\n\n\ be imported into the type specified. For example, to create a \
--type-add 'src:include:cpp,py,md'\n\n\ type called src that matches C++, Python and Markdown files, one \
Additional glob rules can still be added to the src type by \ can use:\n\n\
using the --type-add flag again:\n\n\ --type-add 'src:include:cpp,py,md'\n\n\
--type-add 'src:include:cpp,py,md' --type-add 'src:*.foo'\n\n\ Additional glob rules can still be added to the src type by \
Note that type names must consist only of Unicode letters or \ using the --type-add flag again:\n\n\
numbers. Punctuation characters are not allowed."); --type-add 'src:include:cpp,py,md' --type-add 'src:*.foo'\n\n\
doc!(h, "type-clear", Note that type names must consist only of Unicode letters or \
"Clear globs for given file type.", numbers. Punctuation characters are not allowed."
"Clear the file type globs previously defined for TYPE. This \ );
only clears the default type definitions that are found inside \ doc!(
of ripgrep.\n\nNote that this MUST be passed to every \ h,
invocation of ripgrep. Type settings are NOT persisted."); "type-clear",
"Clear globs for given file type.",
"Clear the file type globs previously defined for TYPE. This \
only clears the default type definitions that are found inside \
of ripgrep.\n\nNote that this MUST be passed to every \
invocation of ripgrep. Type settings are NOT persisted."
);
h h
}; };

View file

@ -106,7 +106,7 @@ where
#[doc(hidden)] #[doc(hidden)]
pub g_settings: AppFlags, pub g_settings: AppFlags,
#[doc(hidden)] #[doc(hidden)]
pub args: MKeyMap, pub args: MKeyMap<'a, 'b>,
#[doc(hidden)] #[doc(hidden)]
pub subcommands: Vec<App<'a, 'b>>, pub subcommands: Vec<App<'a, 'b>>,
#[doc(hidden)] #[doc(hidden)]
@ -990,7 +990,7 @@ impl<'a, 'b> App<'a, 'b> {
{ {
let i = self let i = self
.args .args
.iter() .values()
.enumerate() .enumerate()
.filter_map(|(i, a)| if a.name == arg { Some(i) } else { None }) .filter_map(|(i, a)| if a.name == arg { Some(i) } else { None })
.next(); .next();
@ -1412,7 +1412,7 @@ impl<'a, 'b> App<'a, 'b> {
let global_arg_vec: Vec<&str> = (&self) let global_arg_vec: Vec<&str> = (&self)
.args .args
.iter() .values()
.filter(|a| a.is_set(ArgSettings::Global)) .filter(|a| a.is_set(ArgSettings::Global))
.map(|ga| ga.name) .map(|ga| ga.name)
.collect(); .collect();
@ -1446,7 +1446,7 @@ impl<'a, 'b> App<'a, 'b> {
} }
true true
}); });
//TODO add .values_mut() for MKeyMap
for a in self.args.values_mut() { for a in self.args.values_mut() {
// Fill in the groups // Fill in the groups
if let Some(ref grps) = a.groups { if let Some(ref grps) = a.groups {
@ -1483,16 +1483,18 @@ impl<'a, 'b> App<'a, 'b> {
debugln!("App::app_debug_asserts;"); debugln!("App::app_debug_asserts;");
// * Args listed inside groups should exist // * Args listed inside groups should exist
// * Groups should not have naming conflicts with Args // * Groups should not have naming conflicts with Args
let g = groups!(self).find(|g| {
g.args // * Will be removed as a part of removing String types
.iter() // let g = groups!(self).find(|g| {
.any(|arg| !(find!(self, arg).is_some() || groups!(self).any(|g| &g.name == arg))) // g.args
}); // .iter()
assert!( // .any(|arg| !(find!(self, arg).is_some() || groups!(self).any(|g| &g.name == arg)))
g.is_none(), // });
"The group '{}' contains an arg that doesn't exist or has a naming conflict with a group.", // assert!(
g.unwrap().name // g.is_none(),
); // "The group '{}' contains an arg that doesn't exist or has a naming conflict with a group.",
// g.unwrap().name
// );
true true
} }
@ -1519,7 +1521,7 @@ impl<'a, 'b> App<'a, 'b> {
sc.max_w = self.max_w; sc.max_w = self.max_w;
} }
{ {
for a in self.args.iter().filter(|a| a.is_set(ArgSettings::Global)) { for a in self.args.values().filter(|a| a.is_set(ArgSettings::Global)) {
sc.args.push(a.clone()); sc.args.push(a.clone());
} }
} }
@ -1744,9 +1746,11 @@ impl<'a, 'b> App<'a, 'b> {
ColorWhen::Auto ColorWhen::Auto
} }
} }
pub(crate) fn contains_long(&self, l: &str) -> bool { longs!(self).any(|al| al == l) } pub(crate) fn contains_long(&self, l: &str) -> bool {
longs!(self).any(|&al| al == OsString::from(l).as_os_str())
}
pub(crate) fn contains_short(&self, s: char) -> bool { shorts!(self).any(|arg_s| arg_s == s) } pub(crate) fn contains_short(&self, s: char) -> bool { shorts!(self).any(|&arg_s| arg_s == s) }
pub fn is_set(&self, s: AppSettings) -> bool { pub fn is_set(&self, s: AppSettings) -> bool {
self.settings.is_set(s) || self.g_settings.is_set(s) self.settings.is_set(s) || self.g_settings.is_set(s)
@ -1770,9 +1774,13 @@ impl<'a, 'b> App<'a, 'b> {
pub fn has_positionals(&self) -> bool { positionals!(self).count() > 0 } pub fn has_positionals(&self) -> bool { positionals!(self).count() > 0 }
pub fn has_visible_opts(&self) -> bool { opts!(self).any(|o| !o.is_set(ArgSettings::Hidden)) } pub fn has_visible_opts(&self) -> bool {
opts!(self).any(|(k, o)| !o.is_set(ArgSettings::Hidden))
}
pub fn has_visible_flags(&self) -> bool { flags!(self).any(|o| !o.is_set(ArgSettings::Hidden)) } pub fn has_visible_flags(&self) -> bool {
flags!(self).any(|(k, o)| !o.is_set(ArgSettings::Hidden))
}
pub fn has_visible_positionals(&self) -> bool { pub fn has_visible_positionals(&self) -> bool {
positionals!(self).any(|o| !o.is_set(ArgSettings::Hidden)) positionals!(self).any(|o| !o.is_set(ArgSettings::Hidden))

View file

@ -1,8 +1,8 @@
// Std // Std
#[allow(unused_imports)] #[allow(unused_imports)]
use std::ascii::AsciiExt; use std::ascii::AsciiExt;
use std::str::FromStr;
use std::ops::BitOr; use std::ops::BitOr;
use std::str::FromStr;
bitflags! { bitflags! {
struct Flags: u64 { struct Flags: u64 {
@ -62,8 +62,11 @@ impl BitOr for AppFlags {
impl Default for AppFlags { impl Default for AppFlags {
fn default() -> Self { fn default() -> Self {
AppFlags( AppFlags(
Flags::NEEDS_LONG_VERSION | Flags::NEEDS_LONG_HELP | Flags::NEEDS_SC_HELP Flags::NEEDS_LONG_VERSION
| Flags::UTF8_NONE | Flags::COLOR_AUTO, | Flags::NEEDS_LONG_HELP
| Flags::NEEDS_SC_HELP
| Flags::UTF8_NONE
| Flags::COLOR_AUTO,
) )
} }
} }
@ -713,7 +716,10 @@ pub enum AppSettings {
/// ///
/// assert!(m.subcommand_matches("foo").is_none()); /// assert!(m.subcommand_matches("foo").is_none());
/// ``` /// ```
#[deprecated(since = "2.27.0", note = "No longer required to propagate values")] #[deprecated(
since = "2.27.0",
note = "No longer required to propagate values"
)]
PropagateGlobalValuesDown, PropagateGlobalValuesDown,
/// Allows [`SubCommand`]s to override all requirements of the parent command. /// Allows [`SubCommand`]s to override all requirements of the parent command.
@ -925,23 +931,32 @@ pub enum AppSettings {
/// [`SubCommand`]: ./struct.SubCommand.html /// [`SubCommand`]: ./struct.SubCommand.html
WaitOnError, WaitOnError,
#[doc(hidden)] NeedsLongVersion, #[doc(hidden)]
NeedsLongVersion,
#[doc(hidden)] NeedsLongHelp, #[doc(hidden)]
NeedsLongHelp,
#[doc(hidden)] NeedsSubcommandHelp, #[doc(hidden)]
NeedsSubcommandHelp,
#[doc(hidden)] LowIndexMultiplePositional, #[doc(hidden)]
LowIndexMultiplePositional,
#[doc(hidden)] TrailingValues, #[doc(hidden)]
TrailingValues,
#[doc(hidden)] ValidNegNumFound, #[doc(hidden)]
ValidNegNumFound,
#[doc(hidden)] Propagated, #[doc(hidden)]
Propagated,
#[doc(hidden)] ValidArgFound, #[doc(hidden)]
ValidArgFound,
#[doc(hidden)] ContainsLast, #[doc(hidden)]
ContainsLast,
} }
impl FromStr for AppSettings { impl FromStr for AppSettings {

View file

@ -86,7 +86,7 @@ pub enum ArgSettings {
AllowEmptyValues, AllowEmptyValues,
/// Sets an arg to be global (i.e. exist in all subcommands) /// Sets an arg to be global (i.e. exist in all subcommands)
/// **DEPRECATED** /// **DEPRECATED**
#[deprecated(since="2.32.0", note="Use `App::global_arg` instead")] #[deprecated(since = "2.32.0", note = "Use `App::global_arg` instead")]
Global, Global,
/// Hides an arg from the help message /// Hides an arg from the help message
Hidden, Hidden,
@ -119,8 +119,10 @@ pub enum ArgSettings {
HiddenShortHelp, HiddenShortHelp,
/// The argument should **not** be shown in long help text /// The argument should **not** be shown in long help text
HiddenLongHelp, HiddenLongHelp,
#[doc(hidden)] RequiredUnlessAll, #[doc(hidden)]
#[doc(hidden)] ValueDelimiterNotSet, RequiredUnlessAll,
#[doc(hidden)]
ValueDelimiterNotSet,
} }
impl FromStr for ArgSettings { impl FromStr for ArgSettings {

View file

@ -129,7 +129,9 @@ impl<'a> ArgGroup<'a> {
/// # } /// # }
/// ``` /// ```
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
pub fn from_yaml(y: &'a yaml_rust::Yaml) -> ArgGroup<'a> { ArgGroup::from(y.as_hash().unwrap()) } pub fn from_yaml(y: &'a yaml_rust::Yaml) -> ArgGroup<'a> {
ArgGroup::from(y.as_hash().unwrap())
}
/// Adds an [argument] to this group by name /// Adds an [argument] to this group by name
/// ///

View file

@ -1,72 +1,75 @@
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
macro_rules! yaml_tuple2 { macro_rules! yaml_tuple2 {
($a:ident, $v:ident, $c:ident) => {{ ($a:ident, $v:ident, $c:ident) => {{
if let Some(vec) = $v.as_vec() { if let Some(vec) = $v.as_vec() {
for ys in vec { for ys in vec {
if let Some(tup) = ys.as_vec() { if let Some(tup) = ys.as_vec() {
debug_assert_eq!(2, tup.len()); debug_assert_eq!(2, tup.len());
$a = $a.$c(yaml_str!(tup[0]), yaml_str!(tup[1])); $a = $a.$c(yaml_str!(tup[0]), yaml_str!(tup[1]));
} else { } else {
panic!("Failed to convert YAML value to vec"); panic!("Failed to convert YAML value to vec");
}
} }
} else {
panic!("Failed to convert YAML value to vec");
} }
$a } else {
panic!("Failed to convert YAML value to vec");
} }
}; $a
}};
} }
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
macro_rules! yaml_tuple3 { macro_rules! yaml_tuple3 {
($a:ident, $v:ident, $c:ident) => {{ ($a:ident, $v:ident, $c:ident) => {{
if let Some(vec) = $v.as_vec() { if let Some(vec) = $v.as_vec() {
for ys in vec { for ys in vec {
if let Some(tup) = ys.as_vec() { if let Some(tup) = ys.as_vec() {
debug_assert_eq!(3, tup.len()); debug_assert_eq!(3, tup.len());
$a = $a.$c(yaml_str!(tup[0]), yaml_opt_str!(tup[1]), yaml_str!(tup[2])); $a = $a.$c(yaml_str!(tup[0]), yaml_opt_str!(tup[1]), yaml_str!(tup[2]));
} else { } else {
panic!("Failed to convert YAML value to vec"); panic!("Failed to convert YAML value to vec");
}
} }
} else {
panic!("Failed to convert YAML value to vec");
} }
$a } else {
panic!("Failed to convert YAML value to vec");
} }
}; $a
}};
} }
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
macro_rules! yaml_vec_or_str { macro_rules! yaml_vec_or_str {
($v:ident, $a:ident, $c:ident) => {{ ($v:ident, $a:ident, $c:ident) => {{
let maybe_vec = $v.as_vec(); let maybe_vec = $v.as_vec();
if let Some(vec) = maybe_vec { if let Some(vec) = maybe_vec {
for ys in vec { for ys in vec {
if let Some(s) = ys.as_str() { if let Some(s) = ys.as_str() {
$a = $a.$c(s);
} else {
panic!("Failed to convert YAML value {:?} to a string", ys);
}
}
} else {
if let Some(s) = $v.as_str() {
$a = $a.$c(s); $a = $a.$c(s);
} else { } else {
panic!("Failed to convert YAML value {:?} to either a vec or string", $v); panic!("Failed to convert YAML value {:?} to a string", ys);
} }
} }
$a } else {
if let Some(s) = $v.as_str() {
$a = $a.$c(s);
} else {
panic!(
"Failed to convert YAML value {:?} to either a vec or string",
$v
);
}
} }
}; $a
}};
} }
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
macro_rules! yaml_opt_str { macro_rules! yaml_opt_str {
($v:expr) => {{ ($v:expr) => {{
if $v.is_null() { if $v.is_null() {
Some($v.as_str().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v))) Some(
$v.as_str()
.unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v)),
)
} else { } else {
None None
} }
@ -76,7 +79,8 @@ macro_rules! yaml_opt_str {
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
macro_rules! yaml_str { macro_rules! yaml_str {
($v:expr) => {{ ($v:expr) => {{
$v.as_str().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v)) $v.as_str()
.unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v))
}}; }};
} }
@ -90,20 +94,28 @@ macro_rules! yaml_to_str {
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
macro_rules! yaml_to_bool { macro_rules! yaml_to_bool {
($a:ident, $v:ident, $c:ident) => {{ ($a:ident, $v:ident, $c:ident) => {{
$a.$c($v.as_bool().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v))) $a.$c($v
.as_bool()
.unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v)))
}}; }};
} }
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
macro_rules! yaml_to_u64 { macro_rules! yaml_to_u64 {
($a:ident, $v:ident, $c:ident) => {{ ($a:ident, $v:ident, $c:ident) => {{
$a.$c($v.as_i64().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v)) as u64) $a.$c($v
.as_i64()
.unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v))
as u64)
}}; }};
} }
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
macro_rules! yaml_to_usize { macro_rules! yaml_to_usize {
($a:ident, $v:ident, $c:ident) => {{ ($a:ident, $v:ident, $c:ident) => {{
$a.$c($v.as_i64().unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v)) as usize) $a.$c($v
.as_i64()
.unwrap_or_else(|| panic!("failed to convert YAML {:?} value to a string", $v))
as usize)
}}; }};
} }

View file

@ -7,7 +7,7 @@ pub mod arg;
mod arg_group; mod arg_group;
mod usage_parser; mod usage_parser;
pub use self::usage_parser::UsageParser;
pub use self::app::{App, AppFlags, AppSettings, Propagation}; pub use self::app::{App, AppFlags, AppSettings, Propagation};
pub use self::arg::{Arg, ArgFlags, ArgSettings}; pub use self::arg::{Arg, ArgFlags, ArgSettings};
pub use self::arg_group::ArgGroup; pub use self::arg_group::ArgGroup;
pub use self::usage_parser::UsageParser;

View file

@ -1,7 +1,7 @@
// Internal // Internal
use INTERNAL_ERROR_MSG;
use build::{Arg, ArgSettings}; use build::{Arg, ArgSettings};
use util::VecMap; use util::VecMap;
use INTERNAL_ERROR_MSG;
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
enum UsageToken { enum UsageToken {
@ -74,7 +74,8 @@ impl<'a> UsageParser<'a> {
fn name(&mut self, arg: &mut Arg<'a, 'a>) { fn name(&mut self, arg: &mut Arg<'a, 'a>) {
debugln!("UsageParser::name;"); debugln!("UsageParser::name;");
if *self.usage if *self
.usage
.as_bytes() .as_bytes()
.get(self.pos) .get(self.pos)
.expect(INTERNAL_ERROR_MSG) == b'<' && !self.explicit_name_set .expect(INTERNAL_ERROR_MSG) == b'<' && !self.explicit_name_set
@ -122,7 +123,8 @@ impl<'a> UsageParser<'a> {
fn short_or_long(&mut self, arg: &mut Arg<'a, 'a>) { fn short_or_long(&mut self, arg: &mut Arg<'a, 'a>) {
debugln!("UsageParser::short_or_long;"); debugln!("UsageParser::short_or_long;");
self.pos += 1; self.pos += 1;
if *self.usage if *self
.usage
.as_bytes() .as_bytes()
.get(self.pos) .get(self.pos)
.expect(INTERNAL_ERROR_MSG) == b'-' .expect(INTERNAL_ERROR_MSG) == b'-'

View file

@ -8,7 +8,7 @@ use INTERNAL_ERROR_MSG;
pub struct ElvishGen<'a, 'b, 'c> pub struct ElvishGen<'a, 'b, 'c>
where where
'a: 'b, 'a: 'b,
'b: 'c 'b: 'c,
{ {
app: &'c App<'a, 'b>, app: &'c App<'a, 'b>,
} }
@ -20,10 +20,10 @@ impl<'a, 'b, 'c> ElvishGen<'a, 'b, 'c> {
let bin_name = self.app.bin_name.as_ref().unwrap(); let bin_name = self.app.bin_name.as_ref().unwrap();
let mut names = vec![]; let mut names = vec![];
let subcommands_cases = let subcommands_cases = generate_inner(self.app, "", &mut names);
generate_inner(self.app, "", &mut names);
let result = format!(r#" let result = format!(
r#"
edit:completion:arg-completer[{bin_name}] = [@words]{{ edit:completion:arg-completer[{bin_name}] = [@words]{{
fn spaces [n]{{ fn spaces [n]{{
repeat $n ' ' | joins '' repeat $n ' ' | joins ''
@ -54,10 +54,10 @@ edit:completion:arg-completer[{bin_name}] = [@words]{{
// Escape string inside single quotes // Escape string inside single quotes
fn escape_string(string: &str) -> String { string.replace("'", "''") } 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<&str>, data: T) -> String {
match help { match help {
Some(help) => escape_string(help), Some(help) => escape_string(help),
_ => data.to_string() _ => data.to_string(),
} }
} }
@ -65,7 +65,11 @@ fn generate_inner<'a, 'b, 'c>(
p: &'c App<'a, 'b>, p: &'c App<'a, 'b>,
previous_command_name: &str, previous_command_name: &str,
names: &mut Vec<&'a str>, names: &mut Vec<&'a str>,
) -> String where 'a: 'b, 'b: 'c{ ) -> String
where
'a: 'b,
'b: 'c,
{
debugln!("ElvishGen::generate_inner;"); debugln!("ElvishGen::generate_inner;");
let command_name = if previous_command_name.is_empty() { let command_name = if previous_command_name.is_empty() {
p.bin_name.as_ref().expect(INTERNAL_ERROR_MSG).clone() p.bin_name.as_ref().expect(INTERNAL_ERROR_MSG).clone()
@ -113,13 +117,11 @@ fn generate_inner<'a, 'b, 'c>(
r" r"
&'{}'= {{{} &'{}'= {{{}
}}", }}",
&command_name, &command_name, completions
completions
); );
for subcommand in &p.subcommands { for subcommand in &p.subcommands {
let subcommand_subcommands_cases = let subcommand_subcommands_cases = generate_inner(&subcommand, &command_name, names);
generate_inner(&subcommand, &command_name, names);
subcommands_cases.push_str(&subcommand_subcommands_cases); subcommands_cases.push_str(&subcommand_subcommands_cases);
} }

View file

@ -24,5 +24,5 @@ macro_rules! get_zsh_arg_conflicts {
} else { } else {
String::new() String::new()
} }
} };
} }

View file

@ -1,23 +1,23 @@
#[macro_use] #[macro_use]
mod macros; mod macros;
mod bash; mod bash;
mod fish;
mod zsh;
mod powershell;
mod elvish; mod elvish;
mod fish;
mod powershell;
mod shell; mod shell;
mod zsh;
// Std // Std
use std::io::Write; use std::io::Write;
// Internal // Internal
use build::App;
use self::bash::BashGen; use self::bash::BashGen;
use self::fish::FishGen;
use self::zsh::ZshGen;
use self::powershell::PowerShellGen;
use self::elvish::ElvishGen; use self::elvish::ElvishGen;
use self::fish::FishGen;
use self::powershell::PowerShellGen;
pub use self::shell::Shell; pub use self::shell::Shell;
use self::zsh::ZshGen;
use build::App;
pub struct ComplGen<'a, 'b>(&'b App<'a, 'b>) pub struct ComplGen<'a, 'b>(&'b App<'a, 'b>)
where where
@ -33,7 +33,7 @@ impl<'a, 'b> ComplGen<'a, 'b> {
Shell::Zsh => ZshGen::new(self.0).generate_to(buf), Shell::Zsh => ZshGen::new(self.0).generate_to(buf),
Shell::PowerShell => PowerShellGen::new(self.0).generate_to(buf), Shell::PowerShell => PowerShellGen::new(self.0).generate_to(buf),
Shell::Elvish => ElvishGen::new(self.0).generate_to(buf), Shell::Elvish => ElvishGen::new(self.0).generate_to(buf),
_ => panic!("Unsupported shell type for generating completions") _ => panic!("Unsupported shell type for generating completions"),
} }
} }
} }

View file

@ -16,10 +16,10 @@ impl<'a, 'b> PowerShellGen<'a, 'b> {
let bin_name = self.0.bin_name.as_ref().unwrap(); let bin_name = self.0.bin_name.as_ref().unwrap();
let mut names = vec![]; let mut names = vec![];
let subcommands_cases = let subcommands_cases = generate_inner(self.0, "", &mut names);
generate_inner(self.0, "", &mut names);
let result = format!(r#" let result = format!(
r#"
using namespace System.Management.Automation using namespace System.Management.Automation
using namespace System.Management.Automation.Language using namespace System.Management.Automation.Language
@ -57,10 +57,10 @@ Register-ArgumentCompleter -Native -CommandName '{bin_name}' -ScriptBlock {{
// Escape string inside single quotes // Escape string inside single quotes
fn escape_string(string: &str) -> String { string.replace("'", "''") } 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<&str>, data: T) -> String {
match help { match help {
Some(help) => escape_string(&help), Some(help) => escape_string(&help),
_ => data.to_string() _ => data.to_string(),
} }
} }
@ -83,14 +83,22 @@ fn generate_inner<'a, 'b, 'p>(
if let Some(data) = option.short { if let Some(data) = option.short {
let tooltip = get_tooltip(option.help, data); let tooltip = get_tooltip(option.help, data);
completions.push_str(&preamble); completions.push_str(&preamble);
completions.push_str(format!("'-{}', '{}', {}, '{}')", completions.push_str(
data, data, "[CompletionResultType]::ParameterName", tooltip).as_str()); format!(
"'-{}', '{}', {}, '{}')",
data, data, "[CompletionResultType]::ParameterName", tooltip
).as_str(),
);
} }
if let Some(data) = option.long { if let Some(data) = option.long {
let tooltip = get_tooltip(option.help, data); let tooltip = get_tooltip(option.help, data);
completions.push_str(&preamble); completions.push_str(&preamble);
completions.push_str(format!("'--{}', '{}', {}, '{}')", completions.push_str(
data, data, "[CompletionResultType]::ParameterName", tooltip).as_str()); format!(
"'--{}', '{}', {}, '{}')",
data, data, "[CompletionResultType]::ParameterName", tooltip
).as_str(),
);
} }
} }
@ -98,14 +106,22 @@ fn generate_inner<'a, 'b, 'p>(
if let Some(data) = flag.short { if let Some(data) = flag.short {
let tooltip = get_tooltip(flag.help, data); let tooltip = get_tooltip(flag.help, data);
completions.push_str(&preamble); completions.push_str(&preamble);
completions.push_str(format!("'-{}', '{}', {}, '{}')", completions.push_str(
data, data, "[CompletionResultType]::ParameterName", tooltip).as_str()); format!(
"'-{}', '{}', {}, '{}')",
data, data, "[CompletionResultType]::ParameterName", tooltip
).as_str(),
);
} }
if let Some(data) = flag.long { if let Some(data) = flag.long {
let tooltip = get_tooltip(flag.help, data); let tooltip = get_tooltip(flag.help, data);
completions.push_str(&preamble); completions.push_str(&preamble);
completions.push_str(format!("'--{}', '{}', {}, '{}')", completions.push_str(
data, data, "[CompletionResultType]::ParameterName", tooltip).as_str()); format!(
"'--{}', '{}', {}, '{}')",
data, data, "[CompletionResultType]::ParameterName", tooltip
).as_str(),
);
} }
} }
@ -113,8 +129,12 @@ fn generate_inner<'a, 'b, 'p>(
let data = &subcommand.name; let data = &subcommand.name;
let tooltip = get_tooltip(subcommand.about, data); let tooltip = get_tooltip(subcommand.about, data);
completions.push_str(&preamble); completions.push_str(&preamble);
completions.push_str(format!("'{}', '{}', {}, '{}')", completions.push_str(
data, data, "[CompletionResultType]::ParameterValue", tooltip).as_str()); format!(
"'{}', '{}', {}, '{}')",
data, data, "[CompletionResultType]::ParameterValue", tooltip
).as_str(),
);
} }
let mut subcommands_cases = format!( let mut subcommands_cases = format!(
@ -122,13 +142,11 @@ fn generate_inner<'a, 'b, 'p>(
'{}' {{{} '{}' {{{}
break break
}}", }}",
&command_name, &command_name, completions
completions
); );
for subcommand in &p.subcommands { for subcommand in &p.subcommands {
let subcommand_subcommands_cases = let subcommand_subcommands_cases = generate_inner(&subcommand, &command_name, names);
generate_inner(&subcommand, &command_name, names);
subcommands_cases.push_str(&subcommand_subcommands_cases); subcommands_cases.push_str(&subcommand_subcommands_cases);
} }

View file

@ -1,7 +1,7 @@
#[allow(unused_imports)] #[allow(unused_imports)]
use std::ascii::AsciiExt; use std::ascii::AsciiExt;
use std::str::FromStr;
use std::fmt; use std::fmt;
use std::str::FromStr;
/// Describes which shell to produce a completions file for /// Describes which shell to produce a completions file for
#[cfg_attr(feature = "lints", allow(enum_variant_names))] #[cfg_attr(feature = "lints", allow(enum_variant_names))]
@ -36,7 +36,9 @@ impl FromStr for Shell {
"BASH" | _ if s.eq_ignore_ascii_case("bash") => Ok(Shell::Bash), "BASH" | _ if s.eq_ignore_ascii_case("bash") => Ok(Shell::Bash),
"POWERSHELL" | _ if s.eq_ignore_ascii_case("powershell") => Ok(Shell::PowerShell), "POWERSHELL" | _ if s.eq_ignore_ascii_case("powershell") => Ok(Shell::PowerShell),
"ELVISH" | _ if s.eq_ignore_ascii_case("elvish") => Ok(Shell::Elvish), "ELVISH" | _ if s.eq_ignore_ascii_case("elvish") => Ok(Shell::Elvish),
_ => Err(String::from("[valid values: bash, fish, zsh, powershell, elvish]")), _ => Err(String::from(
"[valid values: bash, fish, zsh, powershell, elvish]",
)),
} }
} }
} }
@ -49,7 +51,7 @@ impl fmt::Display for Shell {
Shell::Zsh => write!(f, "ZSH"), Shell::Zsh => write!(f, "ZSH"),
Shell::PowerShell => write!(f, "POWERSHELL"), Shell::PowerShell => write!(f, "POWERSHELL"),
Shell::Elvish => write!(f, "ELVISH"), Shell::Elvish => write!(f, "ELVISH"),
_ => panic!("Unsupported shell type for completion generation") _ => panic!("Unsupported shell type for completion generation"),
} }
} }
} }

View file

@ -1,7 +1,7 @@
// Std // Std
use std::io::Write;
#[allow(unused_imports)] #[allow(unused_imports)]
use std::ascii::AsciiExt; use std::ascii::AsciiExt;
use std::io::Write;
// Internal // Internal
use build::{App, ArgSettings}; use build::{App, ArgSettings};
@ -86,9 +86,8 @@ _{name} \"$@\"",
fn subcommand_details(p: &App) -> String { fn subcommand_details(p: &App) -> String {
debugln!("ZshGen::subcommand_details;"); debugln!("ZshGen::subcommand_details;");
// First we do ourself // First we do ourself
let mut ret = vec![ let mut ret = vec![format!(
format!( "\
"\
(( $+functions[_{bin_name_underscore}_commands] )) || (( $+functions[_{bin_name_underscore}_commands] )) ||
_{bin_name_underscore}_commands() {{ _{bin_name_underscore}_commands() {{
local commands; commands=( local commands; commands=(
@ -96,11 +95,10 @@ _{bin_name_underscore}_commands() {{
) )
_describe -t commands '{bin_name} commands' commands \"$@\" _describe -t commands '{bin_name} commands' commands \"$@\"
}}", }}",
bin_name_underscore = p.bin_name.as_ref().unwrap().replace(" ", "__"), bin_name_underscore = p.bin_name.as_ref().unwrap().replace(" ", "__"),
bin_name = p.bin_name.as_ref().unwrap(), bin_name = p.bin_name.as_ref().unwrap(),
subcommands_and_args = subcommands_of(p) subcommands_and_args = subcommands_of(p)
), )];
];
// Next we start looping through all the children, grandchildren, etc. // Next we start looping through all the children, grandchildren, etc.
let mut all_subcommands = completions::all_subcommands(p); let mut all_subcommands = completions::all_subcommands(p);
@ -145,7 +143,8 @@ fn subcommands_of(p: &App) -> String {
let s = format!( let s = format!(
"\"{name}:{help}\" \\", "\"{name}:{help}\" \\",
name = n, name = n,
help = sc.about help = sc
.about
.unwrap_or("") .unwrap_or("")
.replace("[", "\\[") .replace("[", "\\[")
.replace("]", "\\]") .replace("]", "\\]")
@ -458,20 +457,24 @@ fn write_positionals_of(p: &App) -> String {
"" ""
}, },
name = arg.name, name = arg.name,
help = arg.help help = arg
.help
.map_or("".to_owned(), |v| " -- ".to_owned() + v) .map_or("".to_owned(), |v| " -- ".to_owned() + v)
.replace("[", "\\[") .replace("[", "\\[")
.replace("]", "\\]"), .replace("]", "\\]"),
action = arg.possible_vals action = arg
.possible_vals
.as_ref() .as_ref()
.map_or("_files".to_owned(), |values| format!( .map_or("_files".to_owned(), |values| {
"({})", format!(
values "({})",
.iter() values
.map(|v| escape_value(*v)) .iter()
.collect::<Vec<String>>() .map(|v| escape_value(*v))
.join(" ") .collect::<Vec<String>>()
)) .join(" ")
)
})
); );
debugln!("write_positionals_of:iter: Wrote...{}", a); debugln!("write_positionals_of:iter: Wrote...{}", a);

View file

@ -517,7 +517,11 @@
//! [license]: https://raw.githubusercontent.com/kbknapp/clap-rs/master/LICENSE-MIT //! [license]: https://raw.githubusercontent.com/kbknapp/clap-rs/master/LICENSE-MIT
#![crate_type = "lib"] #![crate_type = "lib"]
<<<<<<< HEAD
#![doc(html_root_url = "https://docs.rs/clap/3.0.0-alpha.1")] #![doc(html_root_url = "https://docs.rs/clap/3.0.0-alpha.1")]
=======
#![doc(html_root_url = "https://docs.rs/clap/3.0.0-alpha1")]
>>>>>>> WIP. Big reformat
#![deny( #![deny(
missing_docs, missing_docs,
missing_debug_implementations, missing_debug_implementations,
@ -582,11 +586,17 @@ use std::result::Result as StdResult;
#[macro_use] #[macro_use]
mod macros; mod macros;
<<<<<<< HEAD <<<<<<< HEAD
<<<<<<< HEAD
mod build; mod build;
======= =======
mod mkeymap; mod mkeymap;
>>>>>>> WIP changing macros into MKeyMap calls >>>>>>> WIP changing macros into MKeyMap calls
mod completions; mod completions;
=======
mod build;
mod completions;
mod mkeymap;
>>>>>>> WIP. Big reformat
mod output; mod output;
mod parse; mod parse;
mod util; mod util;

View file

@ -26,9 +26,10 @@
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
#[macro_export] #[macro_export]
macro_rules! load_yaml { macro_rules! load_yaml {
($yml:expr) => ( ($yml:expr) => {
&::clap::YamlLoader::load_from_str(include_str!($yml)).expect("failed to load YAML file")[0] &::clap::YamlLoader::load_from_str(include_str!($yml))
); .expect("failed to load YAML file")[0]
};
} }
/// Convenience macro getting a typed value `T` where `T` implements [`std::str::FromStr`] from an /// Convenience macro getting a typed value `T` where `T` implements [`std::str::FromStr`] from an
@ -66,9 +67,10 @@ macro_rules! value_t {
if let Some(v) = $m.value_of($v) { if let Some(v) = $m.value_of($v) {
match v.parse::<$t>() { match v.parse::<$t>() {
Ok(val) => Ok(val), Ok(val) => Ok(val),
Err(_) => Err(_) => Err(::clap::Error::value_validation_auto(format!(
Err(::clap::Error::value_validation_auto( "The argument '{}' isn't a valid value",
format!("The argument '{}' isn't a valid value", v))), v
))),
} }
} else { } else {
Err(::clap::Error::argument_not_found_auto($v)) Err(::clap::Error::argument_not_found_auto($v))
@ -111,9 +113,10 @@ macro_rules! value_t_or_exit {
if let Some(v) = $m.value_of($v) { if let Some(v) = $m.value_of($v) {
match v.parse::<$t>() { match v.parse::<$t>() {
Ok(val) => val, Ok(val) => val,
Err(_) => Err(_) => ::clap::Error::value_validation_auto(format!(
::clap::Error::value_validation_auto( "The argument '{}' isn't a valid value",
format!("The argument '{}' isn't a valid value", v)).exit(), v
)).exit(),
} }
} else { } else {
::clap::Error::argument_not_found_auto($v).exit() ::clap::Error::argument_not_found_auto($v).exit()
@ -163,9 +166,11 @@ macro_rules! values_t {
match pv.parse::<$t>() { match pv.parse::<$t>() {
Ok(rv) => tmp.push(rv), Ok(rv) => tmp.push(rv),
Err(..) => { Err(..) => {
err = Some(::clap::Error::value_validation_auto( err = Some(::clap::Error::value_validation_auto(format!(
format!("The argument '{}' isn't a valid value", pv))); "The argument '{}' isn't a valid value",
break pv
)));
break;
} }
} }
} }
@ -218,10 +223,13 @@ macro_rules! values_t_or_exit {
}; };
($m:ident.values_of($v:expr), $t:ty) => { ($m:ident.values_of($v:expr), $t:ty) => {
if let Some(vals) = $m.values_of($v) { if let Some(vals) = $m.values_of($v) {
vals.map(|v| v.parse::<$t>().unwrap_or_else(|_|{ vals.map(|v| {
::clap::Error::value_validation_auto( v.parse::<$t>().unwrap_or_else(|_| {
format!("One or more arguments aren't valid values")).exit() ::clap::Error::value_validation_auto(format!(
})).collect::<Vec<$t>>() "One or more arguments aren't valid values"
)).exit()
})
}).collect::<Vec<$t>>()
} else { } else {
::clap::Error::argument_not_found_auto($v).exit() ::clap::Error::argument_not_found_auto($v).exit()
} }
@ -451,12 +459,14 @@ macro_rules! crate_version {
macro_rules! crate_authors { macro_rules! crate_authors {
($sep:expr) => {{ ($sep:expr) => {{
use std::ops::Deref; use std::ops::Deref;
use std::sync::{ONCE_INIT, Once}; use std::sync::{Once, ONCE_INIT};
#[allow(missing_copy_implementations)] #[allow(missing_copy_implementations)]
#[allow(dead_code)] #[allow(dead_code)]
struct CargoAuthors { __private_field: () }; struct CargoAuthors {
__private_field: (),
};
impl Deref for CargoAuthors { impl Deref for CargoAuthors {
type Target = str; type Target = str;
@ -476,7 +486,9 @@ macro_rules! crate_authors {
} }
} }
&*CargoAuthors { __private_field: () } &*CargoAuthors {
__private_field: (),
}
}}; }};
() => { () => {
env!("CARGO_PKG_AUTHORS") env!("CARGO_PKG_AUTHORS")
@ -845,16 +857,16 @@ mod debug_macros {
#[cfg_attr(not(feature = "debug"), macro_use)] #[cfg_attr(not(feature = "debug"), macro_use)]
mod debug_macros { mod debug_macros {
macro_rules! debugln { macro_rules! debugln {
($fmt:expr) => (); ($fmt:expr) => {};
($fmt:expr, $($arg:tt)*) => (); ($fmt:expr, $($arg:tt)*) => {};
} }
macro_rules! sdebugln { macro_rules! sdebugln {
($fmt:expr) => (); ($fmt:expr) => {};
($fmt:expr, $($arg:tt)*) => (); ($fmt:expr, $($arg:tt)*) => {};
} }
macro_rules! debug { macro_rules! debug {
($fmt:expr) => (); ($fmt:expr) => {};
($fmt:expr, $($arg:tt)*) => (); ($fmt:expr, $($arg:tt)*) => {};
} }
} }
@ -863,12 +875,12 @@ mod debug_macros {
// src/args/arg_builder/*.rs // src/args/arg_builder/*.rs
// src/app/mod.rs // src/app/mod.rs
macro_rules! write_nspaces { macro_rules! write_nspaces {
($dst:expr, $num:expr) => ({ ($dst:expr, $num:expr) => {{
debugln!("write_spaces!: num={}", $num); debugln!("write_spaces!: num={}", $num);
for _ in 0..$num { for _ in 0..$num {
$dst.write_all(b" ")?; $dst.write_all(b" ")?;
} }
}) }};
} }
macro_rules! args { macro_rules! args {
@ -876,70 +888,94 @@ macro_rules! args {
$app.args.$how() $app.args.$how()
}; };
($app:expr) => { ($app:expr) => {
args!($app, iter) args!($app, values)
} };
} }
macro_rules! args_mut { macro_rules! args_mut {
($app:expr) => { ($app:expr) => {
args!($app, iter_mut) args!($app, values_mut)
} };
} }
macro_rules! flags { macro_rules! flags {
($app:expr, $how:ident) => { ($app:expr, $how:ident) => {{
$app.args.$how() use mkeymap::KeyType::*;
.filter(|a| !a.settings.is_set(::build::ArgSettings::TakesValue)) $app.args
.filter(|a| a.short.is_some() || a.long.is_some()) .$how()
.filter(|a| !a.help_heading.is_some()) .filter(|(k, a)| !a.settings.is_set(::build::ArgSettings::TakesValue))
}; .filter(|(k, a)| match k {
Long(_) => true,
Short(_) => true,
Position(_) => false,
})
.filter(|(k, a)| !a.help_heading.is_some())
}};
($app:expr) => { ($app:expr) => {
flags!($app, iter) flags!($app, iter)
} };
} }
#[allow(unused_macros)] #[allow(unused_macros)]
macro_rules! flags_mut { macro_rules! flags_mut {
($app:expr) => { ($app:expr) => {
flags!($app, iter_mut) flags!($app, iter_mut)
} };
} }
macro_rules! opts { macro_rules! opts {
($app:expr, $how:ident) => { ($app:expr, $how:ident) => {{
$app.args.$how() use mkeymap::KeyType::*;
.filter(|a| a.settings.is_set(::build::ArgSettings::TakesValue)) $app.args
.filter(|a| a.short.is_some() || a.long.is_some()) .$how()
.filter(|a| !a.help_heading.is_some()) .filter(|(k, a)| a.settings.is_set(::build::ArgSettings::TakesValue))
}; .filter(|(k, a)| match k {
Long(_) => true,
Short(_) => true,
Position(_) => false,
})
.filter(|(k, a)| !a.help_heading.is_some())
}};
($app:expr) => { ($app:expr) => {
opts!($app, iter) opts!($app, iter)
} };
} }
#[allow(unused_macros)] #[allow(unused_macros)]
macro_rules! opts_mut { macro_rules! opts_mut {
($app:expr) => { ($app:expr) => {
opts!($app, iter_mut) opts!($app, iter_mut)
} };
} }
// * We need a way to iterate through all the Positional Args
// macro_rules! positionals {
// ($app:expr, $how:ident) => {
// $app.args
// .$how()
// .filter(|a| !a.help_heading.is_some())
// .filter(|a| !(a.short.is_some() || a.long.is_some()))
// };
// ($app:expr) => {
// positionals!($app, values)
// };
// }
macro_rules! positionals { macro_rules! positionals {
($app:expr, $how:ident) => { ($app:expr) => {
$app.args.$how() $app.args
.filter(|a| !a.help_heading.is_some()) .values()
.filter(|a| !(a.short.is_some() || a.long.is_some())) .filter(|a| !(a.short.is_some() || a.long.is_some()))
}; };
($app:expr) => {
positionals!($app, iter)
}
} }
#[allow(unused_macros)] #[allow(unused_macros)]
macro_rules! positionals_mut { macro_rules! positionals_mut {
($app:expr) => { ($app:expr) => {
positionals!($app, iter_mut) $app.args
} .values_mut()
.filter(|a| !(a.short.is_some() || a.long.is_some()))
};
} }
#[allow(unused_macros)] #[allow(unused_macros)]
@ -948,15 +984,15 @@ macro_rules! custom_headings {
$app.args.$how().filter(|a| (a.help_heading.is_some())) $app.args.$how().filter(|a| (a.help_heading.is_some()))
}; };
($app:expr) => { ($app:expr) => {
custom_headings!($app, iter) custom_headings!($app, values)
} };
} }
#[allow(unused_macros)] #[allow(unused_macros)]
macro_rules! custom_headings_mut { macro_rules! custom_headings_mut {
($app:expr) => { ($app:expr) => {
custom_headings!($app, iter_mut) custom_headings!($app, values_mut)
} };
} }
macro_rules! subcommands_cloned { macro_rules! subcommands_cloned {
@ -965,7 +1001,7 @@ macro_rules! subcommands_cloned {
}; };
($app:expr) => { ($app:expr) => {
subcommands_cloned!($app, iter) subcommands_cloned!($app, iter)
} };
} }
macro_rules! subcommands { macro_rules! subcommands {
@ -974,13 +1010,13 @@ macro_rules! subcommands {
}; };
($app:expr) => { ($app:expr) => {
subcommands!($app, iter) subcommands!($app, iter)
} };
} }
macro_rules! subcommands_mut { macro_rules! subcommands_mut {
($app:expr) => { ($app:expr) => {
subcommands!($app, iter_mut) subcommands!($app, iter_mut)
} };
} }
macro_rules! groups { macro_rules! groups {
@ -989,13 +1025,13 @@ macro_rules! groups {
}; };
($app:expr) => { ($app:expr) => {
groups!($app, iter) groups!($app, iter)
} };
} }
macro_rules! groups_mut { macro_rules! groups_mut {
($app:expr) => { ($app:expr) => {
groups!($app, iter_mut) groups!($app, iter_mut)
} };
} }
macro_rules! find_from { macro_rules! find_from {
@ -1015,13 +1051,14 @@ macro_rules! find_from {
} }
// Finds an arg by name // Finds an arg by name
// ! look up usages and find ways to improve performance
macro_rules! find { macro_rules! find {
($app:expr, $name:expr, $what:ident) => { ($app:expr, $name:expr, $what:ident) => {
$what!($app).find(|a| &a.name == $name) $what!($app).find(|a| &a.name == $name)
}; };
($app:expr, $name:expr) => { ($app:expr, $name:expr) => {
$app.args.iter().find(|a| &a.name == $name) $app.args.values().find(|a| &a.name == $name)
} };
} }
// macro_rules! find_by_long { // macro_rules! find_by_long {
@ -1050,15 +1087,13 @@ macro_rules! find {
macro_rules! find_subcmd_cloned { macro_rules! find_subcmd_cloned {
($_self:expr, $sc:expr) => {{ ($_self:expr, $sc:expr) => {{
subcommands_cloned!($_self) subcommands_cloned!($_self).find(|a| match_alias!(a, $sc, &*a.name))
.find(|a| match_alias!(a, $sc, &*a.name))
}}; }};
} }
macro_rules! find_subcmd { macro_rules! find_subcmd {
($app:expr, $sc:expr) => {{ ($app:expr, $sc:expr) => {{
subcommands!($app) subcommands!($app).find(|a| match_alias!(a, $sc, &*a.name))
.find(|a| match_alias!(a, $sc, &*a.name))
}}; }};
} }
@ -1085,35 +1120,45 @@ macro_rules! find_subcmd {
// }}; // }};
// } // }
//TODO change into one macro (repeated structure) //TODO change into one macro (repeated structure) + Positionals
macro_rules! longs { macro_rules! longs {
($app:expr) => ({ ($app:expr) => {{
use mkeymap::KeyType; use mkeymap::KeyType;
$app.args.keys().filter_map(|a| if let KeyType::Long(v) = a {Some(v)} else {None}) $app.args.keys().filter_map(|a| {
}); if let KeyType::Long(v) = a {
Some(v)
} else {
None
}
})
}};
} }
macro_rules! shorts { macro_rules! shorts {
($app:expr) => ({ ($app:expr) => {{
use mkeymap::KeyType; use mkeymap::KeyType;
$app.args.keys().filter_map(|a| if let KeyType::Short(v) = a {Some(v)} else {None}) $app.args.keys().filter_map(|a| {
}) if let KeyType::Short(v) = a {
Some(v)
} else {
None
}
})
}};
} }
macro_rules! _names { macro_rules! _names {
(@args $app:expr) => {{ (@args $app:expr) => {{
$app.args.iter().map(|a| &*a.name) $app.args.values().map(|a| &*a.name)
}}; }};
(@sc $app:expr) => {{ (@sc $app:expr) => {{
$app.subcommands $app.subcommands.iter().map(|s| &*s.name).chain(
.iter() $app.subcommands
.map(|s| &*s.name) .iter()
.chain($app.subcommands .filter(|s| s.aliases.is_some())
.iter() .flat_map(|s| s.aliases.as_ref().unwrap().iter().map(|&(n, _)| n)),
.filter(|s| s.aliases.is_some()) )
.flat_map(|s| s.aliases.as_ref().unwrap().iter().map(|&(n, _)| n))) }};
}}
} }
macro_rules! arg_names { macro_rules! arg_names {
@ -1128,15 +1173,16 @@ macro_rules! sc_names {
}}; }};
} }
// macro_rules! match_alias { //probably scrap it altogether, as instead of iterating and matching alias we can just find by a Long
// ($a:expr, $to:expr, $what:expr) => {{ macro_rules! match_alias {
// $what == $to || ($a:expr, $to:expr, $what:expr) => {{
// ($a.aliases.is_some() && $what == $to
// $a.aliases || ($a.aliases.is_some()
// .as_ref() && $a
// .unwrap() .aliases
// .iter() .as_ref()
// .any(|alias| alias.0 == $to)) .unwrap()
.iter()
// }} .any(|alias| alias.0 == $to))
// } }};
}

View file

@ -65,7 +65,7 @@ impl<'a, 'b> MKeyMap<'a, 'b> {
set set
}); });
} }
index index
} }
//TODO ::push_many([x, y]) //TODO ::push_many([x, y])
@ -89,6 +89,8 @@ impl<'a, 'b> MKeyMap<'a, 'b> {
self.keys.insert(key, index); self.keys.insert(key, index);
} }
// ! Arg mutation functionality
pub fn get(&self, key: KeyType<'a>) -> Option<&Arg<'a, 'b>> { pub fn get(&self, key: KeyType<'a>) -> Option<&Arg<'a, 'b>> {
self.keys self.keys
.get(&key) .get(&key)
@ -97,13 +99,11 @@ impl<'a, 'b> MKeyMap<'a, 'b> {
//TODO ::get_first([KeyA, KeyB]) //TODO ::get_first([KeyA, KeyB])
pub fn get_mut(&mut self, key: KeyType<'a>) -> Option<&mut Arg<'a, 'b>> { pub fn get_mut(&mut self, key: KeyType<'a>) -> Option<&mut Arg<'a, 'b>> {
if let Some(&idx) = self if let Some(&idx) = self.keys.get(&key) {
.keys self.value_index.get_mut(idx)
.get(&key) { } else {
self.value_index.get_mut(idx) None
} else { }
None
}
} }
pub fn is_empty(&self) -> bool { self.keys.is_empty() && self.values.is_empty() } pub fn is_empty(&self) -> bool { self.keys.is_empty() && self.values.is_empty() }
@ -122,13 +122,13 @@ impl<'a, 'b> MKeyMap<'a, 'b> {
} }
} }
pub fn values(&'a self) -> Values<'a, Arg> { pub fn values(&'a self) -> Values<'a, Arg<'a, 'b>> {
Values { Values {
iter: self.value_index.iter(), iter: self.value_index.iter(),
} }
} }
pub fn values_mut(&'a mut self) -> ValuesMut<'a, Arg> { pub fn values_mut(&'a mut self) -> ValuesMut<'a, Arg<'a, 'b>> {
ValuesMut { ValuesMut {
iter: self.value_index.iter_mut(), iter: self.value_index.iter_mut(),
} }
@ -173,14 +173,19 @@ impl<'a, V> Iterator for ValuesMut<'a, V> {
fn next(&mut self) -> Option<Self::Item> { self.iter.next() } fn next(&mut self) -> Option<Self::Item> { self.iter.next() }
} }
pub struct Iter<'a, 'b, 'c> where pub struct Iter<'a, 'b, 'c>
'a: 'b, where
'b: 'c { 'a: 'b,
'b: 'c,
{
map: &'c MKeyMap<'a, 'b>, map: &'c MKeyMap<'a, 'b>,
keys: Keys<'c, usize>, keys: Keys<'c, usize>,
} }
impl <'a, 'b, 'c> Iterator for Iter<'a, 'b, 'c> impl<'a, 'b, 'c> Iterator for Iter<'a, 'b, 'c>
where
'a: 'b,
'b: 'c,
{ {
type Item = (&'c KeyType<'a>, &'c Arg<'a, 'b>); type Item = (&'c KeyType<'a>, &'c Arg<'a, 'b>);
@ -203,11 +208,12 @@ mod tests {
fn get_some_value() { fn get_some_value() {
let mut map: MKeyMap = MKeyMap::new(); let mut map: MKeyMap = MKeyMap::new();
{ map.insert(Long(&OsStr::new("One")), Arg::with_name("Value1"));
map.insert(Long(&OsStr::new("One")), Arg::with_name("Value1"));
}
assert_eq!(map.get(Long(&OsStr::new("One"))), &Arg::with_name("Value1")); assert_eq!(
map.get(Long(&OsStr::new("One"))),
Some(&Arg::with_name("Value1"))
);
} }
#[test] #[test]
@ -217,6 +223,8 @@ mod tests {
map.insert(Long(&OsStr::new("One")), Arg::with_name("Value1")); map.insert(Long(&OsStr::new("One")), Arg::with_name("Value1"));
map.get(Long(&OsStr::new("Two"))); map.get(Long(&OsStr::new("Two")));
assert_eq!(map.get(Long(&OsStr::new("Two"))), None);
} }
// #[test] // #[test]
@ -301,7 +309,7 @@ mod tests {
assert_eq!( assert_eq!(
map.get_mut(Long(&OsStr::new("One"))), map.get_mut(Long(&OsStr::new("One"))),
&mut Arg::with_name("Value1") Some(&mut Arg::with_name("Value1"))
); );
} }

View file

@ -6,8 +6,8 @@ use ansi_term::Colour::{Green, Red, Yellow};
#[cfg(feature = "color")] #[cfg(feature = "color")]
use atty; use atty;
use std::fmt;
use std::env; use std::env;
use std::fmt;
#[doc(hidden)] #[doc(hidden)]
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
@ -164,9 +164,9 @@ impl<T: fmt::Display> fmt::Display for Format<T> {
#[cfg(all(test, feature = "color", not(target_os = "windows")))] #[cfg(all(test, feature = "color", not(target_os = "windows")))]
mod test { mod test {
use super::Format;
use ansi_term::ANSIString; use ansi_term::ANSIString;
use ansi_term::Colour::{Green, Red, Yellow}; use ansi_term::Colour::{Green, Red, Yellow};
use super::Format;
#[test] #[test]
fn colored_output() { fn colored_output() {

View file

@ -7,18 +7,18 @@ use std::usize;
// Internal // Internal
use build::{App, AppSettings, Arg, ArgSettings}; use build::{App, AppSettings, Arg, ArgSettings};
use parse::Parser;
use parse::errors::{Error, Result as ClapResult};
use output::fmt::{Colorizer, ColorizerOption, Format}; use output::fmt::{Colorizer, ColorizerOption, Format};
use output::Usage; use output::Usage;
use parse::errors::{Error, Result as ClapResult};
use parse::Parser;
use util::VecMap; use util::VecMap;
use INTERNAL_ERROR_MSG; use INTERNAL_ERROR_MSG;
// Third Party // Third Party
use unicode_width::UnicodeWidthStr;
#[cfg(feature = "wrap_help")] #[cfg(feature = "wrap_help")]
use term_size; use term_size;
use textwrap; use textwrap;
use unicode_width::UnicodeWidthStr;
#[cfg(not(feature = "wrap_help"))] #[cfg(not(feature = "wrap_help"))]
mod term_size { mod term_size {
@ -173,18 +173,16 @@ impl<'w> Help<'w> {
impl<'w> Help<'w> { impl<'w> Help<'w> {
/// Writes help for each argument in the order they were declared to the wrapped stream. /// Writes help for each argument in the order they were declared to the wrapped stream.
fn write_args_unsorted<'a, 'b, I>(&mut self, args: I) -> io::Result<()> fn write_args_unsorted<'a, 'b, I>(&mut self, args: I) -> io::Result<()>
where where
'a: 'b, 'a: 'b,
I: Iterator<Item=&'b Arg<'a, 'b>>, I: Iterator<Item = &'b Arg<'a, 'b>>,
{ {
debugln!("Help::write_args_unsorted;"); debugln!("Help::write_args_unsorted;");
// The shortest an arg can legally be is 2 (i.e. '-x') // The shortest an arg can legally be is 2 (i.e. '-x')
self.longest = 2; self.longest = 2;
let mut arg_v = Vec::with_capacity(10); let mut arg_v = Vec::with_capacity(10);
let use_long = self.use_long; let use_long = self.use_long;
for arg in args.filter(|arg| { for arg in args.filter(|arg| should_show_arg(use_long, *arg)) {
should_show_arg(use_long, *arg)
}) {
if arg.longest_filter() { if arg.longest_filter() {
self.longest = cmp::max(self.longest, str_width(arg.to_string().as_str())); self.longest = cmp::max(self.longest, str_width(arg.to_string().as_str()));
} }
@ -207,9 +205,9 @@ impl<'w> Help<'w> {
/// Sorts arguments by length and display order and write their help to the wrapped stream. /// Sorts arguments by length and display order and write their help to the wrapped stream.
fn write_args<'a, 'b, I>(&mut self, args: I) -> io::Result<()> fn write_args<'a, 'b, I>(&mut self, args: I) -> io::Result<()>
where where
'a: 'b, 'a: 'b,
I: Iterator<Item=&'b Arg<'a, 'b>>, I: Iterator<Item = &'b Arg<'a, 'b>>,
{ {
debugln!("Help::write_args;"); debugln!("Help::write_args;");
// The shortest an arg can legally be is 2 (i.e. '-x') // The shortest an arg can legally be is 2 (i.e. '-x')
@ -346,7 +344,8 @@ impl<'w> Help<'w> {
let h_w = str_width(h) + str_width(&*spec_vals); let h_w = str_width(h) + str_width(&*spec_vals);
let nlh = self.next_line_help || arg.is_set(ArgSettings::NextLineHelp); let nlh = self.next_line_help || arg.is_set(ArgSettings::NextLineHelp);
let taken = self.longest + 12; let taken = self.longest + 12;
self.force_next_line = !nlh && self.term_w >= taken self.force_next_line = !nlh
&& self.term_w >= taken
&& (taken as f32 / self.term_w as f32) > 0.40 && (taken as f32 / self.term_w as f32) > 0.40
&& h_w > (self.term_w - taken); && h_w > (self.term_w - taken);
@ -427,7 +426,12 @@ impl<'w> Help<'w> {
} }
/// Writes argument's help to the wrapped stream. /// Writes argument's help to the wrapped stream.
fn help<'b, 'c>(&mut self, arg: &Arg<'b, 'c>, spec_vals: &str, prevent_nlh: bool) -> io::Result<()> { fn help<'b, 'c>(
&mut self,
arg: &Arg<'b, 'c>,
spec_vals: &str,
prevent_nlh: bool,
) -> io::Result<()> {
debugln!("Help::help;"); debugln!("Help::help;");
let h = if self.use_long { let h = if self.use_long {
arg.long_help.unwrap_or_else(|| arg.help.unwrap_or("")) arg.long_help.unwrap_or_else(|| arg.help.unwrap_or(""))
@ -577,7 +581,8 @@ impl<'w> Help<'w> {
let h_w = str_width(h) + str_width(&*spec_vals); let h_w = str_width(h) + str_width(&*spec_vals);
let nlh = self.next_line_help; let nlh = self.next_line_help;
let taken = self.longest + 12; let taken = self.longest + 12;
self.force_next_line = !nlh && self.term_w >= taken self.force_next_line = !nlh
&& self.term_w >= taken
&& (taken as f32 / self.term_w as f32) > 0.40 && (taken as f32 / self.term_w as f32) > 0.40
&& h_w > (self.term_w - taken); && h_w > (self.term_w - taken);
@ -739,16 +744,22 @@ impl<'w> Help<'w> {
first = false; first = false;
} }
if custom_headings { if custom_headings {
for heading in parser.app.help_headings.iter() for heading in parser
.app
.help_headings
.iter()
.filter(|heading| heading.is_some()) .filter(|heading| heading.is_some())
.map(|heading| heading.unwrap()) { .map(|heading| heading.unwrap())
if !first { {
self.writer.write_all(b"\n\n")?; if !first {
} self.writer.write_all(b"\n\n")?;
color!(self, format!("{}:\n", heading), warning)?;
self.write_args(custom_headings!(parser.app).filter(|a| a.help_heading.unwrap() == heading))?;
first = false
} }
color!(self, format!("{}:\n", heading), warning)?;
self.write_args(
custom_headings!(parser.app).filter(|a| a.help_heading.unwrap() == heading),
)?;
first = false
}
} }
} }
@ -832,8 +843,7 @@ impl<'w> Help<'w> {
($thing:expr) => {{ ($thing:expr) => {{
let mut owned_thing = $thing.to_owned(); let mut owned_thing = $thing.to_owned();
owned_thing = owned_thing.replace("{n}", "\n"); owned_thing = owned_thing.replace("{n}", "\n");
write!(self.writer, "{}\n", write!(self.writer, "{}\n", wrap_help(&owned_thing, self.term_w))?
wrap_help(&owned_thing, self.term_w))?
}}; }};
} }
// Print the version // Print the version
@ -1113,16 +1123,19 @@ impl<'w> Help<'w> {
} }
fn should_show_arg(use_long: bool, arg: &Arg) -> bool { fn should_show_arg(use_long: bool, arg: &Arg) -> bool {
debugln!("Help::should_show_arg: use_long={:?}, arg={}", use_long, arg.name); debugln!(
"Help::should_show_arg: use_long={:?}, arg={}",
use_long,
arg.name
);
if arg.is_set(ArgSettings::Hidden) { if arg.is_set(ArgSettings::Hidden) {
return false; return false;
} }
(!arg.is_set(ArgSettings::HiddenLongHelp) && use_long) || (!arg.is_set(ArgSettings::HiddenLongHelp) && use_long)
(!arg.is_set(ArgSettings::HiddenShortHelp) && !use_long) || || (!arg.is_set(ArgSettings::HiddenShortHelp) && !use_long)
arg.is_set(ArgSettings::NextLineHelp) || arg.is_set(ArgSettings::NextLineHelp)
} }
fn wrap_help(help: &str, avail_chars: usize) -> String { fn wrap_help(help: &str, avail_chars: usize) -> String {
let wrapper = textwrap::Wrapper::new(avail_chars).break_words(false); let wrapper = textwrap::Wrapper::new(avail_chars).break_words(false);
help.lines() help.lines()

View file

@ -4,4 +4,4 @@ mod usage;
pub mod fmt; pub mod fmt;
pub use self::help::Help; pub use self::help::Help;
pub use self::usage::Usage; pub use self::usage::Usage;

View file

@ -1 +1 @@
pub mod suggestions; pub mod suggestions;

View file

@ -2,6 +2,6 @@ mod arg_matches;
mod matched_arg; mod matched_arg;
mod subcommand; mod subcommand;
pub use self::arg_matches::{ArgMatches, Values, OsValues}; pub use self::arg_matches::{ArgMatches, OsValues, Values};
pub use self::matched_arg::MatchedArg;
pub use self::subcommand::SubCommand; pub use self::subcommand::SubCommand;
pub use self::matched_arg::MatchedArg;

View file

@ -1,13 +1,13 @@
pub mod errors; pub mod errors;
pub mod features; pub mod features;
mod matches;
mod arg_matcher; mod arg_matcher;
mod matches;
mod parser; mod parser;
mod validator; mod validator;
pub use self::parser::{Parser, ParseResult};
pub use self::matches::ArgMatches;
pub use self::arg_matcher::ArgMatcher; pub use self::arg_matcher::ArgMatcher;
pub use self::matches::{Values, OsValues, SubCommand, MatchedArg}; pub use self::matches::ArgMatches;
pub use self::validator::Validator; pub use self::matches::{MatchedArg, OsValues, SubCommand, Values};
pub use self::parser::{ParseResult, Parser};
pub use self::validator::Validator;

View file

@ -27,6 +27,7 @@ use util::VecMap;
use build::app::Propagation; use build::app::Propagation;
use build::AppSettings as AS; use build::AppSettings as AS;
use build::{App, Arg, ArgSettings}; use build::{App, Arg, ArgSettings};
use mkeymap::KeyType;
use output::Help; use output::Help;
use output::Usage; use output::Usage;
use parse::errors::Error as ClapError; use parse::errors::Error as ClapError;
@ -40,7 +41,6 @@ use INVALID_UTF8;
use INTERNAL_ERROR_MSG; use INTERNAL_ERROR_MSG;
use parse::features::suggestions; use parse::features::suggestions;
use output::Usage; use output::Usage;
use mkeymap::KeyType;
#[derive(Debug, PartialEq, Copy, Clone)] #[derive(Debug, PartialEq, Copy, Clone)]
#[doc(hidden)] #[doc(hidden)]
@ -64,10 +64,10 @@ where
pub required: Vec<&'a str>, pub required: Vec<&'a str>,
pub r_ifs: Vec<(&'a str, &'b str, &'a str)>, pub r_ifs: Vec<(&'a str, &'b str, &'a str)>,
pub overriden: Vec<&'a str>, pub overriden: Vec<&'a str>,
cache: Option<&'a str>, //cache: Option<&'a str>,
num_opts: usize, num_opts: usize,
num_flags: usize, num_flags: usize,
pub positionals: VecMap<&'a str>, //pub positionals: VecMap<&'a str>,
seen: Vec<&'a str>, seen: Vec<&'a str>,
cur_idx: Cell<usize>, cur_idx: Cell<usize>,
} }
@ -107,7 +107,7 @@ where
pub fn new(app: &'c mut App<'a, 'b>) -> Self { pub fn new(app: &'c mut App<'a, 'b>) -> Self {
let reqs = app let reqs = app
.args .args
.iter() .values()
.filter(|a| a.settings.is_set(ArgSettings::Required)) .filter(|a| a.settings.is_set(ArgSettings::Required))
.map(|a| a.name) .map(|a| a.name)
.collect(); .collect();
@ -117,10 +117,8 @@ where
required: reqs, required: reqs,
r_ifs: Vec::new(), r_ifs: Vec::new(),
overriden: Vec::new(), overriden: Vec::new(),
cache: None,
num_opts: 0, num_opts: 0,
num_flags: 0, num_flags: 0,
positionals: VecMap::new(),
seen: Vec::new(), seen: Vec::new(),
cur_idx: Cell::new(0), cur_idx: Cell::new(0),
} }
@ -135,15 +133,41 @@ where
// Firt we verify that the index highest supplied index, is equal to the number of // Firt we verify that the index highest supplied index, is equal to the number of
// positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3 // positional arguments to verify there are no gaps (i.e. supplying an index of 1 and 3
// but no 2) // but no 2)
#[cfg(feature = "vec_map")]
fn _highest_idx(map: &VecMap<&str>) -> usize { map.keys().last().unwrap_or(0) }
#[cfg(not(feature = "vec_map"))] // #[cfg(feature = "vec_map")]
fn _highest_idx(map: &VecMap<&str>) -> usize { *map.keys().last().unwrap_or(&0) } // fn _highest_idx(map: &VecMap<&str>) -> usize { map.keys().last().unwrap_or(0) }
let highest_idx = _highest_idx(&self.positionals); // #[cfg(not(feature = "vec_map"))]
// fn _highest_idx(map: &VecMap<&str>) -> usize { *map.keys().last().unwrap_or(&0) }
let num_p = self.positionals.len(); let highest_idx = *self
.app
.args
.keys()
.filter_map(|x| {
if let KeyType::Position(n) = x {
Some(n)
} else {
None
}
})
.max()
.unwrap_or(&0);
//_highest_idx(&self.positionals);
let num_p = self
.app
.args
.keys()
.filter(|x| {
if let KeyType::Position(_) = x {
true
} else {
false
}
})
.count();
assert!( assert!(
highest_idx == num_p, highest_idx == num_p,
@ -162,14 +186,29 @@ where
// * a value terminator // * a value terminator
// * ArgSettings::Last // * ArgSettings::Last
// * The last arg is Required // * The last arg is Required
let mut it = self.positionals.values().rev(); let mut it = self.app.args.keys().filter(|x| {
if let KeyType::Position(_) = x {
true
} else {
false
}
});
//self.positionals.values().rev();
// We can't pass the closure (it.next()) to the macro directly because each call to // We can't pass the closure (it.next()) to the macro directly because each call to
// find() (iterator, not macro) gets called repeatedly. // find() (iterator, not macro) gets called repeatedly.
let last_name = it.next().expect(INTERNAL_ERROR_MSG); let last = self
let second_to_last_name = it.next().expect(INTERNAL_ERROR_MSG); .app
let last = find!(self.app, last_name).expect(INTERNAL_ERROR_MSG); .args
let second_to_last = find!(self.app, second_to_last_name).expect(INTERNAL_ERROR_MSG); .get(KeyType::Position(highest_idx))
.expect(INTERNAL_ERROR_MSG);
//let second_to_last_name = it.next().expect(INTERNAL_ERROR_MSG);
//let last = find!(self.app, last_name).expect(INTERNAL_ERROR_MSG);
let second_to_last = self
.app
.args
.get(KeyType::Position(highest_idx - 1))
.expect(INTERNAL_ERROR_MSG);
// Either the final positional is required // Either the final positional is required
// Or the second to last has a terminator or .last(true) set // Or the second to last has a terminator or .last(true) set
@ -217,9 +256,10 @@ where
// index are also required. // index are also required.
let mut found = false; let mut found = false;
let mut foundx2 = false; let mut foundx2 = false;
//? What is going on here?
for p in self for p in self
.positionals .positionals
.values()
.rev() .rev()
.map(|p_name| find!(self.app, p_name).expect(INTERNAL_ERROR_MSG)) .map(|p_name| find!(self.app, p_name).expect(INTERNAL_ERROR_MSG))
{ {
@ -308,7 +348,7 @@ where
for (i, a) in self.app.args.values_mut().enumerate() { for (i, a) in self.app.args.values_mut().enumerate() {
if let Some(index) = a.index { if let Some(index) = a.index {
self.app.args.insert_key(KeyType::Positional(index), i); self.app.args.insert_key(KeyType::Position(index), i);
} else { } else {
if let Some(c) = a.short { if let Some(c) = a.short {
self.app.args.insert_key(KeyType::Short(c), i); self.app.args.insert_key(KeyType::Short(c), i);
@ -318,7 +358,9 @@ where
} }
if let Some(v) = a.aliases { if let Some(v) = a.aliases {
for (item, _) in &v { for (item, _) in &v {
self.app.args.insert_key(KeyType::Long(&OsStr::new(item)), i); self.app
.args
.insert_key(KeyType::Long(&OsStr::new(item)), i);
} }
} }
} }
@ -356,7 +398,13 @@ where
// Set the LowIndexMultiple flag if required // Set the LowIndexMultiple flag if required
if positionals!(self.app).any(|a| { if positionals!(self.app).any(|a| {
a.is_set(ArgSettings::MultipleValues) a.is_set(ArgSettings::MultipleValues)
&& (a.index.unwrap_or(0) as usize != self.positionals.len()) && (a.index.unwrap_or(0) as usize
!= self
.app
.args
.keys()
.filter(|x| if let Position(_) = x { true } else { false })
.count())
}) && self.positionals.values().last().map_or(false, |p_name| { }) && self.positionals.values().last().map_or(false, |p_name| {
!find!(self.app, p_name) !find!(self.app, p_name)
.expect(INTERNAL_ERROR_MSG) .expect(INTERNAL_ERROR_MSG)
@ -1030,39 +1078,34 @@ where
sdebugln!("No"); sdebugln!("No");
full_arg.trim_left_matches(b'-') full_arg.trim_left_matches(b'-')
}; };
// opts?? Should probably now check once, then check whether it's opt or flag, or sth else // opts?? Should probably now check once, then check whether it's opt or flag, or sth else
if let Some(opt) = self.app.args.get(KeyType::Long(arg)) if let Some(opt) = self.app.args.get(KeyType::Long(arg)) {
{
debugln!( debugln!(
"Parser::parse_long_arg: Found valid opt '{}'", "Parser::parse_long_arg: Found valid opt '{}'",
opt.to_string() opt.to_string()
); );
self.app.settings.set(AS::ValidArgFound); self.app.settings.set(AS::ValidArgFound);
let ret = self.parse_opt(val, opt, val.is_some(), matcher)?;
if self.cache.map_or(true, |name| name != opt.name) { if opt.is_set(ArgSettings::TakesValue) {
self.cache = Some(opt.name); let ret = self.parse_opt(val, opt, val.is_some(), matcher)?;
// if self.cache.map_or(true, |name| name != opt.name) {
// self.cache = Some(opt.name);
// }
return Ok(ret);
} else {
// Only flags could be help or version, and we need to check the raw long
// so this is the first point to check
self.check_for_help_and_version_str(arg)?;
self.parse_flag(opt, matcher)?;
// if self.cache.map_or(true, |name| name != opt.name) {
// self.cache = Some(opt.name);
// }
return Ok(ParseResult::Flag);
} }
return Ok(ret);
//flags??
} else if let Some(flag) = self.app.args.get(KeyType::Long(arg)) {
debugln!(
"Parser::parse_long_arg: Found valid flag '{}'",
flag.to_string()
);
self.app.settings.set(AS::ValidArgFound);
// Only flags could be help or version, and we need to check the raw long
// so this is the first point to check
self.check_for_help_and_version_str(arg)?;
self.parse_flag(flag, matcher)?;
// Handle conflicts, requirements, etc.
if self.cache.map_or(true, |name| name != flag.name) {
self.cache = Some(flag.name);
}
return Ok(ParseResult::Flag);
} else if self.is_set(AS::AllowLeadingHyphen) { } else if self.is_set(AS::AllowLeadingHyphen) {
return Ok(ParseResult::MaybeHyphenValue); return Ok(ParseResult::MaybeHyphenValue);
} else if self.is_set(AS::ValidNegNumFound) { } else if self.is_set(AS::ValidNegNumFound) {
@ -1487,7 +1530,7 @@ where
} }
pub(crate) fn add_env(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> { pub(crate) fn add_env(&mut self, matcher: &mut ArgMatcher<'a>) -> ClapResult<()> {
for a in &self.app.args { for a in self.app.args.values() {
if let Some(ref val) = a.env { if let Some(ref val) = a.env {
if matcher if matcher
.get(a.name) .get(a.name)
@ -1678,7 +1721,20 @@ where
pub(crate) fn has_flags(&self) -> bool { self.app.has_flags() } pub(crate) fn has_flags(&self) -> bool { self.app.has_flags() }
pub(crate) fn has_positionals(&self) -> bool { !self.positionals.is_empty() } pub(crate) fn has_positionals(&self) -> bool {
!self
.app
.args
.keys()
.filter(|x| {
if let KeyType::Position(_) = x {
true
} else {
false
}
})
.count() == 0
}
pub(crate) fn has_subcommands(&self) -> bool { self.app.has_subcommands() } pub(crate) fn has_subcommands(&self) -> bool { self.app.has_subcommands() }

View file

@ -6,8 +6,8 @@ pub use self::vec_map::{Values, VecMap};
#[cfg(not(feature = "vec_map"))] #[cfg(not(feature = "vec_map"))]
mod vec_map { mod vec_map {
use std::collections::BTreeMap;
use std::collections::btree_map; use std::collections::btree_map;
use std::collections::BTreeMap;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
#[derive(Clone, Default, Debug)] #[derive(Clone, Default, Debug)]

View file

@ -6,4 +6,4 @@ pub use self::map::{Values, VecMap};
pub use self::osstringext::OsStrExt2; pub use self::osstringext::OsStrExt2;
#[cfg(any(target_os = "windows", target_arch = "wasm32"))] #[cfg(any(target_os = "windows", target_arch = "wasm32"))]
pub use self::osstringext::OsStrExt3; pub use self::osstringext::OsStrExt3;
pub use self::strext::_StrExt; pub use self::strext::_StrExt;

View file

@ -1,8 +1,8 @@
#[cfg(any(target_os = "windows", target_arch = "wasm32"))]
use INVALID_UTF8;
use std::ffi::OsStr; use std::ffi::OsStr;
#[cfg(not(any(target_os = "windows", target_arch = "wasm32")))] #[cfg(not(any(target_os = "windows", target_arch = "wasm32")))]
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
#[cfg(any(target_os = "windows", target_arch = "wasm32"))]
use INVALID_UTF8;
#[cfg(any(target_os = "windows", target_arch = "wasm32"))] #[cfg(any(target_os = "windows", target_arch = "wasm32"))]
pub trait OsStrExt3 { pub trait OsStrExt3 {

View file

@ -498,8 +498,7 @@ fn leading_double_hyphen_trailingvararg() {
#[test] #[test]
fn unset_setting() { fn unset_setting() {
let m = App::new("unset_setting") let m = App::new("unset_setting").setting(AppSettings::AllArgsOverrideSelf);
.setting(AppSettings::AllArgsOverrideSelf);
assert!(m.is_set(AppSettings::AllArgsOverrideSelf)); assert!(m.is_set(AppSettings::AllArgsOverrideSelf));
let m = m.unset_setting(AppSettings::AllArgsOverrideSelf); let m = m.unset_setting(AppSettings::AllArgsOverrideSelf);
@ -660,7 +659,10 @@ fn missing_positional_no_hyphen() {
let expected_args = vec!["arg1", "arg2", "arg3"]; let expected_args = vec!["arg1", "arg2", "arg3"];
assert_eq!(m.value_of("BENCH"), expected_bench); assert_eq!(m.value_of("BENCH"), expected_bench);
assert_eq!(m.values_of("ARGS").unwrap().collect::<Vec<_>>(), &*expected_args); assert_eq!(
m.values_of("ARGS").unwrap().collect::<Vec<_>>(),
&*expected_args
);
} }
#[test] #[test]
@ -678,7 +680,10 @@ fn missing_positional_hyphen() {
let expected_args = vec!["arg1", "arg2", "arg3"]; let expected_args = vec!["arg1", "arg2", "arg3"];
assert_eq!(m.value_of("BENCH"), expected_bench); assert_eq!(m.value_of("BENCH"), expected_bench);
assert_eq!(m.values_of("ARGS").unwrap().collect::<Vec<_>>(), &*expected_args); assert_eq!(
m.values_of("ARGS").unwrap().collect::<Vec<_>>(),
&*expected_args
);
} }
#[test] #[test]
@ -702,7 +707,10 @@ fn missing_positional_hyphen_far_back() {
assert_eq!(m.value_of("BENCH1"), expected_bench1); assert_eq!(m.value_of("BENCH1"), expected_bench1);
assert_eq!(m.value_of("BENCH2"), expected_bench2); assert_eq!(m.value_of("BENCH2"), expected_bench2);
assert_eq!(m.value_of("BENCH3"), expected_bench3); assert_eq!(m.value_of("BENCH3"), expected_bench3);
assert_eq!(m.values_of("ARGS").unwrap().collect::<Vec<_>>(), &*expected_args); assert_eq!(
m.values_of("ARGS").unwrap().collect::<Vec<_>>(),
&*expected_args
);
} }
#[test] #[test]
@ -911,7 +919,7 @@ fn aaos_opts_mult_req_delims() {
.setting(AppSettings::AllArgsOverrideSelf) .setting(AppSettings::AllArgsOverrideSelf)
.arg(Arg::from("--opt [val]... 'some option'")) .arg(Arg::from("--opt [val]... 'some option'"))
.get_matches_from_safe(vec![ .get_matches_from_safe(vec![
"", "--opt", "first", "overides", "--opt", "some", "other", "val" "", "--opt", "first", "overides", "--opt", "some", "other", "val",
]); ]);
assert!(res.is_ok()); assert!(res.is_ok());
let m = res.unwrap(); let m = res.unwrap();

View file

@ -59,22 +59,26 @@ fn multiple_aliases_of_option() {
.help("multiple aliases") .help("multiple aliases")
.aliases(&vec!["alias1", "alias2", "alias3"]), .aliases(&vec!["alias1", "alias2", "alias3"]),
); );
let long = a.clone() let long = a
.clone()
.get_matches_from_safe(vec!["", "--aliases", "value"]); .get_matches_from_safe(vec!["", "--aliases", "value"]);
assert!(long.is_ok()); assert!(long.is_ok());
let long = long.unwrap(); let long = long.unwrap();
let als1 = a.clone() let als1 = a
.clone()
.get_matches_from_safe(vec!["", "--alias1", "value"]); .get_matches_from_safe(vec!["", "--alias1", "value"]);
assert!(als1.is_ok()); assert!(als1.is_ok());
let als1 = als1.unwrap(); let als1 = als1.unwrap();
let als2 = a.clone() let als2 = a
.clone()
.get_matches_from_safe(vec!["", "--alias2", "value"]); .get_matches_from_safe(vec!["", "--alias2", "value"]);
assert!(als2.is_ok()); assert!(als2.is_ok());
let als2 = als2.unwrap(); let als2 = als2.unwrap();
let als3 = a.clone() let als3 = a
.clone()
.get_matches_from_safe(vec!["", "--alias3", "value"]); .get_matches_from_safe(vec!["", "--alias3", "value"]);
assert!(als3.is_ok()); assert!(als3.is_ok());
let als3 = als3.unwrap(); let als3 = als3.unwrap();

View file

@ -721,45 +721,63 @@ fn build_app_with_name(s: &'static str) -> App<'static, 'static> {
App::new(s) App::new(s)
.about("Tests completions") .about("Tests completions")
.arg(Arg::with_name("file").help("some input file")) .arg(Arg::with_name("file").help("some input file"))
.subcommand(SubCommand::with_name("test") .subcommand(
.about("tests things") SubCommand::with_name("test").about("tests things").arg(
.arg(Arg::with_name("case") Arg::with_name("case")
.long("case") .long("case")
.takes_value(true) .takes_value(true)
.help("the case to test"))) .help("the case to test"),
),
)
} }
fn build_app_special_commands() -> App<'static, 'static> { fn build_app_special_commands() -> App<'static, 'static> {
build_app_with_name("my_app") build_app_with_name("my_app")
.subcommand(SubCommand::with_name("some_cmd") .subcommand(
.about("tests other things") SubCommand::with_name("some_cmd")
.arg(Arg::with_name("config") .about("tests other things")
.long("--config") .arg(
.takes_value(true) Arg::with_name("config")
.help("the other case to test"))) .long("--config")
.takes_value(true)
.help("the other case to test"),
),
)
.subcommand(SubCommand::with_name("some-cmd-with-hypens")) .subcommand(SubCommand::with_name("some-cmd-with-hypens"))
} }
fn build_app_special_help() -> App<'static, 'static> { fn build_app_special_help() -> App<'static, 'static> {
App::new("my_app") App::new("my_app")
.arg(Arg::with_name("single-quotes") .arg(
.long("single-quotes") Arg::with_name("single-quotes")
.help("Can be 'always', 'auto', or 'never'")) .long("single-quotes")
.arg(Arg::with_name("double-quotes") .help("Can be 'always', 'auto', or 'never'"),
.long("double-quotes") )
.help("Can be \"always\", \"auto\", or \"never\"")) .arg(
.arg(Arg::with_name("backticks") Arg::with_name("double-quotes")
.long("backticks") .long("double-quotes")
.help("For more information see `echo test`")) .help("Can be \"always\", \"auto\", or \"never\""),
.arg(Arg::with_name("backslash") )
.long("backslash") .arg(
.help("Avoid '\\n'")) Arg::with_name("backticks")
.arg(Arg::with_name("brackets") .long("backticks")
.long("brackets") .help("For more information see `echo test`"),
.help("List packages [filter]")) )
.arg(Arg::with_name("expansions") .arg(
.long("expansions") Arg::with_name("backslash")
.help("Execute the shell command with $SHELL")) .long("backslash")
.help("Avoid '\\n'"),
)
.arg(
Arg::with_name("brackets")
.long("brackets")
.help("List packages [filter]"),
)
.arg(
Arg::with_name("expansions")
.long("expansions")
.help("Execute the shell command with $SHELL"),
)
} }
#[test] #[test]

View file

@ -79,13 +79,19 @@ fn hidden_short_args() {
.short('c') .short('c')
.long("config") .long("config")
.hidden_short_help(true) .hidden_short_help(true)
.help("Some help text describing the --config arg"), .help("Some help text describing the --config arg"),
Arg::with_name("visible") Arg::with_name("visible")
.short('v') .short('v')
.long("visible") .long("visible")
.help("This text should be visible")]); .help("This text should be visible"),
]);
assert!(test::compare_output(app, "test -h", HIDDEN_SHORT_ARGS, false)); assert!(test::compare_output(
app,
"test -h",
HIDDEN_SHORT_ARGS,
false
));
} }
/// Ensure visible with opposite option /// Ensure visible with opposite option
@ -100,13 +106,19 @@ fn hidden_short_args_long_help() {
.short('c') .short('c')
.long("config") .long("config")
.hidden_short_help(true) .hidden_short_help(true)
.help("Some help text describing the --config arg"), .help("Some help text describing the --config arg"),
Arg::with_name("visible") Arg::with_name("visible")
.short('v') .short('v')
.long("visible") .long("visible")
.help("This text should be visible")]); .help("This text should be visible"),
]);
assert!(test::compare_output(app, "test --help", HIDDEN_SHORT_ARGS_LONG_HELP, false)); assert!(test::compare_output(
app,
"test --help",
HIDDEN_SHORT_ARGS_LONG_HELP,
false
));
} }
static HIDDEN_LONG_ARGS: &'static str = "test 2.31.2 static HIDDEN_LONG_ARGS: &'static str = "test 2.31.2
@ -137,13 +149,19 @@ fn hidden_long_args() {
.short('c') .short('c')
.long("config") .long("config")
.hidden_long_help(true) .hidden_long_help(true)
.help("Some help text describing the --config arg"), .help("Some help text describing the --config arg"),
Arg::with_name("visible") Arg::with_name("visible")
.short('v') .short('v')
.long("visible") .long("visible")
.help("This text should be visible")]); .help("This text should be visible"),
]);
assert!(test::compare_output(app, "test --help", HIDDEN_LONG_ARGS, false)); assert!(test::compare_output(
app,
"test --help",
HIDDEN_LONG_ARGS,
false
));
} }
static HIDDEN_LONG_ARGS_SHORT_HELP: &'static str = "test 2.31.2 static HIDDEN_LONG_ARGS_SHORT_HELP: &'static str = "test 2.31.2
@ -170,11 +188,17 @@ fn hidden_long_args_short_help() {
.short('c') .short('c')
.long("config") .long("config")
.hidden_long_help(true) .hidden_long_help(true)
.help("Some help text describing the --config arg"), .help("Some help text describing the --config arg"),
Arg::with_name("visible") Arg::with_name("visible")
.short('v') .short('v')
.long("visible") .long("visible")
.help("This text should be visible")]); .help("This text should be visible"),
]);
assert!(test::compare_output(app, "test -h", HIDDEN_LONG_ARGS_SHORT_HELP, false)); assert!(test::compare_output(
} app,
"test -h",
HIDDEN_LONG_ARGS_SHORT_HELP,
false
));
}

View file

@ -109,9 +109,9 @@ fn quoted_arg_long_name() {
(@arg scpositional: index(1) "tests positionals")) (@arg scpositional: index(1) "tests positionals"))
); );
let matches = let matches = app
app.get_matches_from_safe(vec!["bin_name", "value1", "value2", "--long-option-2"]) .get_matches_from_safe(vec!["bin_name", "value1", "value2", "--long-option-2"])
.expect("Expected to successfully match the given args."); .expect("Expected to successfully match the given args.");
assert!(matches.is_present("option2")); assert!(matches.is_present("option2"));
} }
@ -147,9 +147,9 @@ fn quoted_arg_name() {
(@arg scpositional: index(1) "tests positionals")) (@arg scpositional: index(1) "tests positionals"))
); );
let matches = let matches = app
app.get_matches_from_safe(vec!["bin_name", "value1", "value2", "--long-option-2"]) .get_matches_from_safe(vec!["bin_name", "value1", "value2", "--long-option-2"])
.expect("Expected to successfully match the given args."); .expect("Expected to successfully match the given args.");
assert!(matches.is_present("option2")); assert!(matches.is_present("option2"));
} }

View file

@ -398,10 +398,7 @@ fn issue_1047_min_zero_vals_default_val() {
fn issue_1105_setup(argv: Vec<&'static str>) -> Result<ArgMatches<'static>, clap::Error> { fn issue_1105_setup(argv: Vec<&'static str>) -> Result<ArgMatches<'static>, clap::Error> {
App::new("opts") App::new("opts")
.arg( .arg(Arg::from("-o, --option [opt] 'some option'").setting(ArgSettings::AllowEmptyValues))
Arg::from("-o, --option [opt] 'some option'")
.setting(ArgSettings::AllowEmptyValues),
)
.arg(Arg::from("--flag 'some flag'")) .arg(Arg::from("--flag 'some flag'"))
.try_get_matches_from(argv) .try_get_matches_from(argv)
} }

View file

@ -4,8 +4,8 @@ use clap::{App, Arg, ErrorKind};
#[test] #[test]
fn flag_overrides_itself() { fn flag_overrides_itself() {
let res = App::new("posix") let res = App::new("posix")
.arg(Arg::from("--flag 'some flag'").overrides_with("flag")) .arg(Arg::from("--flag 'some flag'").overrides_with("flag"))
.get_matches_from_safe(vec!["", "--flag", "--flag"]); .get_matches_from_safe(vec!["", "--flag", "--flag"]);
assert!(res.is_ok()); assert!(res.is_ok());
let m = res.unwrap(); let m = res.unwrap();
assert!(m.is_present("flag")); assert!(m.is_present("flag"));
@ -15,8 +15,8 @@ fn flag_overrides_itself() {
#[test] #[test]
fn mult_flag_overrides_itself() { fn mult_flag_overrides_itself() {
let res = App::new("posix") let res = App::new("posix")
.arg(Arg::from("--flag... 'some flag'").overrides_with("flag")) .arg(Arg::from("--flag... 'some flag'").overrides_with("flag"))
.get_matches_from_safe(vec!["", "--flag", "--flag", "--flag", "--flag"]); .get_matches_from_safe(vec!["", "--flag", "--flag", "--flag", "--flag"]);
assert!(res.is_ok()); assert!(res.is_ok());
let m = res.unwrap(); let m = res.unwrap();
assert!(m.is_present("flag")); assert!(m.is_present("flag"));
@ -26,8 +26,8 @@ fn mult_flag_overrides_itself() {
#[test] #[test]
fn option_overrides_itself() { fn option_overrides_itself() {
let res = App::new("posix") let res = App::new("posix")
.arg(Arg::from("--opt [val] 'some option'").overrides_with("opt")) .arg(Arg::from("--opt [val] 'some option'").overrides_with("opt"))
.get_matches_from_safe(vec!["", "--opt=some", "--opt=other"]); .get_matches_from_safe(vec!["", "--opt=some", "--opt=other"]);
assert!(res.is_ok()); assert!(res.is_ok());
let m = res.unwrap(); let m = res.unwrap();
assert!(m.is_present("opt")); assert!(m.is_present("opt"));
@ -38,54 +38,67 @@ fn option_overrides_itself() {
#[test] #[test]
fn mult_option_require_delim_overrides_itself() { fn mult_option_require_delim_overrides_itself() {
let res = App::new("posix") let res = App::new("posix")
.arg(Arg::from("--opt [val]... 'some option'") .arg(
.overrides_with("opt") Arg::from("--opt [val]... 'some option'")
.number_of_values(1) .overrides_with("opt")
.require_delimiter(true)) .number_of_values(1)
.get_matches_from_safe(vec!["", "--opt=some", "--opt=other", "--opt=one,two"]); .require_delimiter(true),
)
.get_matches_from_safe(vec!["", "--opt=some", "--opt=other", "--opt=one,two"]);
assert!(res.is_ok()); assert!(res.is_ok());
let m = res.unwrap(); let m = res.unwrap();
assert!(m.is_present("opt")); assert!(m.is_present("opt"));
assert_eq!(m.occurrences_of("opt"), 3); assert_eq!(m.occurrences_of("opt"), 3);
assert_eq!(m.values_of("opt").unwrap().collect::<Vec<_>>(), &["some", "other", "one", "two"]); assert_eq!(
m.values_of("opt").unwrap().collect::<Vec<_>>(),
&["some", "other", "one", "two"]
);
} }
#[test] #[test]
fn mult_option_overrides_itself() { fn mult_option_overrides_itself() {
let res = App::new("posix") let res = App::new("posix")
.arg(Arg::from("--opt [val]... 'some option'") .arg(Arg::from("--opt [val]... 'some option'").overrides_with("opt"))
.overrides_with("opt")) .get_matches_from_safe(vec![
.get_matches_from_safe(vec!["", "--opt", "first", "overides", "--opt", "some", "other", "val"]); "", "--opt", "first", "overides", "--opt", "some", "other", "val",
]);
assert!(res.is_ok()); assert!(res.is_ok());
let m = res.unwrap(); let m = res.unwrap();
assert!(m.is_present("opt")); assert!(m.is_present("opt"));
assert_eq!(m.occurrences_of("opt"), 2); assert_eq!(m.occurrences_of("opt"), 2);
assert_eq!(m.values_of("opt").unwrap().collect::<Vec<_>>(), &["first", "overides", "some", "other", "val"]); assert_eq!(
m.values_of("opt").unwrap().collect::<Vec<_>>(),
&["first", "overides", "some", "other", "val"]
);
} }
#[test] #[test]
fn option_use_delim_false_override_itself() { fn option_use_delim_false_override_itself() {
let m = App::new("posix") let m = App::new("posix")
.arg(Arg::from("--opt [val] 'some option'") .arg(Arg::from("--opt [val] 'some option'").overrides_with("opt"))
.overrides_with("opt")) .get_matches_from(vec!["", "--opt=some,other", "--opt=one,two"]);
.get_matches_from(vec!["", "--opt=some,other", "--opt=one,two"]);
assert!(m.is_present("opt")); assert!(m.is_present("opt"));
assert_eq!(m.occurrences_of("opt"), 1); assert_eq!(m.occurrences_of("opt"), 1);
assert_eq!(m.values_of("opt").unwrap().collect::<Vec<_>>(), &["one,two"]); assert_eq!(
m.values_of("opt").unwrap().collect::<Vec<_>>(),
&["one,two"]
);
} }
#[test] #[test]
fn pos_mult_overrides_itself() { fn pos_mult_overrides_itself() {
// opts with multiple // opts with multiple
let res = App::new("posix") let res = App::new("posix")
.arg(Arg::from("[val]... 'some pos'").overrides_with("val")) .arg(Arg::from("[val]... 'some pos'").overrides_with("val"))
.get_matches_from_safe(vec!["", "some", "other", "value"]); .get_matches_from_safe(vec!["", "some", "other", "value"]);
assert!(res.is_ok()); assert!(res.is_ok());
let m = res.unwrap(); let m = res.unwrap();
assert!(m.is_present("val")); assert!(m.is_present("val"));
assert_eq!(m.occurrences_of("val"), 3); assert_eq!(m.occurrences_of("val"), 3);
assert_eq!(m.values_of("val").unwrap().collect::<Vec<_>>(), &["some", "other", "value"]); assert_eq!(
m.values_of("val").unwrap().collect::<Vec<_>>(),
&["some", "other", "value"]
);
} }
#[test] #[test]
fn posix_compatible_flags_long() { fn posix_compatible_flags_long() {

View file

@ -196,19 +196,15 @@ fn issue_753() {
"-l, --list 'List available interfaces (and stop there)'", "-l, --list 'List available interfaces (and stop there)'",
)) ))
.arg( .arg(
Arg::from( Arg::from("-i, --iface=[INTERFACE] 'Ethernet interface for fetching NTP packets'")
"-i, --iface=[INTERFACE] 'Ethernet interface for fetching NTP packets'", .required_unless("list"),
).required_unless("list"),
) )
.arg( .arg(
Arg::from("-f, --file=[TESTFILE] 'Fetch NTP packets from pcap file'") Arg::from("-f, --file=[TESTFILE] 'Fetch NTP packets from pcap file'")
.conflicts_with("iface") .conflicts_with("iface")
.required_unless("list"), .required_unless("list"),
) )
.arg( .arg(Arg::from("-s, --server=[SERVER_IP] 'NTP server IP address'").required_unless("list"))
Arg::from("-s, --server=[SERVER_IP] 'NTP server IP address'")
.required_unless("list"),
)
.arg(Arg::from("-p, --port=[SERVER_PORT] 'NTP server port'").default_value("123")) .arg(Arg::from("-p, --port=[SERVER_PORT] 'NTP server port'").default_value("123"))
.get_matches_from_safe(vec!["test", "--list"]); .get_matches_from_safe(vec!["test", "--list"]);
assert!(m.is_ok()); assert!(m.is_ok());

View file

@ -220,15 +220,32 @@ fn issue_1161_multiple_hyphen_hyphen() {
.arg(Arg::with_name("eff").short('f')) .arg(Arg::with_name("eff").short('f'))
.arg(Arg::with_name("pea").short('p').takes_value(true)) .arg(Arg::with_name("pea").short('p').takes_value(true))
.arg(Arg::with_name("slop").multiple(true).last(true)) .arg(Arg::with_name("slop").multiple(true).last(true))
.get_matches_from_safe(vec!["-f", "-p=bob", "--", "sloppy", "slop", "-a", "--", "subprogram", "position", "args"]); .get_matches_from_safe(vec![
"-f",
"-p=bob",
"--",
"sloppy",
"slop",
"-a",
"--",
"subprogram",
"position",
"args",
]);
assert!(res.is_ok(), "{:?}", res.unwrap_err().kind); assert!(res.is_ok(), "{:?}", res.unwrap_err().kind);
let m = res.unwrap(); let m = res.unwrap();
let expected = Some(vec!["sloppy", "slop", "-a", "--", "subprogram", "position", "args"]); let expected = Some(vec![
let actual = m "sloppy",
.values_of("slop") "slop",
.map(|vals| vals.collect::<Vec<_>>()); "-a",
"--",
"subprogram",
"position",
"args",
]);
let actual = m.values_of("slop").map(|vals| vals.collect::<Vec<_>>());
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }

View file

@ -2,9 +2,9 @@
extern crate clap; extern crate clap;
use clap::{App, AppSettings, Arg, ErrorKind};
use std::ffi::OsString; use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt; use std::os::unix::ffi::OsStringExt;
use clap::{App, AppSettings, Arg, ErrorKind};
#[test] #[test]
fn invalid_utf8_strict_positional() { fn invalid_utf8_strict_positional() {