Fixed comment.
[0publish.git] / 0publish
blob39093ed8283ff6d60d2073d500797e098357f257
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.13'
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("-c", "--create", help="create file if nonexistant", action='store_true')
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='store', 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 while True:
103 # Validate the input...
104 try:
105 validator.check(data, warnings = False) # Don't warn on load AND save!
106 break
107 except validator.InvalidInterface, ex:
108 print "Invalid interface: " + str(ex)
110 while True:
111 ans = raw_input("Interface is invalid. (E)dit or (A)bort?").lower()
112 if ans in ('e', 'edit'):
113 data = edit.edit(data)
114 options.edit = False # Don't edit twice
115 break
116 if ans in ('a', 'abort'): sys.exit(1)
118 # Process it...
119 if options.xmlsign:
120 sign_fn = signing.sign_xml
121 if options.unsign:
122 sign_fn = signing.sign_unsigned
123 if options.gpgsign:
124 sign_fn = signing.sign_plain
125 if options.key:
126 print "Changing key from '%s' to '%s'" % (key, options.key)
127 key = options.key
128 if options.set_interface_uri:
129 import release
130 data = release.set_interface_uri(data, options.set_interface_uri)
131 if options.add_version:
132 import release
133 data = release.add_version(data, options.add_version)
134 if options.set_id or options.set_version or options.set_released or \
135 options.set_stability or options.set_arch or options.set_main:
136 import release
137 data = release.make_release(data, options.set_id,
138 options.set_version, options.set_released, options.set_stability,
139 options.set_main, options.set_arch)
140 if options.stable:
141 import stable
142 data = stable.mark_stable(data)
143 if options.archive_url:
144 import archive
145 data = archive.add_archive(data, options.archive_url, options.archive_file, options.archive_extract, options.manifest_algorithm)
146 elif options.archive_file or options.archive_extract:
147 raise Exception('Must use --archive-uri option')
148 if options.local:
149 import merge
150 data = merge.merge(data, options.local)
151 if options.edit:
152 data = edit.edit(data)
154 while True:
155 # Validate the result...
156 try:
157 validator.check(data)
158 break
159 except validator.InvalidInterface, ex:
160 print "Invalid interface: " + str(ex)
162 while True:
163 ans = raw_input("Interface is invalid. (E)dit or (A)bort?").lower()
164 if ans in ('e', 'edit'):
165 data = edit.edit(data)
166 break
167 if ans in ('a', 'abort'): sys.exit(1)
169 if (old_data == data and sign_fn == old_sign_fn and key == old_key) and not force_save:
170 print "Interface unchanged. Not writing."
171 sys.exit(1)
173 # Tidy up the XML
174 doc = minidom.parseString(data)
175 data = create.xml_header + doc.documentElement.toxml()
177 # Write it back out
178 if not data.endswith('\n'): data += '\n'
179 sign_fn(interface, data, key)
181 info("Wrote '%s'", interface)
183 if sign_fn != signing.sign_unsigned:
184 # Read it back in to find out what key we signed it with
185 # and ensure that the key has been exported
186 contents = file(interface).read()
187 saved_data, saved_sign_fn, saved_key = signing.check_signature(interface)
188 assert saved_data == data
189 assert saved_sign_fn == sign_fn
190 signing.export_key(os.path.dirname(interface), saved_key)
191 except KeyboardInterrupt, ex:
192 print >>sys.stderr, "Aborted at user's request"
193 sys.exit(1)
194 except SafeException, ex:
195 if options.verbose: raise
196 print >>sys.stderr, ex
197 sys.exit(1)