https://github.com/stwind/ecli
Erlang Command Line Toolkit
https://github.com/stwind/ecli
Last synced: about 1 year ago
JSON representation
Erlang Command Line Toolkit
- Host: GitHub
- URL: https://github.com/stwind/ecli
- Owner: stwind
- Created: 2013-09-15T02:13:49.000Z (over 12 years ago)
- Default Branch: master
- Last Pushed: 2013-09-29T02:16:56.000Z (over 12 years ago)
- Last Synced: 2025-02-14T06:36:18.335Z (over 1 year ago)
- Language: Erlang
- Size: 254 KB
- Stars: 2
- Watchers: 3
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
Erlang Command Line Toolkit
===========================
With [Rebar](https://github.com/rebar/rebar/wiki/Rebar-commands)'s `escriptize` feature, you can easily build a escript out of a typical OTP application.
**Ecli** is a library that help you to build more powerful escriptized command-line tool.
## Usage
Just add Ecli dep to you `rebar.config`:
```erlang
{deps, [
{ecli, ".*",
{git, "https://github.com/stwind/ecli.git", {branch, "develop"}}}
]}.
```
And include `ecli` and `getopt` in your escript:
```erlang
{escript_incl_apps, [ecli, getopt]}.
```
## Features
* [Subcommand](#subcommand)
* [Output Formatting](#outputting)
### Subcommand
Instead of having a bunch of standalone escript file, it is always helpful to have a unified command line interface to a collection of commands. Just like [npm](https://npmjs.org/) or [vagrant](http://vagrantup.com). Ecli makes this easy.
#### Requirements
Ecli supposes that your subcommnds are something like this:
```
SCRIPT [command …] [] []
```
That is a command that has a script name `SCRIPT` followed by one or more `command`, then argument `arg`, and finally some `option`s. The order of `comamnd`, `arg` and `option` can not be arbitrary, in order to make parsing more easier.
#### Usage
Just call `ecli:start/2` in the escript entry function `main/1`, providing the command-line argumets `Args` and a subcommand spec.
E.g.
```erlang
-module(ectl).
-export([main/1]).
main(Args) ->
ecli:start(Args, spec()).
spec() ->
%% describe below.
```
#### Command Specification
Take [ectl](https://github.com/stwind/ectl) for example, given the following commands:
```
ectl redbug [-c] [-m] [-t] [-p]
ectl ping [-c]
```
you should have a spec like this:
```erlang
[
{script, "ectl"},
{vsn, "0.1.0"},
{config_file, "ectl.config"},
{commands,
[
{"ping", [node], ectl_ping,
[
{cookie, $c, "cookie", string, "Erlang cookie to use"}.
]},
{"redbug", [node, trace_pattern], ectl_redbug,
[
{cookie, $c, "cookie", string, "Erlang cookie to use"}.
{time, $t, "time", {integer, 15000}, "stop trace after this many ms"},
{msgs, $m, "msgs", {integer, 10}, "stop trace after this many msgs"},
{proc, $p, "proc", {string, "all"}, "Erlang process all|pid()|atom(RegName)"}
]}
]}
].
```
The elements are:
* `script`: name of your script, here it is `ectl`.
* `vsn`: version of your script, which will be shown when ran with `--version`
* `config_file`: a file from with to read options, so you don't have to provide them on command-line every call. Options in config file will always be override by the command-line ones.
* `commands`: command options that Ecli will use to decide what function to call for a command invocation.
Now let's look closer to the `commands`, here is the spec of command option:
```erlang
-type spec() :: [option()].
-type option() ::
{script, string()} |
{vsn, string()} |
{config_file, string()} |
{commands, [command()]}.
-type command() :: cmd_collection() | cmd_spec().
-type cmd_collection() :: {cmd_name(), [command()]}.
-type cmd_name() :: string().
-type cmd_spec() :: {cmd_name(), [cmd_arg()], cmd_fun(), [cmd_opt()]}.
-type cmd_arg() :: atom() | '...'.
-type cmd_fun() :: {module(), atom()} | module().
-type cmd_opt() :: getopt:option_spec().
```
Take the `ectl ping` command for example:
```erlang
{"ping", [node], ectl_ping,
[
{cookie, $c, "cookie", string, "Erlang cookie to use"}.
]}
```
The tuple has four elements:
* `"ping"`: the name of the command
* `[node]`: command arguments. Here it is exactly one, and the value will be bound to `node`. You can later find it with `ecli:opt/2`.
* `ectl_ping`: the module and method to execute, by default it will call `module:run/1`, here it is the same to provide the value as `{ectl_ping, run}`, Ecli will call `ectl_ping:run/1` for this call.
* `options`: the options for this command, which has the same format as [getopt](https://github.com/jcomellas/getopt)
To summarize:
* running `ectl ping my_node@localhost` will call `ectl_ping:/run1`.
* running `ectl redbug my_node@localhost "erlang:memory() -> return"` will call `ectl_redbug:run/1`.
* running `ectl dummy` will show usage info of `ectl`, since it dosen't match any command.
#### Value Binding
The function to handle a command call takes on argument, with it you can query the command `option`s and `arg`s by using `ectl:binding/2` and `ectl:opt/2`.
E.g. calling:
```bash
ectl ping my_node@localhost -c my_cookie
```
In the handler function, values can be queried like this:
```erlang
run(Opt) ->
"my_node@localhost" = ecli:binding(node, Opt),
"my_cookie" = ecli:opt(cookie, Opt).
```
#### Command Usage
If a command run match a subcommand, but dosen't match its argument, the usage of this subcommand will be shown. This is the same as providing a `-h` options.
Running `ectl ping` will shows:
```bash
Usage: ectl ping [...] [options]
-c, --cookie Erlang cookie to use
```
Running `ectl redbug my_node@localhost` will shows:
```bash
Usage: ectl redbug [options]
-c, --cookie Erlang cookie to use
-t, --time stop trace after this many ms [default: 15000]
-m, --msgs stop trace after this many msgs [default: 10]
-p, --proc Erlang process all|pid()|atom(RegName) [default: all]
```
Running `ectl dummy` will shows:
```bash
Usage: ectl [] [options]
-h, --help Print this help.
-v, --version Print the version and exit.
Available subcommands:
redbug
ping
For help on any individual command run `ectl COMMAND -h`
```
And finally running `ectl -v` will shows the script version provided:
```bash
ectl 0.1.0
```
### Outputting
Most of times it would be nice to display the results in table format for better visualizaiton, or json format which could be consumed by programs like [jq](http://stedolan.github.io/jq/) at another end of pipe.
Ecli has builtin support for `table` format output, you can easily achieve this by adding an `output` option to your command.
First include the `ecli.hrl` lib to your module:
```erlang
-include_lib("ecli/include/ecli.hrl").
```
then add `?OTP_OUTPUT` to your subcommand spec, here is example from `ectl ping`:
```erlang
{"ping", [node, '...'], ectl_ping,
[
{cookie, $c, "cookie", string, "Erlang cookie to use"},
?OPT_OUTPUT
]}
```
Now your command will have a `output` option:
```bash
$ ./ectl ping
Usage: ectl ping [...] [options]
-c, --cookie Erlang cookie to use
-o, --output output format: table|json|plain [default: plain]
```
In your command handler function, use the `ecli:output/3` to output the results, here again is example from `ectl ping`:
```bash
$ ./ectl ping my_node@127.0.0.1 -c my_cookie -o table
┌──────────────────────┬────────┐
│ node │ result │
├──────────────────────┼────────┤
│ yunio_core@127.0.0.1 │ pang │
└──────────────────────┴────────┘
```