2 import os, sys, urllib, textwrap
4 from twisted.python import usage
5 from allmydata.util.assertutil import precondition
6 from allmydata.util.encodingutil import unicode_to_url, quote_output, \
7 quote_local_unicode_path, argv_to_abspath
8 from allmydata.scripts.default_nodedir import _default_nodedir
10 def get_default_nodedir():
11 return _default_nodedir
13 def wrap_paragraphs(text, width):
14 # like textwrap.wrap(), but preserve paragraphs (delimited by double
15 # newlines) and leading whitespace, and remove internal whitespace.
16 text = textwrap.dedent(text)
17 if text.startswith("\n"):
19 return "\n\n".join([textwrap.fill(paragraph, width=width)
20 for paragraph in text.split("\n\n")])
22 class BaseOptions(usage.Options):
24 super(BaseOptions, self).__init__()
25 self.command_name = os.path.basename(sys.argv[0])
26 if self.command_name == 'trial':
27 self.command_name = 'tahoe'
29 # Only allow "tahoe --version", not e.g. "tahoe start --version"
30 def opt_version(self):
31 raise usage.UsageError("--version not allowed on subcommands")
34 description_unwrapped = None
37 width = int(os.environ.get('COLUMNS', '80'))
38 s = (self.getSynopsis() + '\n' +
39 "(use 'tahoe --help' to view global options)\n" +
43 s += '\n' + wrap_paragraphs(self.description, width) + '\n'
44 if self.description_unwrapped:
45 du = textwrap.dedent(self.description_unwrapped)
46 if du.startswith("\n"):
51 class BasedirOptions(BaseOptions):
52 default_nodedir = _default_nodedir
55 ["basedir", "C", None, "Specify which Tahoe base directory should be used. [default: %s]"
56 % quote_local_unicode_path(_default_nodedir)],
59 def parseArgs(self, basedir=None):
60 # This finds the node-directory option correctly even if we are in a subcommand.
62 while root.parent is not None:
65 if root['node-directory'] and self['basedir']:
66 raise usage.UsageError("The --node-directory (or -d) and --basedir (or -C) options cannot both be used.")
67 if root['node-directory'] and basedir:
68 raise usage.UsageError("The --node-directory (or -d) option and a basedir argument cannot both be used.")
69 if self['basedir'] and basedir:
70 raise usage.UsageError("The --basedir (or -C) option and a basedir argument cannot both be used.")
73 b = argv_to_abspath(basedir)
75 b = argv_to_abspath(self['basedir'])
76 elif root['node-directory']:
77 b = argv_to_abspath(root['node-directory'])
78 elif self.default_nodedir:
79 b = self.default_nodedir
81 raise usage.UsageError("No default basedir available, you must provide one with --node-directory, --basedir, or a basedir argument")
83 self['node-directory'] = b
85 def postOptions(self):
86 if not self['basedir']:
87 raise usage.UsageError("A base directory for the node must be provided.")
89 class NoDefaultBasedirOptions(BasedirOptions):
90 default_nodedir = None
93 ["basedir", "C", None, "Specify which Tahoe base directory should be used."],
96 # This is overridden in order to ensure we get a "Wrong number of arguments."
97 # error when more than one argument is given.
98 def parseArgs(self, basedir=None):
99 BasedirOptions.parseArgs(self, basedir)
101 def getSynopsis(self):
102 return "Usage: %s [global-options] %s [options] NODEDIR" % (self.command_name, self.subcommand_name)
105 DEFAULT_ALIAS = u"tahoe"
108 def get_aliases(nodedir):
110 aliasfile = os.path.join(nodedir, "private", "aliases")
111 rootfile = os.path.join(nodedir, "private", "root_dir.cap")
113 f = open(rootfile, "r")
114 rootcap = f.read().strip()
116 aliases[DEFAULT_ALIAS] = rootcap
117 except EnvironmentError:
120 f = codecs.open(aliasfile, "r", "utf-8")
121 for line in f.readlines():
123 if line.startswith("#") or not line:
125 name, cap = line.split(u":", 1)
126 # normalize it: remove http: prefix, urldecode
127 cap = cap.strip().encode('utf-8')
129 except EnvironmentError:
133 class DefaultAliasMarker:
136 pretend_platform_uses_lettercolon = False # for tests
137 def platform_uses_lettercolon_drivename():
138 if ("win32" in sys.platform.lower()
139 or "cygwin" in sys.platform.lower()
140 or pretend_platform_uses_lettercolon):
145 class TahoeError(Exception):
146 def __init__(self, msg):
147 Exception.__init__(self, msg)
150 def display(self, err):
151 print >>err, self.msg
154 class UnknownAliasError(TahoeError):
155 def __init__(self, msg):
156 TahoeError.__init__(self, "error: " + msg)
159 def get_alias(aliases, path_unicode, default):
161 Transform u"work:path/filename" into (aliases[u"work"], u"path/filename".encode('utf-8')).
162 If default=None, then an empty alias is indicated by returning
163 DefaultAliasMarker. We special-case strings with a recognized cap URI
164 prefix, to make it easy to access specific files/directories by their
166 If the transformed alias is either not found in aliases, or is blank
167 and default is not found in aliases, an UnknownAliasError is
170 precondition(isinstance(path_unicode, unicode), path_unicode)
172 from allmydata import uri
173 path = path_unicode.encode('utf-8').strip(" ")
174 if uri.has_uri_prefix(path):
175 # We used to require "URI:blah:./foo" in order to get a subpath,
176 # stripping out the ":./" sequence. We still allow that for compatibility,
177 # but now also allow just "URI:blah/foo".
178 sep = path.find(":./")
180 return path[:sep], path[sep+3:]
183 return path[:sep], path[sep+1:]
185 colon = path.find(":")
189 return DefaultAliasMarker, path
190 if default not in aliases:
191 raise UnknownAliasError("No alias specified, and the default %s alias doesn't exist. "
192 "To create it, use 'tahoe create-alias %s'."
193 % (quote_output(default), quote_output(default, quotemarks=False)))
194 return uri.from_string_dirnode(aliases[default]).to_string(), path
195 if colon == 1 and default is None and platform_uses_lettercolon_drivename():
196 # treat C:\why\must\windows\be\so\weird as a local path, not a tahoe
197 # file in the "C:" alias
198 return DefaultAliasMarker, path
200 # decoding must succeed because path is valid UTF-8 and colon & space are ASCII
201 alias = path[:colon].decode('utf-8')
203 # no alias, but there's a colon in a dirname/filename, like
206 return DefaultAliasMarker, path
207 if default not in aliases:
208 raise UnknownAliasError("No alias specified, and the default %s alias doesn't exist. "
209 "To create it, use 'tahoe create-alias %s'."
210 % (quote_output(default), quote_output(default, quotemarks=False)))
211 return uri.from_string_dirnode(aliases[default]).to_string(), path
212 if alias not in aliases:
213 raise UnknownAliasError("Unknown alias %s, please create it with 'tahoe add-alias' or 'tahoe create-alias'." %
215 return uri.from_string_dirnode(aliases[alias]).to_string(), path[colon+1:]
217 def escape_path(path):
218 # this always returns bytes, specifically US-ASCII, valid URL characters
219 segments = path.split("/")
220 return "/".join([urllib.quote(unicode_to_url(s)) for s in segments])