Argparse Experiments

This is a test of some of the fish-shell's argparse options to see if I get how it works.

Boolean Options

The basic option definition creates a boolean option - a variable for the flag will be created if the user passes in the option and it won't be created if the user doesn't.

This example creates a short-version (-t) and a long-version (--test) for the option.

## Create the Option
set options "t/test"

## Create the Arguments
set arguments "--test"

# Parse It
argparse $options  -- $arguments

# Check if the variable was created
if set --query _flag_test
    echo "test set"
end

# If we don't define an option but check it's variable it won't exist
# this will emit the statement only if =_flag_untest= exists
if set --query _flag_untest
    echo "untested set"
end
test set

Coming from a python background this feels to me like it should raise an exception, but that's not how it works.

Passing In Values To Options

Putting an equals sign (=) tells argparse that we're (possibly) expecting a value to be passed in with the flag.

## The Options
# first a long option that's either set or not set (no values expected)
set options "test"

# now a short option that expects a value
set --append options "v="

## The Arguments
set arguments "--test" "-v 5"

## parse the arguments
argparse $options  -- $arguments

if set --query _flag_test
    echo "tested"
end

if set --query _flag_v
    echo "v=$_flag_v"
end  
tested  
v= 5

Optional Values

Missing Required Value

The prior case of creating an option with an equals sign after it creates a required value - if you use the option you have to pass in a value or it will be an error.

argparse "--value=" --  "--value"

The Result:

argparse: --value=: unknown option

Optional Value Without Equal

If you use a question mark after the option definition (-v=?) then the option expects one or no value to be passed in.

  • if you pass in more than one value it takes the first one
  • if you pass in no value it behaves like a boolean option

But, there's now a catch to passing in a value when compared to passing in a required value. Here's a case where I try to pass in an optional value.

set options "t/test" "v/value=?"

set arguments "-v 6"

argparse  -- $arguments

if set --query _flag_test
    echo "tested"
end

if set --query _flag_v
    echo "v=$_flag_v"
end

The Result:

argparse: -v 6: unknown option

This fails because the option definition v=? means the value passed in with the option (-v) is optional - so there can't be any spaces between the option and the value. To make it work the value has to be smashed up against a short-option (-v6) or passed in using an equal sign with a long option (--value=6).

Optional Values (the right way this time)

This time we'll use two versions of the optional-values syntax - ? means zero or one, * means 0 or more (it creates a list so you can pass in multiple valuse by repeating the flag).

## Defining The Options
# The number of option-values an option takes mirrors the cardinality
# symbols used with regular expressions

# A '?' means 0 or 1 value
set options "v=?"

# '*' means 0 or more
set --append options "cats=*"
set --append options "dogs=*"

## The Arguments Being Passed In
# short option values need to be smashed together with no spaces
set arguments "-v5"

# long arguments need values passed with an equal sign
set --append arguments "--cats=8"
set --append arguments "--cats=Too-Many"

# optional values are optional - passing nothing will make the flag
# act like a boolean
set --append arguments "--dogs"

argparse $options -- $arguments

if set --query _flag_v
    echo "v $_flag_v"
end

if set --query _flag_cats
    for amount in $_flag_cats
        echo "cats $amount"
    end
end

# since we defined it as taking an (optional) value
# but didn't pass in a value, the variable is created
# but there's no value in it
if set --query _flag_dogs
    echo "dogs $_flag_dogs"
end
v 5
cats 8
cats Too-Many
dogs 

Testing If A Path Argument Exists

If you put an exclamation mark (!) after the option definition it turns whatever you put after the ! into a validator to check the value passed in by the user. In this case it'll use the test command to check if the path passed in is a directory.

# here's out definition (using '+' as the cardinality (one or more)
# and a validator to check if the value passed in is a directory
set options 'p/path=+!test -d "$_flag_value"'

# a trio of paths to pass in as arguments
set arguments "--path=/tmp" "-p/home" "--p=/"

argparse $options  -- $arguments

if set --query _flag_path
    for p in $_flag_path
        echo "path: $p"
    end
end
path: /tmp
path: /home
path: /

Note that using the option multiple times creates multiple entries for the path option and that using two dashes for the short 'p' option works (weirdly) but changes it to use an equal sign instead of smashing the value up against the option (sort of a hybrid long and short option).

Also, the test checks that all the values in _flag_path are directories - if even one fails then the option isn't set.

set options 'p/path=+!test -d "$_flag_value"'

# a pair of valid paths as arguments
set arguments "--path=/tmp" "-p/home"

# and an invalid path as well
set --append arguments "--path=/oogabooga"

argparse $options  -- $arguments

if set --query _flag_path
    for p in $_flag_path
        echo "path: $p"
    end
else
    echo "No paths available."
end
No paths available.

So if you want it to be more forgiving and let some values fail you need to not test it with argparse and check the values afterwards.