Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(builder): Custom parser for arg values #3732

Merged
merged 56 commits into from May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
cf0282b
refactor: Remove dead code
epage May 12, 2022
dcf69d1
refactor(parser): Track str/OsStr values via Box
epage May 12, 2022
200f662
feat(parser): Add type information to arg values
epage May 12, 2022
73fc240
refactor(env): Generalize bool policy
epage May 12, 2022
56aaf0b
feat(parser): Expose env-like bool parsers
epage May 12, 2022
2902f19
refactor(error): More flexible callers
epage May 13, 2022
ecc5b2f
feat(parser): Support ArgEnum with ValueParser
epage May 13, 2022
9d07b3c
feat(parser): non-empty string ValueParser
epage May 13, 2022
a798858
feat(parser): Verify possible values
epage May 13, 2022
d52be32
fix(parser): Don't bake-in ArgEnum
epage May 13, 2022
a396591
doc(parser): Explain built-in motivation
epage May 13, 2022
50e7308
refactor(parser): Reduce chance of bugs in specialization
epage May 13, 2022
352025f
refactor(parser): Tweak specialization precedence
epage May 13, 2022
f9be321
refactor(parser): Minor clean up
epage May 13, 2022
ec18648
refactor(parser): Have ValueParser delegate built-ins
epage May 13, 2022
2e57f1f
fix(parser): Disallow empty paths
epage May 13, 2022
0088b16
feat(parser): Track ValueParser's possible values
epage May 13, 2022
f3d8ef2
feat(complete): Complete value_parser possible values
epage May 13, 2022
6f2c4af
refactor(parser): Clean up ValueParser
epage May 13, 2022
bdf9d84
doc(parser): Expand examples for ValueParser
epage May 13, 2022
f16a6fb
refactor(parser): Move AnyValue definition closer to use
epage May 13, 2022
8b582c3
refactor(parser): Make AnyValue opaque
epage May 13, 2022
9920d5b
fix(parser): Improve AnyValue debug output
epage May 13, 2022
bc6da81
fix(parser): Unify handling of AnyValue's type id
epage May 13, 2022
f296c5c
refactor(parser): Fix naming of ty
epage May 13, 2022
8c3f540
refactor(parser): Clarify name for starting occurrences
epage May 16, 2022
144cd4c
refactor(parser): Group default/env
epage May 16, 2022
c74d3fb
refactor(parser): Reduce visibility
epage May 16, 2022
0e04367
fix(parser): Consistently log parser
epage May 16, 2022
772c5e3
feat(lex): Allow checking if at end of input
epage May 16, 2022
d538012
refactor(parser): separate concerns over external subcommands
epage May 16, 2022
e6b1468
fix(parser): Set source/occurrences for external subcommands
epage May 16, 2022
8a58884
refactor(parser): Initialize MatchedArg from its source
epage May 16, 2022
4845296
refactor(parser): Clarify default value code
epage May 16, 2022
5532bfc
refactor(parser): Clarify env code
epage May 16, 2022
0d6be4e
refactor(parser): Consistently start occurrences
epage May 16, 2022
7cb2a09
refactor(parser): Force explicit occurrence start
epage May 16, 2022
66570ee
refactor(parser): Set source at start of occurrence
epage May 16, 2022
c331be9
fix(parser): Separate dev errros from user errors
epage May 16, 2022
3623695
fix(parser): Allow invalid id lookups without panics
epage May 16, 2022
c7bd8c8
perf(parser): Don't allocate on every call
epage May 16, 2022
792fb54
fix(parser): Be consistently strict with ArgMatches types
epage May 16, 2022
d2ca181
feat(parser): Allow removing values from ArgMatches
epage May 16, 2022
41f13bd
feat(complete): Path value parsers imply Path completion hints
epage May 16, 2022
72c44a3
fix(derive): Move subcommands to new 'get_many' API
epage May 13, 2022
d826ab9
fix(derive): Move args to new 'get_one'/'get_many' API
epage May 13, 2022
f86d881
docs(parser): Polish ValueParser docs
epage May 16, 2022
2ffa38f
feat(parser): Implicitly turn possible values into a value parser
epage May 16, 2022
6b0306d
feat(parsr): Expose all built-in TypedValueParsers
epage May 16, 2022
63aa236
feat(parser): Convenient range value parsers
epage May 17, 2022
0033bf6
style: Address clippy complaints
epage May 17, 2022
675ac68
fix(parser): Give more flexibility for changes
epage May 17, 2022
91fe9ee
feat(parser): Transfer ownership of sub-matches
epage May 17, 2022
c9e3b81
doc(parser): Specify what unwraps are for
epage May 17, 2022
acdf26a
style: Make clippy happy
epage May 17, 2022
4a733cd
fix: Bump MSRV to 1.56.0
epage May 17, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .clippy.toml
@@ -1 +1 @@
msrv = "1.54.0" # MSRV
msrv = "1.56.0" # MSRV
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Expand Up @@ -78,7 +78,7 @@ jobs:
build: [msrv, wasm, wasm-wasi, debug, release]
include:
- build: msrv
rust: 1.54.0 # MSRV
rust: 1.56.0 # MSRV
target: x86_64-unknown-linux-gnu
features: full
- build: wasm
Expand Down Expand Up @@ -122,7 +122,7 @@ jobs:
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: 1.54.0 # MSRV
toolchain: 1.56.0 # MSRV
profile: minimal
override: true
- uses: Swatinem/rust-cache@v1
Expand Down Expand Up @@ -172,7 +172,7 @@ jobs:
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: 1.54.0 # MSRV
toolchain: 1.56.0 # MSRV
profile: minimal
override: true
components: clippy
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/rust-next.yml
Expand Up @@ -92,9 +92,9 @@ jobs:
strategy:
matrix:
rust:
- 1.54.0 # MSRV
- 1.56.0 # MSRV
- stable
continue-on-error: ${{ matrix.rust != '1.54.0' }} # MSRV
continue-on-error: ${{ matrix.rust != '1.56.0' }} # MSRV
runs-on: ubuntu-latest
steps:
- name: Checkout repository
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -10,6 +10,7 @@ members = [
[package]
name = "clap"
version = "3.1.18"
rust-version = "1.56.0" # MSRV
description = "A simple to use, efficient, and full-featured Command Line Argument Parser"
repository = "https://github.com/clap-rs/clap"
documentation = "https://docs.rs/clap/"
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -10,7 +10,7 @@ ifneq (${TOOLCHAIN_TARGET},)
ARGS+=--target ${TOOLCHAIN_TARGET}
endif

MSRV?=1.54.0
MSRV?=1.56.0

_FEATURES = minimal default wasm full debug release
_FEATURES_minimal = --no-default-features --features "std"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -93,7 +93,7 @@ OPTIONS:
- Leverage feature flags to keep to one active branch
- Being under [WG-CLI](https://github.com/rust-cli/team/) to increase the bus factor
- We follow semver and will wait about 6-9 months between major breaking changes
- We will support the last two minor Rust releases (MSRV, currently 1.54.0)
- We will support the last two minor Rust releases (MSRV, currently 1.56.0)

While these aspirations can be at odds with fast build times and low binary
size, we will still strive to keep these reasonable for the flexibility you
Expand Down
2 changes: 1 addition & 1 deletion clap_complete/src/dynamic.rs
Expand Up @@ -438,7 +438,7 @@ complete OPTIONS -F _clap_complete_NAME EXECUTABLES
let mut values = Vec::new();
debug!("complete_arg_value: arg={:?}, value={:?}", arg, value);

if let Some(possible_values) = arg.get_possible_values() {
if let Some(possible_values) = crate::generator::utils::possible_values(arg) {
if let Ok(value) = value {
values.extend(possible_values.into_iter().filter_map(|p| {
let name = p.get_name();
Expand Down
13 changes: 13 additions & 0 deletions clap_complete/src/generator/utils.rs
Expand Up @@ -126,6 +126,19 @@ pub fn flags<'help>(p: &Command<'help>) -> Vec<Arg<'help>> {
.collect()
}

/// Get the possible values for completion
pub fn possible_values<'help>(a: &Arg<'help>) -> Option<Vec<clap::PossibleValue<'help>>> {
if let Some(pvs) = a.get_possible_values() {
// Check old first in case the user explicitly set possible values and the derive inferred
// a `ValueParser` with some.
Some(pvs.to_vec())
} else {
a.get_value_parser()
.possible_values()
.map(|pvs| pvs.collect())
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion clap_complete/src/shells/bash.rs
Expand Up @@ -174,7 +174,7 @@ fn option_details_for_path(cmd: &Command, path: &str) -> String {
fn vals_for(o: &Arg) -> String {
debug!("vals_for: o={}", o.get_id());

if let Some(vals) = o.get_possible_values() {
if let Some(vals) = crate::generator::utils::possible_values(o) {
format!(
"$(compgen -W \"{}\" -- \"${{cur}}\")",
vals.iter()
Expand Down
2 changes: 1 addition & 1 deletion clap_complete/src/shells/fish.rs
Expand Up @@ -152,7 +152,7 @@ fn value_completion(option: &Arg) -> String {
return "".to_string();
}

if let Some(data) = option.get_possible_values() {
if let Some(data) = crate::generator::utils::possible_values(option) {
// We return the possible values with their own empty description e.g. {a\t,b\t}
// this makes sure that a and b don't get the description of the option or argument
format!(
Expand Down
2 changes: 1 addition & 1 deletion clap_complete/src/shells/zsh.rs
Expand Up @@ -359,7 +359,7 @@ fn get_args_of(parent: &Command, p_global: Option<&Command>) -> String {

// Uses either `possible_vals` or `value_hint` to give hints about possible argument values
fn value_completion(arg: &Arg) -> Option<String> {
if let Some(values) = &arg.get_possible_values() {
if let Some(values) = crate::generator::utils::possible_values(arg) {
if values
.iter()
.any(|value| !value.is_hide_set() && value.get_help().is_some())
Expand Down
2 changes: 1 addition & 1 deletion clap_complete_fig/src/fig.rs
Expand Up @@ -350,7 +350,7 @@ fn gen_args(arg: &Arg, indent: usize) -> String {
));
}

if let Some(data) = arg.get_possible_values() {
if let Some(data) = generator::utils::possible_values(arg) {
buffer.push_str(&format!(
"{:indent$}suggestions: [\n",
"",
Expand Down
51 changes: 33 additions & 18 deletions clap_derive/src/derives/args.rs
Expand Up @@ -529,25 +529,25 @@ fn gen_parsers(
let span = parser.kind.span();
let convert_type = inner_type(**ty, &field.ty);
let id = attrs.id();
let (value_of, values_of, mut parse) = match *parser.kind {
let (get_one, get_many, mut parse) = match *parser.kind {
FromStr => (
quote_spanned!(span=> value_of),
quote_spanned!(span=> values_of),
quote_spanned!(span=> get_one::<String>),
quote_spanned!(span=> get_many::<String>),
quote_spanned!(func.span()=> |s| ::std::result::Result::Ok::<_, clap::Error>(#func(s))),
),
TryFromStr => (
quote_spanned!(span=> value_of),
quote_spanned!(span=> values_of),
quote_spanned!(span=> get_one::<String>),
quote_spanned!(span=> get_many::<String>),
quote_spanned!(func.span()=> |s| #func(s).map_err(|err| clap::Error::raw(clap::ErrorKind::ValueValidation, format!("Invalid value for {}: {}", #id, err)))),
),
FromOsStr => (
quote_spanned!(span=> value_of_os),
quote_spanned!(span=> values_of_os),
quote_spanned!(span=> get_one::<::std::ffi::OsString>),
quote_spanned!(span=> get_many::<::std::ffi::OsString>),
quote_spanned!(func.span()=> |s| ::std::result::Result::Ok::<_, clap::Error>(#func(s))),
),
TryFromOsStr => (
quote_spanned!(span=> value_of_os),
quote_spanned!(span=> values_of_os),
quote_spanned!(span=> get_one::<::std::ffi::OsString>),
quote_spanned!(span=> get_many::<::std::ffi::OsString>),
quote_spanned!(func.span()=> |s| #func(s).map_err(|err| clap::Error::raw(clap::ErrorKind::ValueValidation, format!("Invalid value for {}: {}", #id, err)))),
),
FromOccurrences => (
Expand Down Expand Up @@ -587,24 +587,32 @@ fn gen_parsers(

Ty::Option => {
quote_spanned! { ty.span()=>
#arg_matches.#value_of(#id)
#arg_matches.#get_one(#id)
.expect("unexpected type")
.map(|s| ::std::ops::Deref::deref(s))
.map(#parse)
.transpose()?
}
}

Ty::OptionOption => quote_spanned! { ty.span()=>
if #arg_matches.is_present(#id) {
Some(#arg_matches.#value_of(#id).map(#parse).transpose()?)
Some(
#arg_matches.#get_one(#id)
.expect("unexpected type")
.map(|s| ::std::ops::Deref::deref(s))
.map(#parse).transpose()?
)
} else {
None
}
},

Ty::OptionVec => quote_spanned! { ty.span()=>
if #arg_matches.is_present(#id) {
Some(#arg_matches.#values_of(#id)
.map(|v| v.map::<::std::result::Result<#convert_type, clap::Error>, _>(#parse).collect::<::std::result::Result<Vec<_>, clap::Error>>())
Some(#arg_matches.#get_many(#id)
.expect("unexpected type")
.map(|v| v.map(|s| ::std::ops::Deref::deref(s)).map::<::std::result::Result<#convert_type, clap::Error>, _>(#parse).collect::<::std::result::Result<Vec<_>, clap::Error>>())
.transpose()?
.unwrap_or_else(Vec::new))
} else {
Expand All @@ -614,24 +622,31 @@ fn gen_parsers(

Ty::Vec => {
quote_spanned! { ty.span()=>
#arg_matches.#values_of(#id)
.map(|v| v.map::<::std::result::Result<#convert_type, clap::Error>, _>(#parse).collect::<::std::result::Result<Vec<_>, clap::Error>>())
#arg_matches.#get_many(#id)
.expect("unexpected type")
.map(|v| v.map(|s| ::std::ops::Deref::deref(s)).map::<::std::result::Result<#convert_type, clap::Error>, _>(#parse).collect::<::std::result::Result<Vec<_>, clap::Error>>())
.transpose()?
.unwrap_or_else(Vec::new)
}
}

Ty::Other if occurrences => quote_spanned! { ty.span()=>
#parse(#arg_matches.#value_of(#id))
#parse(
#arg_matches.#get_one(#id)
)
},

Ty::Other if flag => quote_spanned! { ty.span()=>
#parse(#arg_matches.is_present(#id))
#parse(
#arg_matches.is_present(#id)
)
},

Ty::Other => {
quote_spanned! { ty.span()=>
#arg_matches.#value_of(#id)
#arg_matches.#get_one(#id)
.expect("unexpected type")
.map(|s| ::std::ops::Deref::deref(s))
.ok_or_else(|| clap::Error::raw(clap::ErrorKind::MissingRequiredArgument, format!("The following required argument was not provided: {}", #id)))
.and_then(#parse)?
}
Expand Down
24 changes: 10 additions & 14 deletions clap_derive/src/derives/subcommand.rs
Expand Up @@ -430,20 +430,12 @@ fn gen_from_arg_matches(
),
};

let (span, str_ty, values_of) = match subty_if_name(ty, "Vec") {
let (span, str_ty) = match subty_if_name(ty, "Vec") {
Some(subty) => {
if is_simple_ty(subty, "String") {
(
subty.span(),
quote!(::std::string::String),
quote!(values_of),
)
(subty.span(), quote!(::std::string::String))
} else if is_simple_ty(subty, "OsString") {
(
subty.span(),
quote!(::std::ffi::OsString),
quote!(values_of_os),
)
(subty.span(), quote!(::std::ffi::OsString))
} else {
abort!(
ty.span(),
Expand All @@ -460,7 +452,7 @@ fn gen_from_arg_matches(
),
};

ext_subcmd = Some((span, &variant.ident, str_ty, values_of));
ext_subcmd = Some((span, &variant.ident, str_ty));
None
} else {
Some((variant, attrs))
Expand Down Expand Up @@ -518,11 +510,15 @@ fn gen_from_arg_matches(
});

let wildcard = match ext_subcmd {
Some((span, var_name, str_ty, values_of)) => quote_spanned! { span=>
Some((span, var_name, str_ty)) => quote_spanned! { span=>
::std::result::Result::Ok(#name::#var_name(
::std::iter::once(#str_ty::from(#subcommand_name_var))
.chain(
#sub_arg_matches_var.#values_of("").into_iter().flatten().map(#str_ty::from)
#sub_arg_matches_var
.get_many::<#str_ty>("")
.expect("unexpected type")
.into_iter().flatten() // `""` isn't present, bug in `unstable-v4`
.map(#str_ty::from)
)
.collect::<::std::vec::Vec<_>>()
))
Expand Down
5 changes: 5 additions & 0 deletions clap_lex/src/lib.rs
Expand Up @@ -235,6 +235,11 @@ impl RawArgs {
insert_items.iter().map(OsString::from),
);
}

/// Any remaining args?
pub fn is_end(&self, cursor: &ArgCursor) -> bool {
self.peek_os(cursor).is_none()
}
}

impl<I, T> From<I> for RawArgs
Expand Down