Package zeroinstall :: Package cmd
[frames] | no frames]

Source Code for Package zeroinstall.cmd

  1  """ 
  2  The B{0install} command-line interface. 
  3  """ 
  4   
  5  # Copyright (C) 2011, Thomas Leonard 
  6  # See the README file for details, or visit http://0install.net. 
  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   
21 -class UsageError(Exception): pass
22
23 -def _ensure_standard_fds():
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
34 -class NoCommand(object):
35 """Handle --help and --version""" 36
37 - def add_options(self, parser):
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
53 -class _Completion(object):
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 # Bash does crazy splitting (e.g. "http://foo" becomes "http" ":" "//foo") 67 # Do our best to reverse that splitting here (inspired by Git completion code) 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 # For --opt=value, we get ['--opt', '=', value]. Just get rid of the '='. 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 #print(command_args, self.cword, file = sys.stderr) 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 # Split "--foo=bar" into "--foo", "bar" 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
106 - def got_command(self, command, pos):
107 #print("found %s at %s [cword = %d]" % (command, pos, self.cword), file = sys.stderr) 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
115 - def complete(self, parser, cmd):
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 # (option, args, arg pos) 128 #logger.warning("%s at %d", self.command_args, self.cword) 129 for i, a in enumerate(self.command_args): 130 #logger.warning("%d %s (%d)", i, a, options_possible) 131 if consume_args > 0: 132 #print("consume " + a, file=sys.stderr) 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 # Does it take an argument? 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 #logger.warning("complete option arg %s %s as %s", args[1:], complete_option_arg, metavar) 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 # When completing the URI, contextualise to the app's selections, if possible 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 # Otherwise, complete on all cached URIs 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 #else: logger.warning("%r", metavar) 204
205 - def _complete_option(self, parser):
206 if len(self.current) < 2 or self.current.startswith('--'): 207 # Long option, or nothing yet 208 for opt in parser.option_list: 209 for o in opt._long_opts: 210 self.add("filter", o) 211 else: 212 # Short option: if it's valid, complete it. 213 # Otherwise, reject it. 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):
222 """@type uri: str 223 @type maybe_app: bool 224 @type range_ok: bool""" 225 if maybe_app: 226 app = self.config.app_mgr.lookup_app(uri, missing_ok = True) 227 if app: 228 uri = app.get_requirements().interface_uri 229 230 iface_cache = self.config.iface_cache 231 iface = iface_cache.get_interface(uri) 232 versions = [impl.get_version() for impl in iface_cache.get_implementations(iface)] 233 234 if range_ok and '..' in self.current: 235 prefix = self.current.split('..', 1)[0] + '..!' 236 else: 237 prefix = '' 238 239 for v in sorted(versions): 240 #logger.warning(prefix + v) 241 self.add("filter", prefix + v)
242
243 - def expand_apps(self):
244 for app in self.config.app_mgr.iterate_apps(): 245 self.add("filter", app)
246
247 - def expand_files(self):
248 print("file")
249
250 - def expand_interfaces(self):
251 c = self.current 252 if 'http://'.startswith(c[:7]) or 'https://'.startswith(c[:8]): 253 if c.count('/') < 3: 254 # Start with just the domains 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
273 - def add_filtered(self, value):
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 #print(">%s<" % value, file = sys.stderr) 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 # command_args[2] == "0install" 311 completion = _Completion(config, command_args, shell = shell) 312 313 # The first non-option argument is the command name (or "help" if none is found). 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 # Configure a parser for the given command 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