scripts: improve rendering of synopsis/usage
authorBrian Warner <warner@lothar.com>
Tue, 26 May 2015 18:29:49 +0000 (11:29 -0700)
committerBrian Warner <warner@lothar.com>
Tue, 26 May 2015 18:29:49 +0000 (11:29 -0700)
Subcommands "--help" is now rendered as:

```
 tahoe [global-options] COMMAND [options] ARGS
 (use 'tahoe --help' to view global options)
 USAGE (flags/options)
 DESCRIPTION
 DESCRIPTION_UNWRAPPED
```

The new .description and .description_unwrapped fields allow
commands (subclasses of twisted.python.usage.Usage) better control over
how their explanations are rendered: the old .longdesc field was wrapped
unpleasantly.

src/allmydata/scripts/common.py
src/allmydata/scripts/runner.py

index 2894d3dd77723c50d219c62e2dda4b691c16ef96..3ebb3e8ca807650478ca56e2ad38bbba7a30c58a 100644 (file)
@@ -1,5 +1,5 @@
 
-import os, sys, urllib
+import os, sys, urllib, textwrap
 import codecs
 from twisted.python import usage
 from allmydata.util.assertutil import precondition
@@ -10,6 +10,14 @@ from allmydata.scripts.default_nodedir import _default_nodedir
 def get_default_nodedir():
     return _default_nodedir
 
+def wrap_paragraphs(text, width):
+    # like textwrap.wrap(), but preserve paragraphs (delimited by double
+    # newlines) and leading whitespace, and remove internal whitespace.
+    text = textwrap.dedent(text)
+    if text.startswith("\n"):
+        text = text[1:]
+    return "\n\n".join([textwrap.fill(paragraph, width=width)
+                        for paragraph in text.split("\n\n")])
 
 class BaseOptions(usage.Options):
     def __init__(self):
@@ -22,6 +30,24 @@ class BaseOptions(usage.Options):
     def opt_version(self):
         raise usage.UsageError("--version not allowed on subcommands")
 
+    description = None
+    description_unwrapped = None
+
+    def __str__(self):
+        width = int(os.environ.get('COLUMNS', '80'))
+        s = (self.getSynopsis() + '\n' +
+             "(use 'tahoe --help' to view global options)\n" +
+             '\n' +
+             self.getUsage())
+        if self.description:
+            s += '\n' + wrap_paragraphs(self.description, width) + '\n'
+        if self.description_unwrapped:
+            du = textwrap.dedent(self.description_unwrapped)
+            if du.startswith("\n"):
+                du = du[1:]
+            s += '\n' + du + '\n'
+        return s
+
 class BasedirOptions(BaseOptions):
     default_nodedir = _default_nodedir
 
index f295a98021a8de0b70da40b94ccc49df6911ce6a..4767e59e067138d1ae6436249c980ae8c452b3f1 100644 (file)
@@ -66,11 +66,15 @@ class Options(usage.Options):
         print >>self.stdout, allmydata.get_package_versions_string(show_paths=True, debug=True)
         self.no_command_needed = True
 
-    def getSynopsis(self):
-        return "\nUsage: tahoe [global-opts] <command> [command-options]"
+    def __str__(self):
+        return ("\nUsage: tahoe [global-options] <command> [command-options]\n"
+                + self.getUsage())
+
+    synopsis = "\nUsage: tahoe [global-opts]" # used only for subcommands
 
     def getUsage(self, **kwargs):
         t = usage.Options.getUsage(self, **kwargs)
+        t = t.replace("Options:", "\nGlobal options:", 1)
         return t + "\nPlease run 'tahoe <command> --help' for more details on each command.\n"
 
     def postOptions(self):