| 1 | # -*- Mode: Python -*- |
|---|
| 2 | # vi:si:et:sw=4:sts=4:ts=4 |
|---|
| 3 | |
|---|
| 4 | import os |
|---|
| 5 | import sys |
|---|
| 6 | import urllib |
|---|
| 7 | import tarfile |
|---|
| 8 | |
|---|
| 9 | from moap.util import util, mail |
|---|
| 10 | from moap.doap import doap |
|---|
| 11 | import bug |
|---|
| 12 | |
|---|
| 13 | |
|---|
| 14 | def urlgrab(url, filename): |
|---|
| 15 | opener = urllib.URLopener() |
|---|
| 16 | try: |
|---|
| 17 | (t, h) = opener.retrieve(url, filename) |
|---|
| 18 | except IOError, e: |
|---|
| 19 | if len(e.args) == 4: |
|---|
| 20 | # http error masquerading as IO error |
|---|
| 21 | if e.args[0] != 'http error': |
|---|
| 22 | raise e |
|---|
| 23 | code = e.args[1] |
|---|
| 24 | if code == 404: |
|---|
| 25 | print "URL %s not found" % url |
|---|
| 26 | raise e |
|---|
| 27 | else: |
|---|
| 28 | raise e |
|---|
| 29 | |
|---|
| 30 | |
|---|
| 31 | class Freshmeat(util.LogCommand): |
|---|
| 32 | summary = "submit to Freshmeat" |
|---|
| 33 | description = """This command submits a release to Freshmeat. |
|---|
| 34 | Login details are taken from $HOME/.netrc. Add a section for a machine named |
|---|
| 35 | "freshmeat" with login and password settings. |
|---|
| 36 | """ |
|---|
| 37 | |
|---|
| 38 | def handleOptions(self, options): |
|---|
| 39 | self.options = options |
|---|
| 40 | |
|---|
| 41 | def do(self, args): |
|---|
| 42 | self.debug('submitting to freshmeat') |
|---|
| 43 | d = self.parentCommand.doap |
|---|
| 44 | |
|---|
| 45 | if not self.parentCommand.version: |
|---|
| 46 | sys.stderr.write('Please specify a version to submit with -v.\n') |
|---|
| 47 | return 3 |
|---|
| 48 | |
|---|
| 49 | # FIXME: add hide-from-front-page |
|---|
| 50 | project = d.getProject() |
|---|
| 51 | |
|---|
| 52 | from moap.publish import freshmeat |
|---|
| 53 | fm = freshmeat.Session() |
|---|
| 54 | try: |
|---|
| 55 | fm.login() |
|---|
| 56 | except freshmeat.SessionException, e: |
|---|
| 57 | sys.stderr.write('Could not login to Freshmeat: %s\n' % |
|---|
| 58 | e.message) |
|---|
| 59 | return 3 |
|---|
| 60 | |
|---|
| 61 | |
|---|
| 62 | release = project.getRelease(self.parentCommand.version) |
|---|
| 63 | if not release: |
|---|
| 64 | sys.stderr.write('No revision %s found.\n' % |
|---|
| 65 | self.parentCommand.version) |
|---|
| 66 | return 3 |
|---|
| 67 | |
|---|
| 68 | # FIXME: fm.fetch_release() seems to lie to me when I use it |
|---|
| 69 | # on gstreamer |
|---|
| 70 | |
|---|
| 71 | # branches on Freshmeat are called "Default" by default |
|---|
| 72 | branch = release.version.branch or "Default" |
|---|
| 73 | # submit |
|---|
| 74 | # FIXME: how do we get changes and release_focus ? |
|---|
| 75 | args = { |
|---|
| 76 | 'project_name': project.shortname, |
|---|
| 77 | 'branch_name': branch, |
|---|
| 78 | 'version': release.version.revision, |
|---|
| 79 | 'changes': "Unknown", |
|---|
| 80 | 'release_focus': 4, |
|---|
| 81 | 'hide_from_frontpage': 'N', |
|---|
| 82 | } |
|---|
| 83 | |
|---|
| 84 | for uri in release.version.file_release: |
|---|
| 85 | mapping = { |
|---|
| 86 | '.tar.gz': 'tgz', |
|---|
| 87 | '.tgz': 'tgz', |
|---|
| 88 | '.tar.bz2': 'bz2', |
|---|
| 89 | '.rpm': 'rpm', |
|---|
| 90 | } |
|---|
| 91 | for ext in mapping.keys(): |
|---|
| 92 | if uri.endswith(ext): |
|---|
| 93 | key = 'url_%s' % mapping[ext] |
|---|
| 94 | self.stdout.write("- %s: %s\n" % (key, uri)) |
|---|
| 95 | args[key] = uri |
|---|
| 96 | |
|---|
| 97 | self.stdout.write( |
|---|
| 98 | "Submitting release of %s %s on branch %s\n" % ( |
|---|
| 99 | project.name, self.parentCommand.version, branch)) |
|---|
| 100 | try: |
|---|
| 101 | fm.publish_release(**args) |
|---|
| 102 | except freshmeat.SessionError, e: |
|---|
| 103 | if e.code == 40: |
|---|
| 104 | self.stderr.write("ERROR: denied releasing %r\n" % |
|---|
| 105 | self.parentCommand.version) |
|---|
| 106 | elif e.code == 51: |
|---|
| 107 | self.stderr.write( |
|---|
| 108 | "Freshmeat already knows about this version\n") |
|---|
| 109 | elif e.code == 81: |
|---|
| 110 | self.stderr.write("Freshmeat does not know the project %s\n" % |
|---|
| 111 | project.shortname) |
|---|
| 112 | else: |
|---|
| 113 | self.stderr.write("ERROR: %r\n" % e) |
|---|
| 114 | |
|---|
| 115 | class Mail(util.LogCommand): |
|---|
| 116 | summary = "send release announcement through mail" |
|---|
| 117 | usage = "mail [mail-options] [TO]..." |
|---|
| 118 | description = """Send out release announcement mail. |
|---|
| 119 | The To: addresses can be specified as arguments to the mail command.""" |
|---|
| 120 | |
|---|
| 121 | def addOptions(self): |
|---|
| 122 | self.parser.add_option('-f', '--from', |
|---|
| 123 | action="store", dest="fromm", |
|---|
| 124 | help="address to send from") |
|---|
| 125 | self.parser.add_option('-n', '--dry-run', |
|---|
| 126 | action="store_true", dest="dry_run", |
|---|
| 127 | help="show the mail that would have been sent") |
|---|
| 128 | self.parser.add_option('-R', '--release-notes', |
|---|
| 129 | action="store", dest="release_notes", |
|---|
| 130 | help="release notes to use (otherwise looked up in tarball)") |
|---|
| 131 | |
|---|
| 132 | def handleOptions(self, options): |
|---|
| 133 | self.options = options |
|---|
| 134 | |
|---|
| 135 | def do(self, args): |
|---|
| 136 | d = self.parentCommand.doap |
|---|
| 137 | |
|---|
| 138 | if not self.parentCommand.version: |
|---|
| 139 | sys.stderr.write('Please specify a version to submit with -v.\n') |
|---|
| 140 | return 3 |
|---|
| 141 | |
|---|
| 142 | version = self.parentCommand.version |
|---|
| 143 | |
|---|
| 144 | if not self.options.fromm: |
|---|
| 145 | sys.stderr.write('Please specify a From: address with -f.\n') |
|---|
| 146 | return 3 |
|---|
| 147 | |
|---|
| 148 | if len(args) < 1: |
|---|
| 149 | sys.stderr.write('Please specify one or more To: addresses.\n') |
|---|
| 150 | return 3 |
|---|
| 151 | to = args |
|---|
| 152 | |
|---|
| 153 | project = d.getProject() |
|---|
| 154 | |
|---|
| 155 | release = project.getRelease(version) |
|---|
| 156 | if not release: |
|---|
| 157 | sys.stderr.write('No revision %s found.\n' % version) |
|---|
| 158 | return 3 |
|---|
| 159 | |
|---|
| 160 | # get a list of release files |
|---|
| 161 | keep = [] |
|---|
| 162 | extensions = ['.tar.gz', '.tgz', '.tar.bz2'] |
|---|
| 163 | for uri in release.version.file_release: |
|---|
| 164 | for ext in extensions: |
|---|
| 165 | if uri.endswith(ext): |
|---|
| 166 | keep.append(uri) |
|---|
| 167 | |
|---|
| 168 | self.debug('Release files: %r' % keep) |
|---|
| 169 | |
|---|
| 170 | # now that we have a list of candidates, check if any of them |
|---|
| 171 | # exists in the current directory |
|---|
| 172 | found = False |
|---|
| 173 | for uri in keep: |
|---|
| 174 | filename = os.path.basename(uri) |
|---|
| 175 | if os.path.exists(filename): |
|---|
| 176 | sys.stdout.write("Found release %s in current directory.\n" % |
|---|
| 177 | filename) |
|---|
| 178 | found = True |
|---|
| 179 | break |
|---|
| 180 | |
|---|
| 181 | # if we don't have a local archive, try and get a uri one |
|---|
| 182 | if not found: |
|---|
| 183 | for uri in keep: |
|---|
| 184 | if uri.startswith('http') or uri.startswith('ftp:'): |
|---|
| 185 | filename = os.path.basename(uri) |
|---|
| 186 | sys.stdout.write('Downloading %s ... ' % uri) |
|---|
| 187 | sys.stdout.flush() |
|---|
| 188 | urlgrab(uri, filename) |
|---|
| 189 | sys.stdout.write('done.\n') |
|---|
| 190 | |
|---|
| 191 | sys.stdout.write( |
|---|
| 192 | "Downloaded %s in current dir\n" % filename) |
|---|
| 193 | found = True |
|---|
| 194 | break |
|---|
| 195 | |
|---|
| 196 | if not found: |
|---|
| 197 | self.stderr.write("ERROR: no file found\n") |
|---|
| 198 | return 1 |
|---|
| 199 | |
|---|
| 200 | # filename now is the path to a tar/bz2 |
|---|
| 201 | self.debug('Found %s' % filename) |
|---|
| 202 | |
|---|
| 203 | # Find the release notes |
|---|
| 204 | RELEASE = None |
|---|
| 205 | if self.options.release_notes: |
|---|
| 206 | RELEASE = open(self.options.release_notes).read() |
|---|
| 207 | else: |
|---|
| 208 | tar_archive = tarfile.open(mode="r", name=filename) |
|---|
| 209 | for tarinfo in tar_archive: |
|---|
| 210 | if tarinfo.name.endswith('RELEASE'): |
|---|
| 211 | RELEASE = tar_archive.extractfile(tarinfo).read() |
|---|
| 212 | tar_archive.close() |
|---|
| 213 | |
|---|
| 214 | # now send out the mail with the release notes attached |
|---|
| 215 | d = { |
|---|
| 216 | 'projectName': project.name, |
|---|
| 217 | 'version': version, |
|---|
| 218 | 'releaseName': release.version.name |
|---|
| 219 | } |
|---|
| 220 | subject = "RELEASE: %(projectName)s %(version)s '%(releaseName)s'" % d |
|---|
| 221 | content = "This mail announces the release of " |
|---|
| 222 | content += "%(projectName)s %(version)s '%(releaseName)s'.\n\n" % d |
|---|
| 223 | content += "%s\n" % project.description |
|---|
| 224 | if project.homepage: |
|---|
| 225 | content += "For more information, see %s\n" % project.homepage |
|---|
| 226 | if project.bug_database: |
|---|
| 227 | content += "To file bugs, go to %s\n" % project.bug_database |
|---|
| 228 | |
|---|
| 229 | message = mail.Message(subject, to, self.options.fromm) |
|---|
| 230 | message.setContent(content) |
|---|
| 231 | |
|---|
| 232 | if RELEASE: |
|---|
| 233 | message.addAttachment('RELEASE', 'text/plain', RELEASE) |
|---|
| 234 | |
|---|
| 235 | if self.options.dry_run: |
|---|
| 236 | self.stdout.write(message.get()) |
|---|
| 237 | else: |
|---|
| 238 | self.stdout.write('Sending release announcement ... ') |
|---|
| 239 | message.send() |
|---|
| 240 | self.stdout.write('sent.\n') |
|---|
| 241 | |
|---|
| 242 | return 0 |
|---|
| 243 | |
|---|
| 244 | class Show(util.LogCommand): |
|---|
| 245 | description = "Show project information" |
|---|
| 246 | |
|---|
| 247 | def do(self, args): |
|---|
| 248 | __pychecker__ = 'no-argsused' |
|---|
| 249 | d = self.parentCommand.doap |
|---|
| 250 | project = d.getProject() |
|---|
| 251 | |
|---|
| 252 | self.stdout.write("DOAP file: %s\n" % d.path) |
|---|
| 253 | self.stdout.write("project: %s\n" % project.name) |
|---|
| 254 | if project.shortdesc: |
|---|
| 255 | self.stdout.write("short description: %s\n" % project.shortdesc) |
|---|
| 256 | if project.created: |
|---|
| 257 | self.stdout.write("created: %s\n" % project.created) |
|---|
| 258 | if project.homepage: |
|---|
| 259 | self.stdout.write("homepage: %s\n" % project.homepage) |
|---|
| 260 | if project.bug_database: |
|---|
| 261 | self.stdout.write("bug database: %s\n" % project.bug_database) |
|---|
| 262 | if project.download_page: |
|---|
| 263 | self.stdout.write("download page: %s\n" % project.download_page) |
|---|
| 264 | if not project.release: |
|---|
| 265 | self.stdout.write(" No releases made.\n") |
|---|
| 266 | else: |
|---|
| 267 | v = project.release[0].version |
|---|
| 268 | self.stdout.write( |
|---|
| 269 | "Latest release: version %s '%s' on branch %s.\n" % ( |
|---|
| 270 | v.revision, v.name, v.branch)) |
|---|
| 271 | |
|---|
| 272 | class Doap(util.LogCommand): |
|---|
| 273 | """ |
|---|
| 274 | @ivar doap: the L{doap.Doap} object. |
|---|
| 275 | """ |
|---|
| 276 | |
|---|
| 277 | usage = "doap [doap-options] %command" |
|---|
| 278 | description = "read and act on DOAP file" |
|---|
| 279 | subCommandClasses = [Freshmeat, Mail, Show, bug.Bug] |
|---|
| 280 | |
|---|
| 281 | doap = None |
|---|
| 282 | |
|---|
| 283 | def addOptions(self): |
|---|
| 284 | self.parser.add_option('-f', '--file', |
|---|
| 285 | action="store", dest="file", |
|---|
| 286 | help=".doap file to act on") |
|---|
| 287 | self.parser.add_option('-v', '--version', |
|---|
| 288 | action="store", dest="version", |
|---|
| 289 | help="version to submit") |
|---|
| 290 | |
|---|
| 291 | |
|---|
| 292 | def handleOptions(self, options): |
|---|
| 293 | self.path = None |
|---|
| 294 | if options.file: self.path = options.file |
|---|
| 295 | self.version = options.version |
|---|
| 296 | |
|---|
| 297 | try: |
|---|
| 298 | d = doap.findDoapFile(self.path) |
|---|
| 299 | except doap.DoapException, e: |
|---|
| 300 | sys.stdout.write(e.args[0]) |
|---|
| 301 | return 3 |
|---|
| 302 | self.doap = d |
|---|
| 303 | |
|---|
| 304 | |
|---|