Appearance
Advanced Features
Where values come from besides the command line (environment variables, configuration files), and the checks FLAP can run on them (exclusive sets, paths, ranges), plus deprecations, alternate actions and case-insensitive matching.
Value sources
A value comes from the first source that has one, in this order:
The command line always wins; a value from any explicit source (command line, environment, configuration file) satisfies a required option. cli%get_source and cli%provenance tell where each value comes from (see Parsing).
Environment variables
envvar names the variable of an option; init(auto_envvar_prefix=...) generates one for every option:
f90
call cli%init(progname='environment', auto_envvar_prefix='SOLVER')
call cli%add(switch='--mesh-file', help='Mesh', required=.true., act='store') ! SOLVER_MESH_FILE
call cli%add(switch='--workers', help='Workers', required=.false., act='store', nargs='+', def='1') ! SOLVER_WORKERS
call cli%add(switch='--fast', help='Fast mode', required=.false., act='store_true', def='.false.') ! SOLVER_FAST
call cli%add(switch='--token', help='API token', required=.false., act='store', def='none', envvar='MY_TOKEN')$ SOLVER_MESH_FILE=wing.grd MY_TOKEN=abc environment
mesh = wing.grd, token = abc, workers = 1
fast = F
--mesh-file = wing.grd [environment: SOLVER_MESH_FILE]
--workers = 1 [default]
--fast = .false. [default]
--token = abc [environment: MY_TOKEN]$ SOLVER_MESH_FILE=wing.grd environment --mesh-file body.grd
mesh = body.grd, token = none, workers = 1
fast = F
--mesh-file = body.grd [command line]
--workers = 1 [default]
--fast = .false. [default]
--token = none [default]- A variable counts when it is set and not blank; the bare switch (
--tokenalone) reads it too. envvaris valid for namedstoreoptions (lists included),store_trueandstore_false; not for positionals,store*,countandappend(ERROR_ENVVAR_NOT_STORE, 17).- A flag reads
1/0,true/false,t/f,yes/no,y/n,on/off, in any case; anything else makesgetfail withERROR_CASTING_LOGICAL(10). - The help shows the name of the variable of each option.
$ environment --help
usage: environment --mesh-file value [--workers value#1 [value#2...]] [--fast] [--token value] [--help] [--markdown] [--version]
Required switches:
--mesh-file value
environment variable name "SOLVER_MESH_FILE"
Mesh
Optional switches:
--workers value#1 [value#2...]
environment variable name "SOLVER_WORKERS"
default value 1
Workers
--fast
environment variable name "SOLVER_FAST"
default value .false.
Fast mode
--token value
environment variable name "MY_TOKEN"
default value none
API token
--help, -h
Print this help message
--markdown, -md
Save this help message in a Markdown file
--version, -v
Print versionLists from the environment
A list option (nargs) reads its variable as one line of comma-separated values, not blank-separated as on the command line and in def=: an environment value is a single shell word, where commas are the convention.
$ SOLVER_MESH_FILE=wing.grd SOLVER_WORKERS='1, 2 ,3' SOLVER_FAST=yes environment
mesh = wing.grd, token = none, workers = 1,2,3
fast = T
--mesh-file = wing.grd [environment: SOLVER_MESH_FILE]
--workers = 1 2 3 [environment: SOLVER_WORKERS]
--fast = .true. [environment: SOLVER_FAST]
--token = none [default]| Variable | List |
|---|---|
WORKERS='1,99' | 1, 99 |
WORKERS='1, 2 , 3' | 1, 2, 3 (blanks around a value trimmed) |
FILES='"my file.h5", other.h5' | my file.h5, other.h5 (a quoted value may hold commas and blanks) |
TITLES='"say ""hi""",x' | say "hi", x ("" inside quotes is a ") |
WORKERS='1,,3' | 1, empty, 3 (a numeric get then fails its cast) |
WORKERS='1,"99' | error ERROR_ENVVAR_CSV (43): unterminated quote |
A scalar option takes its variable verbatim, commas included. The number of values of nargs='N' is checked by get.
Generated variable names
With init(auto_envvar_prefix='SOLVER'), every named store, store_true or store_false option added without an envvar gets one, PREFIX[_COMMAND]_NAME in upper case, NAME being the long switch without its dashes and - becoming _: --mesh-file is SOLVER_MESH_FILE, the --format of the command post is SOLVER_POST_FORMAT. An explicit envvar= wins over the generated name; positionals, store*, count and append get none. init must come before the add calls.
Ignoring the environment
For reproducible runs (a batch job whose environment must not leak in, tests, CI sandboxes), init(ignore_env=.true.) turns every environment lookup off. The envvar definitions are unchanged and still shown in the help:
f90
call cli%init(progname='ignore_env', ignore_env=.true.)
call cli%add(switch='--threads', help='Threads', required=.false., act='store', def='1', envvar='OMP_NUM_THREADS')$ OMP_NUM_THREADS=64 ignore_env
threads = 1Configuration files
cli%set_config(file, required, error) names an INI file supplying values below the environment and above the defaults; an option with act='config' lets the user choose the file:
f90
call cli%init(progname='config', auto_envvar_prefix='SOLVER')
call cli%add(switch='--mesh-file', help='Mesh', required=.true., act='store')
call cli%add(switch='--cfl', help='CFL', required=.false., act='store', def='0.5')
call cli%add(switch='--config', help='Configuration file', required=.false., act='config', def='solver.ini')
call cli%add_group(group='post', description='Post processing')
call cli%add(group='post', switch='--format', help='Format', required=.false., act='store', def='vtk')ini
# solver.ini
mesh-file = wing.grd ; keys are the long switches without the dashes
cfl = 0.8
[post] # a section is a command
format = vtu$ config
--mesh-file = wing.grd [config: solver.ini]
--cfl = 0.8 [config: solver.ini]
--config = solver.ini [default]$ SOLVER_CFL=0.6 config --mesh-file body.grd post
--mesh-file = body.grd [command line]
--cfl = 0.6 [environment: SOLVER_CFL]
--config = solver.ini [default]
post --format = vtu [config: solver.ini]- Keys are the long switches without the dashes; keys before any section belong to the top level, a
[section]holds the options of that command. - A value is the text after
=, blanks trimmed; one pair of quotes ('or") is stripped, and a quoted value keeps its#and;. An inline comment starts at a#or;preceded by a blank. An empty value counts as unset. - A list (
nargs) is blank separated, as indef=; a flag readsyes/no,on/off,1/0,true/false, ... - An unknown key or section, a key naming an option that takes no value (
count,append,store*), or a line that is notkey = value,[section]or a comment isERROR_CONFIG_UNKNOWN_KEY(1007), naming the line; withinit(ignore_unknown_clas=.true.)such lines are ignored. The last of repeated keys wins. - The file is the one named on the command line (
--config my.ini), else in the environment variable of theconfigoption, else the one ofset_config, else the default of theconfigoption. A file named on the command line or in the environment must exist (ERROR_CONFIG_NOT_FOUND, 1006); a missing default is skipped, unlessset_config(..., required=.true.).getof theconfigoption returns the file name. - The file is read by
parseafter--help/--version, so a broken file never blocks the help. A value from it satisfies a required option and is checked againstchoicesand ranges byget.
$ config --config other.ini
config: error: configuration file "other.ini" not found!
Try 'config --help' for help.
[exit status 1]Mutually exclusive sets
cli%set_mutually_exclusive_switches(switches, required, group, pref, error) declares a set of switches of which at most one may be given; with required=.true., exactly one. It is the recommended mechanism over the pairwise exclude:
f90
call cli%add(switch='--mesh', switch_ab='-m', help='Mesh file', required=.false., act='store', def='')
call cli%add(switch='--restart', switch_ab='-r', help='Restart file', required=.false., act='store', def='')
call cli%add(switch='--left', help='Go left', required=.false., act='store_true', def='.false.')
call cli%add(switch='--right', help='Go right', required=.false., act='store_true', def='.false.')
call cli%set_mutually_exclusive_switches(switches='--mesh,--restart', required=.true.) ! exactly one
call cli%set_mutually_exclusive_switches(switches='--left,--right') ! at most one$ exclusive --mesh m.grd --left
ok$ exclusive --mesh m.grd --restart r.h5
exclusive: error: switches "--mesh", "--restart" are mutually exclusive!
usage: exclusive [--json] [--csv] (--mesh value | --restart value) [--left | --right] [--help] [--markdown] [--version]
Try 'exclusive --help' for help.
[exit status 1]$ exclusive
exclusive: error: one of "--mesh", "--restart" is required!
usage: exclusive [--json] [--csv] (--mesh value | --restart value) [--left | --right] [--help] [--markdown] [--version]
Try 'exclusive --help' for help.
[exit status 1]The usage shows the sets in docopt notation, (a | b) for a required set and [a | b] otherwise. Rules:
- the members are comma separated, named by switch or abbreviation, and must be already added to the group (pass
group=for the options of a command); a set of a command is checked only when the command is called; - a member cannot be individually
required, and a switch belongs to at most one set; - every explicit value counts, from the command line, the environment or a configuration file; a default neither satisfies a required set nor violates a set. When a member is on the command line, the environment and configuration values of the other members fall back to their defaults: the command line wins (a variable set for a batch job never makes a command line alternative a violation);
- the sets are checked after help/version and after the required options, as the last validation;
- an invalid set is not added: the call returns
ERROR_M_EXCLUDE_SET_DEFINITION(104), andparsereturns the same error, so a wrong definition cannot go unnoticed.
Path checks
must_exist, readable, writable and allow_dash make parse check a file name, whatever its source (command line, environment, configuration file or default):
f90
call cli%add(switch='--mesh', help='Mesh file', required=.true., act='store', readable=.true.)
call cli%add(switch='--log', help='Log file', required=.false., act='store', def='-', writable=.true., allow_dash=.true.)$ paths --mesh wnig.grd
paths: error: option "--mesh": path "wnig.grd" does not exist!
Try 'paths --help' for help.
[exit status 1]| Keyword | Check |
|---|---|
must_exist | the file exists (ERROR_PATH_NOT_FOUND, 33) |
readable | it exists and opens for reading (ERROR_PATH_NOT_READABLE, 34, with the reason given by the system) |
writable | if it exists, it opens for writing, nothing written (ERROR_PATH_NOT_WRITABLE, 35); a missing file passes and is not created |
allow_dash | - passes every check (your program maps it to standard input/output); otherwise - is a file name |
- Every item of a list is checked; an empty value (
def='') is not checked. The options of a command are checked only when the command is called. - Only for options taking a value (
store,store*,append): elsewhere the keywords areERROR_PATH_INCONSISTENT(49). - Standard Fortran leaves the existence of a directory to the compiler: with gfortran a directory exists and opens for reading, with Intel ifx it does not exist.
- nvfortran 26.5: opening a read-only file for writing succeeds (the error comes at the first write), so
writabledoes not detect a read-only file with that compiler.
Numeric ranges
min= and max= (strings, like def=) give a numeric option a range; min_open=.true./max_open=.true. exclude the bound, clamp=.true. replaces an out-of-range value with the bound instead of failing:
f90
call cli%add(switch='--cfl', help='CFL number', required=.false., act='store', def='0.8', &
min='0', min_open=.true., max='1') ! (0, 1]
call cli%add(switch='--threads', help='OpenMP threads', required=.false., act='store', def='1', &
min='1', max='256', clamp=.true.) ! [1, 256], out-of-range values clamped$ ranges --cfl 0.5 --threads 999
cfl = 0.50, threads = 256$ ranges --cfl 1.5
ranges: error: value "1.5" of "--cfl" is out of range (0, 1]!
[exit status 1]- The value is checked by
get, after its conversion, in the kind of your variable, whatever its source; every element of a list is checked.ERROR_OUT_OF_RANGEis 31. - With
clamp, an open integer bound clamps to the next integer inside (bound + 1/bound - 1); a real cannot be clamped to an open bound:getreportsERROR_RANGE_DEFINITION(30) when it would have to. - An invalid range (a bound that is not a number,
min > max, an empty open interval, a range on a flag) isERROR_RANGE_DEFINITION(30) atadd;getinto acharacterorlogicalisERROR_RANGE_TYPE(32). - The help shows it:
range (0, 1].
Deprecated options and commands
add(..., deprecated='message') and add_group(..., deprecated='message') mark an option or a command as deprecated (deprecated='': without a message). Using it is not an error: parse prints a warning on the error unit and goes on.
f90
call cli%add(switch='--grid', help='Grid file', required=.false., act='store', def='g.grd', &
deprecated='use --mesh instead')
call cli%add(switch='--mesh', help='Mesh file', required=.false., act='store', def='m.grd')
call cli%add_group(group='legacy', description='The old solver', deprecated='use run')
call cli%add_group(group='run', description='The solver')$ deprecated --grid w.grd legacy
deprecated: warning: option "--grid" is deprecated: use --mesh instead
deprecated: warning: command "legacy" is deprecated: use run
ok- An option warns when its value comes from the command line or from its environment variable, not when it comes from a configuration file or its default (as in click); a command warns when it is called.
- The help marks them:
Grid file (DEPRECATED: use --mesh instead). - A required option cannot be deprecated:
ERROR_DEPRECATED_REQUIRED(44).
Alternate actions
An option with act='alternate' is an auxiliary action of the program (--list-models, --dump-config), not an input value: when it is passed, parse returns STATUS_ALTERNATE (also in standalone mode: FLAP never stops on it) and skips the value validation, so it works even without the required options. The program dispatches on is_passed:
f90
call cli%add(switch='--mesh', help='Mesh file', required=.true., act='store')
call cli%add(switch='--list-models', help='List the turbulence models and exit', act='alternate')f90
call cli%parse(error=error)
if (error == STATUS_ALTERNATE) then
if (cli%is_passed(switch='--list-models')) print '(A)', 'spalart-allmaras, k-omega-sst'
stop
elseif (error /= 0) then
stop 1, quiet=.true.
endif$ alternate --list-models
spalart-allmaras, k-omega-sst- Skipped: required options, mutually exclusive sets,
exclude=pairs, exclusive commands, path checks, unknown configuration keys and invalid environment lists. Still reported: syntax errors (an unknown or repeated switch);--help,--versionand--markdowncome first. Deprecation warnings are still printed. getworks as usual after it (a value out ofchoicesis still reported byget).- An alternate is a flag:
nargs,envvar,choices,exclude,requiredandpositionalareERROR_ALTERNATE_INCONSISTENT(37). The usage shows it as[--list-models].
Case-insensitive matching
init(case_insensitive=.true.) matches switches (abbreviations and negations included) and command names in any case; values keep their case (for choices in any case, see case_sensitive=.false. in Defining Arguments):
f90
call cli%init(progname='case_insensitive', case_insensitive=.true.) ! before any add$ case_insensitive --MESH wing.grd RUN
mesh = wing.grd, run = TTwo switches of a group differing only by case are then a consistency error (100). The "did you mean" suggestions compare in any case too.