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

Optional positional arguments that get skipped if corresponding validation fails #2122

Open
ducaale opened this issue Sep 1, 2020 · 3 comments
Labels
A-parsing Area: Parser's logic and needs it changed somehow. A-validators Area: ArgMatches validation logi C-enhancement Category: Raise on the bar on expectations S-waiting-on-design Status: Waiting on user-facing design to be resolved before implementing

Comments

@ducaale
Copy link
Contributor

ducaale commented Sep 1, 2020

Describe your use case

I am trying re-implement HTTPie cli using clap-rs/structopt. I would like to have something like this

yahc.exe [METHOD] <URL> [REQUEST_ITEM]...

where METHOD is an optional positional arg enum with a default value

Describe the solution you'd like

It would be good if clap-rs allowed multiple optional positional arguments that get filled with a default value whenever the corresponding value fails to parse. For example, if we had:

test.exe [arg1] [arg2] [arg3]...

where:

  • arg1 has a type of enum Arg1 { Foo, Bar } and defaults to Arg1::Bar
  • arg2 has a type of u32 and defaults to 0
  • arg3 has a type of Vec<String>

a user can use the CLI app in the following ways:

  • test.exe: arg1 = default_value, arg2 = default_value, arg3 = default_value
  • test.exe foo: arg1 = foo, arg2 = default_value, arg3 = default_value
  • test.exe 1: arg1 = default_value, arg2 = 1, arg3 = default_value
  • test.exe hello: arg1 = default_value, arg2 = default_value, arg3 = [hello]
  • test.exe foo hello: arg1 = foo, arg2 = default_value, arg3 = [hello]

Alternatives, if applicable

I tried using the following but they didn't fit my use case:

  • AllowMissingPositional can only be used when the last positional argument is required
  • optional subcommands cannot be followed by positional args if the subcommand is omitted

Additional context

#2120

@pksunkara
Copy link
Member

Might be related to #975

@epage
Copy link
Member

epage commented Dec 13, 2021

This is reminding me of #3035

Currently, value validation happens in a distinct phase. We are looking at modifying it so people are more likely to put in more type information, see #2683.

Granted, relying on value validation can only work in fairly limited cases.

Personally, I think I'd provide custom usage statements and parse as all Vec<String>. You can get clap like errors now with App::error.

I am curious how HTTPie, an app built on argparse, is doing this. Understanding other parsers provides us opportunities to learn from others rather than reinvent the wheel.

@epage epage added the S-waiting-on-design Status: Waiting on user-facing design to be resolved before implementing label Dec 13, 2021
@ducaale
Copy link
Contributor Author

ducaale commented Dec 13, 2021

Personally, I think I'd provide custom usage statements and parse as all Vec. You can get clap like errors now with App::error.

Yes. In the end, we ended up with something like that

#[derive(StructOpt, Debug)]
#[structopt(
    name = "xh",
    long_version = long_version(),
    settings = &[
        AppSettings::AllArgsOverrideSelf,
    ],
)]
pub struct Cli {
 
    /// HTTP version to use
    #[structopt(long, value_name = "VERSION", possible_values = &["1", "1.0", "1.1", "2"])]
    pub http_version: Option<HttpVersion>,

    /// The request URL, preceded by an optional HTTP method.
    ///
    /// METHOD can be `get`, `post`, `head`, `put`, `patch`, `delete` or `options`.
    /// If omitted, either a GET or a POST will be done depending on whether the
    /// request sends data.
    /// {n}{n}{n}
    #[structopt(value_name = "[METHOD] URL")]
    raw_method_or_url: String,

    /// Optional key-value pairs to be included in the request
    ///
    ///   - key==value to add a parameter to the URL
    ///   - key=value to add a JSON field (--json) or form field (--form)
    ///   - key:=value to add a complex JSON value (e.g. `numbers:=[1,2,3]`)
    ///   - key@filename to upload a file from filename (with --form)
    ///   - @filename to use a file as the request body
    ///   - header:value to add a header
    ///   - header: to unset a header
    ///   - header; to add a header with an empty value
    ///
    /// A backslash can be used to escape special characters (e.g. weird\:key=value).
    #[structopt(value_name = "REQUEST_ITEM", verbatim_doc_comment)]
    raw_rest_args: Vec<String>,

    /// The HTTP method, if supplied.
    #[structopt(skip)]
    pub method: Option<Method>,

    /// The request URL.
    #[structopt(skip = ("http://placeholder".parse::<Url>().unwrap()))]
    pub url: String,

    /// Optional key-value pairs to be included in the request.
    #[structopt(skip)]
    pub request_items: RequestItems,
}

Notice how method, url, and request_items are all skipped. Later, we are parsing the raw_rest_args and filling any skipped args as appropriate.

pub fn from_iter_safe<I>(iter: I) -> clap::Result<Self>
where
    I: IntoIterator,
    I::Item: Into<OsString> + Clone,
{
    let mut app = Self::clap();
    let matches = app.get_matches_from_safe_borrow(iter)?;
    let mut cli = Self::from_clap(&matches);

    let mut rest_args = mem::take(&mut cli.raw_rest_args).into_iter();
    let raw_url = match parse_method(&cli.raw_method_or_url) {
        Some(method) => {
            cli.method = Some(method);
            rest_args.next().ok_or_else(|| {
                Error::with_description("Missing URL", ErrorKind::MissingArgumentOrSubcommand)
            })?
        }
        None => {
            cli.method = None;
            mem::take(&mut cli.raw_method_or_url)
        }
    };
    for request_item in rest_args {
        cli.request_items.items.push(request_item.parse()?);
    }
}

This is the file that contains all sorts of workarounds around structopt/clap: https://github.com/ducaale/xh/blob/master/src/cli.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-parsing Area: Parser's logic and needs it changed somehow. A-validators Area: ArgMatches validation logi C-enhancement Category: Raise on the bar on expectations S-waiting-on-design Status: Waiting on user-facing design to be resolved before implementing
Projects
None yet
Development

No branches or pull requests

4 participants