eclip
所属分类:其他
开发工具:Erlang
文件大小:0KB
下载次数:0
上传日期:2023-12-06 13:07:09
上 传 者:
sh-1993
说明: Erlang命令行分析器
(Erlang command line parser)
文件列表:
LICENSE (1073, 2023-12-11)
Makefile (354, 2023-12-11)
doc/ (0, 2023-12-11)
doc/eclip.md (19927, 2023-12-11)
erlang.mk (277662, 2023-12-11)
src/ (0, 2023-12-11)
src/eclip.erl (56498, 2023-12-11)
test/ (0, 2023-12-11)
test/calc (1326, 2023-12-11)
test/calc.lux (1293, 2023-12-11)
test/hello (567, 2023-12-11)
test/hello.lux (1037, 2023-12-11)
test/mycmd1 (1303, 2023-12-11)
test/mycmd1.lux (2650, 2023-12-11)
test/mycmd2 (283, 2023-12-11)
test/mycmd2.lux (717, 2023-12-11)
test/mycmd3 (457, 2023-12-11)
test/mycmd3.lux (500, 2023-12-11)
test/mycmd4 (504, 2023-12-11)
test/mycmd4-dir/ (0, 2023-12-11)
test/mycmd4-dir/aa1/ (0, 2023-12-11)
test/mycmd4-dir/aa1/foo (0, 2023-12-11)
test/mycmd4-dir/aa2/ (0, 2023-12-11)
test/mycmd4-dir/bar (0, 2023-12-11)
test/mycmd4-dir/baz (0, 2023-12-11)
test/mycmd4.lux (816, 2023-12-11)
test/mycmd5 (644, 2023-12-11)
test/mycmd5.lux (943, 2023-12-11)
test/mycmd6 (486, 2023-12-11)
test/mycmd6.lux (999, 2023-12-11)
tools/ (0, 2023-12-11)
tools/gen-md.sh (6633, 2023-12-11)
# Eclip - An Erlang library for command line parsing
Eclip is a command line parser for Erlang programs. It is similar in
functionality to Python's `click`.
The erlang module is called `eclip` and is documented in
[eclip.md](https://github.com/mbj4668/eclip/blob/master/doc/eclip.md).
Here's an example of a simple script:
```erlang
#!/usr/bin/env escript
-mode(compile).
main(Args) ->
eclip:parse(Args, spec(), #{}).
spec() ->
#{help => "Simple program that greets NAME for a total of COUNT times.",
opts => [#{long => "name", required => true,
help => "The person to greet"},
#{long => "count", default => 1,
help => "Number of greetings"}],
cb => fun hello/4}.
hello(_, _, Name, Count) ->
lists:foreach(
fun(_) -> io:format("Hello ~s!\n", [Name]) end,
lists:seq(1, Count)).
```
It looks likes this when it is run:
```shell-session
$ hello --name Martin --count 3
Hello Martin!
Hello Martin!
Hello Martin!
```
Eclip automatically generates nice help text:
```shell-session
$ hello --help
Usage: ./hello [OPTIONS]
Simple program that greets NAME for a total of COUNT times.
Options:
-h, --help Show this help and exit
--completion [SHELL] Print sourceable bash/zsh completion script. If no
parameter is given, a guess will be made based on
$SHELL.
--name NAME The person to greet
--count COUNT Number of greetings
```
And it automatically generates shell completion scripts:
```shell-session
$ source <(./hello --completion)
$ hello --n
$ hello --name
```
## Features
- Simple to use for the programmer (specify as little as possible)
- Short and long options, with or without arguments
(e.g., `--force -p 2 3`)
- Positional arguments
- Hierarchical subcommands
- Autogenerated help, also for subcommands
- Autogenerated completion for bash/zsh
- Validation of argument values
- Option groups in help
- Command groups in help
- Clear distinction of `command`, `option`, and `argument`
- Opinionated, but _some_ customization support
### Stretch goals / later
- Mutually exclusive options
- Generation of man pages
### Non-goals
- Support for variations of the command line syntax above
(e.g., "+" or "/" instead of "-" for an option; or long options
prefixed with a single "-"; or using "=" to set an option).
- Support for abbreviated given long options
(e.g., "--read-full-rec" instead of "--read-full-records")
# Command line syntax
Three ways of describing the command line syntax supported by the
parser:
### Plain text
A _command_ has a set of _options_, followed by a list of _arguments_
or a _(sub)command_ (which in turn has options and arguments or
subcommands).
An option has a name, followed by a list of arguments.
### Pseudo-spec
```
COMMAND [OPTIONS] [--] [ARGUMENTS]
COMMAND [OPTIONS] [SUBCOMMAND [OPTIONS] [--] [ARGUMENTS]]
COMMAND [OPTIONS] [SUBCOMMAND [OPTIONS] [SUBSUBCOMMAND ...]]
```
### ABNF grammar of a command line
```
command-line = command [WSP parameters]
command = 1*CHAR
parameters = options [WSP (["--" WSP] arguments) / command-line]
options = option *(WSP option)
option = (short-opt / long-opt) [arguments]
short-opt = "-" CHAR
/ 2*CHAR ; multiple short opts at the same time
long-opt = "--" 1*CHAR
arguments = argument *(WSP argument)
argument = 1*CHAR
WSP = < whitespace >
CHAR = < printable unicode character >
```
## Options
There are two styles of options supported; short and long.
### Short options
In short-option style, each option letter is prefixed with a single
dash. If a short option takes an argument, the argument follows as
a separate command line word.
Any number of short options not taking arguments can be clustered
together after a single dash, e.g., `-vkp` is equivalent to `-v -k
-p`. Options that take arguments can appear at the end of such a
cluster, e.g., `-vkpf foo.txt`.
Note that the legacy style of passing an argument to a short option
without separating whitespace is not supported.
See https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02.
### Long options
In long-option style, each option begins with two dashes and has a
meaningful name, usually consisting of lower-case letters and dashes
(this is not enforced). If a long option takes an argument, the
argument follows as a separate command line word.
Note that abbreviated long options are not supported.
Also note that passing an argument to a long option by separating it
with an equal sign instead of whitespace is not supported.
# Examples
### Option with one argument
The command takes one option, specified as `-l` or `--label`. The
option requires a label (string) as argument.
```erlang
%% mycmd --label foo
parse(["--label", "foo"],
#{cmd => "mycmd",
opts => [#{name => label, short => $l, long => "label",
type => string}]}).
> {ok, {_Env, [], #{label => "foo"}, #{}}
```
When an `opt()` is specified, the default `type` is `string`, and the
default `name` is the long option as an atom, so the above can also be
written as:
```erlang
%% mycmd --label foo
parse(["--label", "foo"],
#{cmd => "mycmd",
opts => [#{short => $l, long => "label"}]}).
> {ok, {_Env, [], #{label => "foo"}, #{}}}
```
### Flag option
The command has one option with no argument.
In an `opt()`, if no `name` and no `long` fields are given, the `name`
defaults to the short option as an atom.
```erlang
%% mycmd -s
parse(["-s"],
#{cmd => "mycmd",
opts => [#{short => $s, type => flag}]}).
> {ok, {_Env, [], #{s => true}, #{}}}
```
We can also give the option a better name.
```erlang
%% mycmd
parse([],
#{cmd => "mycmd",
opts => [#{name => silent, short => $s, type => flag}]}).
> {ok, {_Env, [], #{silent => false}, #{}}}
```
### Boolean option
The command has two boolean options.
```erlang
%% mycmd -s
parse(["--no-implicit-names", "--enforce-checks"],
#{cmd => "mycmd",
opts => [#{long => "implicit-names", type => boolean},
#{long => "enforce-checks", type => boolean}]}).
> {ok, {_Env, [], #{enforce_checks => true, implicit_names => false}, #{}}}
```
### Count option
The command has an option `-v` which can be given multiple times to
increase the verbosity.
```erlang
%% mycmd -vvv -v
parse(["-vvv", "-v"],
#{cmd => "mycmd",
opts => [#{name => verbosity, short => $v, type => count}]}).
> {ok, {_Env, [], #{verbosity => 4}, #{}}}
```
### Option with one argument, and one argument to the command
```erlang
%% mycmd -l foo log.txt
parse(["-l", "foo", "log.txt"],
#{cmd => "mycmd",
opts => [#{name => label, short => $l, type => string}],
args => [#{name => filename}]}).
> {ok, {_Env, [], #{label => "foo"}, #{filename => "log.txt"}}}
```
### Option with two arguments
The command should have an option `--user` that takes a required
`name`, and an optional `id` argument.
For this, we cannot use the simple `type` field in the `opt()`, but
must instead use the more generic and flexible `args` field.
```erlang
%% mycmd --user joe 42
parse(["--user", "joe", "42"],
#{cmd => "mycmd",
opts => [#{name => user, long => "user",
args => [#{name => name, type => string},
#{name => id, type => integer, nargs => '?'}]}]}).
> {ok, {_Env, [], #{user => #{id => 42, name => "joe"}}, #{}}
```
### Command with variable number of arguments
The command should take a possibly empty list of files as arguments.
```erlang
%% mycmd foo.txt bar.txt
parse(["foo.txt", "bar.txt"],
#{cmd => "mycmd",
args => [#{name => filename, type => file, nargs => '*'}]}).
> {ok, {_Env, [], #{}, #{filename => ["foo.txt","bar.txt"]}}}
```
### Callbacks
The parser can either return the parse result, or automatically invoke
a callback associated with the selected command. This is especially
useful when there are multiple subcommands available.
If a callback is given, it must either have arity `1` or `2 + number of options +
number of arguments`. For example, the `list` command below has two
options and one argument, so the arity should be 5:
```erlang
%% mycmd -v list --foo bar.txt
parse(["-v", "list", "--foo", "bar.txt"],
#{cmd => "mycmd",
opts => [#{name => verbosity, short => $v, type => count}],
cmds => [#{cmd => "list",
opts => [#{long => "foo", type => boolean},
#{long => "bar", type => boolean}],
args => [#{name => filename}],
cb => fun do_list/5}]}).
do_list(_Env, CmdStack, Foo, Bar, Filename) ->
io:format("Stack = ~p\nFoo = ~p\nBar = ~p\nFilename = ~s\n",
[CmdStack, Foo, Bar, Filename]).
% results in:
CmdStack = [{mycmd, #{verbosity => 1}}]
Foo = true
Bar = false
Filename = "bar.txt"
```
Alternatively, the arity can be 1, in which case it will be invoked
as:
```erlang
do_list({Env,
[{mycmd,#{verbose => 1}}],
#{bar => false,foo => true},
#{filename => "bar.txt"}}).
```
### Command with subcommands
The following example implements a basic calculator, and demonstrates
a command with two levels of subcommands:
```erlang
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -pa ../ebin
%% eclip version of original example in https://github.com/max-au/argparse
-mode(compile).
main(Args) ->
case eclip:parse(Args, spec(), #{}) of
{done, _} ->
ok;
R ->
io:format("~p\n", [R])
end.
spec() ->
#{cmds =>
[#{cmd => "sum",
args => [#{name => term, type => int, nargs => '+'}],
cb => fun(_, _, Terms) -> lists:sum(Terms) end},
#{cmd => "div",
args => [#{name => denominator, type => float},
#{name => numerator, type => float}],
cb => fun(_, _, D, N) -> D / N end},
#{cmd => "math",
cmds =>
[#{cmd => "sin",
args => [#{name => num, type => float}],
cb => fun do_sin/3},
#{cmd => "cos",
args => [#{name => num, type => float}],
cb => fun do_cos/3},
#{cmd => "tan",
args => [#{name => num, type => float}],
cb => fun do_tan/3}]}]}.
do_sin(_, _, Num) ->
math:sin(Num).
do_cos(_, _, Num) ->
math:cos(Num).
do_tan(_, _, Num) ->
math:tan(Num).
```
The calculator provides a `sum` command that prints a sum of integer
numbers:
```shell-session
$ ./calc sum 1 2 3
6
```
Math subcommands provide trigonometric functions:
```shell-session
$ ./calc math cos 1.2
0.3623577544766736
$ ./calc math sin 1
0.8414709848078965
```
### Shell completion
We can enable completion for the previous example command.
In Bash:
```shell-session
$ source <(./calc --completion)
$ ./calc
div sum --help
math --completion -h
$ ./calc m
$ ./calc math
cos sin tan --help -h
```
In Zsh:
```shell-session
% source <(./calc --completion zsh)
% ./calc
div sum --help
math --completion -h
$ ./calc m
$ ./calc math
cos sin tan --help -h
```
# Related work
- getopt (various implementations exist)
- very simple, no support for command hierarchies
- no shell completion
- erlang-cli
- no support for command hierarchies
- no shell completion
- argparse
- doesn't handle subcommands as truly separate commands,
e.g. doesn't allow `cmd -f subcmd -f`;
- doesn't handle option / command groups;
- no shell completion
近期下载者:
相关文件:
收藏者: