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