Skip to content

Commit

Permalink
Merge pull request #3732 from epage/domain
Browse files Browse the repository at this point in the history
feat(builder): Custom parser for arg values
  • Loading branch information
epage committed May 18, 2022
2 parents 740bb39 + 4a733cd commit 28ee3d3
Show file tree
Hide file tree
Showing 34 changed files with 3,104 additions and 650 deletions.
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

0 comments on commit 28ee3d3

Please sign in to comment.