diff --git a/contrib/helloworld-plugin/main.py b/contrib/helloworld-plugin/main.py index fd5eaa042..290b6981a 100755 --- a/contrib/helloworld-plugin/main.py +++ b/contrib/helloworld-plugin/main.py @@ -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": [ { diff --git a/lightningd/options.c b/lightningd/options.c index b5391be5f..d70a9a810 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -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, diff --git a/lightningd/plugin.c b/lightningd/plugin.c index d009132b4..8de88edae 100644 --- a/lightningd/plugin.c +++ b/lightningd/plugin.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -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)