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