plugin: Register plugin cli options

We also make `--help` a non-early arg so it allows for the plugins to
register their options before printing the help message. The options
themselves are stored in a separate struct inbetween them being
registered and them being forwarded to the plugin. Currently only
supports string options.

Signed-off-by: Christian Decker <@cdecker>
This commit is contained in:
Christian Decker 2018-11-02 01:19:47 +01:00
parent 10338983a6
commit 55d6d6b0e7
3 changed files with 116 additions and 3 deletions

View File

@ -19,7 +19,10 @@ def json_hello(request):
def json_init(request):
return {
"options": [
{"name": "greeting", "type": "string", "default": "World"},
{"name": "greeting",
"type": "string",
"default": "World",
"description": "What name should I call you?"},
],
"rpcmethods": [
{

View File

@ -724,7 +724,7 @@ void register_opts(struct lightningd *ld)
{
opt_set_alloc(opt_allocfn, tal_reallocfn, tal_freefn);
opt_register_early_noarg("--help|-h", opt_lightningd_usage, ld,
opt_register_noarg("--help|-h", opt_lightningd_usage, ld,
"Print this message.");
opt_register_early_noarg("--test-daemons-only",
test_subdaemons_and_exit,

View File

@ -3,6 +3,7 @@
#include <ccan/intmap/intmap.h>
#include <ccan/io/io.h>
#include <ccan/list/list.h>
#include <ccan/opt/opt.h>
#include <ccan/pipecmd/pipecmd.h>
#include <ccan/tal/str/str.h>
#include <lightningd/json.h>
@ -25,17 +26,22 @@ struct plugin {
const char *outbuf;
struct log *log;
/* List of options that this plugin registered */
struct list_head plugin_opts;
};
struct plugin_request {
u64 id;
struct plugin *plugin;
/* Method to be called */
const char *method;
/* JSON encoded params, either a dict or an array */
const char *json_params;
const char *response;
const jsmntok_t *resulttok, *errortok;
const jsmntok_t *resulttok, *errortok, *toks;
/* The response handler to be called on success or error */
void (*cb)(const struct plugin_request *, void *);
@ -56,6 +62,15 @@ struct json_output {
const char *json;
};
/* Simple storage for plugin options inbetween registering them on the
* command line and passing them off to the plugin */
struct plugin_opt {
struct list_node list;
const char *name;
const char *description;
char *value;
};
struct plugins *plugins_new(const tal_t *ctx, struct log *log){
struct plugins *p;
p = tal(ctx, struct plugins);
@ -74,6 +89,7 @@ void plugin_register(struct plugins *plugins, const char* path TAKES)
p->plugins = plugins;
p->cmd = tal_strdup(p, path);
p->log = plugins->log;
list_head_init(&p->plugin_opts);
}
/**
@ -149,6 +165,7 @@ static bool plugin_read_json_one(struct plugin *plugin)
request->response = plugin->buffer;
request->errortok = errortok;
request->resulttok = resulttok;
request->toks = toks;
request->cb(request, request->arg);
/* Move this object out of the buffer */
@ -217,6 +234,7 @@ static void plugin_request_send_(
req->json_params = tal_strdup(req, params);
req->cb = cb;
req->arg = arg;
req->plugin = plugin;
/* Add to map so we can find it later when routing the response */
uintmap_add(&plugin->plugins->pending_requests, req->id, req);
@ -261,6 +279,90 @@ static struct io_plan *plugin_conn_init(struct io_conn *conn,
}
}
/* Callback called when parsing options. It just stores the value in
* the plugin_opt */
static char *plugin_opt_set(const char *arg, struct plugin_opt *popt)
{
popt->value = tal_strdup(popt, arg);
return NULL;
}
/* Add a single plugin option to the plugin as well as registering it with the
* command line options. */
static bool plugin_opt_add(struct plugin *plugin, const char *buffer,
const jsmntok_t *opt)
{
const jsmntok_t *nametok, *typetok, *defaulttok, *desctok;
struct plugin_opt *popt;
nametok = json_get_member(buffer, opt, "name");
typetok = json_get_member(buffer, opt, "type");
desctok = json_get_member(buffer, opt, "description");
defaulttok = json_get_member(buffer, opt, "default");
if (!typetok || !nametok || !desctok) {
plugin_kill(plugin,
"An option is missing either \"name\", \"description\" or \"type\"");
return false;
}
/* FIXME(cdecker) Support numeric and boolean options as well */
if (!json_tok_streq(buffer, typetok, "string")) {
plugin_kill(plugin,
"Only \"string\" options currently supported");
return false;
}
popt = tal(plugin, struct plugin_opt);
popt->name = tal_fmt(plugin, "--%.*s", nametok->end - nametok->start,
buffer + nametok->start);
popt->value = NULL;
if (defaulttok) {
popt->value = tal_strndup(plugin, buffer + defaulttok->start,
defaulttok->end - defaulttok->start);
popt->description = tal_fmt(
plugin, "%.*s (default: %s)", desctok->end - desctok->start,
buffer + desctok->start, popt->value);
} else {
popt->description = tal_strndup(plugin, buffer + desctok->start,
desctok->end - desctok->start);
}
list_add_tail(&plugin->plugin_opts, &popt->list);
opt_register_arg(popt->name, plugin_opt_set, NULL, popt,
popt->description);
return true;
}
/* Iterate through the options in the init response, and add them to
* the plugin and the command line options */
static bool plugin_opts_add(const struct plugin_request *req)
{
const char *buffer = req->plugin->buffer;
const jsmntok_t *cur, *options;
/* This is the parent for all elements in the "options" array */
int optpos;
options =
json_get_member(req->plugin->buffer, req->resulttok, "options");
if (!options)
return false;
optpos = options - req->toks;
if (options->type != JSMN_ARRAY) {
plugin_kill(req->plugin, "\"result.options\" is not an array");
return false;
}
for (cur = options + 1; cur->parent == optpos; cur = json_next(cur))
if (!plugin_opt_add(req->plugin, buffer, cur))
return false;
return true;
}
/**
* Callback for the plugin_init request.
*/
@ -270,6 +372,14 @@ static void plugin_init_cb(const struct plugin_request *req, struct plugin *plug
plugin->plugins->pending_init--;
if (plugin->plugins->pending_init == 0)
io_break(plugin->plugins);
if (req->resulttok->type != JSMN_OBJECT) {
plugin_kill(plugin, "\"init\" response is not an object");
return;
}
if (!plugin_opts_add(req))
return;
}
void plugins_init(struct plugins *plugins)