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