Release 0.18
[0publish.git] / 0publish
blob5bacaeca6774dc1941769f52d7867ee5cddae3be
1 #!/usr/bin/env python
2 from zeroinstall import SafeException
3 from xml.dom import minidom
4 from optparse import OptionParser
5 import os, sys
6 import signing
7 from logging import info, debug
8 import edit, validator, create
10 version = '0.18'
12 parser = OptionParser(usage="usage: %prog [options] interface")
13 parser.add_option("--add-types", help="add missing MIME-type attributes", action='store_true')
14 parser.add_option("--add-version", help="add a new implementation", action='store', metavar='VERSION')
15 parser.add_option("--archive-url", help="add archive at this URL", action='store', metavar='URL')
16 parser.add_option("--archive-file", help="local copy of archive-url", action='store', metavar='FILE')
17 parser.add_option("--archive-extract", help="subdirectory of archive to extract", action='store', metavar='DIR')
18 parser.add_option("-c", "--create", help="create file if nonexistant", action='store_true')
19 parser.add_option("-d", "--add-digest", help="add extra digests", action='store', metavar='ALG')
20 parser.add_option("-e", "--edit", help="edit with $EDITOR", action='store_true')
21 parser.add_option("-g", "--gpgsign", help="add a GPG signature block", action='store_true')
22 parser.add_option("-k", "--key", help="key to use for signing")
23 parser.add_option("-l", "--local", help="create feed from local interface")
24 parser.add_option("--manifest-algorithm", help="select algorithm for manifests", action='append', metavar='ALG')
25 parser.add_option("--set-interface-uri", help="set interface URI", action='store', metavar='URI')
26 parser.add_option("--set-id", help="set implementation ID", action='store', metavar='DIGEST')
27 parser.add_option("--set-main", help="set main executable", action='store', metavar='EXEC')
28 parser.add_option("--set-arch", help="set architecture", action='store', metavar='ARCH')
29 parser.add_option("--set-released", help="set release date", action='store', metavar='DATE')
30 parser.add_option("--set-stability", help="set stability", action='store', metavar='STABILITY')
31 parser.add_option("--set-version", help="set version number", action='store', metavar='VERSION')
32 parser.add_option("-s", "--stable", help="mark testing version stable", action='store_true')
33 parser.add_option("", "--select-version", help="select version to use in --set-* commands", action='store', metavar='VERSION')
34 parser.add_option("-x", "--xmlsign", help="add an XML signature block", action='store_true')
35 parser.add_option("-u", "--unsign", help="remove any signature", action='store_true')
36 parser.add_option("-v", "--verbose", help="more verbose output", action='count')
37 parser.add_option("-V", "--version", help="display version information", action='store_true')
39 (options, args) = parser.parse_args()
41 force_save = options.create
43 if options.version:
44 print "0publish (zero-install) " + version
45 print "Copyright (C) 2005-2010 Thomas Leonard"
46 print "This program comes with ABSOLUTELY NO WARRANTY,"
47 print "to the extent permitted by law."
48 print "You may redistribute copies of this program"
49 print "under the terms of the GNU General Public License."
50 print "For more information about these matters, see the file named COPYING."
51 sys.exit(0)
53 if options.verbose:
54 import logging
55 logger = logging.getLogger()
56 if options.verbose == 1:
57 logger.setLevel(logging.INFO)
58 else:
59 logger.setLevel(logging.DEBUG)
61 if len(args) != 1:
62 parser.print_help()
63 sys.exit(1)
64 interface = args[0]
66 def confirm(q):
67 while True:
68 ans = raw_input(q + " [Y/N] ").lower()
69 if ans in ('y', 'yes'): return True
70 if ans in ('n', 'no'): return False
72 try:
73 # Load or create the starting data...
75 if os.path.exists(interface):
76 contents = file(interface).read()
77 data, sign_fn, key = signing.check_signature(interface)
78 elif options.local:
79 if os.path.exists(options.local):
80 data = create.create_from_local(options.local)
81 sign_fn = signing.sign_unsigned
82 key = None
83 force_save = True
84 options.local = False
85 else:
86 raise Exception("File '%s' does not exist." % options.local)
87 else:
88 if options.create or confirm("Interface file '%s' does not exist. Create it?" % interface):
89 data = create.create(interface)
90 sign_fn = signing.sign_unsigned
91 key = None
92 options.edit = not options.create
93 else:
94 sys.exit(1)
96 debug("Original data: %s", data)
97 info("Original signing method: %s", sign_fn.__name__)
98 info("Original key: %s", key)
100 old_data = data
101 old_sign_fn = sign_fn
102 old_key = key
104 if sign_fn is signing.sign_unsigned and options.key:
105 sign_fn = signing.sign_xml
107 while True:
108 # Validate the input...
109 try:
110 validator.check(data, warnings = False) # Don't warn on load AND save!
111 break
112 except validator.InvalidInterface, ex:
113 print "Invalid interface: " + str(ex)
115 while True:
116 ans = raw_input("Interface is invalid. (E)dit or (A)bort?").lower()
117 if ans in ('e', 'edit'):
118 data = edit.edit(data)
119 options.edit = False # Don't edit twice
120 break
121 if ans in ('a', 'abort'): sys.exit(1)
123 # Process it...
124 if options.xmlsign:
125 sign_fn = signing.sign_xml
126 if options.unsign:
127 sign_fn = signing.sign_unsigned
128 if options.gpgsign:
129 sign_fn = signing.sign_plain
130 if options.key:
131 print "Changing key from '%s' to '%s'" % (key, options.key)
132 key = options.key
133 if options.set_interface_uri:
134 import release
135 data = release.set_interface_uri(data, options.set_interface_uri)
136 if options.add_version:
137 import release
138 data = release.add_version(data, options.add_version)
139 if options.set_id or options.set_version or options.set_released or \
140 options.set_stability or options.set_arch or options.set_main:
141 import release
142 data = release.set_attributes(data, options.select_version,
143 id = options.set_id,
144 version = options.set_version,
145 released = options.set_released,
146 stability = options.set_stability,
147 main = options.set_main,
148 arch = options.set_arch)
149 if options.stable:
150 assert not options.select_version, "Use --set-stability=stable --select-version=... instead"
151 import stable
152 data = stable.mark_stable(data)
153 if options.archive_url:
154 import archive
155 algs = options.manifest_algorithm
156 if algs is None:
157 algs = ['sha1new']
158 import hashlib
159 if hasattr(hashlib, 'sha256'):
160 algs.append('sha256')
161 data = archive.add_archive(data, options.archive_url, options.archive_file, options.archive_extract, algs)
162 elif options.archive_file or options.archive_extract:
163 raise Exception('Must use --archive-uri option')
164 if options.local:
165 import merge
166 data = merge.merge(data, options.local)
167 if options.add_digest:
168 import digest
169 data = digest.add_digests(data, alg = options.add_digest)
170 if options.add_types:
171 import mimetypes
172 data = mimetypes.add_types(data)
173 if options.edit:
174 data = edit.edit(data)
176 while True:
177 # Validate the result...
178 try:
179 validator.check(data)
180 break
181 except validator.InvalidInterface, ex:
182 print "Invalid interface: " + str(ex)
184 while True:
185 ans = raw_input("Interface is invalid. (E)dit or (A)bort?").lower()
186 if ans in ('e', 'edit'):
187 data = edit.edit(data)
188 break
189 if ans in ('a', 'abort'): sys.exit(1)
191 if (old_data == data and sign_fn == old_sign_fn and key == old_key) and not force_save:
192 print "Interface unchanged. Not writing."
193 sys.exit(1)
195 # Tidy up the XML
196 doc = minidom.parseString(data)
197 data = create.xml_header + doc.documentElement.toxml('utf-8')
199 # Write it back out
200 if not data.endswith('\n'): data += '\n'
201 sign_fn(interface, data, key)
203 info("Wrote '%s'", interface)
205 if sign_fn != signing.sign_unsigned:
206 # Read it back in to find out what key we signed it with
207 # and ensure that the key has been exported
208 contents = file(interface).read()
209 saved_data, saved_sign_fn, saved_key = signing.check_signature(interface)
210 assert saved_data == data
211 assert saved_sign_fn == sign_fn
212 signing.export_key(os.path.dirname(interface), saved_key)
213 except KeyboardInterrupt, ex:
214 print >>sys.stderr, "Aborted at user's request"
215 sys.exit(1)
216 except SafeException, ex:
217 if options.verbose: raise
218 print >>sys.stderr, ex
219 sys.exit(1)