max-au / argparse Goto Github PK
View Code? Open in Web Editor NEWargparse: simple framework to create complex CLI
License: BSD 3-Clause "New" or "Revised" License
argparse: simple framework to create complex CLI
License: BSD 3-Clause "New" or "Revised" License
The following results in an error when displaying help text:
cli() ->
#{
handler => fun display/1,
arguments => [
#{
name => text,
default => <<"★"/utf8>>
}
]
}.
$ ./escript
escript: exception error: bad argument
in function io_lib:format/2
called as io_lib:format(" ~-4s ~s\n",
["text",
[116,101,120,116,32,40,
[60,60,"\"\x{2605}\"","/utf8>>"],
41]])
in call from argparse:'-format_help/2-lc$^4/1-2-'/2 (/Users/user/tool/_build/tool/lib/argparse/src/argparse.erl, line 1081)
in call from argparse:format_help/2 (/Users/user/tool/_build/tool/lib/argparse/src/argparse.erl, line 1081)
in call from argparse:format_error/3 (/Users/user/tool/_build/tool/lib/argparse/src/argparse.erl, line 314)
in call from cli:dispatch/4 (/Users/user/tool/_build/tool/lib/argparse/src/cli.erl, line 177)
in call from escript:run/2 (escript.erl, line 750)
in call from escript:start/1 (escript.erl, line 277)
in call from init:start_em/1
The script is built with the Rebar argument {escript_emu_args, "%%! +pc unicode"}
. I'd expect the unicode string to be printed out in the help text.
This does raise the bigger question, what about unicode support in other places? E.g. something weird like #{name => foo, long => "-★"}
?
When setting a specific handler on the top level:
main(Args) -> cli:run(Args).
cli() -> #{handler => {?MODULE, foobar}}.
cli(_Args) -> ok.
It is ignored and instead the program returns successfully.
cli:run/2 accepts a run_options()
which is later passed directly to functions of argparse
expecting a parser_options()
. This leads to dialyzer errors when passing keys which are only allowed in run_options()
; e.g., error
.
Dialyzing the calc
example app amended to include the following (in a thingy.erl
module):
main(Args) ->
cli:run(Args, #{error => error, progname => "calc"}).
results in:
thingy.erl:5:1: Function main/1 has no local return
thingy.erl:6:19: The call cli:run
(Args :: any(),
#{error => error, progname => "calc"}) will never return since it differs in the 2nd argument from the success typing arguments:
(any(),
#{'error' => 'ok', _ => _})
The example in the README.md
has
cli:run(Args, #{progname => "simple").
and should be
cli:run(Args, #{progname => "simple"}).
By default, a long flag named foobar
will generate the following text in the command usage section: [--foobar <foobar>]
. It would be nice to be able to specify a custom format text so it could be changed to [--foobar <baz>]
.
Perhaps this is a bigger question of if all parameters should allow atoms in many places, but for progname I found it neat if I would be able to do this:
main(Args) -> cli:run(Args, #{progname => ?MODULE}).
Given the following configuration:
main(Args) ->
cli:run(Args).
cli() -> #{commands => #{"foo" => #{}}}.
foo(_Opts) -> ok.
When calling the program with ./progname foo --help
this error happens:
escript: exception error: {badkey,"erl"}
in function map_get/2
called as map_get("erl",#{"foo" => #{handler => {progname,foo}}})
in call from argparse:collect_options/3 (/.../_build/default/lib/argparse/src/argparse.erl, line 1012)
in call from argparse:format_help/2 (/.../_build/default/lib/argparse/src/argparse.erl, line 970)
in call from cli:run_impl/4 (/.../_build/default/lib/argparse/src/cli.erl, line 212)
in call from escript:run/2 (escript.erl, line 758)
in call from escript:start/1 (escript.erl, line 277)
in call from init:start_em/1
in call from init:do_boot/3
Just calling ./progname --help
works.
Hey @max-au,
Is there a plan to switch the documentation generation to https://github.com/starbelly/rebar3_ex_doc?
e.g., #{name => foo, help => "bar", type => {int, [{min, 1}]}}
is rendered in help as --foo bar (int > 1)
, but should be --foo bar (int >= 1)
; responsible lines.
Is there/will there be any support for named arguments in the parameter list before the description? E.g DEPTH
in the following example:
-L, --level DEPTH limit the depth of recursion
-module(thingy).
-behaviour(cli).
-export([cli/0, main/1, go/1]).
cli() ->
#{
commands => #{
"go" => #{
arguments => [
#{
name => thingy,
required => true,
long => "-thingy",
short => $t,
help => "do the thing"
}
]
}
}
}.
main(Args) ->
cli:run(Args, #{progname => ?MODULE}).
go(Args) ->
io:format(user, "~p", [Args]).
$ thingy go
error: thingy go: required argument missing: thingy
usage: thingy go [-t <thingy>] --thingy <thingy>
Optional arguments:
-t, --thingy do the thing
Code snippet:
-type command_spec() :: {Name :: string(), command()}. %% Command name with command spec
Here "Name" is actually a list of command names (subcommands).
When using an arg with a multiple-choice string type (e.g., type => {string, ["a", "b", "c"]}
), the choices are properly respected but the help text erroneously concatenates those values, which appears to be due to this line which contains an extra list wrapper around the already-list arg.
With the addition of argparse
to stdlib
in OTP26 packing up an application using this library fails with:
===> Error generating release:
Duplicated modules:
argparse specified in stdlib and argparse
Not sure what the best solution is - perhaps simply renaming the top level module to arg_parse
would be a sensible option?!
Running the same application on OTP23-OTP26 having two different versions of the same thing isn't really an appealing option. But once we reach OTP29 we should be able to drop this.
This works on 1.1.4, but not 1.2.1:
-module(thingy).
-compile([nowarn_export_all, export_all]).
-behaviour(cli).
main(Args) ->
cli:run(Args, #{progname => ?MODULE}).
cli() ->
#{handler => {?MODULE, x},
arguments => [#{name => y,
short => $y,
required => false,
default => 1}]}.
x(#{y := Y}) ->
{ok, Y}.
failure:
escript: exception error: no function clause matching thingy:x(#{}) (thingy.erl, line 16)
in function escript:run/2 (escript.erl, line 750)
in call from escript:start/1 (escript.erl, line 277)
in call from init:start_em/1
in call from init:do_boot/3
Omitting required => false
prevents the error on 1.2.1, but it's not clear to me why this should be the case, and so I assume it's a bug.
Is there any plan to add this functionality?
I see that it's in the possible features listed to be done after 1.0.0, so I am wondering if there is any timeline for those.
I am trying to migrate an existing CLI to argparse
that already uses this form, and I would prefer not to break all its existing users.
Thanks
this line implies to me an intent to support "atom choice" args, i.e., args which are interpreted as one of the given atoms and invalid otherwise. e.g.,
#{name => foo, type => {atom, ['bar', 'baz']}}
should allow --foo bar
and --foo baz
, the corresponding arg values being the atoms 'bar'
and 'baz'
, but should fail with an invalid choice error otherwise.
attempting the above fails / the containing command is not recognized.
Currently flags which have custom parsing generate "nonsense" help text. For example, the following argument:
DefaultRange = {{2020, 1, 1}, {2020, 6, 22}},
#{
arguments => [
#{
name => range,
long => "-range",
short => $r,
help => "date range ([<from>]..[<to>])",
type => {custom, fun(S) -> parse_range(S, DefaultRange) end},
default => DefaultRange
}
]
}
Generates the following text:
-r, --range date range ([<from>]..[<to>]), [{{2020,1,1},{2020,6,22}}]
However, if parse_range(S, DefaultRange)
wants to e.g. take ISO dates there should be a way to display a default help string as well:
-r, --range date range ([<from>]..[<to>]), [2020-01-01..2020-06-22]
or when the default help string is set to ""
(or undefined
or something similar):
-r, --range date range ([<from>]..[<to>])
It would be nice if the short
and long
options for arguments could take atoms, or at least throw errors if they won't. I had this configuration:
#{
arguments => [
#{name => backgrounds, short => b, long => backgrounds, type => binary, default => "0..8"}
]
}
Calling the escript CLI gave this output:
$ ./tool
usage: colortool
$ ./tool -b foo
error: tool: unrecognised argument: -b
usage: tool
Of course, it works if I change it to $b
. This one took a long time to debug 😄
If they should take atoms, I think short
should only take one letter atoms and error out otherwise, and long
should take any string atom and automatically prepend it with "-"
so that the default flag format is --flag
(since most UNIX CLIs follow that).
It looks like #32, which added support for {atom, Choices}
types, adds a format_type clause which also captures {atom, unsafe}
, which it can't handle (using it leads to this error: escript: exception error: bad generator unsafe
).
I am finding it convenient to sometimes define default => undefined
args to support handlers like this:
handler(#{foo := Foo, bar := Bar} = Args) -> ...
where bar
was defined as default => undefined
. I.E., not require use of maps:get(bar, Args, undefined)
or defaults merging for optional args. This works, but then help text contains something like this unless overriding the long description:
Optional arguments:
--bar some bar thing (undefined)
(the default value, undefined
, is printed. I think this would just confuse the user.)
I suppose I am asking if one of the following could be done:
undefined
values? (Saw a comment describing the current behavior, but not why, and this seems conceptually similar enough if considering undefined
as also meaning "absence of value".)undefined
values?I'm not a fan of how the default types and values currently are displayed:
-b, --initial-balance Initial balance value, float, 0.0
-u, --user-email User email to query, binary
binary
is not really usable since there is no "binary" type in the shell, only "string"Not sure what the solution here is...
When using the cli
framework, if invalid arguments are provided or arguments are missing, the exit status code of the program is unexpectedly (to me) 0; e.g., using the readme example:
$ ./erm ; echo $?
error: simple: required argument missing: dir
usage: simple [-fr] <dir>
Arguments:
dir dir
Optional arguments:
-f force (false)
-r recursive (false)
0
$ ./erm -f 123 asdf ; echo $?
error: simple: unrecognised argument: asdf
usage: simple [-fr] <dir>
Arguments:
dir dir
Optional arguments:
-f force (false)
-r recursive (false)
0
I would expect a nonzero exit status code for these cases (I'm aware of the escript convention wrt crashes and exit codes). Furthermore, cli:run/2
returns ok
for these cases. If it instead returned an error tuple (or exception), then at least the user could explicitly halt with a different status code without requiring use of a different "success" value. Although as a "framework", arguably it could be in scope for cli
to handle these issues transparently to the user.
string()
)string() | atom()
)(found by calling cli:run
with an atom progname, which works, but dialyzer complains)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.