Start development series 0.25-post
[0publish.git] / 0publish
blob6a8a976bef3eb05519e2b1a8c3fdb013d4e73a90
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.25'
12 parser = OptionParser(usage="usage: %prog [options] interface")
13 parser.add_option("-a", "--add-from", help="add implementations from FEED", metavar='FEED')
14 parser.add_option("--add-types", help="add missing MIME-type attributes", action='store_true')
15 parser.add_option("--add-version", help="add a new implementation", action='store', metavar='VERSION')
16 parser.add_option("--archive-url", help="add archive at this URL", action='store', metavar='URL')
17 parser.add_option("--archive-file", help="local copy of archive-url", action='store', metavar='FILE')
18 parser.add_option("--archive-extract", help="subdirectory of archive to extract", action='store', metavar='DIR')
19 parser.add_option("-c", "--create", help="create file if nonexistant", action='store_true')
20 parser.add_option("-d", "--add-digest", help="add extra digests", action='store', metavar='ALG')
21 parser.add_option("-e", "--edit", help="edit with $EDITOR", action='store_true')
22 parser.add_option("-k", "--key", help="key to use for signing")
23 parser.add_option("-l", "--local", help="deprecated; use --add-from instead", dest='add_from', metavar='LOCAL')
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.add_from:
79 if os.path.exists(options.add_from):
80 data = create.create_from_local(options.add_from)
81 sign_fn = signing.sign_unsigned
82 key = None
83 force_save = True
84 options.add_from = False
85 else:
86 raise Exception("File '%s' does not exist." % options.add_from)
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.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.set_attributes(data, options.select_version,
141 id = options.set_id,
142 version = options.set_version,
143 released = options.set_released,
144 stability = options.set_stability,
145 main = options.set_main,
146 arch = options.set_arch)
147 if options.stable:
148 assert not options.select_version, "Use --set-stability=stable --select-version=... instead"
149 import stable
150 data = stable.mark_stable(data)
151 if options.archive_url:
152 import archive
153 algs = options.manifest_algorithm
154 if algs is None:
155 algs = ['sha1new']
156 import hashlib
157 if hasattr(hashlib, 'sha256'):
158 algs.append('sha256new')
159 data = archive.add_archive(data, options.archive_url, options.archive_file, options.archive_extract, algs)
160 elif options.archive_file or options.archive_extract:
161 raise Exception('Must use --archive-url option')
162 if options.add_from:
163 import merge
164 data = merge.merge(data, options.add_from)
165 if options.add_digest:
166 import digest
167 data = digest.add_digests(data, alg = options.add_digest)
168 if options.add_types:
169 import mimetypes
170 data = mimetypes.add_types(data)
171 if options.edit:
172 data = edit.edit(data)
174 while True:
175 # Validate the result...
176 try:
177 validator.check(data)
178 break
179 except validator.InvalidInterface, ex:
180 print "Invalid interface: " + str(ex)
182 while True:
183 ans = raw_input("Interface is invalid. (E)dit or (A)bort?").lower()
184 if ans in ('e', 'edit'):
185 data = edit.edit(data)
186 break
187 if ans in ('a', 'abort'): sys.exit(1)
189 if (old_data == data and sign_fn == old_sign_fn and key == old_key) and not force_save:
190 print "Interface unchanged. Not writing."
191 sys.exit(1)
193 # Tidy up the XML
194 doc = minidom.parseString(data)
195 data = create.xml_header + doc.documentElement.toxml('utf-8')
197 # Write it back out
198 if not data.endswith('\n'): data += '\n'
199 sign_fn(interface, data, key)
201 info("Wrote '%s'", interface)
203 if sign_fn != signing.sign_unsigned:
204 # Read it back in to find out what key we signed it with
205 # and ensure that the key has been exported
206 contents = file(interface).read()
207 saved_data, saved_sign_fn, saved_key = signing.check_signature(interface)
208 assert saved_data == data
209 assert saved_sign_fn == sign_fn
210 signing.export_key(os.path.dirname(interface), saved_key)
211 except KeyboardInterrupt, ex:
212 print >>sys.stderr, "Aborted at user's request"
213 sys.exit(1)
214 except SafeException, ex:
215 if options.verbose: raise
216 print >>sys.stderr, ex
217 sys.exit(1)