Package moap :: Package command :: Module doap
[hide private]
[frames] | no frames]

Source Code for Module moap.command.doap

  1  # -*- Mode: Python; test-case-name: moap.test.test_commands_doap -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3   
  4  import os 
  5  import glob 
  6  import sys 
  7  import urllib 
  8  import tarfile 
  9   
 10  from moap.util import util, mail 
 11  from moap.doap import doap 
 12  import bug 
 13   
 14   
15 -def urlgrab(url, filename):
16 opener = urllib.URLopener() 17 try: 18 (t, h) = opener.retrieve(url, filename) 19 except IOError, e: 20 if len(e.args) == 4: 21 # http error masquerading as IO error 22 if e.args[0] != 'http error': 23 raise e 24 code = e.args[1] 25 if code == 404: 26 print "URL %s not found" % url 27 raise e 28 else: 29 raise e
30 31
32 -class Freshmeat(util.LogCommand):
33 summary = "submit to Freshmeat" 34 description = """This command submits a release to Freshmeat. 35 Login details are taken from $HOME/.netrc. Add a section for a machine named 36 "freshmeat" with login and password settings. 37 """ 38
39 - def addOptions(self):
40 self.parser.add_option('-b', '--branch', 41 action="store", dest="branch", 42 help="branch to submit, overriding the doap branch")
43
44 - def handleOptions(self, options):
45 self.options = options
46
47 - def do(self, args):
48 self.debug('submitting to freshmeat') 49 d = self.parentCommand.doap 50 51 if not self.parentCommand.version: 52 sys.stderr.write('Please specify a version to submit with -v.\n') 53 return 3 54 55 # FIXME: add hide-from-front-page 56 project = d.getProject() 57 58 from moap.publish import freshmeat 59 fm = freshmeat.Session() 60 try: 61 fm.login() 62 except freshmeat.SessionException, e: 63 sys.stderr.write('Could not login to Freshmeat: %s\n' % 64 e.message) 65 return 3 66 67 68 release = project.getRelease(self.parentCommand.version) 69 if not release: 70 sys.stderr.write('No revision %s found.\n' % 71 self.parentCommand.version) 72 return 3 73 74 # FIXME: fm.fetch_release() seems to lie to me when I use it 75 # on gstreamer 76 77 # branches on Freshmeat are called "Default" by default 78 branch = self.options.branch or release.version.branch or "Default" 79 # submit 80 # FIXME: how do we get changes and release_focus ? 81 args = { 82 'project_name': project.shortname, 83 'branch_name': branch, 84 'version': release.version.revision, 85 'changes': "Unknown", 86 'release_focus': 4, 87 'hide_from_frontpage': 'N', 88 } 89 90 for uri in release.version.file_release: 91 mapping = { 92 '.tar.gz': 'tgz', 93 '.tgz': 'tgz', 94 '.tar.bz2': 'bz2', 95 '.rpm': 'rpm', 96 } 97 for ext in mapping.keys(): 98 if uri.endswith(ext): 99 key = 'url_%s' % mapping[ext] 100 self.stdout.write("- %s: %s\n" % (key, uri)) 101 args[key] = uri 102 103 self.stdout.write( 104 "Submitting release of %s %s on branch %s\n" % ( 105 project.name, self.parentCommand.version, branch)) 106 try: 107 fm.publish_release(**args) 108 except freshmeat.SessionError, e: 109 if e.code == 40: 110 self.stderr.write("ERROR: denied releasing %r\n" % 111 self.parentCommand.version) 112 if e.code == 30: 113 self.stderr.write( 114 """ERROR: Freshmeat does not know the branch '%s'. 115 Most projects on Freshmeat have a branch named Default. 116 You can override the branch name manually with -b/--branch. 117 """ % branch) 118 elif e.code == 51: 119 self.stderr.write( 120 "Freshmeat already knows about this version\n") 121 elif e.code == 81: 122 self.stderr.write("Freshmeat does not know the project %s\n" % 123 project.shortname) 124 else: 125 self.stderr.write("ERROR: %r\n" % e)
126
127 -class Search(util.LogCommand):
128 description = "look up rank of project's home page based on keywords" 129 130 _engines = ["google", "yahoo"] 131 _default = "yahoo" 132
133 - def addOptions(self):
134 self.parser.add_option('-e', '--engine', 135 action="store", dest="engine", default=self._default, 136 help="search engine to use (out of %s; defaults to %s)" % ( 137 ", ".join(self._engines), self._default)) 138 self.parser.add_option('-l', '--limit', 139 action="store", dest="limit", default="100", 140 help="maximum number of results to look at")
141
142 - def handleOptions(self, options):
143 self._limit = int(options.limit) 144 self._engine = options.engine
145
146 - def do(self, args):
147 if not args: 148 self.stderr.write('Please provide a search query.\n') 149 return 3 150 151 d = self.parentCommand.doap 152 project = d.getProject() 153 154 rank = 0 155 found = False 156 query = " ".join(args) 157 158 def foundURL(target, url): 159 # returns true if the url is close enough to the target 160 if target.endswith('/'): 161 target = target[:-1] 162 if url.endswith('/'): 163 url = url[:-1] 164 return url == target
165 166 if self._engine == 'google': 167 from pygoogle import google 168 169 while not found: 170 self.debug('Doing Google search for %s starting from %d' % ( 171 " ".join(args), rank)) 172 value = google.doGoogleSearch(" ".join(args), start=rank) 173 for result in value.results: 174 rank += 1 175 self.debug('Hit %d: URL %s' % (rank, result.URL)) 176 if foundURL(project.homepage, result.URL): 177 found = True 178 break 179 180 if rank >= self._limit: 181 break 182 183 elif self._engine == 'yahoo': 184 from yahoo.search import web 185 186 # yahoo's start is 1-based 187 while not found: 188 search = web.WebSearch('moapmoap', query=query, start=rank+1) 189 info = search.parse_results() 190 for result in info.results: 191 rank += 1 192 self.debug('Hit %d: URL %s' % (rank, result['Url'])) 193 if foundURL(project.homepage, result['Url']): 194 found = True 195 break 196 197 if rank >= self._limit: 198 break 199 200 else: 201 self.stderr.write("Unknown search engine '%s'.\n" % self._engine) 202 self.stderr.write("Please choose from %s.\n" % 203 ", ".join(self._engines)) 204 return 3 205 206 if found: 207 self.stdout.write("Found homepage as hit %d\n." % rank) 208 else: 209 self.stdout.write("Did not find homepage in first %d hits.\n" % 210 self._limit)
211
212 -class Ical(util.LogCommand):
213 description = "Output iCal stream from project releases" 214
215 - def do(self, args):
216 __pychecker__ = 'no-argsused' 217 self.stdout.write("""BEGIN:VCALENDAR 218 PRODID:-//thomas.apestaart.org//moap//EN 219 VERSION:2.0 220 221 """) 222 entries = [] # created, dict 223 i = 0 224 for d in self.parentCommand.doaps: 225 i += 1 # count projects to resolve created clashes 226 project = d.getProject() 227 228 for r in project.release: 229 d = { 230 'projectName': project.name, 231 'projectId': project.shortname, 232 'revision': r.version.revision, 233 'name': r.version.name, 234 'created': r.version.created, 235 # DATE should be without dashes 236 'date': "".join(r.version.created.split('-')), 237 } 238 entries.append((r.version.created, i, d)) 239 240 # sort entries on created, then doap file order 241 entries.sort() 242 for c, i, d in entries: 243 # evolution needs UID set for webcal:// calendars 244 self.stdout.write("""BEGIN:VEVENT 245 SUMMARY:%(projectName)s %(revision)s '%(name)s' released 246 UID:%(created)s-%(projectId)s-%(revision)s@moap 247 CLASS:PUBLIC 248 PRIORITY:3 249 DTSTART;VALUE=DATE:%(date)s 250 DTEND;VALUE=DATE:%(date)s 251 END:VEVENT 252 253 """ % d) 254 255 self.stdout.write("\nEND:VCALENDAR\n")
256
257 -class Mail(util.LogCommand):
258 summary = "send release announcement through mail" 259 usage = "mail [mail-options] [TO]..." 260 description = """Send out release announcement mail. 261 The To: addresses can be specified as arguments to the mail command.""" 262
263 - def addOptions(self):
264 self.parser.add_option('-f', '--from', 265 action="store", dest="fromm", 266 help="address to send from") 267 self.parser.add_option('-n', '--dry-run', 268 action="store_true", dest="dry_run", 269 help="show the mail that would have been sent") 270 self.parser.add_option('-R', '--release-notes', 271 action="store", dest="release_notes", 272 help="release notes to use (otherwise looked up in tarball)")
273
274 - def handleOptions(self, options):
275 self.options = options
276
277 - def do(self, args):
278 d = self.parentCommand.doap 279 280 if not self.parentCommand.version: 281 sys.stderr.write('Please specify a version to submit with -v.\n') 282 return 3 283 284 version = self.parentCommand.version 285 286 if not self.options.fromm: 287 sys.stderr.write('Please specify a From: address with -f.\n') 288 return 3 289 290 if len(args) < 1: 291 sys.stderr.write('Please specify one or more To: addresses.\n') 292 return 3 293 to = args 294 295 project = d.getProject() 296 297 release = project.getRelease(version) 298 if not release: 299 sys.stderr.write('No revision %s found.\n' % version) 300 return 3 301 302 # get a list of release files 303 keep = [] 304 extensions = ['.tar.gz', '.tgz', '.tar.bz2'] 305 for uri in release.version.file_release: 306 for ext in extensions: 307 if uri.endswith(ext): 308 keep.append(uri) 309 310 self.debug('Release files: %r' % keep) 311 312 # now that we have a list of candidates, check if any of them 313 # exists in the current directory 314 found = False 315 for uri in keep: 316 filename = os.path.basename(uri) 317 if os.path.exists(filename): 318 sys.stdout.write("Found release %s in current directory.\n" % 319 filename) 320 found = True 321 break 322 323 # if we don't have a local archive, try and get a uri one 324 if not found: 325 for uri in keep: 326 if uri.startswith('http') or uri.startswith('ftp:'): 327 filename = os.path.basename(uri) 328 sys.stdout.write('Downloading %s ... ' % uri) 329 sys.stdout.flush() 330 urlgrab(uri, filename) 331 sys.stdout.write('done.\n') 332 333 sys.stdout.write( 334 "Downloaded %s in current dir\n" % filename) 335 found = True 336 break 337 338 if not found: 339 self.stderr.write("ERROR: no file found\n") 340 return 1 341 342 # filename now is the path to a tar/bz2 343 self.debug('Found %s' % filename) 344 345 # Find the release notes 346 RELEASE = None 347 if self.options.release_notes: 348 RELEASE = open(self.options.release_notes).read() 349 else: 350 tar_archive = tarfile.open(mode="r", name=filename) 351 for tarinfo in tar_archive: 352 if tarinfo.name.endswith('RELEASE'): 353 RELEASE = tar_archive.extractfile(tarinfo).read() 354 tar_archive.close() 355 356 # now send out the mail with the release notes attached 357 d = { 358 'projectName': project.name, 359 'version': version, 360 'releaseName': release.version.name 361 } 362 subject = "RELEASE: %(projectName)s %(version)s '%(releaseName)s'" % d 363 content = "This mail announces the release of " 364 content += "%(projectName)s %(version)s '%(releaseName)s'.\n\n" % d 365 content += "%s\n" % project.description 366 if project.homepage: 367 content += "For more information, see %s\n" % project.homepage 368 if project.bug_database: 369 content += "To file bugs, go to %s\n" % project.bug_database 370 371 message = mail.Message(subject, to, self.options.fromm) 372 message.setContent(content) 373 374 if RELEASE: 375 message.addAttachment('RELEASE', 'text/plain', RELEASE) 376 377 if self.options.dry_run: 378 self.stdout.write(message.get()) 379 else: 380 self.stdout.write('Sending release announcement ... ') 381 message.send() 382 self.stdout.write('sent.\n') 383 384 return 0
385
386 -class Rss(util.LogCommand):
387 description = "Output RSS 2 feed from project releases" 388
389 - def addOptions(self):
390 self.parser.add_option('-t', '--template-language', 391 action="store", dest="language", 392 help="template language to use (genshi/cheetah)")
393
394 - def handleOptions(self, options):
395 self._language = options.language or 'genshi'
396
397 - def do(self, args):
398 from moap.doap import rss 399 template = None 400 401 # if one is specified, prefer it 402 if args: 403 # FIXME: maybe find a default one based on the doap name ? 404 # like .doap -> .rss2.tmpl ? 405 path = args[0] 406 try: 407 handle = open(path) 408 template = handle.read() 409 handle.close() 410 except: 411 self.stderr.write("Could not read template %s.\n" % path) 412 return 3 413 self.debug("Using requested template %s" % template) 414 415 # FIXME: if one can be found close to the .doap file, use it 416 text = rss.doapsToRss(self.parentCommand.doaps, template, 417 templateType=self._language) 418 self.stdout.write(text)
419
420 -class Show(util.LogCommand):
421 description = "Show project information" 422
423 - def do(self, args):
424 __pychecker__ = 'no-argsused' 425 d = self.parentCommand.doap 426 project = d.getProject() 427 428 self.stdout.write("DOAP file: %s\n" % d.path) 429 self.stdout.write("project: %s\n" % project.name) 430 if project.shortdesc: 431 self.stdout.write("short description: %s\n" % project.shortdesc) 432 if project.created: 433 self.stdout.write("created: %s\n" % project.created) 434 if project.homepage: 435 self.stdout.write("homepage: %s\n" % project.homepage) 436 if project.bug_database: 437 self.stdout.write("bug database: %s\n" % project.bug_database) 438 if project.download_page: 439 self.stdout.write("download page: %s\n" % project.download_page) 440 if project.wiki: 441 self.stdout.write("wiki: %s\n" % project.wiki) 442 if not project.release: 443 self.stdout.write(" No releases made.\n") 444 else: 445 v = project.release[0].version 446 self.stdout.write( 447 "Latest release: version %s '%s' on branch %s.\n" % ( 448 v.revision, v.name, v.branch))
449
450 -class Doap(util.LogCommand):
451 """ 452 @ivar doap: the L{doap.Doap} object. 453 """ 454 455 usage = "doap [doap-options] %command" 456 description = "read and act on DOAP file" 457 subCommandClasses = [Freshmeat, Ical, Mail, Rss, Search, Show, bug.Bug] 458 459 doap = None 460
461 - def addOptions(self):
462 self.parser.add_option('-f', '--file', 463 action="append", dest="files", 464 help=".doap file(s) to act on (glob wildcards allowed)") 465 self.parser.add_option('-v', '--version', 466 action="store", dest="version", 467 help="version to submit")
468
469 - def handleOptions(self, options):
470 self.paths = [] 471 self.doaps = [] 472 if options.files: 473 for f in options.files: 474 self.paths.extend(glob.glob(f)) 475 self.debug('%d doap paths' % len(self.paths)) 476 self.version = options.version 477 478 if not self.paths: 479 # nothing specified, try and find the default 480 try: 481 self.doap = doap.findDoapFile(None) 482 self.doaps = [self.doap, ] 483 except doap.DoapException, e: 484 sys.stdout.write(e.args[0]) 485 return 3 486 487 return 488 489 for p in self.paths: 490 try: 491 d = doap.findDoapFile(p) 492 except doap.DoapException, e: 493 sys.stdout.write(e.args[0]) 494 return 3 495 self.doaps.append(d) 496 # FIXME: compat, remove in users 497 self.doap = self.doaps[0]
498