]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/scripts/common.py
CLI: make all of the option descriptions imperative sentences.
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / scripts / common.py
1
2 import os, sys, urllib
3 import codecs
4 from twisted.python import usage
5 from allmydata.util.assertutil import precondition
6 from allmydata.util.encodingutil import unicode_to_url, quote_output, argv_to_abspath
7 from allmydata.util.fileutil import abspath_expanduser_unicode
8
9
10 _default_nodedir = None
11 if sys.platform == 'win32':
12     from allmydata.windows import registry
13     path = registry.get_base_dir_path()
14     if path:
15         precondition(isinstance(path, unicode), path)
16         _default_nodedir = abspath_expanduser_unicode(path)
17
18 if _default_nodedir is None:
19     path = abspath_expanduser_unicode(u"~/.tahoe")
20     precondition(isinstance(path, unicode), path)
21     _default_nodedir = path
22
23 def get_default_nodedir():
24     return _default_nodedir
25
26
27 class BaseOptions(usage.Options):
28     # unit tests can override these to point at StringIO instances
29     stdin = sys.stdin
30     stdout = sys.stdout
31     stderr = sys.stderr
32
33     optFlags = [
34         ["quiet", "q", "Operate silently."],
35         ["version", "V", "Display version numbers and exit."],
36         ["version-and-path", None, "Display version numbers and paths to their locations and exit."],
37     ]
38     optParameters = [
39         ["node-directory", "d", None, "Specify which Tahoe node directory should be used." + (
40             _default_nodedir and (" [default for most commands: " + quote_output(_default_nodedir) + "]") or "")],
41     ]
42
43     def opt_version(self):
44         import allmydata
45         print >>self.stdout, allmydata.get_package_versions_string()
46         sys.exit(0)
47
48     def opt_version_and_path(self):
49         import allmydata
50         print >>self.stdout, allmydata.get_package_versions_string(show_paths=True)
51         sys.exit(0)
52
53
54 class BasedirMixin:
55     default_nodedir = _default_nodedir
56     allow_multiple = True
57
58     optParameters = [
59         ["basedir", "C", None, "Same as --node-directory."],
60     ]
61     optFlags = [
62         ["multiple", "m", "Specify multiple node directories at once."],
63     ]
64
65     def parseArgs(self, *args):
66         if self['node-directory'] and self['basedir']:
67             raise usage.UsageError("The --node-directory (or -d) and --basedir (or -C) "
68                                    "options cannot both be used.")
69
70         if self['node-directory'] or self['basedir']:
71             self.basedirs = [argv_to_abspath(self['node-directory'] or self['basedir'])]
72         else:
73             self.basedirs = []
74
75         if self.allow_multiple and self['multiple']:
76             self.basedirs.extend(map(argv_to_abspath, args))
77         else:
78             if len(args) > 1:
79                 raise usage.UsageError("I wasn't expecting so many arguments." +
80                     (self.allow_multiple and
81                      " Use the --multiple option to specify more than one node directory." or ""))
82
83             if len(args) == 0 and self.default_nodedir and not self.basedirs:
84                 self.basedirs.append(self.default_nodedir)
85             elif len(args) > 0:
86                 self.basedirs.append(argv_to_abspath(args[0]))
87
88     def postOptions(self):
89         if not self.basedirs:
90             raise usage.UsageError("A base directory for the node must be provided.")
91         del self['basedir']
92         self['basedirs'] = self.basedirs
93
94
95 DEFAULT_ALIAS = u"tahoe"
96
97
98 def get_aliases(nodedir):
99     from allmydata import uri
100     aliases = {}
101     aliasfile = os.path.join(nodedir, "private", "aliases")
102     rootfile = os.path.join(nodedir, "private", "root_dir.cap")
103     try:
104         f = open(rootfile, "r")
105         rootcap = f.read().strip()
106         if rootcap:
107             aliases[DEFAULT_ALIAS] = uri.from_string_dirnode(rootcap).to_string()
108     except EnvironmentError:
109         pass
110     try:
111         f = codecs.open(aliasfile, "r", "utf-8")
112         for line in f.readlines():
113             line = line.strip()
114             if line.startswith("#") or not line:
115                 continue
116             name, cap = line.split(u":", 1)
117             # normalize it: remove http: prefix, urldecode
118             cap = cap.strip().encode('utf-8')
119             aliases[name] = uri.from_string_dirnode(cap).to_string()
120     except EnvironmentError:
121         pass
122     return aliases
123
124 class DefaultAliasMarker:
125     pass
126
127 pretend_platform_uses_lettercolon = False # for tests
128 def platform_uses_lettercolon_drivename():
129     if ("win32" in sys.platform.lower()
130         or "cygwin" in sys.platform.lower()
131         or pretend_platform_uses_lettercolon):
132         return True
133     return False
134
135
136 class TahoeError(Exception):
137     def __init__(self, msg):
138         Exception.__init__(self, msg)
139         self.msg = msg
140
141     def display(self, err):
142         print >>err, self.msg
143
144
145 class UnknownAliasError(TahoeError):
146     def __init__(self, msg):
147         TahoeError.__init__(self, "error: " + msg)
148
149
150 def get_alias(aliases, path_unicode, default):
151     """
152     Transform u"work:path/filename" into (aliases[u"work"], u"path/filename".encode('utf-8')).
153     If default=None, then an empty alias is indicated by returning
154     DefaultAliasMarker. We special-case strings with a recognized cap URI
155     prefix, to make it easy to access specific files/directories by their
156     caps.
157     If the transformed alias is either not found in aliases, or is blank
158     and default is not found in aliases, an UnknownAliasError is
159     raised.
160     """
161     precondition(isinstance(path_unicode, unicode), path_unicode)
162
163     from allmydata import uri
164     path = path_unicode.encode('utf-8').strip(" ")
165     if uri.has_uri_prefix(path):
166         # We used to require "URI:blah:./foo" in order to get a subpath,
167         # stripping out the ":./" sequence. We still allow that for compatibility,
168         # but now also allow just "URI:blah/foo".
169         sep = path.find(":./")
170         if sep != -1:
171             return path[:sep], path[sep+3:]
172         sep = path.find("/")
173         if sep != -1:
174             return path[:sep], path[sep+1:]
175         return path, ""
176     colon = path.find(":")
177     if colon == -1:
178         # no alias
179         if default == None:
180             return DefaultAliasMarker, path
181         if default not in aliases:
182             raise UnknownAliasError("No alias specified, and the default "
183                                     "'tahoe' alias doesn't exist. To create "
184                                     "it, use 'tahoe create-alias tahoe'.")
185         return aliases[default], path
186     if colon == 1 and default is None and platform_uses_lettercolon_drivename():
187         # treat C:\why\must\windows\be\so\weird as a local path, not a tahoe
188         # file in the "C:" alias
189         return DefaultAliasMarker, path
190
191     # decoding must succeed because path is valid UTF-8 and colon & space are ASCII
192     alias = path[:colon].decode('utf-8')
193     if u"/" in alias:
194         # no alias, but there's a colon in a dirname/filename, like
195         # "foo/bar:7"
196         if default == None:
197             return DefaultAliasMarker, path
198         if default not in aliases:
199             raise UnknownAliasError("No alias specified, and the default "
200                                     "'tahoe' alias doesn't exist. To create "
201                                     "it, use 'tahoe create-alias tahoe'.")
202         return aliases[default], path
203     if alias not in aliases:
204         raise UnknownAliasError("Unknown alias %s, please create it with 'tahoe add-alias' or 'tahoe create-alias'." %
205                                 quote_output(alias))
206     return aliases[alias], path[colon+1:]
207
208 def escape_path(path):
209     segments = path.split("/")
210     return "/".join([urllib.quote(unicode_to_url(s)) for s in segments])