2 """Tell me who you are!
7 from debian_bundle import deb822
9 from ConfigParser import SafeConfigParser
10 from optparse import OptionParser, Option, OptionGroup, OptionConflictError
19 from jinja2 import Environment, PackageLoader
21 from pprint import PrettyPrinter
24 class AptListsCache(object):
25 def __init__(self, cachedir='build/cache',
28 self.cachedir = cachedir
30 if not ro_cachedirs is None:
31 self.ro_cachedirs = ro_cachedirs
33 self.ro_cachedirs = []
36 create_dir(self.cachedir)
38 def get(self, url, update=False):
39 """Looks in the cache if the file is there and takes the cached one.
40 Otherwise it is downloaded first.
42 Knows how to deal with http:// and svn:// URLs.
47 # look whether it is compressed
48 cext = url.split('.')[-1]
49 if cext in ['gz', 'bz2']:
50 target_url = url[:-1 * len(cext) -1]
52 # assume not compressed
56 # turn url into a filename -- mimik what APT does for
58 tfilename = '_'.join(target_url.split('/')[2:])
60 # if we need to download anyway do not search
62 cfilename = os.path.join(self.cachedir, tfilename)
64 # look for the uncompressed file anywhere in the cache
66 for cp in [self.cachedir] + self.ro_cachedirs:
67 if os.path.exists(os.path.join(cp, tfilename)):
68 cfilename = os.path.join(cp, tfilename)
73 cfilename = os.path.join(self.cachedir, tfilename)
76 # if updated needed -- download
78 #print 'Caching file from %s' % url
80 if url.startswith('svn://'):
82 pysvn.Client().export(url, cfilename)
83 if url.startswith('http://'):
85 tempfile, ignored = urllib.urlretrieve(url)
92 decompressor = 'bzip2'
97 "Don't know how to decompress %s files" \
100 if not decompressor is None:
101 if subprocess.call([decompressor, '-d', '-q', '-f',
103 raise RuntimeError, \
104 "Something went wrong while decompressing '%s'" \
107 # move decompressed file into cache
108 shutil.move(os.path.splitext(tempfile)[0], cfilename)
110 # XXX do we need that if explicit filename is provided?
114 fh = codecs.open(cfilename, 'r', 'utf-8')
119 def add_pkgfromtaskfile(db, urls):
120 cache = AptListsCache()
126 # loop over all stanzas
127 for stanza in deb822.Packages.iter_paragraphs(fh):
128 if stanza.has_key('Depends'):
129 pkg = stanza['Depends']
130 elif stanza.has_key('Suggests'):
131 pkg = stanza['Suggests']
135 # account for multiple packages per line
137 pkgs += [p.strip() for p in pkg.split(',')]
139 pkgs.append(pkg.strip())
142 if not db.has_key(p):
143 db[p] = get_emptydbentry()
147 def get_emptydbentry():
150 def import_blendstask(db, url):
151 cache = AptListsCache()
155 # figure out blend's task page URL, since they differ from blend to blend
156 urlsec = url.split('/')
157 blendname = urlsec[-3]
158 if blendname == 'debian-med':
159 taskpage_url = 'http://debian-med.alioth.debian.org/tasks/'
160 elif blendname == 'debian-science':
161 taskpage_url = 'http://blends.alioth.debian.org/science/tasks/'
163 raise ValueError('Unknown blend "%s"' % blendname)
164 taskpage_url += urlsec[-1]
166 for st in deb822.Packages.iter_paragraphs(fh):
167 if st.has_key('Task'):
168 task_name = st['Task']
169 task = (blendname, task_name, taskpage_url)
171 if st.has_key('Depends'):
173 elif st.has_key('Suggests'):
176 # print 'Warning: Cannot determine name of prospective package ' \
177 # '... ignoring. Dump follows:'
181 # take care of pkg lists
182 for p in pkg.split(', '):
183 if not db.has_key(p):
184 print 'Ignoring blend package "%s"' % p
190 info['tasks'] = [task]
191 if st.has_key('License'):
192 info['license'] = st['License']
193 if st.has_key('Responsible'):
194 info['responsible'] = st['Responsible']
197 if st.has_key('Pkg-Description'):
198 descr = st['Pkg-Description'].replace('%', '%%').split('\n')
199 info['description'] = descr[0].strip()
200 info['long_description'] = u' '.join([l.strip() for l in descr[1:]])
202 # charge the basic property set
203 db[p]['main']['description'] = info['description']
204 db[p]['main']['long_description'] = info['long_description']
205 if st.has_key('WNPP'):
206 db[p]['main']['debian_itp'] = st['WNPP']
207 if st.has_key('Pkg-URL'):
208 db[p]['main']['other_pkg'] = st['Pkg-URL']
209 if st.has_key('Homepage'):
210 db[p]['main']['homepage'] = st['Homepage']
212 # only store if there isn't something already
213 if not db[p].has_key('blends'):
214 db[p]['blends'] = info
216 # just add this tasks name and id
217 db[p]['blends']['tasks'].append(task)
222 def get_releaseinfo(rurl):
223 cache = AptListsCache()
224 # root URL of the repository
225 baseurl = '/'.join(rurl.split('/')[:-1])
226 # get the release file from the cache
227 release_file = cache.get(rurl)
229 # create parser instance
230 rp = deb822.Release(release_file)
232 # architectures on this dist
233 archs = rp['Architectures'].split()
234 components = rp['Components'].split()
235 # compile a new codename that also considers the repository label
236 # to distinguish between official and unofficial repos.
238 origin = rp['Origin']
239 codename = rp['Codename']
240 labelcode = '_'.join([rp['Label'], rp['Codename']])
245 return {'baseurl': baseurl, 'archs': archs, 'components': components,
246 'codename': codename, 'label': label, 'labelcode': labelcode,
250 def build_pkgsurl(baseurl, component, arch):
251 return '/'.join([baseurl, component, 'binary-' + arch, 'Packages.bz2'])
254 def import_release(cfg, db, rurl):
255 cache = AptListsCache()
257 ri = get_releaseinfo(rurl)
259 # compile the list of Packages files to parse and parse them
260 for c in ri['components']:
261 for a in ri['archs']:
262 # compile packages URL
263 pkgsurl = build_pkgsurl(ri['baseurl'], c, a)
265 # retrieve from cache
266 packages_file = cache.get(pkgsurl)
269 for stanza in deb822.Packages.iter_paragraphs(packages_file):
270 db = _store_pkg(cfg, db, stanza, ri['origin'], ri['codename'], c, ri['baseurl'])
273 packages_file.close()
277 def _store_pkg(cfg, db, st, origin, codename, component, baseurl):
284 # only care for known packages
285 if not db.has_key(pkg):
286 # print 'Ignoring NeuroDebian package "%s"' % pkg
289 distkey = (trans_codename(codename, cfg), 'neurodebian-' + codename)
291 if db[pkg].has_key(distkey):
292 info = db[pkg][distkey]
294 info = {'architecture': []}
297 if not st['Architecture'] in info['architecture']:
298 info['architecture'].append(st['Architecture'])
299 info['maintainer'] = st['Maintainer']
300 if st.has_key('Homepage'):
301 info['homepage'] = st['Homepage']
302 info['version'] = st['Version']
305 info['distribution'] = origin
306 info['release'] = codename
307 info['component'] = component
310 info['poolurl'] = '/'.join([os.path.dirname(st['Filename'])])
313 descr = st['Description'].replace('%', '%%').split('\n')
314 info['description'] = descr[0].strip()
315 info['long_description'] = u' '.join([l.strip() for l in descr[1:]])
317 db[pkg][distkey] = info
319 # charge the basic property set
320 db[pkg]['main']['description'] = info['description']
321 db[pkg]['main']['long_description'] = info['long_description']
322 if st.has_key('Homepage'):
323 db[pkg]['main']['homepage'] = st['Homepage']
328 def trans_codename(codename, cfg):
329 """Translate a known codename into a release description.
331 Unknown codenames will simply be returned as is.
333 # if we know something, tell
334 if codename in cfg.options('release codenames'):
335 return cfg.get('release codenames', codename)
340 def create_dir(path):
341 if os.path.exists(path):
344 ps = path.split(os.path.sep)
346 for i in range(1,len(ps) + 1):
347 p = os.path.sep.join(ps[:i])
349 if not os.path.exists(p):
355 return json.read(urllib2.urlopen(url+"?t=json").read())['r']
356 except (urllib2.HTTPError, StopIteration):
357 print "SCREWED:", url
361 def import_dde(cfg, db):
362 query_url = cfg.get('dde', 'pkgquery_url')
365 q = dde_get(query_url + "/packages/all/%s" % p)
368 # get latest popcon info for debian and ubuntu
369 # cannot use origin field itself, since it is none for few packages
371 origin = q['drc'].split()[0]
372 print 'popcon query for', p
373 if origin == 'ubuntu':
374 print 'have ubuntu first'
375 if q.has_key('popcon'):
376 print 'ubuntu has popcon'
377 db[p]['main']['ubuntu_popcon'] = q['popcon']
378 # if we have ubuntu, need to get debian
379 q = dde_get(query_url + "/packages/prio-debian-sid/%s" % p)
380 if q and q.has_key('popcon'):
381 print 'debian has popcon'
382 db[p]['main']['debian_popcon'] = q['popcon']
383 elif origin == 'debian':
384 print 'have debian first'
385 if q.has_key('popcon'):
386 print 'debian has popcon'
387 db[p]['main']['debian_popcon'] = q['popcon']
388 # if we have debian, need to get ubuntu
389 q = dde_get(query_url + "/packages/prio-ubuntu-karmic/%s" % p)
390 if q and q.has_key('popcon'):
391 print 'ubuntu has popcon'
392 db[p]['main']['ubuntu_popcon'] = q['popcon']
394 print("Ignoring unkown origin '%s' for package '%s'." \
397 # now get info for package from all releases in UDD
398 q = dde_get(query_url + "/dist/p:%s" % p)
401 # hold all info about this package per distribution release
404 distkey = (trans_codename(cp['release'], cfg),
405 "%s-%s" % (cp['distribution'], cp['release']))
406 if not info.has_key(distkey):
408 # turn into a list to append others later
409 info[distkey]['architecture'] = [info[distkey]['architecture']]
410 # accumulate data for multiple over archs
412 comp = apt.VersionCompare(cp['version'],
413 info[distkey]['version'])
414 # found another arch for the same version
416 info[distkey]['architecture'].append(cp['architecture'])
417 # found newer version, dump the old ones
420 # turn into a list to append others later
421 info[distkey]['architecture'] = [info[distkey]['architecture']]
422 # simply ignore older versions
426 # finally assign the new package data
427 for k, v in info.iteritems():
433 def generate_pkgpage(pkg, cfg, db, template, addenum_dir):
434 # local binding for ease of use
436 # do nothing if there is not at least the very basic stuff
437 if not db['main'].has_key('description'):
439 title = '**%s** -- %s' % (pkg, db['main']['description'])
440 underline = '*' * (len(title) + 2)
441 title = '%s\n %s\n%s' % (underline, title, underline)
443 # preprocess long description
444 ld = db['main']['long_description']
446 for i, l in enumerate(ld):
448 ld[i] = '#NEWLINEMARKER#'
449 ld = u' '.join([l.lstrip() for l in ld])
450 ld = ld.replace('#NEWLINEMARKER# ', '\n\n')
452 page = template.render(pkg=pkg,
457 # the following can be replaced by something like
458 # {% include "sidebar.html" ignore missing %}
459 # in the template whenever jinja 2.2 becomes available
460 addenum = os.path.join(os.path.abspath(addenum_dir), '%s.rst' % pkg)
461 if os.path.exists(addenum):
462 page += '\n\n.. include:: %s\n' % addenum
466 def store_db(db, filename):
467 pp = PrettyPrinter(indent=2)
468 f = codecs.open(filename, 'w', 'utf-8')
469 f.write(pp.pformat(db))
473 def read_db(filename):
474 f = codecs.open(filename, 'r', 'utf-8')
478 def write_sourceslist(jinja_env, cfg, outdir):
480 create_dir(os.path.join(outdir, '_static'))
483 for release in cfg.options('release codenames'):
484 transrel = trans_codename(release, cfg)
486 for mirror in cfg.options('mirrors'):
487 listname = 'neurodebian.%s.%s.sources.list' % (release, mirror)
488 repos[transrel].append((mirror, listname))
489 lf = open(os.path.join(outdir, '_static', listname), 'w')
490 aptcfg = '%s %s main contrib non-free\n' % (cfg.get('mirrors', mirror),
492 lf.write('deb %s' % aptcfg)
493 lf.write('deb-src %s' % aptcfg)
496 srclist_template = jinja_env.get_template('sources_lists.rst')
497 sl = open(os.path.join(outdir, 'sources_lists'), 'w')
498 sl.write(srclist_template.render(repos=repos))
502 def write_pkgpages(jinja_env, cfg, db, outdir, addenum_dir):
504 create_dir(os.path.join(outdir, 'pkgs'))
506 # generate the TOC with all packages
507 toc_template = jinja_env.get_template('pkgs_toc.rst')
508 toc = codecs.open(os.path.join(outdir, 'pkgs.rst'), 'w', 'utf-8')
509 toc.write(toc_template.render(pkgs=db.keys()))
512 # and now each individual package page
513 pkg_template = jinja_env.get_template('pkg.rst')
515 page = generate_pkgpage(p, cfg, db, pkg_template, addenum_dir)
516 # when no page is available skip this package
519 pf = codecs.open(os.path.join(outdir, 'pkgs', p + '.rst'), 'w', 'utf-8')
520 pf.write(generate_pkgpage(p, cfg, db, pkg_template, addenum_dir))
524 def prepOptParser(op):
525 # use module docstring for help output
526 op.usage = "%s [OPTIONS]\n\n" % sys.argv[0] + __doc__
528 op.add_option("--db",
529 action="store", type="string", dest="db",
531 help="Database file to read. Default: None")
533 op.add_option("--cfg",
534 action="store", type="string", dest="cfg",
536 help="Repository config file.")
538 op.add_option("-o", "--outdir",
539 action="store", type="string", dest="outdir",
541 help="Target directory for ReST output. Default: None")
543 op.add_option("-r", "--release-url",
544 action="append", dest="release_urls",
547 op.add_option("--pkgaddenum", action="store", dest="addenum_dir",
548 type="string", default=None, help="None")
552 op = OptionParser(version="%prog 0.0.2")
555 (opts, args) = op.parse_args()
558 print('There needs to be exactly one command')
564 print("'--cfg' option is mandatory.")
567 print("'--db' option is mandatory.")
571 cfg = SafeConfigParser()
574 # load existing db, unless renew is requested
575 if cmd == 'updatedb':
577 if cfg.has_option('packages', 'select taskfiles'):
578 db = add_pkgfromtaskfile(db, cfg.get('packages',
579 'select taskfiles').split())
581 # add additional package names from config file
582 if cfg.has_option('packages', 'select names'):
583 for p in cfg.get('packages', 'select names').split():
584 if not db.has_key(p):
585 db[p] = get_emptydbentry()
587 # get info from task files
588 if cfg.has_option('packages', 'prospective'):
589 for url in cfg.get('packages', 'prospective').split():
590 db = import_blendstask(db, url)
592 # parse NeuroDebian repository
593 if cfg.has_option('neurodebian', 'releases'):
594 for rurl in cfg.get('neurodebian', 'releases').split():
595 db = import_release(cfg, db, rurl)
597 # collect package information from DDE
598 db = import_dde(cfg, db)
600 store_db(db, opts.db)
604 # load the db from file
605 db = read_db(opts.db)
608 jinja_env = Environment(loader=PackageLoader('neurodebian', 'templates'))
610 # generate package pages and TOC and write them to files
611 write_pkgpages(jinja_env, cfg, db, opts.outdir, opts.addenum_dir)
613 write_sourceslist(jinja_env, cfg, opts.outdir)
615 if __name__ == "__main__":