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

Negative int value gets parsed as a new flag. #129

Closed
mcourteaux opened this issue Aug 6, 2018 · 12 comments
Closed

Negative int value gets parsed as a new flag. #129

mcourteaux opened this issue Aug 6, 2018 · 12 comments
Labels

Comments

@mcourteaux
Copy link

mcourteaux commented Aug 6, 2018

With an int-valued flag, one cannot enter a negative integer as parameter, because it gets parsed as new flag.

Eg:

# Does work:
./foo --number 42 --file test.txt
# Doesn't work
./foo --number -1 --file test.txt
@mcourteaux mcourteaux changed the title Negative int value gets interpreted as a new flag. Negative int value gets parsed as a new flag. Aug 6, 2018
@TeXitoi
Copy link
Owner

TeXitoi commented Aug 6, 2018

Should work

./foo --number=-1 --file test.txt

You may also be interested by https://docs.rs/clap/2.32.0/clap/enum.AppSettings.html#variant.AllowLeadingHyphen and https://docs.rs/clap/2.32.0/clap/enum.AppSettings.html#variant.AllowNegativeNumbers

See https://github.com/TeXitoi/structopt/blob/master/examples/raw_attributes.rs for an example using AppSettings.

I'll add an example later.

Does it fix your problem?

@TeXitoi TeXitoi added the doc label Aug 6, 2018
@mcourteaux
Copy link
Author

mcourteaux commented Aug 6, 2018 via email

@1Dragoon
Copy link

1Dragoon commented Jun 17, 2021

I couldn't find any examples for how to do this without using the equals sign (TeXitoi's third link has a 404 and I can't find it in the examples dir, or maybe I'm not looking hard enough?) but for anybody else who lands here from google, I figured it out and it works like this:

#[derive(Debug, StructOpt)]
#[structopt(about = "My little program.", settings = &[AppSettings::AllowNegativeNumbers])]
struct Opt {
    /// My parameter that accepts negative numbers
    param: i32,
}

you can then run it like so:

./myprogram --param -20

@cdstanford
Copy link

cdstanford commented Nov 14, 2021

TeXitoi's workaround also doesn't apply for multiple int arguments, like:

#[derive(StructOpt)]
#[structopt(about = "CLI accepting a list of integers.")]
struct VecInt {
    #[structopt(short, required = true)]
    ints: Vec<isize>,
},

I haven't figured out how to escape the ints on the command line yet.

@cdstanford
Copy link

There's an allow_hyphen_values(true) option discussed here, but doesn't seem to work for my use case. In an example with multiple Vec<isize> arguments, all arguments past the first one no longer work because the hyphens are ignored starting from where the Vec starts parsing.

I would be happy with a solution where I escape the negative values somewhow.

@epage
Copy link
Contributor

epage commented Nov 15, 2021

@cdstanford can you provide an example command line that fails? I want to make sure I'm finding a solution to your problem and not my guess as to your problem.

@cdstanford
Copy link

Absolutely -- apologies for not providing a full example before. Here's a CLI that tries to add up lists of integers:

use structopt::StructOpt;

#[derive(StructOpt)]
#[structopt(about = "Sum one or more lists of ints.")]
enum Cli {
    SumOne {
        #[structopt(short, required = true, allow_hyphen_values(true))]
        a: Vec<isize>,
    },
    SumTwo {
        #[structopt(short, required = true, allow_hyphen_values(true))]
        a: Vec<isize>,
        #[structopt(short, required = true, allow_hyphen_values(true))]
        b: Vec<isize>,
    },
}

fn main() {
    fn sum(l: &[isize]) -> isize {
        l.iter().sum()
    }
    match Cli::from_args() {
        Cli::SumOne { a } => println!("{}", sum(&a)),
        Cli::SumTwo { a, b } => println!("{} {}", sum(&a), sum(&b)),
    }
}

The SumOne command works: cargo run -- sum-one -a 1 2 -3 gives 0. But the SumTwo command is not able to parse the -b argument. Error message for cargo run -- sum-two -a 1 2 3 -b 1 2 -3:

error: The following required arguments were not provided:
    -b <b>...

USAGE:
    structopt sum-two -a <a>... -b <b>...

@cdstanford
Copy link

P.S. Let me know if I should rase a new issue for this case, it's diverged a bit from the original post here.

@epage
Copy link
Contributor

epage commented Nov 15, 2021

If you add this to your Cargo.toml:

clap = { version = "2", features = ["debug"] }

You can see whats going wrong under the hood

DEBUG:clap:Parser::get_matches_with: Begin parsing '"3"' ([51])      
DEBUG:clap:Parser::is_new_arg:"3":Opt("a")                  
DEBUG:clap:Parser::is_new_arg: arg_allows_tac=true
DEBUG:clap:Parser::is_new_arg: probably value
DEBUG:clap:Parser::is_new_arg: starts_new_arg=false
DEBUG:clap:Parser::add_val_to_arg; arg=a, val="3" 
DEBUG:clap:Parser::add_val_to_arg; trailing_vals=false, DontDelimTrailingVals=false
DEBUG:clap:Parser::add_single_val_to_arg;            
DEBUG:clap:Parser::add_single_val_to_arg: adding val..."3" 
DEBUG:clap:Parser::groups_for_arg: name=a              
DEBUG:clap:Parser::groups_for_arg: No groups defined                                                                                               
DEBUG:clap:ArgMatcher::needs_more_vals: o=a

DEBUG:clap:Parser::get_matches_with: Begin parsing '"-b"' ([45, 98])
DEBUG:clap:Parser::is_new_arg:"-b":Opt("a")
DEBUG:clap:Parser::is_new_arg: arg_allows_tac=true
DEBUG:clap:Parser::is_new_arg: - found
DEBUG:clap:Parser::is_new_arg: starts_new_arg=false                                                                                                
DEBUG:clap:Parser::add_val_to_arg; arg=a, val="-b"               
DEBUG:clap:Parser::add_val_to_arg; trailing_vals=false, DontDelimTrailingVals=false
DEBUG:clap:Parser::add_single_val_to_arg;                        
DEBUG:clap:Parser::add_single_val_to_arg: adding val..."-b"
DEBUG:clap:Parser::groups_for_arg: name=a                    
DEBUG:clap:Parser::groups_for_arg: No groups defined
DEBUG:clap:ArgMatcher::needs_more_vals: o=a    

DEBUG:clap:Parser::get_matches_with: Begin parsing '"1"' ([49])
DEBUG:clap:Parser::is_new_arg:"1":Opt("a")          
DEBUG:clap:Parser::is_new_arg: arg_allows_tac=true    
DEBUG:clap:Parser::is_new_arg: probably value           
DEBUG:clap:Parser::is_new_arg: starts_new_arg=false
DEBUG:clap:Parser::add_val_to_arg; arg=a, val="1"
  • Newlines added to make it more clear
  • Before and after case provided for contrast

-b starts with a hyphen, so clap is picking that up as an argument for -a. The fact that a only takes isize doesn't come into the picture until later, when validating the values associated with each argument.

What I find strange is AllowNegativeNumbers is meant to address this but isn't working

use structopt::StructOpt;

#[derive(StructOpt)]
#[structopt(about = "Sum one or more lists of ints.")]
#[structopt(settings = &[structopt::clap::AppSettings::AllowNegativeNumbers])]
enum Cli {
    SumOne {
        #[structopt(short, required = true)]
        a: Vec<isize>,
    },
    SumTwo {
        #[structopt(short, required = true)]
        a: Vec<isize>,
        #[structopt(short, required = true)]
        b: Vec<isize>,
    },
}

fn main() {
    fn sum(l: &[isize]) -> isize {
        l.iter().sum()
    }
    match Cli::from_args() {
        Cli::SumOne { a } => println!("{}", sum(&a)),
        Cli::SumTwo { a, b } => println!("{} {}", sum(&a), sum(&b)),
    }
}

gives

❯ cargo run -- sum-one -a 1 2 -3
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/test-clap sum-one -a 1 2 -3`
error: Found argument '-3' which wasn't expected, or isn't valid in this context

USAGE:
    test-clap sum-one -a <a>...

For more information try --help

@epage
Copy link
Contributor

epage commented Nov 15, 2021

Nevermind, I made the common mistake of forgetting global_setting. We need to fix that in clap.

This works:

use structopt::StructOpt;

#[derive(StructOpt)]
#[structopt(about = "Sum one or more lists of ints.")]
#[structopt(global_setting = structopt::clap::AppSettings::AllowNegativeNumbers)]
enum Cli {
    SumOne {
        #[structopt(short, required = true)]
        a: Vec<isize>,
    },
    SumTwo {
        #[structopt(short, required = true)]
        a: Vec<isize>,
        #[structopt(short, required = true)]
        b: Vec<isize>,
    },
}

fn main() {
    fn sum(l: &[isize]) -> isize {
        l.iter().sum()
    }
    match Cli::from_args() {
        Cli::SumOne { a } => println!("{}", sum(&a)),
        Cli::SumTwo { a, b } => println!("{} {}", sum(&a), sum(&b)),
    }
}
``

@epage
Copy link
Contributor

epage commented Nov 15, 2021

I created clap-rs/clap#3028 about the API trap of setting vs global_setting

@cdstanford
Copy link

Excellent!! Thanks so much for the help!

walfie added a commit to walfie/vtubestudio-cli that referenced this issue Nov 24, 2021
Previously a negative number would be interpreted as a flag.

TeXitoi/structopt#129
walfie added a commit to walfie/vtubestudio-cli that referenced this issue Nov 24, 2021
Previously a negative number would be interpreted as a flag.

TeXitoi/structopt#129
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants