1
2
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
16 opener = urllib.URLopener()
17 try:
18 (t, h) = opener.retrieve(url, filename)
19 except IOError, e:
20 if len(e.args) == 4:
21
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
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
40 self.parser.add_option('-b', '--branch',
41 action="store", dest="branch",
42 help="branch to submit, overriding the doap branch")
43
45 self.options = options
46
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
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
75
76
77
78 branch = self.options.branch or release.version.branch or "Default"
79
80
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
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
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
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
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 = []
223 i = 0
224 for d in self.parentCommand.doaps:
225 i += 1
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
236 'date': "".join(r.version.created.split('-')),
237 }
238 entries.append((r.version.created, i, d))
239
240
241 entries.sort()
242 for c, i, d in entries:
243
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
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
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
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
313
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
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
343 self.debug('Found %s' % filename)
344
345
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
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
387 description = "Output RSS 2 feed from project releases"
388
390 self.parser.add_option('-t', '--template-language',
391 action="store", dest="language",
392 help="template language to use (genshi/cheetah)")
393
395 self._language = options.language or 'genshi'
396
398 from moap.doap import rss
399 template = None
400
401
402 if args:
403
404
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
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):
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
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
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
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
497 self.doap = self.doaps[0]
498