2 # Copyright (C) 2010, Thomas Leonard
3 # See the README file for details, or visit http://0install.net.
5 from __future__
import print_function
9 from logging
import warn
11 locale
.setlocale(locale
.LC_ALL
, '')
13 warn('Error setting locale (eg. Invalid locale)')
19 from optparse
import OptionParser
21 from zeroinstall
.injector
import reader
, model
22 from zeroinstall
.injector
.config
import load_config
23 from zeroinstall
import support
, alias
, helpers
, _
24 from zeroinstall
.support
import basedir
26 config
= load_config()
28 def export(name
, value
):
29 """Try to guess the command to set an environment variable."""
30 shell
= os
.environ
.get('SHELL', '?')
32 return "setenv %s %s" % (name
, value
)
33 return "export %s=%s" % (name
, value
)
36 """Find the first writable path in the list,
37 skipping /bin, /sbin and everything under /usr except /usr/local/bin"""
39 if path
.startswith('/usr/') and not path
.startswith('/usr/local/bin'):
40 # (/usr/local/bin is OK if we're running as root)
42 elif path
.startswith('/bin') or path
.startswith('/sbin'):
44 elif os
.path
.realpath(path
).startswith(basedir
.xdg_cache_home
):
45 pass # print "Skipping cache", first_path
46 elif not os
.access(path
, os
.W_OK
):
47 pass # print "No access", first_path
55 # Do this here so we can include it in the help message.
56 # But, don't abort if there isn't one because we might
57 # be doing something else (e.g. --manpage)
58 first_path
= find_path(os
.environ
['PATH'].split(':'))
59 in_path
= first_path
is not None
61 first_path
= os
.path
.expanduser('~/bin/')
63 parser
= OptionParser(usage
="usage: %%prog [options] alias [interface [main]]\n\n"
64 "Creates a script to run 'interface' (will be created in\n"
65 "%s unless overridden by --dir).\n\n"
66 "If no interface is given, edits the policy for an existing alias.\n"
67 "For interfaces providing more than one executable, the desired\n"
68 "'main' binary or command may also be given." % first_path
)
69 parser
.add_option("-c", "--command", help="the command the alias will run (default 'run')", metavar
='COMMNAD')
70 parser
.add_option("-m", "--manpage", help="show the manual page for an existing alias", action
='store_true')
71 parser
.add_option("-r", "--resolve", help="show the URI for an alias", action
='store_true')
72 parser
.add_option("-v", "--verbose", help="more verbose output", action
='count')
73 parser
.add_option("-V", "--version", help="display version information", action
='store_true')
74 parser
.add_option("-d", "--dir", help="install in DIR", dest
="user_path", metavar
="DIR")
76 (options
, args
) = parser
.parse_args()
79 logger
= logging
.getLogger()
80 if options
.verbose
== 1:
81 logger
.setLevel(logging
.INFO
)
83 logger
.setLevel(logging
.DEBUG
)
84 hdlr
= logging
.StreamHandler()
85 fmt
= logging
.Formatter("%(levelname)s:%(message)s")
86 hdlr
.setFormatter(fmt
)
87 logger
.addHandler(hdlr
)
91 print("0alias (zero-install) " + zeroinstall
.version
)
92 print("Copyright (C) 2010 Thomas Leonard")
93 print("This program comes with ABSOLUTELY NO WARRANTY,")
94 print("to the extent permitted by law.")
95 print("You may redistribute copies of this program")
96 print("under the terms of the GNU Lesser General Public License.")
97 print("For more information about these matters, see the file named COPYING.")
102 os
.execlp('man', 'man', *args
)
105 if len(args
) < 1 or len(args
) > 3:
108 alias_prog
, interface_uri
, main
= (list(args
) + [None, None])[:3]
109 command
= options
.command
111 if options
.resolve
or options
.manpage
:
112 if interface_uri
is not None:
116 if options
.user_path
:
117 first_path
= options
.user_path
119 if interface_uri
is None:
121 print("Can't use --command when editing an existing alias", file=sys
.stderr
)
124 if not os
.path
.isabs(alias_prog
):
125 full_path
= support
.find_in_path(alias_prog
)
127 raise alias
.NotAnAliasScript("Not found in $PATH: " + alias_prog
)
129 full_path
= alias_prog
131 alias_info
= alias
.parse_script(full_path
)
132 interface_uri
= alias_info
.uri
133 main
= alias_info
.main
134 command
= alias_info
.command
135 except (alias
.NotAnAliasScript
, IOError) as ex
:
136 # (we get IOError if e.g. the script isn't readable)
138 logging
.debug("not a 0alias script '%s': %s", alias_prog
, ex
)
139 os
.execlp('man', 'man', *args
)
140 print(str(ex
), file=sys
.stderr
)
143 interface_uri
= model
.canonical_iface_uri(interface_uri
)
150 sels
= helpers
.ensure_cached(interface_uri
, command
= command
or 'run')
156 selected_command
= sels
.commands
[0]
158 print("No <command> in selections!", file=sys
.stderr
)
159 selected_impl
= sels
.selections
[interface_uri
]
161 impl_path
= selected_impl
.local_path
or config
.iface_cache
.stores
.lookup_any(selected_impl
.digests
)
164 main
= selected_command
.path
166 print("No main program for interface '%s'" % interface_uri
, file=sys
.stderr
)
169 prog_name
= os
.path
.basename(main
)
170 alias_name
= os
.path
.basename(args
[0])
174 # TODO: the feed should say where the man-pages are, but for now we'll accept
175 # a directory called man in some common locations...
176 for mandir
in ['man', 'share/man', 'usr/man', 'usr/share/man']:
177 manpath
= os
.path
.join(impl_path
, mandir
)
178 if os
.path
.isdir(manpath
):
179 # Note: unlike "man -M", this also copes with LANG settings...
180 os
.environ
['MANPATH'] = manpath
181 os
.execlp('man', 'man', prog_name
)
184 # No man directory given or found, so try searching for man files
187 for root
, dirs
, files
in os
.walk(impl_path
):
189 if f
.endswith('.gz'):
190 manpage_file
= f
[:-3]
193 if manpage_file
.endswith('.1') or \
194 manpage_file
.endswith('.6') or \
195 manpage_file
.endswith('.8'):
196 manpage_prog
= manpage_file
[:-2]
197 if manpage_prog
== prog_name
or manpage_prog
== alias_name
:
198 os
.execlp('man', 'man', os
.path
.join(root
, f
))
201 manpages
.append((root
, f
))
203 print("No matching manpage was found for '%s' (%s)" % (alias_name
, interface_uri
))
205 print("These non-matching man-pages were found, however:")
206 for root
, file in manpages
:
207 print(os
.path
.join(root
, file))
210 if not os
.path
.isdir(first_path
):
211 print("(creating directory %s)" % first_path
)
212 os
.makedirs(first_path
)
215 os
.execlp('0launch', '0launch', '-gd', '--', interface_uri
)
219 if not options
.user_path
:
220 if alias_prog
== '0launch':
221 raise model
.SafeException(_('Refusing to create an alias named "0launch" (to avoid an infinite loop)'))
223 interface
= model
.Interface(interface_uri
)
224 if not reader
.update_from_cache(interface
):
225 print("Interface '%s' not currently in cache. Fetching..." % interface_uri
, file=sys
.stderr
)
226 if os
.spawnlp(os
.P_WAIT
, '0launch', '0launch', '-d', interface_uri
):
227 raise model
.SafeException("0launch failed")
228 if not reader
.update_from_cache(interface
):
229 raise model
.SafeException("Interface still not in cache. Aborting.")
231 script
= os
.path
.join(first_path
, alias_prog
)
232 if os
.path
.exists(script
):
233 raise model
.SafeException("File '%s' already exists. Delete it first." % script
)
235 except model
.SafeException
as ex
:
236 print(ex
, file=sys
.stderr
)
239 seen
= set([interface_uri
])
241 feed
= config
.iface_cache
.get_feed(interface_uri
)
242 replacement_uri
= feed
.get_replaced_by()
243 if replacement_uri
is None:
245 print(_("(interface {old} has been replaced by {new}; using that instead)").format(old
= interface_uri
, new
= replacement_uri
))
246 assert replacement_uri
not in seen
, "loop detected!"
247 seen
.add(replacement_uri
)
248 interface_uri
= replacement_uri
250 wrapper
= open(script
, 'w')
251 alias
.write_script(wrapper
, interface_uri
, main
, command
= command
)
253 # Make new script executable
254 os
.chmod(script
, 0o111 | os
.fstat(wrapper
.fileno()).st_mode
)
257 #print "Created script '%s'." % script
258 #print "To edit policy: 0alias %s" % alias_prog
259 if options
.user_path
:
260 pass # Assume user knows what they're doing
262 print('Warning: %s is not in $PATH. Add it with:\n%s' % (first_path
, export('PATH', first_path
+ ':$PATH')), file=sys
.stderr
)
264 shell
= os
.environ
.get('SHELL', '?')
265 if not (shell
.endswith('/zsh') or shell
.endswith('/bash')):
266 print("(note: some shells require you to type 'rehash' now)")