1 """
2 The B{0install} command-line interface.
3 """
4
5
6
7
8 from __future__ import print_function
9
10 from zeroinstall import _, logger
11 import os, sys
12 from optparse import OptionParser
13 import logging
14
15 from zeroinstall import SafeException, DryRun
16
17 valid_commands = ['add', 'select', 'show', 'download', 'run', 'update', 'whatchanged', 'destroy',
18 'config', 'import', 'list', 'search', 'add-feed', 'remove-feed', 'list-feeds',
19 'man', 'digest']
20
22
24 """Ensure stdin, stdout and stderr FDs exist, to avoid confusion."""
25 for std in (0, 1, 2):
26 try:
27 os.fstat(std)
28 except OSError:
29 fd = os.open(os.devnull, os.O_RDONLY)
30 if fd != std:
31 os.dup2(fd, std)
32 os.close(fd)
33
35 """Handle --help and --version"""
36
38 parser.add_option("-V", "--version", help=_("display version information"), action='store_true')
39
40 - def handle(self, config, options, args):
41 if options.version:
42 import zeroinstall
43 print("0install (zero-install) " + zeroinstall.version)
44 print("Copyright (C) 2013 Thomas Leonard")
45 print(_("This program comes with ABSOLUTELY NO WARRANTY,"
46 "\nto the extent permitted by law."
47 "\nYou may redistribute copies of this program"
48 "\nunder the terms of the GNU Lesser General Public License."
49 "\nFor more information about these matters, see the file named COPYING."))
50 sys.exit(0)
51 raise UsageError()
52
54 - def __init__(self, config, command_args, shell):
55 """@type command_args: [str]
56 @type shell: str"""
57 assert shell in ('zsh', 'bash'), shell
58 self.shell = shell
59 self.config = config
60 self.cword = int(os.environ['COMP_CWORD']) - 1
61 self.response_prefix = ''
62 if shell == 'zsh':
63 self.cword -= 1
64
65 if shell == 'bash':
66
67
68 command_args = command_args[:]
69 while ':' in command_args[1:]:
70 i = command_args.index(':', 1)
71 combined = command_args[i - 1] + command_args[i]
72 if i + 1 < len(command_args):
73 combined += command_args[i + 1]
74 command_args = command_args[:i - 1] + [combined] + command_args[i + 2:]
75 if self.cword > i:
76 self.cword -= 2
77 elif self.cword == i:
78 self.cword -= 1
79
80 if self.cword > 0 and command_args[self.cword - 1] == '=':
81 del command_args[self.cword - 1]
82 self.cword -= 1
83 elif command_args[self.cword] == '=':
84 command_args[self.cword] = ''
85
86 self.command_args = command_args
87
88 if self.cword < len(command_args):
89 self.current = command_args[self.cword]
90 else:
91 self.current = ''
92
93 if shell == 'zsh':
94 if self.current.startswith('--') and '=' in self.current:
95
96 name, value = self.current.split('=', 1)
97 command_args[self.cword:self.cword + 1] = [name, value]
98 self.cword += 1
99 self.current = command_args[self.cword]
100 self.response_prefix = name + '='
101 else:
102 self.response_prefix = ''
103
104 self.command_args = command_args
105
107
108 """@type command: str
109 @type pos: int"""
110 if pos == self.cword:
111 for command in valid_commands:
112 self.add("filter", command)
113 sys.exit(0)
114
116 opts = {}
117 for opt in parser.option_list:
118 for name in opt._short_opts:
119 opts[name] = opt
120 for name in opt._long_opts:
121 opts[name] = opt
122
123 options_possible = True
124 arg_word = -1
125 args = []
126 consume_args = 0
127 complete_option_arg = None
128
129 for i, a in enumerate(self.command_args):
130
131 if consume_args > 0:
132
133 consume_args -= 1
134 elif a == '--' and options_possible and i != self.cword:
135 options_possible = False
136 elif a.startswith('-') and options_possible:
137 if i == self.cword:
138 self._complete_option(parser)
139 return
140
141 option_with_args = None
142 if a.startswith('--'):
143 opt = opts.get(a, None)
144 if opt and opt.nargs:
145 option_with_args = opt
146 else:
147 for l in a[1:]:
148 opt = opts.get('-' + l, None)
149 if opt and opt.nargs:
150 option_with_args = opt
151 break
152
153 if option_with_args:
154 consume_args = option_with_args.nargs
155
156 option_arg_index = self.cword - i - 1
157 if option_arg_index >= 0 and option_arg_index < consume_args:
158 complete_option_arg = (option_with_args,
159 self.command_args[i + 1 : i + 1 + consume_args],
160 option_arg_index)
161 else:
162 if len(args) > 0 and options_possible and not parser.allow_interspersed_args:
163 options_possible = False
164 args.append(a)
165 if i < self.cword:
166 arg_word += 1
167
168 if complete_option_arg is None:
169 if hasattr(cmd, 'complete'):
170 if arg_word == len(args) - 1: args.append('')
171 cmd.complete(self, args[1:], arg_word)
172 else:
173 metavar = complete_option_arg[0].metavar
174
175 if metavar == 'DIR':
176 self.expand_files()
177 elif metavar == 'OS':
178 for value in ["Cygwin", "Darwin", "FreeBSD", "Linux", "MacOSX", "Windows"]:
179 self.add("filter", value)
180 elif metavar == 'CPU':
181 for value in ["src", "i386", "i486", "i586", "i686", "ppc", "ppc64", "x86_64"]:
182 self.add("filter", value)
183 elif metavar == 'URI RANGE':
184 if complete_option_arg[2] == 0:
185
186 if len(args) > 1:
187 app = self.config.app_mgr.lookup_app(args[1], missing_ok = True)
188 if app:
189 for uri in app.get_selections().selections:
190 self.add("filter", uri)
191 return
192
193 self.expand_interfaces()
194 else:
195 self.expand_range(complete_option_arg[1][0])
196 elif metavar in ('RANGE', 'VERSION'):
197 if len(args) > 1:
198 self.expand_range(args[1], maybe_app = True, range_ok = metavar == 'RANGE')
199 elif metavar == 'HASH':
200 from zeroinstall.zerostore import manifest
201 for alg in sorted(manifest.algorithms):
202 self.add("filter", alg)
203
204
206 if len(self.current) < 2 or self.current.startswith('--'):
207
208 for opt in parser.option_list:
209 for o in opt._long_opts:
210 self.add("filter", o)
211 else:
212
213
214 valid = set()
215 for opt in parser.option_list:
216 for o in opt._short_opts:
217 valid.add(o[1:])
218 if all(char in valid for char in self.current[1:]):
219 self.add("add", self.current)
220
221 - def expand_range(self, uri, maybe_app = False, range_ok = True):
242
246
249
251 c = self.current
252 if 'http://'.startswith(c[:7]) or 'https://'.startswith(c[:8]):
253 if c.count('/') < 3:
254
255 import re
256 start = re.compile('(https?://[^/]+/).*')
257 starts = set()
258 for iface in self.config.iface_cache.list_all_interfaces():
259 if not iface.startswith(c):continue
260 match = start.match(iface)
261 if match:
262 starts.add(match.group(1))
263 for s in sorted(starts):
264 self.add("prefix", s)
265 else:
266 for iface in self.config.iface_cache.list_all_interfaces():
267 if iface.startswith(c):
268 self.add("filter", iface)
269
270 if '://' not in c:
271 self.expand_files()
272
274 """Add this value, but only if it matches the prefix.
275 @type value: str"""
276 self.add("filter", value)
277
278 - def add(self, type, value):
279 """Types are:
280 add - a raw string to add
281 filter - a string to add only if it matches
282 prefix - a completion that doesn't insert a space after it.
283 @type type: str
284 @type value: str"""
285 if self.shell == 'bash':
286 if ':' in self.current:
287 ignored = self.current.rsplit(':', 1)[0] + ':'
288 if not value.startswith(ignored): return
289 value = value[len(ignored):]
290
291 if type != 'prefix':
292 value += ' '
293 print(type, self.response_prefix + value)
294
295 -def main(command_args, config = None):
296 """Act as if 0install was run with the given arguments.
297 @type command_args: [str]
298 @type config: L{zeroinstall.injector.config.Config} | None
299 @arg command_args: array of arguments (e.g. C{sys.argv[1:]})"""
300 _ensure_standard_fds()
301
302 if config is None:
303 from zeroinstall.injector.config import load_config
304 config = load_config()
305
306 completion = None
307 if command_args and command_args[0] == '_complete':
308 shell = command_args[1]
309 command_args = command_args[3:]
310
311 completion = _Completion(config, command_args, shell = shell)
312
313
314 command = None
315 for i, arg in enumerate(command_args):
316 if not arg.startswith('-'):
317 command = arg
318 command_args = command_args[:i] + command_args[i + 1:]
319 if completion:
320 completion.got_command(command, i)
321 break
322 elif arg == '--':
323 break
324 else:
325 if completion:
326 completion.got_command(None, len(command_args))
327
328 verbose = False
329 try:
330
331 if command:
332 if command not in valid_commands:
333 if completion:
334 return
335 raise SafeException(_("Unknown sub-command '%s': try --help") % command)
336
337 module_name = command.replace('-', '_')
338 cmd = __import__('zeroinstall.cmd.' + module_name, globals(), locals(), [module_name], 0)
339 parser = OptionParser(usage=_("usage: %%prog %s [OPTIONS] %s") % (command, cmd.syntax))
340 else:
341 cmd = NoCommand()
342 parser = OptionParser(usage=_("usage: %prog COMMAND\n\nTry --help with one of these:") +
343 "\n\n0install " + '\n0install '.join(valid_commands))
344
345 parser.add_option("-c", "--console", help=_("never use GUI"), action='store_false', dest='gui')
346 parser.add_option("", "--dry-run", help=_("just print what would be executed"), action='store_true')
347 parser.add_option("-g", "--gui", help=_("show graphical policy editor"), action='store_true')
348 parser.add_option("-v", "--verbose", help=_("more verbose output"), action='count')
349 parser.add_option("", "--with-store", help=_("add an implementation cache"), action='append', metavar='DIR')
350
351 cmd.add_options(parser)
352
353 if completion:
354 completion.complete(parser, cmd)
355 return
356
357 (options, args) = parser.parse_args(command_args)
358 verbose = options.verbose
359
360 if options.verbose:
361 if options.verbose == 1:
362 logger.setLevel(logging.INFO)
363 else:
364 logger.setLevel(logging.DEBUG)
365 import zeroinstall
366 logger.info(_("Running 0install %(version)s %(args)s; Python %(python_version)s"), {'version': zeroinstall.version, 'args': repr(command_args), 'python_version': sys.version})
367
368 if options.with_store:
369 from zeroinstall import zerostore
370 for x in options.with_store:
371 config.stores.stores.append(zerostore.Store(os.path.abspath(x)))
372 logger.info(_("Stores search path is now %s"), config.stores.stores)
373
374 config.handler.dry_run = bool(options.dry_run)
375 if config.handler.dry_run:
376 if options.gui is True:
377 raise SafeException(_("Can't use --gui with --dry-run"))
378 options.gui = False
379
380 cmd.handle(config, options, args)
381 except KeyboardInterrupt:
382 logger.info("KeyboardInterrupt")
383 sys.exit(1)
384 except UsageError:
385 parser.print_help()
386 sys.exit(1)
387 except DryRun as ex:
388 print(_("[dry-run]"), ex)
389 except SafeException as ex:
390 if verbose: raise
391 try:
392 from zeroinstall.support import unicode
393 print(unicode(ex), file=sys.stderr)
394 except:
395 print(repr(ex), file=sys.stderr)
396 sys.exit(1)
397 return
398