]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blobdiff - src/allmydata/scripts/cli.py
Remove -u shortcut for 'tahoe ls --uri' which clashes with --node-url. fixes ticket...
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / scripts / cli.py
index b0e4e6dec70834496eab7739654ed29ad717b0ff..722c8d983b5b55694841f62eb0904ee7b4ea6f46 100644 (file)
@@ -2,7 +2,7 @@ import os.path, re, fnmatch
 from twisted.python import usage
 from allmydata.scripts.common import get_aliases, get_default_nodedir, \
      DEFAULT_ALIAS, BaseOptions
 from twisted.python import usage
 from allmydata.scripts.common import get_aliases, get_default_nodedir, \
      DEFAULT_ALIAS, BaseOptions
-from allmydata.util.encodingutil import argv_to_unicode, argv_to_abspath, quote_output
+from allmydata.util.encodingutil import argv_to_unicode, argv_to_abspath, quote_local_unicode_path
 
 NODEURL_RE=re.compile("http(s?)://([^:]*)(:([1-9][0-9]*))?")
 
 
 NODEURL_RE=re.compile("http(s?)://([^:]*)(:([1-9][0-9]*))?")
 
@@ -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'])
 
             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):
 
 class AddAliasOptions(FilesystemOptions):
     def parseArgs(self, alias, cap):
@@ -69,10 +67,8 @@ class AddAliasOptions(FilesystemOptions):
             self.alias = self.alias[:-1]
         self.cap = cap
 
             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):
 
 class CreateAliasOptions(FilesystemOptions):
     def parseArgs(self, alias):
@@ -80,21 +76,17 @@ class CreateAliasOptions(FilesystemOptions):
         if self.alias.endswith(u':'):
             self.alias = self.alias[:-1]
 
         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):
 
 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 = [
         ("long", "l", "Use long format: show file sizes, and timestamps."),
 
 class ListOptions(FilesystemOptions):
     optFlags = [
         ("long", "l", "Use long format: show file sizes, and timestamps."),
-        ("uri", "u", "Show file/directory URIs."),
+        ("uri", None, "Show file/directory URIs."),
         ("readonly-uri", None, "Show read-only file/directory URIs."),
         ("classify", "F", "Append '/' to directory names, and '*' to mutable."),
         ("json", None, "Show the raw JSON output."),
         ("readonly-uri", None, "Show read-only file/directory URIs."),
         ("classify", "F", "Append '/' to directory names, and '*' to mutable."),
         ("json", None, "Show the raw JSON output."),
@@ -102,10 +94,9 @@ class ListOptions(FilesystemOptions):
     def parseArgs(self, where=""):
         self.where = argv_to_unicode(where)
 
     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.
     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:
 
     When the -l or --long option is used, each line is shown in the
     following format:
 
-    drwx <size> <date/time> <name in this directory>
+     drwx <size> <date/time> <name in this directory>
 
     where each of the letters on the left may be replaced by '-'.
     If 'd' is present, it indicates that the object is a directory.
 
     where each of the letters on the left may be replaced by '-'.
     If 'd' is present, it indicates that the object is a directory.
@@ -140,34 +131,26 @@ class GetOptions(FilesystemOptions):
         # tahoe get FOO bar              # write to local file
         # tahoe get tahoe:FOO bar        # same
 
         # tahoe get FOO bar              # write to local file
         # tahoe get tahoe:FOO bar        # same
 
-        self.from_file = argv_to_unicode(arg1)
+        if arg2 == "-":
+            arg2 = None
 
 
-        if arg2:
-            self.to_file = argv_to_unicode(arg2)
-        else:
-            self.to_file = None
-
-        if self.to_file == "-":
-            self.to_file = None
+        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."""
 
     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 = [
 
 class PutOptions(FilesystemOptions):
     optFlags = [
@@ -180,49 +163,40 @@ class PutOptions(FilesystemOptions):
     def parseArgs(self, arg1=None, arg2=None):
         # see Examples below
 
     def parseArgs(self, arg1=None, arg2=None):
         # see Examples below
 
-        if arg1 is not None and arg2 is not None:
-            self.from_file = argv_to_unicode(arg1)
-            self.to_file =  argv_to_unicode(arg2)
-        elif arg1 is not None and arg2 is None:
-            self.from_file = argv_to_unicode(arg1) # might be "-"
-            self.to_file = None
-        else:
-            self.from_file = None
-            self.to_file = None
-        if self.from_file == u"-":
-            self.from_file = None
+        if arg1 == "-":
+            arg1 = None
+
+        self.from_file = None if arg1 is None else argv_to_abspath(arg1)
+        self.to_file   = None if arg2 is None else argv_to_unicode(arg2)
 
         if self['format']:
             if self['format'].upper() not in ("SDMF", "MDMF", "CHK"):
                 raise usage.UsageError("%s is an invalid format" % self['format'])
 
 
         if self['format']:
             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.
 
     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 = [
 
 class CpOptions(FilesystemOptions):
     optFlags = [
@@ -239,10 +213,9 @@ class CpOptions(FilesystemOptions):
         self.sources = map(argv_to_unicode, args[:-1])
         self.destination = argv_to_unicode(args[-1])
 
         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.
     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.
@@ -250,43 +223,47 @@ class CpOptions(FilesystemOptions):
     you have previously set up an alias 'home' with 'tahoe create-alias home',
     here are some examples:
 
     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:
 
 
     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
 
     This command still has some limitations: symlinks and special files
     (device nodes, named pipes) are not handled very well. Arguments should
-    probably not have trailing slashes. 'tahoe cp' does not behave as much
-    like /bin/cp as you would wish, especially with respect to trailing
-    slashes.
+    not have trailing slashes (they are ignored for directory arguments, but
+    trigger errors for file arguments). When copying directories, it can be
+    unclear whether you mean to copy the contents of a source directory, or
+    the source directory itself (i.e. whether the output goes under the
+    target directory, or one directory lower). Tahoe's rule is that source
+    directories with names are referring to the directory as a whole, and
+    source directories without names (e.g. a raw dircap) are referring to the
+    contents.
     """
 
 class UnlinkOptions(FilesystemOptions):
     def parseArgs(self, where):
         self.where = argv_to_unicode(where)
 
     """
 
 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):
 
 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)
 
 
 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'.
 
     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'.
 
@@ -303,10 +280,9 @@ class LnOptions(FilesystemOptions):
         self.from_file = argv_to_unicode(frompath)
         self.to_file = argv_to_unicode(topath)
 
         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
     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
@@ -347,11 +323,10 @@ class BackupOptions(FilesystemOptions):
         self['exclude'] = set()
 
     def parseArgs(self, localdir, topath):
         self['exclude'] = set()
 
     def parseArgs(self, localdir, topath):
-        self.from_dir = argv_to_unicode(localdir)
+        self.from_dir = argv_to_abspath(localdir)
         self.to_dir = argv_to_unicode(topath)
 
         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
 
     def opt_exclude(self, pattern):
         """Ignore files matching a glob pattern. You may give multiple
@@ -368,7 +343,7 @@ class BackupOptions(FilesystemOptions):
         try:
             exclude_file = file(abs_filepath)
         except:
         try:
             exclude_file = file(abs_filepath)
         except:
-            raise BackupConfigurationError('Error opening exclude file %s.' % quote_output(abs_filepath))
+            raise BackupConfigurationError('Error opening exclude file %s.' % quote_local_unicode_path(abs_filepath))
         try:
             for line in exclude_file:
                 self.opt_exclude(line)
         try:
             for line in exclude_file:
                 self.opt_exclude(line)
@@ -393,7 +368,7 @@ class BackupOptions(FilesystemOptions):
             else:
                 yield filename
 
             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
     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
@@ -408,10 +383,10 @@ class WebopenOptions(FilesystemOptions):
     def parseArgs(self, where=''):
         self.where = argv_to_unicode(where)
 
     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."""
 
     directory on the grid. When run without arguments, open the Welcome
     page."""
 
@@ -425,11 +400,10 @@ class ManifestOptions(FilesystemOptions):
     def parseArgs(self, where=''):
         self.where = argv_to_unicode(where)
 
     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 = [
 
 class StatsOptions(FilesystemOptions):
     optFlags = [
@@ -438,11 +412,10 @@ class StatsOptions(FilesystemOptions):
     def parseArgs(self, where=''):
         self.where = argv_to_unicode(where)
 
     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 = [
 
 class CheckOptions(FilesystemOptions):
     optFlags = [
@@ -454,10 +427,8 @@ class CheckOptions(FilesystemOptions):
     def parseArgs(self, *locations):
         self.locations = map(argv_to_unicode, locations)
 
     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."""
     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."""
@@ -473,10 +444,8 @@ class DeepCheckOptions(FilesystemOptions):
     def parseArgs(self, *locations):
         self.locations = map(argv_to_unicode, locations)
 
     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."""
     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."""