Repository: certbot/certbot

Issue:
"run" should be the default, not a specific verb
 @kuba's <tt>cli</tt> branch got the CLI & CLI help closer to where it needs to be, but it added a "run" verb which doesn't really make sense.  "Run" should clearly be the default behaviour of the agent.

Kuba are you up for taking this?



Discussion:
I'll look into that after 27th of May

@kuba nudge

Sorry. CLI work is blocked on #440 and #469. I also picked some higher priority tasks since then.

Nevertheless, I don't think that having optional verb (`run`) is:
1. Possible to implement with `argparse`/`ConfigArgParse` (without nasty hackery).
2. Easy to implement or sensible at all. For instance, how would you handle arguments specific to `run` subparser, when the verb is absent?

Do you know any tool that behaves this way? I can't think of any.

Maybe you want optional `--action {run,auth,...}` instead (or indeed, mutually exclusive `--revoke`, `--run`, ..., i.e. the previous design)? However, this doesn't give us a clear way of specifying arguments specific to given action... c.f. http://stackoverflow.com/a/6394994 ("If the command is optional, then this makes the command an option.") or http://stackoverflow.com/a/5176820 ("I don't think you want to use subcommands. Subcommands are used when the available options and positional arguments change depending on which subcommand is chosen.") and other quick google results

Maybe we should have separate CLI tools? Or maybe one tool for `run`, and another for all other actions?


Resolving patch:
diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py
--- a/letsencrypt/cli.py
+++ b/letsencrypt/cli.py
@@ -361,7 +361,6 @@ class HelpfulArgumentParser(object):
 
     """
     def __init__(self, args, plugins):
-        self.args = args
         plugin_names = [name for name, _p in plugins.iteritems()]
         self.help_topics = HELP_TOPICS + plugin_names + [None]
         self.parser = configargparse.ArgParser(
@@ -374,6 +373,7 @@ def __init__(self, args, plugins):
         self.parser._add_config_file_help = False  # pylint: disable=protected-access
         self.silent_parser = SilentParser(self.parser)
 
+        self.args = self.preprocess_args(args)
         help1 = self.prescan_for_flag("-h", self.help_topics)
         help2 = self.prescan_for_flag("--help", self.help_topics)
         assert max(True, "a") == "a", "Gravity changed direction"
@@ -386,6 +386,17 @@ def __init__(self, args, plugins):
         #print self.visible_topics
         self.groups = {}  # elements are added by .add_group()
 
+    def preprocess_args(self, args):
+        """Work around some limitations in argparse.
+
+        Currently, add the default verb "run" as a default.
+        """
+
+        for token in args:
+            if token in VERBS:
+                return args
+        return ["run"] + args
+
     def prescan_for_flag(self, flag, possible_arguments):
         """Checks cli input for flags.
 
@@ -550,7 +561,12 @@ def create_parser(plugins, args):
 
     _create_subparsers(helpful)
 
-    return helpful.parser
+    return helpful.parser, helpful.args
+
+# For now unfortunately this constant just needs to match the code below;
+# there isn't an elegant way to autogenerate it in time.
+VERBS = ["run", "auth", "install", "revoke", "rollback", "config_changes",\
+         "plugins"]
 
 
 def _create_subparsers(helpful):
@@ -738,7 +754,8 @@ def main(cli_args=sys.argv[1:]):
 
     # note: arg parser internally handles --help (and exits afterwards)
     plugins = plugins_disco.PluginsRegistry.find_all()
-    args = create_parser(plugins, cli_args).parse_args(cli_args)
+    parser, tweaked_cli_args = create_parser(plugins, cli_args)
+    args = parser.parse_args(tweaked_cli_args)
     config = configuration.NamespaceConfig(args)
 
     # Setup logging ASAP, otherwise "No handlers could be found for


Test patch: