From 8f41713fe9cd08373dcfe74ee1bd6721e80b3427 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 26 May 2015 11:31:06 -0700 Subject: [PATCH] cli: improve formatting of all commands Also: * do some light refactoring of create-client/node * make it clear that these commands' --basedir options do the same as the global --node-directory option * use "global-options" instead of "global-opts" --- src/allmydata/scripts/admin.py | 6 +- src/allmydata/scripts/cli.py | 162 ++++++++++-------------- src/allmydata/scripts/common.py | 2 +- src/allmydata/scripts/create_node.py | 25 ++-- src/allmydata/scripts/debug.py | 79 ++++-------- src/allmydata/scripts/runner.py | 2 +- src/allmydata/scripts/startstop_node.py | 9 +- src/allmydata/test/test_cli.py | 56 ++++---- 8 files changed, 145 insertions(+), 196 deletions(-) diff --git a/src/allmydata/scripts/admin.py b/src/allmydata/scripts/admin.py index 092d90a9..6dab86ab 100644 --- a/src/allmydata/scripts/admin.py +++ b/src/allmydata/scripts/admin.py @@ -3,8 +3,6 @@ from twisted.python import usage from allmydata.scripts.common import BaseOptions class GenerateKeypairOptions(BaseOptions): - def getSynopsis(self): - return "Usage: tahoe [global-opts] admin generate-keypair" def getUsage(self, width=None): t = BaseOptions.getUsage(self, width) @@ -26,7 +24,7 @@ class DerivePubkeyOptions(BaseOptions): self.privkey = privkey def getSynopsis(self): - return "Usage: tahoe [global-opts] admin derive-pubkey PRIVKEY" + return "Usage: tahoe [global-options] admin derive-pubkey PRIVKEY" def getUsage(self, width=None): t = BaseOptions.getUsage(self, width) @@ -57,7 +55,7 @@ class AdminCommand(BaseOptions): if not hasattr(self, 'subOptions'): raise usage.UsageError("must specify a subcommand") def getSynopsis(self): - return "Usage: tahoe [global-opts] admin SUBCOMMAND" + return "Usage: tahoe [global-options] admin SUBCOMMAND" def getUsage(self, width=None): t = BaseOptions.getUsage(self, width) t += """ diff --git a/src/allmydata/scripts/cli.py b/src/allmydata/scripts/cli.py index 18ecc659..ef4fc71f 100644 --- a/src/allmydata/scripts/cli.py +++ b/src/allmydata/scripts/cli.py @@ -57,10 +57,8 @@ class MakeDirectoryOptions(FilesystemOptions): if self['format'].upper() not in ("SDMF", "MDMF"): raise usage.UsageError("%s is an invalid format" % self['format']) - def getSynopsis(self): - return "Usage: %s [global-opts] mkdir [options] [REMOTE_DIR]" % (self.command_name,) - - longdesc = """Create a new directory, either unlinked or as a subdirectory.""" + synopsis = "[options] [REMOTE_DIR]" + description = """Create a new directory, either unlinked or as a subdirectory.""" class AddAliasOptions(FilesystemOptions): def parseArgs(self, alias, cap): @@ -69,10 +67,8 @@ class AddAliasOptions(FilesystemOptions): self.alias = self.alias[:-1] self.cap = cap - def getSynopsis(self): - return "Usage: %s [global-opts] add-alias [options] ALIAS[:] DIRCAP" % (self.command_name,) - - longdesc = """Add a new alias for an existing directory.""" + synopsis = "[options] ALIAS[:] DIRCAP" + description = """Add a new alias for an existing directory.""" class CreateAliasOptions(FilesystemOptions): def parseArgs(self, alias): @@ -80,16 +76,12 @@ class CreateAliasOptions(FilesystemOptions): if self.alias.endswith(u':'): self.alias = self.alias[:-1] - def getSynopsis(self): - return "Usage: %s [global-opts] create-alias [options] ALIAS[:]" % (self.command_name,) - - longdesc = """Create a new directory and add an alias for it.""" + synopsis = "[options] ALIAS[:]" + description = """Create a new directory and add an alias for it.""" class ListAliasesOptions(FilesystemOptions): - def getSynopsis(self): - return "Usage: %s [global-opts] list-aliases [options]" % (self.command_name,) - - longdesc = """Display a table of all configured aliases.""" + synopsis = "[options]" + description = """Display a table of all configured aliases.""" class ListOptions(FilesystemOptions): optFlags = [ @@ -102,10 +94,9 @@ class ListOptions(FilesystemOptions): def parseArgs(self, where=""): self.where = argv_to_unicode(where) - def getSynopsis(self): - return "Usage: %s [global-opts] ls [options] [PATH]" % (self.command_name,) + synopsis = "[options] [PATH]" - longdesc = """ + description = """ List the contents of some portion of the grid. If PATH is omitted, "tahoe:" is assumed. @@ -113,7 +104,7 @@ class ListOptions(FilesystemOptions): When the -l or --long option is used, each line is shown in the following format: - drwx + drwx where each of the letters on the left may be replaced by '-'. If 'd' is present, it indicates that the object is a directory. @@ -146,24 +137,20 @@ class GetOptions(FilesystemOptions): self.from_file = argv_to_unicode(arg1) self.to_file = None if arg2 is None else argv_to_abspath(arg2) - def getSynopsis(self): - return "Usage: %s [global-opts] get [options] REMOTE_FILE LOCAL_FILE" % (self.command_name,) + synopsis = "[options] REMOTE_FILE LOCAL_FILE" - longdesc = """ + description = """ Retrieve a file from the grid and write it to the local filesystem. If LOCAL_FILE is omitted or '-', the contents of the file will be written to stdout.""" - def getUsage(self, width=None): - t = FilesystemOptions.getUsage(self, width) - t += """ -Examples: - % tahoe get FOO |less # write to stdout - % tahoe get tahoe:FOO |less # same - % tahoe get FOO bar # write to local file - % tahoe get tahoe:FOO bar # same -""" - return t + description_unwrapped = """ + Examples: + % tahoe get FOO |less # write to stdout + % tahoe get tahoe:FOO |less # same + % tahoe get FOO bar # write to local file + % tahoe get tahoe:FOO bar # same + """ class PutOptions(FilesystemOptions): optFlags = [ @@ -186,33 +173,30 @@ class PutOptions(FilesystemOptions): if self['format'].upper() not in ("SDMF", "MDMF", "CHK"): raise usage.UsageError("%s is an invalid format" % self['format']) - def getSynopsis(self): - return "Usage: %s [global-opts] put [options] LOCAL_FILE REMOTE_FILE" % (self.command_name,) + synopsis = "[options] LOCAL_FILE REMOTE_FILE" - longdesc = """ + description = """ Put a file into the grid, copying its contents from the local filesystem. If REMOTE_FILE is missing, upload the file but do not link it into a directory; also print the new filecap to stdout. If LOCAL_FILE is missing or '-', data will be copied from stdin. REMOTE_FILE is assumed to start with tahoe: unless otherwise specified. - If the destination file already exists and is mutable, it will be modified - in-place, whether or not --mutable is specified. (--mutable only affects - creation of new files.)""" - - def getUsage(self, width=None): - t = FilesystemOptions.getUsage(self, width) - t += """ -Examples: - % cat FILE | tahoe put # create unlinked file from stdin - % cat FILE | tahoe put - # same - % tahoe put bar # create unlinked file from local 'bar' - % cat FILE | tahoe put - FOO # create tahoe:FOO from stdin - % tahoe put bar FOO # copy local 'bar' to tahoe:FOO - % tahoe put bar tahoe:FOO # same - % tahoe put bar MUTABLE-FILE-WRITECAP # modify the mutable file in-place -""" - return t + If the destination file already exists and is mutable, it will be + modified in-place, whether or not --mutable is specified. (--mutable only + affects creation of new files.) + """ + + description_unwrapped = """ + Examples: + % cat FILE | tahoe put # create unlinked file from stdin + % cat FILE | tahoe put - # same + % tahoe put bar # create unlinked file from local 'bar' + % cat FILE | tahoe put - FOO # create tahoe:FOO from stdin + % tahoe put bar FOO # copy local 'bar' to tahoe:FOO + % tahoe put bar tahoe:FOO # same + % tahoe put bar MUTABLE-FILE-WRITECAP # modify the mutable file in-place + """ class CpOptions(FilesystemOptions): optFlags = [ @@ -229,10 +213,9 @@ class CpOptions(FilesystemOptions): self.sources = map(argv_to_unicode, args[:-1]) self.destination = argv_to_unicode(args[-1]) - def getSynopsis(self): - return "Usage: %s [global-opts] cp [options] FROM.. TO" % (self.command_name,) + synopsis = "[options] FROM.. TO" - longdesc = """ + description = """ Use 'tahoe cp' to copy files between a local filesystem and a Tahoe grid. Any FROM/TO arguments that begin with an alias indicate Tahoe-side files or non-file arguments. Directories will be copied recursively. @@ -240,15 +223,15 @@ class CpOptions(FilesystemOptions): you have previously set up an alias 'home' with 'tahoe create-alias home', here are some examples: - tahoe cp ~/foo.txt home: # creates tahoe-side home:foo.txt + tahoe cp ~/foo.txt home: # creates tahoe-side home:foo.txt - tahoe cp ~/foo.txt /tmp/bar.txt home: # copies two files to home: + tahoe cp ~/foo.txt /tmp/bar.txt home: # copies two files to home: - tahoe cp ~/Pictures home:stuff/my-pictures # copies directory recursively + tahoe cp ~/Pictures home:stuff/my-pictures # copies directory recursively You can also use a dircap as either FROM or TO target: - tahoe cp URI:DIR2-RO:ixqhc4kdbjxc7o65xjnveoewym:5x6lwoxghrd5rxhwunzavft2qygfkt27oj3fbxlq4c6p45z5uneq/blog.html ./ # copy Zooko's wiki page to a local file + tahoe cp URI:DIR2-RO:ixqhc4kdbjxc7o65xjnveoewym:5x6lwoxghrd5rxhwunzavft2qygfkt27oj3fbxlq4c6p45z5uneq/blog.html ./ # copy Zooko's wiki page to a local file This command still has some limitations: symlinks and special files (device nodes, named pipes) are not handled very well. Arguments should @@ -266,22 +249,21 @@ class UnlinkOptions(FilesystemOptions): def parseArgs(self, where): self.where = argv_to_unicode(where) - def getSynopsis(self): - return "Usage: %s [global-opts] unlink [options] REMOTE_FILE" % (self.command_name,) + synopsis = "[options] REMOTE_FILE" + description = "Remove a named file from its parent directory." class RmOptions(UnlinkOptions): - def getSynopsis(self): - return "Usage: %s [global-opts] rm [options] REMOTE_FILE" % (self.command_name,) + synopsis = "[options] REMOTE_FILE" + description = "Remove a named file from its parent directory." class MvOptions(FilesystemOptions): def parseArgs(self, frompath, topath): self.from_file = argv_to_unicode(frompath) self.to_file = argv_to_unicode(topath) - def getSynopsis(self): - return "Usage: %s [global-opts] mv [options] FROM TO" % (self.command_name,) + synopsis = "[options] FROM TO" - longdesc = """ + description = """ Use 'tahoe mv' to move files that are already on the grid elsewhere on the grid, e.g., 'tahoe mv alias:some_file alias:new_file'. @@ -298,10 +280,9 @@ class LnOptions(FilesystemOptions): self.from_file = argv_to_unicode(frompath) self.to_file = argv_to_unicode(topath) - def getSynopsis(self): - return "Usage: %s [global-opts] ln [options] FROM_LINK TO_LINK" % (self.command_name,) + synopsis = "[options] FROM_LINK TO_LINK" - longdesc = """ + description = """ Use 'tahoe ln' to duplicate a link (directory entry) already on the grid to elsewhere on the grid. For example 'tahoe ln alias:some_file alias:new_file'. causes 'alias:new_file' to point to the same object that @@ -345,8 +326,7 @@ class BackupOptions(FilesystemOptions): self.from_dir = argv_to_abspath(localdir) self.to_dir = argv_to_unicode(topath) - def getSynopsis(self): - return "Usage: %s [global-opts] backup [options] FROM ALIAS:TO" % (self.command_name,) + synopsis = "[options] FROM ALIAS:TO" def opt_exclude(self, pattern): """Ignore files matching a glob pattern. You may give multiple @@ -388,7 +368,7 @@ class BackupOptions(FilesystemOptions): else: yield filename - longdesc = """ + description = """ Add a versioned backup of the local FROM directory to a timestamped subdirectory of the TO/Archives directory on the grid, sharing as many files and directories as possible with earlier backups. Create TO/Latest @@ -403,10 +383,10 @@ class WebopenOptions(FilesystemOptions): def parseArgs(self, where=''): self.where = argv_to_unicode(where) - def getSynopsis(self): - return "Usage: %s [global-opts] webopen [options] [ALIAS:PATH]" % (self.command_name,) + synopsis = "[options] [ALIAS:PATH]" - longdesc = """Open a web browser to the contents of some file or + description = """ + Open a web browser to the contents of some file or directory on the grid. When run without arguments, open the Welcome page.""" @@ -420,11 +400,10 @@ class ManifestOptions(FilesystemOptions): def parseArgs(self, where=''): self.where = argv_to_unicode(where) - def getSynopsis(self): - return "Usage: %s [global-opts] manifest [options] [ALIAS:PATH]" % (self.command_name,) - - longdesc = """Print a list of all files and directories reachable from - the given starting point.""" + synopsis = "[options] [ALIAS:PATH]" + description = """ + Print a list of all files and directories reachable from the given + starting point.""" class StatsOptions(FilesystemOptions): optFlags = [ @@ -433,11 +412,10 @@ class StatsOptions(FilesystemOptions): def parseArgs(self, where=''): self.where = argv_to_unicode(where) - def getSynopsis(self): - return "Usage: %s [global-opts] stats [options] [ALIAS:PATH]" % (self.command_name,) - - longdesc = """Print statistics about of all files and directories - reachable from the given starting point.""" + synopsis = "[options] [ALIAS:PATH]" + description = """ + Print statistics about of all files and directories reachable from the + given starting point.""" class CheckOptions(FilesystemOptions): optFlags = [ @@ -449,10 +427,8 @@ class CheckOptions(FilesystemOptions): def parseArgs(self, *locations): self.locations = map(argv_to_unicode, locations) - def getSynopsis(self): - return "Usage: %s [global-opts] check [options] [ALIAS:PATH]" % (self.command_name,) - - longdesc = """ + synopsis = "[options] [ALIAS:PATH]" + description = """ Check a single file or directory: count how many shares are available and verify their hashes. Optionally repair the file if any problems were found.""" @@ -468,10 +444,8 @@ class DeepCheckOptions(FilesystemOptions): def parseArgs(self, *locations): self.locations = map(argv_to_unicode, locations) - def getSynopsis(self): - return "Usage: %s [global-opts] deep-check [options] [ALIAS:PATH]" % (self.command_name,) - - longdesc = """ + synopsis = "[options] [ALIAS:PATH]" + description = """ Check all files and directories reachable from the given starting point (which must be a directory), like 'tahoe check' but for multiple files. Optionally repair any problems found.""" diff --git a/src/allmydata/scripts/common.py b/src/allmydata/scripts/common.py index 3ebb3e8c..d6246fc0 100644 --- a/src/allmydata/scripts/common.py +++ b/src/allmydata/scripts/common.py @@ -93,7 +93,7 @@ class NoDefaultBasedirOptions(BasedirOptions): BasedirOptions.parseArgs(self, basedir) def getSynopsis(self): - return "Usage: %s [global-opts] %s [options] NODEDIR" % (self.command_name, self.subcommand_name) + return "Usage: %s [global-options] %s [options] NODEDIR" % (self.command_name, self.subcommand_name) DEFAULT_ALIAS = u"tahoe" diff --git a/src/allmydata/scripts/create_node.py b/src/allmydata/scripts/create_node.py index 6663d125..65cbb63a 100644 --- a/src/allmydata/scripts/create_node.py +++ b/src/allmydata/scripts/create_node.py @@ -1,11 +1,12 @@ import os, sys from allmydata.scripts.common import BasedirOptions, NoDefaultBasedirOptions +from allmydata.scripts.default_nodedir import _default_nodedir from allmydata.util.assertutil import precondition -from allmydata.util.encodingutil import listdir_unicode, argv_to_unicode, quote_output +from allmydata.util.encodingutil import listdir_unicode, argv_to_unicode, quote_output, quote_local_unicode_path import allmydata -class CreateClientOptions(BasedirOptions): +class _CreateBaseOptions(BasedirOptions): optParameters = [ # we provide 'create-node'-time options for the most common # configuration knobs. The rest can be controlled by editing @@ -14,28 +15,30 @@ class CreateClientOptions(BasedirOptions): ("introducer", "i", None, "Specify the introducer FURL to use."), ("webport", "p", "tcp:3456:interface=127.0.0.1", "Specify which TCP port to run the HTTP interface on. Use 'none' to disable."), - ] + ("basedir", "C", None, "Specify which Tahoe base directory should be used. This has the same effect as the global --node-directory option. [default: %s]" + % quote_local_unicode_path(_default_nodedir)), - def getSynopsis(self): - return "Usage: %s [global-opts] create-client [options] [NODEDIR]" % (self.command_name,) + ] - # This is overridden in order to ensure we get a "Wrong number of arguments." - # error when more than one argument is given. + # This is overridden in order to ensure we get a "Wrong number of + # arguments." error when more than one argument is given. def parseArgs(self, basedir=None): BasedirOptions.parseArgs(self, basedir) +class CreateClientOptions(_CreateBaseOptions): + synopsis = "[options] [NODEDIR]" + description = "Create a client-only Tahoe-LAFS node (no storage server)." class CreateNodeOptions(CreateClientOptions): optFlags = [ ("no-storage", None, "Do not offer storage service to other nodes."), ] - - def getSynopsis(self): - return "Usage: %s [global-opts] create-node [options] [NODEDIR]" % (self.command_name,) - + synopsis = "[options] [NODEDIR]" + description = "Create a full Tahoe-LAFS node (client+server)." class CreateIntroducerOptions(NoDefaultBasedirOptions): subcommand_name = "create-introducer" + description = "Create a Tahoe-LAFS introducer." client_tac = """ diff --git a/src/allmydata/scripts/debug.py b/src/allmydata/scripts/debug.py index fedd6985..738f911e 100644 --- a/src/allmydata/scripts/debug.py +++ b/src/allmydata/scripts/debug.py @@ -11,25 +11,21 @@ from allmydata.scripts.common import BaseOptions class DumpOptions(BaseOptions): def getSynopsis(self): - return "Usage: tahoe [global-opts] debug dump-share SHARE_FILENAME" + return "Usage: tahoe [global-options] debug dump-share SHARE_FILENAME" optFlags = [ ["offsets", None, "Display a table of section offsets."], ["leases-only", None, "Dump leases but not CHK contents."], ] - def getUsage(self, width=None): - t = BaseOptions.getUsage(self, width) - t += """ + description = """ Print lots of information about the given share, by parsing the share's contents. This includes share type, lease information, encoding parameters, hash-tree roots, public keys, and segment sizes. This command also emits a verify-cap for the file that uses the share. tahoe debug dump-share testgrid/node-3/storage/shares/4v/4vozh77tsrw7mdhnj7qvp5ky74/0 - """ - return t def parseArgs(self, filename): from allmydata.util.encodingutil import argv_to_abspath @@ -408,7 +404,7 @@ def dump_MDMF_share(m, length, options): class DumpCapOptions(BaseOptions): def getSynopsis(self): - return "Usage: tahoe [global-opts] debug dump-cap [options] FILECAP" + return "Usage: tahoe [global-options] debug dump-cap [options] FILECAP" optParameters = [ ["nodeid", "n", None, "Specify the storage server nodeid (ASCII), to construct WE and secrets."], @@ -420,9 +416,7 @@ class DumpCapOptions(BaseOptions): def parseArgs(self, cap): self.cap = cap - def getUsage(self, width=None): - t = BaseOptions.getUsage(self, width) - t += """ + description = """ Print information about the given cap-string (aka: URI, file-cap, dir-cap, read-cap, write-cap). The URI string is parsed and unpacked. This prints the type of the cap, its storage index, and any derived keys. @@ -437,7 +431,6 @@ If additional information is provided (storage server nodeid and/or client base secret), this command will compute the shared secrets used for the write-enabler and for lease-renewal. """ - return t def dump_cap(options): @@ -610,16 +603,14 @@ def dump_uri_instance(u, nodeid, secret, out, show_header=True): class FindSharesOptions(BaseOptions): def getSynopsis(self): - return "Usage: tahoe [global-opts] debug find-shares STORAGE_INDEX NODEDIRS.." + return "Usage: tahoe [global-options] debug find-shares STORAGE_INDEX NODEDIRS.." def parseArgs(self, storage_index_s, *nodedirs): from allmydata.util.encodingutil import argv_to_abspath self.si_s = storage_index_s self.nodedirs = map(argv_to_abspath, nodedirs) - def getUsage(self, width=None): - t = BaseOptions.getUsage(self, width) - t += """ + description = """ Locate all shares for the given storage index. This command looks through one or more node directories to find the shares. It returns a list of filenames, one per line, for each share file found. @@ -630,7 +621,6 @@ It may be useful during testing, when running a test grid in which all the nodes are on a local disk. The share files thus located can be counted, examined (with dump-share), or corrupted/deleted to test checker/repairer. """ - return t def find_shares(options): """Given a storage index and a list of node directories, emit a list of @@ -659,9 +649,6 @@ def find_shares(options): class CatalogSharesOptions(BaseOptions): - """ - - """ def parseArgs(self, *nodedirs): from allmydata.util.encodingutil import argv_to_abspath self.nodedirs = map(argv_to_abspath, nodedirs) @@ -669,11 +656,9 @@ class CatalogSharesOptions(BaseOptions): raise usage.UsageError("must specify at least one node directory") def getSynopsis(self): - return "Usage: tahoe [global-opts] debug catalog-shares NODEDIRS.." + return "Usage: tahoe [global-options] debug catalog-shares NODEDIRS.." - def getUsage(self, width=None): - t = BaseOptions.getUsage(self, width) - t += """ + description = """ Locate all shares in the given node directories, and emit a one-line summary of each share. Run it like this: @@ -691,7 +676,6 @@ you see shares with the same SI but different parameters/filesize/UEB_hash, then something is wrong. The misc/find-share/anomalies.py script may be useful for purpose. """ - return t def call(c, *args, **kwargs): # take advantage of the fact that ImmediateReadBucketProxy returns @@ -882,15 +866,13 @@ def catalog_shares_one_abbrevdir(si_s, si_dir, now, out, err): class CorruptShareOptions(BaseOptions): def getSynopsis(self): - return "Usage: tahoe [global-opts] debug corrupt-share SHARE_FILENAME" + return "Usage: tahoe [global-options] debug corrupt-share SHARE_FILENAME" optParameters = [ ["offset", "o", "block-random", "Specify which bit to flip."], ] - def getUsage(self, width=None): - t = BaseOptions.getUsage(self, width) - t += """ + description = """ Corrupt the given share by flipping a bit. This will cause a verifying/downloading client to log an integrity-check failure incident, and downloads will proceed with a different share. @@ -902,7 +884,6 @@ to flip a single random bit of the block data. Obviously, this command should not be used in normal operation. """ - return t def parseArgs(self, filename): self['filename'] = filename @@ -962,7 +943,7 @@ def corrupt_share(options): class ReplOptions(BaseOptions): def getSynopsis(self): - return "Usage: tahoe [global-opts] debug repl" + return "Usage: tahoe [global-options] debug repl" def repl(options): import code @@ -973,7 +954,7 @@ DEFAULT_TESTSUITE = 'allmydata' class TrialOptions(twisted_trial.Options): def getSynopsis(self): - return "Usage: tahoe [global-opts] debug trial [options] [[file|package|module|TestCase|testmethod]...]" + return "Usage: tahoe [global-options] debug trial [options] [[file|package|module|TestCase|testmethod]...]" def parseOptions(self, all_subargs, *a, **kw): self.trial_args = list(all_subargs) @@ -985,13 +966,10 @@ class TrialOptions(twisted_trial.Options): if not nonoption_args: self.trial_args.append(DEFAULT_TESTSUITE) - def getUsage(self, width=None): - t = twisted_trial.Options.getUsage(self, width) - t += """ -The 'tahoe debug trial' command uses the correct imports for this instance of -Tahoe-LAFS. The default test suite is '%s'. -""" % (DEFAULT_TESTSUITE,) - return t + longdesc = twisted_trial.Options.longdesc + "\n\n" + ( + "The 'tahoe debug trial' command uses the correct imports for this " + "instance of Tahoe-LAFS. The default test suite is '%s'." + % DEFAULT_TESTSUITE) def trial(config): sys.argv = ['trial'] + config.trial_args @@ -1014,9 +992,9 @@ def fixOptionsClass( (subcmd, shortcut, OptionsClass, desc) ): t = OptionsClass.getSynopsis(self) i = t.find("Usage: flogtool ") if i >= 0: - return "Usage: tahoe [global-opts] debug flogtool " + t[i+len("Usage: flogtool "):] + return "Usage: tahoe [global-options] debug flogtool " + t[i+len("Usage: flogtool "):] else: - return "Usage: tahoe [global-opts] debug flogtool %s [options]" % (subcmd,) + return "Usage: tahoe [global-options] debug flogtool %s [options]" % (subcmd,) return (subcmd, shortcut, FixedOptionsClass, desc) class FlogtoolOptions(foolscap_cli.Options): @@ -1025,7 +1003,7 @@ class FlogtoolOptions(foolscap_cli.Options): self.subCommands = map(fixOptionsClass, self.subCommands) def getSynopsis(self): - return "Usage: tahoe [global-opts] debug flogtool (%s) [command options]" % ("|".join([x[0] for x in self.subCommands])) + return "Usage: tahoe [global-options] debug flogtool COMMAND [flogtool-options]" def parseOptions(self, all_subargs, *a, **kw): self.flogtool_args = list(all_subargs) @@ -1037,7 +1015,7 @@ class FlogtoolOptions(foolscap_cli.Options): The 'tahoe debug flogtool' command uses the correct imports for this instance of Tahoe-LAFS. -Please run 'tahoe debug flogtool SUBCOMMAND --help' for more details on each +Please run 'tahoe debug flogtool COMMAND --help' for more details on each subcommand. """ return t @@ -1066,20 +1044,11 @@ class DebugCommand(BaseOptions): def postOptions(self): if not hasattr(self, 'subOptions'): raise usage.UsageError("must specify a subcommand") - def getSynopsis(self): - return "" + synopsis = "COMMAND" + def getUsage(self, width=None): - #t = BaseOptions.getUsage(self, width) - t = """Usage: tahoe debug SUBCOMMAND -Subcommands: - tahoe debug dump-share Unpack and display the contents of a share. - tahoe debug dump-cap Unpack a read-cap or write-cap. - tahoe debug find-shares Locate sharefiles in node directories. - tahoe debug catalog-shares Describe all shares in node dirs. - tahoe debug corrupt-share Corrupt a share by flipping a bit. - tahoe debug repl Open a Python interpreter. - tahoe debug trial Run tests using Twisted Trial with the right imports. - tahoe debug flogtool Utilities to access log files. + t = BaseOptions.getUsage(self, width) + t += """\ Please run e.g. 'tahoe debug dump-share --help' for more details on each subcommand. diff --git a/src/allmydata/scripts/runner.py b/src/allmydata/scripts/runner.py index 4767e59e..c331eee7 100644 --- a/src/allmydata/scripts/runner.py +++ b/src/allmydata/scripts/runner.py @@ -70,7 +70,7 @@ class Options(usage.Options): return ("\nUsage: tahoe [global-options] [command-options]\n" + self.getUsage()) - synopsis = "\nUsage: tahoe [global-opts]" # used only for subcommands + synopsis = "\nUsage: tahoe [global-options]" # used only for subcommands def getUsage(self, **kwargs): t = usage.Options.getUsage(self, **kwargs) diff --git a/src/allmydata/scripts/startstop_node.py b/src/allmydata/scripts/startstop_node.py index 45b7fd11..b1aad6ac 100644 --- a/src/allmydata/scripts/startstop_node.py +++ b/src/allmydata/scripts/startstop_node.py @@ -3,12 +3,17 @@ import os, sys, signal, time from allmydata.scripts.common import BasedirOptions from twisted.scripts import twistd from twisted.python import usage +from allmydata.scripts.default_nodedir import _default_nodedir from allmydata.util import fileutil from allmydata.util.encodingutil import listdir_unicode, quote_local_unicode_path class StartOptions(BasedirOptions): subcommand_name = "start" + optParameters = [ + ("basedir", "C", None, "Specify which Tahoe base directory should be used. This has the same effect as the global --node-directory option. [default: %s]" + % quote_local_unicode_path(_default_nodedir)), + ] def parseArgs(self, basedir=None, *twistd_args): # This can't handle e.g. 'tahoe start --nodaemon', since '--nodaemon' @@ -21,7 +26,7 @@ class StartOptions(BasedirOptions): self.twistd_args = twistd_args def getSynopsis(self): - return "Usage: %s [global-opts] %s [options] [NODEDIR [twistd-options]]" % (self.command_name, self.subcommand_name) + return "Usage: %s [global-options] %s [options] [NODEDIR [twistd-options]]" % (self.command_name, self.subcommand_name) def getUsage(self, width=None): t = BasedirOptions.getUsage(self, width) + "\n" @@ -40,7 +45,7 @@ class StopOptions(BasedirOptions): BasedirOptions.parseArgs(self, basedir) def getSynopsis(self): - return "Usage: %s [global-opts] stop [options] [NODEDIR]" % (self.command_name,) + return "Usage: %s [global-options] stop [options] [NODEDIR]" % (self.command_name,) class RestartOptions(StartOptions): subcommand_name = "restart" diff --git a/src/allmydata/test/test_cli.py b/src/allmydata/test/test_cli.py index 3c73d706..3caf7687 100644 --- a/src/allmydata/test/test_cli.py +++ b/src/allmydata/test/test_cli.py @@ -563,124 +563,124 @@ class CLI(CLITestMixin, unittest.TestCase): class Help(unittest.TestCase): def test_get(self): help = str(cli.GetOptions()) - self.failUnlessIn(" [global-opts] get [options] REMOTE_FILE LOCAL_FILE", help) + self.failUnlessIn("[options] REMOTE_FILE LOCAL_FILE", help) self.failUnlessIn("% tahoe get FOO |less", help) def test_put(self): help = str(cli.PutOptions()) - self.failUnlessIn(" [global-opts] put [options] LOCAL_FILE REMOTE_FILE", help) + self.failUnlessIn("[options] LOCAL_FILE REMOTE_FILE", help) self.failUnlessIn("% cat FILE | tahoe put", help) def test_ls(self): help = str(cli.ListOptions()) - self.failUnlessIn(" [global-opts] ls [options] [PATH]", help) + self.failUnlessIn("[options] [PATH]", help) def test_unlink(self): help = str(cli.UnlinkOptions()) - self.failUnlessIn(" [global-opts] unlink [options] REMOTE_FILE", help) + self.failUnlessIn("[options] REMOTE_FILE", help) def test_rm(self): help = str(cli.RmOptions()) - self.failUnlessIn(" [global-opts] rm [options] REMOTE_FILE", help) + self.failUnlessIn("[options] REMOTE_FILE", help) def test_mv(self): help = str(cli.MvOptions()) - self.failUnlessIn(" [global-opts] mv [options] FROM TO", help) + self.failUnlessIn("[options] FROM TO", help) self.failUnlessIn("Use 'tahoe mv' to move files", help) def test_cp(self): help = str(cli.CpOptions()) - self.failUnlessIn(" [global-opts] cp [options] FROM.. TO", help) + self.failUnlessIn("[options] FROM.. TO", help) self.failUnlessIn("Use 'tahoe cp' to copy files", help) def test_ln(self): help = str(cli.LnOptions()) - self.failUnlessIn(" [global-opts] ln [options] FROM_LINK TO_LINK", help) + self.failUnlessIn("[options] FROM_LINK TO_LINK", help) self.failUnlessIn("Use 'tahoe ln' to duplicate a link", help) def test_mkdir(self): help = str(cli.MakeDirectoryOptions()) - self.failUnlessIn(" [global-opts] mkdir [options] [REMOTE_DIR]", help) + self.failUnlessIn("[options] [REMOTE_DIR]", help) self.failUnlessIn("Create a new directory", help) def test_backup(self): help = str(cli.BackupOptions()) - self.failUnlessIn(" [global-opts] backup [options] FROM ALIAS:TO", help) + self.failUnlessIn("[options] FROM ALIAS:TO", help) def test_webopen(self): help = str(cli.WebopenOptions()) - self.failUnlessIn(" [global-opts] webopen [options] [ALIAS:PATH]", help) + self.failUnlessIn("[options] [ALIAS:PATH]", help) def test_manifest(self): help = str(cli.ManifestOptions()) - self.failUnlessIn(" [global-opts] manifest [options] [ALIAS:PATH]", help) + self.failUnlessIn("[options] [ALIAS:PATH]", help) def test_stats(self): help = str(cli.StatsOptions()) - self.failUnlessIn(" [global-opts] stats [options] [ALIAS:PATH]", help) + self.failUnlessIn("[options] [ALIAS:PATH]", help) def test_check(self): help = str(cli.CheckOptions()) - self.failUnlessIn(" [global-opts] check [options] [ALIAS:PATH]", help) + self.failUnlessIn("[options] [ALIAS:PATH]", help) def test_deep_check(self): help = str(cli.DeepCheckOptions()) - self.failUnlessIn(" [global-opts] deep-check [options] [ALIAS:PATH]", help) + self.failUnlessIn("[options] [ALIAS:PATH]", help) def test_create_alias(self): help = str(cli.CreateAliasOptions()) - self.failUnlessIn(" [global-opts] create-alias [options] ALIAS[:]", help) + self.failUnlessIn("[options] ALIAS[:]", help) def test_add_alias(self): help = str(cli.AddAliasOptions()) - self.failUnlessIn(" [global-opts] add-alias [options] ALIAS[:] DIRCAP", help) + self.failUnlessIn("[options] ALIAS[:] DIRCAP", help) def test_list_aliases(self): help = str(cli.ListAliasesOptions()) - self.failUnlessIn(" [global-opts] list-aliases [options]", help) + self.failUnlessIn("[options]", help) def test_start(self): help = str(startstop_node.StartOptions()) - self.failUnlessIn(" [global-opts] start [options] [NODEDIR [twistd-options]]", help) + self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help) def test_stop(self): help = str(startstop_node.StopOptions()) - self.failUnlessIn(" [global-opts] stop [options] [NODEDIR]", help) + self.failUnlessIn("[options] [NODEDIR]", help) def test_restart(self): help = str(startstop_node.RestartOptions()) - self.failUnlessIn(" [global-opts] restart [options] [NODEDIR [twistd-options]]", help) + self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help) def test_run(self): help = str(startstop_node.RunOptions()) - self.failUnlessIn(" [global-opts] run [options] [NODEDIR [twistd-options]]", help) + self.failUnlessIn("[options] [NODEDIR [twistd-options]]", help) def test_create_client(self): help = str(create_node.CreateClientOptions()) - self.failUnlessIn(" [global-opts] create-client [options] [NODEDIR]", help) + self.failUnlessIn("[options] [NODEDIR]", help) def test_create_node(self): help = str(create_node.CreateNodeOptions()) - self.failUnlessIn(" [global-opts] create-node [options] [NODEDIR]", help) + self.failUnlessIn("[options] [NODEDIR]", help) def test_create_introducer(self): help = str(create_node.CreateIntroducerOptions()) - self.failUnlessIn(" [global-opts] create-introducer [options] NODEDIR", help) + self.failUnlessIn("[options] NODEDIR", help) def test_debug_trial(self): help = str(debug.TrialOptions()) - self.failUnlessIn(" [global-opts] debug trial [options] [[file|package|module|TestCase|testmethod]...]", help) + self.failUnlessIn(" [global-options] debug trial [options] [[file|package|module|TestCase|testmethod]...]", help) self.failUnlessIn("The 'tahoe debug trial' command uses the correct imports", help) def test_debug_flogtool(self): options = debug.FlogtoolOptions() help = str(options) - self.failUnlessIn(" [global-opts] debug flogtool ", help) + self.failUnlessIn(" [global-options] debug flogtool ", help) self.failUnlessIn("The 'tahoe debug flogtool' command uses the correct imports", help) for (option, shortcut, oClass, desc) in options.subCommands: subhelp = str(oClass()) - self.failUnlessIn(" [global-opts] debug flogtool %s " % (option,), subhelp) + self.failUnlessIn(" [global-options] debug flogtool %s " % (option,), subhelp) class Ln(GridTestMixin, CLITestMixin, unittest.TestCase): -- 2.45.2