Added test for HTTP redirects
[zeroinstall/solver.git] / 0alias
blob4d8e41c75550e95b2edd0fe3f7ebde2715281eb3
1 #!/usr/bin/env python
2 # Copyright (C) 2010, Thomas Leonard
3 # See the README file for details, or visit http://0install.net.
5 from __future__ import print_function
7 import locale
8 import logging
9 from logging import warn
10 try:
11 locale.setlocale(locale.LC_ALL, '')
12 except locale.Error:
13 warn('Error setting locale (eg. Invalid locale)')
15 import os, sys
17 ## PATH ##
19 from optparse import OptionParser
21 from zeroinstall.injector import reader, model
22 from zeroinstall import support, alias, helpers
23 from zeroinstall.support import basedir
25 def export(name, value):
26 """Try to guess the command to set an environment variable."""
27 shell = os.environ.get('SHELL', '?')
28 if 'csh' in shell:
29 return "setenv %s %s" % (name, value)
30 return "export %s=%s" % (name, value)
32 def find_path(paths):
33 """Find the first writable path in : separated list."""
34 for path in paths:
35 if os.path.realpath(path).startswith(basedir.xdg_cache_home):
36 pass # print "Skipping cache", first_path
37 elif not os.access(path, os.W_OK):
38 pass # print "No access", first_path
39 else:
40 break
41 else:
42 return None
44 return path
46 # Do this here so we can include it in the help message.
47 # But, don't abort if there isn't one because we might
48 # be doing something else (e.g. --manpage)
49 first_path = find_path(os.environ['PATH'].split(':'))
50 in_path = first_path is not None
51 if not in_path:
52 first_path = os.path.expanduser('~/bin/')
54 parser = OptionParser(usage="usage: %%prog [options] alias [interface [main]]\n\n"
55 "Creates a script to run 'interface' (will be created in\n"
56 "%s unless overridden by --dir).\n\n"
57 "If no interface is given, edits the policy for an existing alias.\n"
58 "For interfaces providing more than one executable, the desired\n"
59 "'main' binary or command may also be given." % first_path)
60 parser.add_option("-c", "--command", help="the command the alias will run (default 'run')", metavar='COMMNAD')
61 parser.add_option("-m", "--manpage", help="show the manual page for an existing alias", action='store_true')
62 parser.add_option("-r", "--resolve", help="show the URI for an alias", action='store_true')
63 parser.add_option("-v", "--verbose", help="more verbose output", action='count')
64 parser.add_option("-V", "--version", help="display version information", action='store_true')
65 parser.add_option("-d", "--dir", help="install in DIR", dest="user_path", metavar="DIR")
67 (options, args) = parser.parse_args()
69 if options.verbose:
70 logger = logging.getLogger()
71 if options.verbose == 1:
72 logger.setLevel(logging.INFO)
73 else:
74 logger.setLevel(logging.DEBUG)
75 hdlr = logging.StreamHandler()
76 fmt = logging.Formatter("%(levelname)s:%(message)s")
77 hdlr.setFormatter(fmt)
78 logger.addHandler(hdlr)
80 if options.version:
81 import zeroinstall
82 print("0alias (zero-install) " + zeroinstall.version)
83 print("Copyright (C) 2010 Thomas Leonard")
84 print("This program comes with ABSOLUTELY NO WARRANTY,")
85 print("to the extent permitted by law.")
86 print("You may redistribute copies of this program")
87 print("under the terms of the GNU Lesser General Public License.")
88 print("For more information about these matters, see the file named COPYING.")
89 sys.exit(0)
91 if options.manpage:
92 if len(args) != 1:
93 os.execlp('man', 'man', *args)
94 sys.exit(1)
96 if len(args) < 1 or len(args) > 3:
97 parser.print_help()
98 sys.exit(1)
99 alias_prog, interface_uri, main = (list(args) + [None, None])[:3]
100 command = options.command
102 if options.resolve or options.manpage:
103 if interface_uri is not None:
104 parser.print_help()
105 sys.exit(1)
107 if options.user_path:
108 first_path = options.user_path
110 if interface_uri is None:
111 if options.command:
112 print("Can't use --command when editing an existing alias", file=sys.stderr)
113 sys.exit(1)
114 try:
115 if not os.path.isabs(alias_prog):
116 full_path = support.find_in_path(alias_prog)
117 if not full_path:
118 raise alias.NotAnAliasScript("Not found in $PATH: " + alias_prog)
119 else:
120 full_path = alias_prog
122 alias_info = alias.parse_script(full_path)
123 interface_uri = alias_info.uri
124 main = alias_info.main
125 command = alias_info.command
126 except (alias.NotAnAliasScript, IOError) as ex:
127 # (we get IOError if e.g. the script isn't readable)
128 if options.manpage:
129 logging.debug("not a 0alias script '%s': %s", alias_prog, ex)
130 os.execlp('man', 'man', *args)
131 print(str(ex), file=sys.stderr)
132 sys.exit(1)
134 interface_uri = model.canonical_iface_uri(interface_uri)
136 if options.resolve:
137 print(interface_uri)
138 sys.exit(0)
140 if options.manpage:
141 sels = helpers.ensure_cached(interface_uri, command = command or 'run')
142 if not sels:
143 # Cancelled by user
144 sys.exit(1)
146 if sels.commands:
147 selected_command = sels.commands[0]
148 else:
149 print("No <command> in selections!", file=sys.stderr)
150 selected_impl = sels.selections[interface_uri]
152 from zeroinstall.injector.iface_cache import iface_cache
153 impl_path = selected_impl.local_path or iface_cache.stores.lookup_any(selected_impl.digests)
155 if main is None:
156 main = selected_command.path
157 if main is None:
158 print("No main program for interface '%s'" % interface_uri, file=sys.stderr)
159 sys.exit(1)
161 prog_name = os.path.basename(main)
162 alias_name = os.path.basename(args[0])
164 assert impl_path
166 # TODO: the feed should say where the man-pages are, but for now we'll accept
167 # a directory called man in some common locations...
168 for mandir in ['man', 'share/man', 'usr/man', 'usr/share/man']:
169 manpath = os.path.join(impl_path, mandir)
170 if os.path.isdir(manpath):
171 # Note: unlike "man -M", this also copes with LANG settings...
172 os.environ['MANPATH'] = manpath
173 os.execlp('man', 'man', prog_name)
174 sys.exit(1)
176 # No man directory given or found, so try searching for man files
178 manpages = []
179 for root, dirs, files in os.walk(impl_path):
180 for f in files:
181 if f.endswith('.gz'):
182 manpage_file = f[:-3]
183 else:
184 manpage_file = f
185 if manpage_file.endswith('.1') or \
186 manpage_file.endswith('.6') or \
187 manpage_file.endswith('.8'):
188 manpage_prog = manpage_file[:-2]
189 if manpage_prog == prog_name or manpage_prog == alias_name:
190 os.execlp('man', 'man', os.path.join(root, f))
191 sys.exit(1)
192 else:
193 manpages.append((root, f))
195 print("No matching manpage was found for '%s' (%s)" % (alias_name, interface_uri))
196 if manpages:
197 print("These non-matching man-pages were found, however:")
198 for root, file in manpages:
199 print(os.path.join(root, file))
200 sys.exit(1)
202 if not os.path.isdir(first_path):
203 print("(creating directory %s)" % first_path)
204 os.makedirs(first_path)
206 if len(args) == 1:
207 os.execlp('0launch', '0launch', '-gd', '--', interface_uri)
208 sys.exit(1)
210 try:
211 interface = model.Interface(interface_uri)
212 if not reader.update_from_cache(interface):
213 print("Interface '%s' not currently in cache. Fetching..." % interface_uri, file=sys.stderr)
214 if os.spawnlp(os.P_WAIT, '0launch', '0launch', '-d', interface_uri):
215 raise model.SafeException("0launch failed")
216 if not reader.update_from_cache(interface):
217 raise model.SafeException("Interface still not in cache. Aborting.")
219 script = os.path.join(first_path, alias_prog)
220 if os.path.exists(script):
221 raise model.SafeException("File '%s' already exists. Delete it first." % script)
222 sys.exit(1)
223 except model.SafeException as ex:
224 print(ex, file=sys.stderr)
225 sys.exit(1)
227 wrapper = open(script, 'w')
228 alias.write_script(wrapper, interface_uri, main, command = command)
230 # Make new script executable
231 os.chmod(script, 0o111 | os.fstat(wrapper.fileno()).st_mode)
232 wrapper.close()
234 #print "Created script '%s'." % script
235 #print "To edit policy: 0alias %s" % alias_prog
236 if options.user_path:
237 pass # Assume user knows what they're doing
238 elif not in_path:
239 print('Warning: %s is not in $PATH. Add it with:\n%s' % (first_path, export('PATH', first_path + ':$PATH')), file=sys.stderr)
240 else:
241 shell = os.environ.get('SHELL', '?')
242 if not (shell.endswith('/zsh') or shell.endswith('/bash')):
243 print("(note: some shells require you to type 'rehash' now)")