Added '0install list' sub-command
[zeroinstall/zeroinstall-afb.git] / zeroinstall / injector / cli.py
blob267be41eebe74f36a49fca326915bedca3afac34
1 """
2 The B{0launch} command-line interface.
4 This code is here, rather than in B{0launch} itself, simply so that it gets byte-compiled at
5 install time.
6 """
8 from zeroinstall import _
9 import os, sys
10 from optparse import OptionParser
11 import logging
13 from zeroinstall import SafeException, NeedDownload
14 from zeroinstall.injector import model, autopolicy, selections
15 from zeroinstall.injector.iface_cache import iface_cache
16 from zeroinstall.cmd import UsageError
18 #def program_log(msg): os.access('MARK: 0launch: ' + msg, os.F_OK)
19 #import __main__
20 #__main__.__builtins__.program_log = program_log
21 #program_log('0launch ' + ' '.join((sys.argv[1:])))
23 def _manage_feeds(options, args):
24 from zeroinstall.injector import writer
25 from zeroinstall.injector.handler import Handler
26 from zeroinstall.injector.policy import Policy
28 def find_feed_import(iface, feed_url):
29 for f in iface.extra_feeds:
30 if f.uri == feed_url:
31 return f
32 return None
34 handler = Handler(dry_run = options.dry_run)
35 if not args: raise UsageError()
36 for x in args:
37 print _("Feed '%s':") % x + '\n'
38 x = model.canonical_iface_uri(x)
39 policy = Policy(x, handler)
40 if options.offline:
41 policy.network_use = model.network_offline
43 feed = iface_cache.get_feed(x)
44 if policy.network_use != model.network_offline and policy.is_stale(feed):
45 blocker = policy.fetcher.download_and_import_feed(x, iface_cache.iface_cache)
46 print _("Downloading feed; please wait...")
47 handler.wait_for_blocker(blocker)
48 print _("Done")
50 interfaces = policy.get_feed_targets(x)
51 for i in range(len(interfaces)):
52 if find_feed_import(interfaces[i], x):
53 print _("%(index)d) Remove as feed for '%(uri)s'") % {'index': i + 1, 'uri': interfaces[i].uri}
54 else:
55 print _("%(index)d) Add as feed for '%(uri)s'") % {'index': i + 1, 'uri': interfaces[i].uri}
56 print
57 while True:
58 try:
59 i = raw_input(_('Enter a number, or CTRL-C to cancel [1]: ')).strip()
60 except KeyboardInterrupt:
61 print
62 raise SafeException(_("Aborted at user request."))
63 if i == '':
64 i = 1
65 else:
66 try:
67 i = int(i)
68 except ValueError:
69 i = 0
70 if i > 0 and i <= len(interfaces):
71 break
72 print _("Invalid number. Try again. (1 to %d)") % len(interfaces)
73 iface = interfaces[i - 1]
74 feed_import = find_feed_import(iface, x)
75 if feed_import:
76 iface.extra_feeds.remove(feed_import)
77 else:
78 iface.extra_feeds.append(model.Feed(x, arch = None, user_override = True))
79 writer.save_interface(iface)
80 print '\n' + _("Feed list for interface '%s' is now:") % iface.get_name()
81 if iface.extra_feeds:
82 for f in iface.extra_feeds:
83 print "- " + f.uri
84 else:
85 print _("(no feeds)")
87 def _download_missing_selections(options, sels):
88 from zeroinstall.injector import fetch
89 from zeroinstall.injector.handler import Handler
90 handler = Handler(dry_run = options.dry_run)
91 fetcher = fetch.Fetcher(handler)
92 blocker = sels.download_missing(iface_cache, fetcher)
93 if blocker:
94 logging.info(_("Waiting for selected implementations to be downloaded..."))
95 handler.wait_for_blocker(blocker)
97 def _get_selections(sels, options):
98 if options.show:
99 from zeroinstall import zerostore
100 done = set() # detect cycles
101 def print_node(uri, command, indent):
102 if uri in done: return
103 done.add(uri)
104 impl = sels.selections.get(uri, None)
105 print indent + "- URI:", uri
106 if impl:
107 print indent + " Version:", impl.version
108 try:
109 if impl.id.startswith('package:'):
110 path = "(" + impl.id + ")"
111 else:
112 path = impl.local_path or iface_cache.stores.lookup_any(impl.digests)
113 except zerostore.NotStored:
114 path = "(not cached)"
115 print indent + " Path:", path
116 indent += " "
117 deps = impl.dependencies
118 if command is not None:
119 deps += sels.commands[command].requires
120 for child in deps:
121 if isinstance(child, model.InterfaceDependency):
122 if child.qdom.name == 'runner':
123 child_command = command + 1
124 else:
125 child_command = None
126 print_node(child.interface, child_command, indent)
127 else:
128 print indent + " No selected version"
131 if sels.commands:
132 print_node(sels.interface, 0, "")
133 else:
134 print_node(sels.interface, None, "")
136 else:
137 doc = sels.toDOM()
138 doc.writexml(sys.stdout)
139 sys.stdout.write('\n')
141 def main(command_args):
142 """Act as if 0launch was run with the given arguments.
143 @arg command_args: array of arguments (e.g. C{sys.argv[1:]})
144 @type command_args: [str]
146 # Ensure stdin, stdout and stderr FDs exist, to avoid confusion
147 for std in (0, 1, 2):
148 try:
149 os.fstat(std)
150 except OSError:
151 fd = os.open('/dev/null', os.O_RDONLY)
152 if fd != std:
153 os.dup2(fd, std)
154 os.close(fd)
156 parser = OptionParser(usage=_("usage: %prog [options] interface [args]\n"
157 " %prog --list [search-term]\n"
158 " %prog --import [signed-interface-files]\n"
159 " %prog --feed [interface]"))
160 parser.add_option("", "--before", help=_("choose a version before this"), metavar='VERSION')
161 parser.add_option("", "--command", help=_("command to select"), metavar='COMMAND')
162 parser.add_option("-c", "--console", help=_("never use GUI"), action='store_false', dest='gui')
163 parser.add_option("", "--cpu", help=_("target CPU type"), metavar='CPU')
164 parser.add_option("-d", "--download-only", help=_("fetch but don't run"), action='store_true')
165 parser.add_option("-D", "--dry-run", help=_("just print actions"), action='store_true')
166 parser.add_option("-f", "--feed", help=_("add or remove a feed"), action='store_true')
167 parser.add_option("", "--get-selections", help=_("write selected versions as XML"), action='store_true', dest='xml')
168 parser.add_option("-g", "--gui", help=_("show graphical policy editor"), action='store_true')
169 parser.add_option("-i", "--import", help=_("import from files, not from the network"), action='store_true')
170 parser.add_option("-l", "--list", help=_("list all known interfaces"), action='store_true')
171 parser.add_option("-m", "--main", help=_("name of the file to execute"))
172 parser.add_option("", "--message", help=_("message to display when interacting with user"))
173 parser.add_option("", "--not-before", help=_("minimum version to choose"), metavar='VERSION')
174 parser.add_option("", "--os", help=_("target operation system type"), metavar='OS')
175 parser.add_option("-o", "--offline", help=_("try to avoid using the network"), action='store_true')
176 parser.add_option("-r", "--refresh", help=_("refresh all used interfaces"), action='store_true')
177 parser.add_option("", "--select-only", help=_("only download the feeds"), action='store_true')
178 parser.add_option("", "--set-selections", help=_("run versions specified in XML file"), metavar='FILE')
179 parser.add_option("", "--show", help=_("show where components are installed"), action='store_true')
180 parser.add_option("-s", "--source", help=_("select source code"), action='store_true')
181 parser.add_option("", "--systray", help=_("download in the background"), action='store_true')
182 parser.add_option("-v", "--verbose", help=_("more verbose output"), action='count')
183 parser.add_option("-V", "--version", help=_("display version information"), action='store_true')
184 parser.add_option("", "--with-store", help=_("add an implementation cache"), action='append', metavar='DIR')
185 parser.add_option("-w", "--wrapper", help=_("execute program using a debugger, etc"), metavar='COMMAND')
186 parser.disable_interspersed_args()
188 (options, args) = parser.parse_args(command_args)
190 if options.verbose:
191 logger = logging.getLogger()
192 if options.verbose == 1:
193 logger.setLevel(logging.INFO)
194 else:
195 logger.setLevel(logging.DEBUG)
196 import zeroinstall
197 logging.info(_("Running 0launch %(version)s %(args)s; Python %(python_version)s"), {'version': zeroinstall.version, 'args': repr(args), 'python_version': sys.version})
199 if options.select_only or options.show:
200 options.download_only = True
202 if options.with_store:
203 from zeroinstall import zerostore
204 for x in options.with_store:
205 iface_cache.stores.stores.append(zerostore.Store(os.path.abspath(x)))
206 logging.info(_("Stores search path is now %s"), iface_cache.stores.stores)
208 try:
209 if options.list:
210 from zeroinstall.cmd import list
211 list.handle(options, args)
212 elif options.version:
213 import zeroinstall
214 print "0launch (zero-install) " + zeroinstall.version
215 print "Copyright (C) 2010 Thomas Leonard"
216 print _("This program comes with ABSOLUTELY NO WARRANTY,"
217 "\nto the extent permitted by law."
218 "\nYou may redistribute copies of this program"
219 "\nunder the terms of the GNU Lesser General Public License."
220 "\nFor more information about these matters, see the file named COPYING.")
221 elif options.set_selections:
222 from zeroinstall.injector import qdom, run
223 sels = selections.Selections(qdom.parse(file(options.set_selections)))
224 _download_missing_selections(options, sels)
225 if options.xml:
226 _get_selections(sels, options)
227 elif not options.download_only:
228 run.execute_selections(sels, args, options.dry_run, options.main, options.wrapper)
229 elif getattr(options, 'import'):
230 # (import is a keyword)
231 cmd = __import__('zeroinstall.cmd.import', globals(), locals(), ["import"], 0)
232 cmd.handle(options, args)
233 elif options.feed:
234 _manage_feeds(options, args)
235 elif options.select_only:
236 from zeroinstall.cmd import select
237 if not options.show:
238 options.quiet = True
239 select.handle(options, args)
240 elif options.download_only or options.xml or options.show:
241 from zeroinstall.cmd import download
242 download.handle(options, args)
243 else:
244 if len(args) < 1:
245 if options.gui:
246 from zeroinstall import helpers
247 return helpers.get_selections_gui(None, [])
248 else:
249 raise UsageError()
250 else:
251 from zeroinstall.cmd import run
252 run.handle(options, args)
253 except NeedDownload, ex:
254 # This only happens for dry runs
255 print ex
256 except UsageError:
257 parser.print_help()
258 sys.exit(1)
259 except SafeException, ex:
260 if options.verbose: raise
261 try:
262 print >>sys.stderr, unicode(ex)
263 except:
264 print >>sys.stderr, repr(ex)
265 sys.exit(1)