From f947faf39d03696ba2804c4c5ba152770a78d018 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Tue, 9 Mar 2021 18:04:55 +0100 Subject: [PATCH] pyln: Plugins can be nice if invoked from CLI too I had way too much fun with this and got a bit carried away with the letter writing. The idea is to be helpful when users start the plugin from the command line, rather than run it under the control of lightningd. We also print detailed information about the user-visible things such as the methods and options exposed by the plugin. Changelog-Added: pyln: Plugins that are run from the command line print helpful information on how to configure c-lightning to include them and print metadata about what RPC methods and options are exposed. Suggested-by: Rusty Russell <@rustyrussell> --- contrib/pyln-client/pyln/client/plugin.py | 94 +++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/contrib/pyln-client/pyln/client/plugin.py b/contrib/pyln-client/pyln/client/plugin.py index f86c44ef85e1..5d3095e7e4bf 100644 --- a/contrib/pyln-client/pyln/client/plugin.py +++ b/contrib/pyln-client/pyln/client/plugin.py @@ -724,7 +724,101 @@ def _multi_dispatch(self, msgs: List[bytes]) -> bytes: return msgs[-1] + def print_usage(self): + import textwrap + + executable = os.path.abspath(sys.argv[0]) + overview = textwrap.dedent(""" + Hi, it looks like you're trying to run a plugin from the + command line. Plugins are usually started and controlled by + lightningd, which allows you to simply specify which plugins + you'd like to run using the --plugin command line option when + starting lightningd. The following is an example of how that'd + look: + + $ lightningd --plugin={executable} + + Since we're here however let me tell you about this plugin. + """).format(executable=executable) + + methods_header = textwrap.dedent(""" + + RPC methods + =========== + + Plugins may provide additional RPC methods that you can simply + call as if they were built-in methods from lightningd + itself. To call them just use lightning-cli or any other + frontend. The following methods are defined by this plugin: + """) + + parts = [overview] + + method_tpl = textwrap.dedent(""" + {name} + {doc} + """) + + for method in self.methods.values(): + if method.name in ['init', 'getmanifest']: + # Skip internal methods provided by all plugins + continue + + if method.mtype != MethodType.RPCMETHOD: + # Don't include non-rpc-methods in the rpc-method + # section + continue + + if methods_header is not None: + # Listen carefully, I shall say this only once :-) + parts.append(methods_header) + methods_header = None + + doc = method.long_desc if method.long_desc is not None else "No documentation found" + parts.append(method_tpl.format( + name=method.name, + doc=textwrap.indent(doc, prefix=" ") + )) + + options_header = textwrap.dedent(""" + Command line options + ==================== + + This plugin exposes the following command line options. They + can be specified just like any other you might gice lightning + at startup. The following options are exposed by this plugin: + """) + + option_tpl = textwrap.dedent(""" + --{name}={typ} (default: {default} + {doc} + """) + for opt in self.options.values(): + if options_header is not None: + parts.append(options_header) + options_header = None + + doc = textwrap.indent(opt['description'], prefix=" ") + + if opt['multi']: + doc += "\n\n This option can be specified multiple times" + + parts.append(option_tpl.format( + name=opt['name'], + doc=doc, + default=opt['default'], + typ=opt['type'], + )) + + sys.stdout.write("".join(parts)) + sys.stdout.write("\n") + def run(self) -> None: + # If we are not running inside lightningd we'll print usage + # and some information about the plugin. + if os.environ.get('LIGHTNINGD_PLUGIN', None) != '1': + return self.print_usage() + partial = b"" for l in self.stdin.buffer: partial += l