New release.
[0publish.git] / 0publish
blob9a12f164c86271e74ff6f6267d06670b5faa7664
1 #!/usr/bin/env python
2 from zeroinstall.injector import gpg
3 from zeroinstall import SafeException
4 from xml.dom import minidom
5 from optparse import OptionParser
6 import os, sys
7 import signing
8 from logging import info, debug
9 import edit, validator, create
11 version = '0.9'
13 parser = OptionParser(usage="usage: %prog [options] interface")
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("-e", "--edit", help="edit with $EDITOR", action='store_true')
19 parser.add_option("-g", "--gpgsign", help="add a GPG signature block", action='store_true')
20 parser.add_option("-k", "--key", help="key to use for signing")
21 parser.add_option("-l", "--local", help="create feed from local interface")
22 parser.add_option("--manifest-algorithm", help="select algorithm for manifests", action='store', metavar='ALG')
23 parser.add_option("--set-interface-uri", help="set interface URI", action='store', metavar='URI')
24 parser.add_option("--set-id", help="set implementation ID", action='store', metavar='DIGEST')
25 parser.add_option("--set-main", help="set main executable", action='store', metavar='EXEC')
26 parser.add_option("--set-arch", help="set architecture", action='store', metavar='ARCH')
27 parser.add_option("--set-released", help="set release date", action='store', metavar='DATE')
28 parser.add_option("--set-stability", help="set stability", action='store', metavar='STABILITY')
29 parser.add_option("--set-version", help="set version number", action='store', metavar='VERSION')
30 parser.add_option("-s", "--stable", help="mark testing version stable", action='store_true')
31 parser.add_option("-x", "--xmlsign", help="add an XML signature block", action='store_true')
32 parser.add_option("-u", "--unsign", help="remove any signature", action='store_true')
33 parser.add_option("-v", "--verbose", help="more verbose output", action='count')
34 parser.add_option("-V", "--version", help="display version information", action='store_true')
36 (options, args) = parser.parse_args()
38 force_save = False
40 if options.version:
41 print "0publish (zero-install) " + version
42 print "Copyright (C) 2005 Thomas Leonard"
43 print "This program comes with ABSOLUTELY NO WARRANTY,"
44 print "to the extent permitted by law."
45 print "You may redistribute copies of this program"
46 print "under the terms of the GNU General Public License."
47 print "For more information about these matters, see the file named COPYING."
48 sys.exit(0)
50 if options.verbose:
51 import logging
52 logger = logging.getLogger()
53 if options.verbose == 1:
54 logger.setLevel(logging.INFO)
55 else:
56 logger.setLevel(logging.DEBUG)
58 if len(args) != 1:
59 parser.print_help()
60 sys.exit(1)
61 interface = args[0]
63 def confirm(q):
64 while True:
65 ans = raw_input(q + " [Y/N] ").lower()
66 if ans in ('y', 'yes'): return True
67 if ans in ('n', 'no'): return False
69 try:
70 # Load or create the starting data...
72 if os.path.exists(interface):
73 contents = file(interface).read()
74 data, sign_fn, key = signing.check_signature(interface)
75 elif options.local:
76 if os.path.exists(options.local):
77 data = create.create_from_local(options.local)
78 sign_fn = signing.sign_unsigned
79 key = None
80 force_save = True
81 options.local = False
82 else:
83 raise Exception("File '%s' does not exist." % options.local)
84 else:
85 if confirm("Interface file '%s' does not exist. Create it?" % interface):
86 data = create.create(interface)
87 sign_fn = signing.sign_unsigned
88 key = None
89 options.edit = True
90 else:
91 sys.exit(1)
93 debug("Original data: %s", data)
94 info("Original signing method: %s", sign_fn.__name__)
95 info("Original key: %s", key)
97 old_data = data
98 old_sign_fn = sign_fn
99 old_key = key
101 while True:
102 # Validate the input...
103 try:
104 validator.check(data, warnings = False) # Don't warn on load AND save!
105 break
106 except validator.InvalidInterface, ex:
107 print "Invalid interface: " + str(ex)
109 while True:
110 ans = raw_input("Interface is invalid. (E)dit or (A)bort?").lower()
111 if ans in ('e', 'edit'):
112 data = edit.edit(data)
113 options.edit = False # Don't edit twice
114 break
115 if ans in ('a', 'abort'): sys.exit(1)
117 # Process it...
118 if options.xmlsign:
119 sign_fn = signing.sign_xml
120 if options.unsign:
121 sign_fn = signing.sign_unsigned
122 if options.gpgsign:
123 sign_fn = signing.sign_plain
124 if options.key:
125 print "Changing key from '%s' to '%s'" % (key, options.key)
126 key = options.key
127 if options.set_interface_uri:
128 import release
129 data = release.set_interface_uri(data, options.set_interface_uri)
130 if options.add_version:
131 import release
132 data = release.add_version(data, options.add_version)
133 if options.set_id or options.set_version or options.set_released or \
134 options.set_stability or options.set_arch or options.set_main:
135 import release
136 data = release.make_release(data, options.set_id,
137 options.set_version, options.set_released, options.set_stability,
138 options.set_main, options.set_arch)
139 if options.stable:
140 import stable
141 data = stable.mark_stable(data)
142 if options.archive_url:
143 import archive
144 data = archive.add_archive(data, options.archive_url, options.archive_file, options.archive_extract, options.manifest_algorithm)
145 elif options.archive_file or options.archive_extract:
146 raise Exception('Must use --archive-uri option')
147 if options.local:
148 import merge
149 data = merge.merge(data, options.local)
150 if options.edit:
151 data = edit.edit(data)
153 while True:
154 # Validate the result...
155 try:
156 validator.check(data)
157 break
158 except validator.InvalidInterface, ex:
159 print "Invalid interface: " + str(ex)
161 while True:
162 ans = raw_input("Interface is invalid. (E)dit or (A)bort?").lower()
163 if ans in ('e', 'edit'):
164 data = edit.edit(data)
165 break
166 if ans in ('a', 'abort'): sys.exit(1)
168 if (old_data == data and sign_fn == old_sign_fn and key == old_key) and not force_save:
169 print "Interface unchanged. Not writing."
170 sys.exit(1)
172 # Tidy up the XML
173 doc = minidom.parseString(data)
174 data = create.xml_header + doc.documentElement.toxml()
176 # Write it back out
177 if not data.endswith('\n'): data += '\n'
178 sign_fn(interface, data, key)
180 info("Wrote '%s'", interface)
182 if sign_fn != signing.sign_unsigned:
183 # Read it back in to find out what key we signed it with
184 # and ensure that the key has been exported
185 contents = file(interface).read()
186 saved_data, saved_sign_fn, saved_key = signing.check_signature(interface)
187 assert saved_data == data
188 assert saved_sign_fn == sign_fn
189 signing.export_key(os.path.dirname(interface), saved_key)
190 except SafeException, ex:
191 if options.verbose: raise
192 print >>sys.stderr, ex
193 sys.exit(1)