___ ___ _ __ _ __
/ __|/ _ \ '__| '_ \
\__ \ __/ | | |_) |
|___/\___|_| | .__/
|_|
Serp is a library for creating powerful modern CLI applications in C3. Inspired by cobra.
Serp provides:
- Easy subcommand-based CLIs:
app serve,app fetch, etc. - Fully POSIX-compliant flags (including short and long versions)
- Nested subcommands
- Automatic help generation for commands and flags
In the works:
- Automatic help flag recognition of
-h,--help(TODO) - Command aliases so you can change things without breaking them
Serp is built on a structure of commands and flags - heavely inspired by cobra.
Commands represent actions and flags are modifiers for those actions.
Well-designed CLI apps follow a certain usage pattern; the commands should
read like sentences: app verb noun or app command argument --flags.
Command is the central point of the application. A command can have children commands an optionally run an action.
Create a command with command::new:
import serp::command;
Command *root = command::new(
use: "app",
run: fn void!(Command *c, String[] args, Option[] opts) {
io::printn("hello app");
}
);
To add subcommands, use the Command.add_command(Command*) function:
root.add_command(
command::new(
use: "init",
run: fn void!(Command *c, String[] args, Option[] opts) {
io::printn("app init");
}
)
);
A flag is a way to modify the behavior of a command. POSIX-compliant flags are supported.
Flags can be defined per command with Command.add_flag(Flag):
root.add_flag({.shortopt = 'p', .longopt = 'path', .optarg = true});The above example will set a flag on the root command that expects an optional
argument. The flag can then be set with -p or --path on the command line.
The parameter to the add_flag function is a struct Flag that
contains the following fields:
struct Flag {
char shortopt; // short option name, e.g. 'p'
String longopt; // long option name, e.g. "path"
bool optarg; // set true if flag has an optional argument
String desc; // describe the purpose of the flag
}
module serp_app;
import serp;
import std::io;
fn void? root_cmd(Command *c, String[] args, Option[] opts)
=> show_args_and_opts("root", c, args, opts);
fn void? init_cmd(Command *c, String[] args, Option[] opts)
=> show_args_and_opts("init", c, args, opts);
fn void? add_cmd(Command *c, String[] args, Option[] opts)
=> show_args_and_opts("add", c, args, opts);
fn void? show_args_and_opts(String cmd, Command *c, String[] args, Option[] opts)
{
io::printfn("Command: %s", cmd);
io::printn("--------");
io::printfn("Positional arguments:\n\t%s", args);
if (opts.len)
{
io::printfn("\nParsed flags:");
foreach (opt : opts) io::printf("\t%s", opt);
}
io::printf("\nUsage message:\n");
io::printf("--------------");
return c.usage();
}
fn void main(String[] args) => @assert_leak()
{
Command *root = command::new(use: "app [commands] [flags]", run: &root_cmd)
.describe("describe your command here")
.add_command(
command::new(use: "init [flags]", run: &init_cmd)
.add_flag({.shortopt = 'p', .desc = "path", .optarg = true})
.add_flag({.shortopt = 'n', .desc = "name", .optarg = true})
.describe("'init' your app with this command")
)
.add_command(
command::new(use: "add [flags] <name>", run: &add_cmd)
.add_flag({.shortopt = 'f', .longopt = "force"})
.describe("'add' something to your app")
);
defer root.free();
root.execute(args)!!;
}See source.
Add the path to the serp.c3l folder to dependency-search-paths and
serp to dependencies in your project.json file:
{
"dependency-search-paths": ["lib", "<path_to_serp.c3l_folder>"],
"dependencies": ["serp"]
}