]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blobdiff - src/allmydata/scripts/runner.py
scripts: improve rendering of synopsis/usage
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / scripts / runner.py
index 0da38bc18409d5245f5563772ff5ad1f891bccb5..4767e59e067138d1ae6436249c980ae8c452b3f1 100644 (file)
-#! /usr/bin/env python
 
-import os, sys, signal, time
+import os, sys
+from cStringIO import StringIO
+
 from twisted.python import usage
 
-class StartOptions(usage.Options):
-    optParameters = [
-        ["basedir", "C", ".", "which directory to start the node in"],
-        ]
+from allmydata.scripts.common import get_default_nodedir
+from allmydata.scripts import debug, create_node, startstop_node, cli, keygen, stats_gatherer, admin
+from allmydata.util.encodingutil import quote_output, quote_local_unicode_path, get_io_encoding
 
-class StopOptions(usage.Options):
-    optParameters = [
-        ["basedir", "C", ".", "which directory to stop the node in"],
-        ]
+def GROUP(s):
+    # Usage.parseOptions compares argv[1] against command[0], so it will
+    # effectively ignore any "subcommand" that starts with a newline. We use
+    # these to insert section headers into the --help output.
+    return [("\n(%s)" % s, None, None, None)]
 
-class RestartOptions(usage.Options):
-    optParameters = [
-        ["basedir", "C", ".", "which directory to restart the node in"],
-        ]
 
-class CreateClientOptions(usage.Options):
-    optParameters = [
-        ["basedir", "C", None, "which directory to create the client in"],
-        ]
+_default_nodedir = get_default_nodedir()
 
-    def parseArgs(self, *args):
-        if len(args) > 0:
-            self['basedir'] = args[0]
-        if len(args) > 1:
-            raise usage.UsageError("I wasn't expecting so many arguments")
+NODEDIR_HELP = ("Specify which Tahoe node directory should be used. The "
+                "directory should either contain a full Tahoe node, or a "
+                "file named node.url that points to some other Tahoe node. "
+                "It should also contain a file named '"
+                + os.path.join('private', 'aliases') +
+                "' which contains the mapping from alias name to root "
+                "dirnode URI.")
+if _default_nodedir:
+    NODEDIR_HELP += " [default for most commands: " + quote_local_unicode_path(_default_nodedir) + "]"
 
-    def postOptions(self):
-        if self['basedir'] is None:
-            raise usage.UsageError("<basedir> parameter is required")
-        self['basedir'] = os.path.abspath(self['basedir'])
-
-class CreateQueenOptions(usage.Options):
+class Options(usage.Options):
+    # unit tests can override these to point at StringIO instances
+    stdin = sys.stdin
+    stdout = sys.stdout
+    stderr = sys.stderr
+
+    synopsis = "\nUsage:  tahoe <command> [command options]"
+    subCommands = ( GROUP("Administration")
+                    +   create_node.subCommands
+                    +   keygen.subCommands
+                    +   stats_gatherer.subCommands
+                    +   admin.subCommands
+                    + GROUP("Controlling a node")
+                    +   startstop_node.subCommands
+                    + GROUP("Debugging")
+                    +   debug.subCommands
+                    + GROUP("Using the filesystem")
+                    +   cli.subCommands
+                    )
+
+    optFlags = [
+        ["quiet", "q", "Operate silently."],
+        ["version", "V", "Display version numbers."],
+        ["version-and-path", None, "Display version numbers and paths to their locations."],
+    ]
     optParameters = [
-        ["basedir", "C", None, "which directory to create the queen in"],
-        ]
+        ["node-directory", "d", None, NODEDIR_HELP],
+    ]
 
-    def parseArgs(self, *args):
-        if len(args) > 0:
-            self['basedir'] = args[0]
-        if len(args) > 1:
-            raise usage.UsageError("I wasn't expecting so many arguments")
+    def opt_version(self):
+        import allmydata
+        print >>self.stdout, allmydata.get_package_versions_string(debug=True)
+        self.no_command_needed = True
 
-    def postOptions(self):
-        if self['basedir'] is None:
-            raise usage.UsageError("<basedir> parameter is required")
-        self['basedir'] = os.path.abspath(self['basedir'])
+    def opt_version_and_path(self):
+        import allmydata
+        print >>self.stdout, allmydata.get_package_versions_string(show_paths=True, debug=True)
+        self.no_command_needed = True
 
-client_tac = """
-# -*- python -*-
+    def __str__(self):
+        return ("\nUsage: tahoe [global-options] <command> [command-options]\n"
+                + self.getUsage())
 
-from allmydata import client
-from twisted.application import service
+    synopsis = "\nUsage: tahoe [global-opts]" # used only for subcommands
 
-c = client.Client()
+    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"
 
-application = service.Application("allmydata_client")
-c.setServiceParent(application)
-"""
-
-queen_tac = """
-# -*- python -*-
+    def postOptions(self):
+        if not hasattr(self, 'subOptions'):
+            if not hasattr(self, 'no_command_needed'):
+                raise usage.UsageError("must specify a command")
+            sys.exit(0)
 
-from allmydata import queen
-from twisted.application import service
 
-c = queen.Queen()
+create_dispatch = {}
+for module in (create_node, keygen, stats_gatherer):
+    create_dispatch.update(module.dispatch)
 
-application = service.Application("allmydata_queen")
-c.setServiceParent(application)
-"""
+def runner(argv,
+           run_by_human=True,
+           stdin=None, stdout=None, stderr=None,
+           install_node_control=True, additional_commands=None):
 
-class Options(usage.Options):
-    synopsis = "Usage:  allmydata <command> [command options]"
+    stdin  = stdin  or sys.stdin
+    stdout = stdout or sys.stdout
+    stderr = stderr or sys.stderr
 
-    subCommands = [
-        ["create-client", None, CreateClientOptions, "Create a client node."],
-        ["create-queen", None, CreateQueenOptions, "Create a queen node."],
-        ["start", None, StartOptions, "Start a node (of any type)."],
-        ["stop", None, StopOptions, "Stop a node."],
-        ["restart", None, RestartOptions, "Restart a node."],
-        ]
+    config = Options()
+    if install_node_control:
+        config.subCommands.extend(startstop_node.subCommands)
 
-    def postOptions(self):
-        if not hasattr(self, 'subOptions'):
-            raise usage.UsageError("must specify a command")
+    ac_dispatch = {}
+    if additional_commands:
+        for ac in additional_commands:
+            config.subCommands.extend(ac.subCommands)
+            ac_dispatch.update(ac.dispatch)
 
-def run():
-    config = Options()
     try:
-        config.parseOptions()
+        config.parseOptions(argv)
     except usage.error, e:
-        print "%s:  %s" % (sys.argv[0], e)
-        print
-        c = getattr(config, 'subOptions', config)
-        print str(c)
-        sys.exit(1)
+        if not run_by_human:
+            raise
+        c = config
+        while hasattr(c, 'subOptions'):
+            c = c.subOptions
+        print >>stdout, str(c)
+        try:
+            msg = e.args[0].decode(get_io_encoding())
+        except Exception:
+            msg = repr(e)
+        print >>stdout, "%s:  %s\n" % (sys.argv[0], quote_output(msg, quotemarks=False))
+        return 1
 
     command = config.subCommand
     so = config.subOptions
 
-    if command == "create-client":
-        rc = create_client(so)
-    elif command == "create-queen":
-        rc = create_queen(so)
-    elif command == "start":
-        rc = start(so)
-    elif command == "stop":
-        rc = stop(so)
-    elif command == "restart":
-        rc = restart(so)
-    rc = rc or 0
-    sys.exit(rc)
-
-def create_client(config):
-    basedir = config['basedir']
-    if os.path.exists(basedir):
-        if os.listdir(basedir):
-            print "The base directory already exists: %s" % basedir
-            print "To avoid clobbering anything, I am going to quit now"
-            print "Please use a different directory, or delete this one"
-            return -1
-        # we're willing to use an empty directory
+    if config['quiet']:
+        stdout = StringIO()
+
+    so.stdout = stdout
+    so.stderr = stderr
+    so.stdin = stdin
+
+    if command in create_dispatch:
+        rc = create_dispatch[command](so, stdout, stderr)
+    elif command in startstop_node.dispatch:
+        rc = startstop_node.dispatch[command](so, stdout, stderr)
+    elif command in debug.dispatch:
+        rc = debug.dispatch[command](so)
+    elif command in admin.dispatch:
+        rc = admin.dispatch[command](so)
+    elif command in cli.dispatch:
+        rc = cli.dispatch[command](so)
+    elif command in ac_dispatch:
+        rc = ac_dispatch[command](so, stdout, stderr)
     else:
-        os.mkdir(basedir)
-    f = open(os.path.join(basedir, "client.tac"), "w")
-    f.write(client_tac)
-    f.close()
-    print "client created in %s" % basedir
-    print " please copy introducer.furl and vdrive.furl into the directory"
-
-def create_queen(config):
-    basedir = config['basedir']
-    if os.path.exists(basedir):
-        if os.listdir(basedir):
-            print "The base directory already exists: %s" % basedir
-            print "To avoid clobbering anything, I am going to quit now"
-            print "Please use a different directory, or delete this one"
-            return -1
-        # we're willing to use an empty directory
-    else:
-        os.mkdir(basedir)
-    f = open(os.path.join(basedir, "queen.tac"), "w")
-    f.write(queen_tac)
-    f.close()
-    print "queen created in %s" % basedir
-
-def start(config):
-    basedir = config['basedir']
-    if os.path.exists(os.path.join(basedir, "client.tac")):
-        tac = "client.tac"
-        type = "client"
-    elif os.path.exists(os.path.join(basedir, "queen.tac")):
-        tac = "queen.tac"
-        type = "queen"
-    else:
-        print "%s does not look like a node directory" % basedir
-        sys.exit(1)
-    os.chdir(basedir)
-    rc = os.system("twistd -y %s" % tac)
-    if rc == 0:
-        print "node probably started"
-        return 0
-    else:
-        print "node probably not started"
-        return 1
+        raise usage.UsageError()
 
-def stop(config):
-    basedir = config['basedir']
-    pidfile = os.path.join(basedir, "twistd.pid")
-    if not os.path.exists(pidfile):
-        print "%s does not look like a running node directory (no twistd.pid)" % basedir
-        return 1
-    pid = open(pidfile, "r").read()
-    pid = int(pid)
-
-    timer = 0
-    os.kill(pid, signal.SIGTERM)
-    time.sleep(0.1)
-    while timer < 5:
-        # poll once per second until twistd.pid goes away, up to 5 seconds
-        try:
-            os.kill(pid, 0)
-        except OSError:
-            print "process %d is dead" % pid
-            return
-        timer += 1
-        time.sleep(1)
-    print "never saw process go away"
-    return 1
-
-def restart(config):
-    rc = stop(config)
-    if rc:
-        print "not restarting"
-        return rc
-    return start(config)
+    return rc
+
+
+def run(install_node_control=True):
+    try:
+        if sys.platform == "win32":
+            from allmydata.windows.fixups import initialize
+            initialize()
+
+        rc = runner(sys.argv[1:], install_node_control=install_node_control)
+    except Exception:
+        import traceback
+        traceback.print_exc()
+        rc = 1
+
+    sys.exit(rc)