]> git.donarmstrong.com Git - neurodebian.git/blob - neurodebian/dde.py
Merge remote branch 'alioth/master'
[neurodebian.git] / neurodebian / dde.py
1 #!/usr/bin/env python
2 """Tell me who you are!
3 """
4
5 import pysvn
6 import json
7 from debian_bundle import deb822
8
9 # Lets first assure no guarding (but annoying) warnings
10 import warnings
11 warnings.simplefilter('ignore', FutureWarning)
12 warnings.filterwarnings('ignore', 'Module debian_bundle was already imported.*', UserWarning)
13
14 import apt
15 from ConfigParser import SafeConfigParser
16 from optparse import OptionParser, Option, OptionGroup, OptionConflictError
17 import sys
18 import os
19 import copy
20 import shutil
21 import urllib2
22 import urllib
23 import codecs
24 import subprocess
25 import time
26 # templating
27 from jinja2 import Environment, PackageLoader
28
29 from pprint import PrettyPrinter
30
31
32 class AptListsCache(object):
33     def __init__(self, cachedir='build/cache',
34                  ro_cachedirs=None,
35                  init_db=None):
36         self.cachedir = cachedir
37
38         if not ro_cachedirs is None:
39             self.ro_cachedirs = ro_cachedirs
40         else:
41             self.ro_cachedirs = []
42
43         # create cachedir
44         create_dir(self.cachedir)
45
46     def get(self, url, update=False):
47         """Looks in the cache if the file is there and takes the cached one.
48         Otherwise it is downloaded first.
49
50         Knows how to deal with http:// and svn:// URLs.
51
52         :Return:
53           file handler
54         """
55         # look whether it is compressed
56         cext = url.split('.')[-1]
57         if cext in ['gz', 'bz2']:
58             target_url = url[:-1 * len(cext) -1]
59         else:
60             # assume not compressed
61             target_url = url
62             cext = None
63
64         # turn url into a filename -- mimik what APT does for
65         # /var/lib/apt/lists/
66         tfilename = '_'.join(target_url.split('/')[2:])
67
68         # if we need to download anyway do not search
69         if update:
70             cfilename = os.path.join(self.cachedir, tfilename)
71         else:
72             # look for the uncompressed file anywhere in the cache
73             cfilename = None
74             for cp in [self.cachedir] + self.ro_cachedirs:
75                 if os.path.exists(os.path.join(cp, tfilename)):
76                     cfilename = os.path.join(cp, tfilename)
77
78         # nothing found?
79         if cfilename is None:
80             # add cache item
81             cfilename = os.path.join(self.cachedir, tfilename)
82             update = True
83
84         # if updated needed -- download
85         if update:
86             #print 'Caching file from %s' % url
87
88             if url.startswith('svn://'):
89                 # export from SVN
90                 pysvn.Client().export(url, cfilename)
91             if url.startswith('http://'):
92                 # download
93                 tempfile, ignored = urllib.urlretrieve(url)
94
95                 # decompress
96                 decompressor = None
97                 if cext == 'gz':
98                     decompressor = 'gzip'
99                 elif cext == 'bz2':
100                     decompressor = 'bzip2'
101                 elif cext == None:
102                     decompressor = None
103                 else:
104                     raise ValueError, \
105                           "Don't know how to decompress %s files" \
106                           % cext
107
108                 if not decompressor is None:
109                     if subprocess.call([decompressor, '-d', '-q', '-f',
110                                        tempfile]) == 1:
111                         raise RuntimeError, \
112                               "Something went wrong while decompressing '%s'" \
113                               % tempfile
114
115                 # move decompressed file into cache
116                 shutil.move(os.path.splitext(tempfile)[0], cfilename)
117
118                 # XXX do we need that if explicit filename is provided?
119                 urllib.urlcleanup()
120
121         # open cached file
122         fh = codecs.open(cfilename, 'r', 'utf-8')
123
124         return fh
125
126
127 def add_pkgfromtaskfile(db, urls):
128     cache = AptListsCache()
129     pkgs = []
130
131     for task in urls:
132         fh = cache.get(task)
133
134         # loop over all stanzas
135         for stanza in deb822.Packages.iter_paragraphs(fh):
136             if stanza.has_key('Depends'):
137                 pkg = stanza['Depends']
138             elif stanza.has_key('Suggests'):
139                 pkg = stanza['Suggests']
140             else:
141                 continue
142
143             # account for multiple packages per line
144             if pkg.count(','):
145                 pkgs += [p.strip() for p in pkg.split(',')]
146             else:
147                 pkgs.append(pkg.strip())
148
149     for p in pkgs:
150         if not db.has_key(p):
151             db[p] = get_emptydbentry()
152
153     return db
154
155 def get_emptydbentry():
156     return {'main': {}}
157
158 def import_blendstask(cfg, db, url):
159     cache = AptListsCache()
160     fh = cache.get(url)
161     task_name = None
162
163     # figure out blend's task page URL, since they differ from blend to blend
164     urlsec = url.split('/')
165     blendname = urlsec[-3]
166     if blendname == 'debian-med':
167         taskpage_url = 'http://debian-med.alioth.debian.org/tasks/'
168     elif blendname == 'debian-science':
169         taskpage_url = 'http://blends.alioth.debian.org/science/tasks/' 
170     else:
171         raise ValueError('Unknown blend "%s"' % blendname)
172     taskpage_url += urlsec[-1]
173
174     for st in deb822.Packages.iter_paragraphs(fh):
175         if st.has_key('Task'):
176             task_name = st['Task']
177             task = (blendname, task_name, taskpage_url)
178
179         if st.has_key('Depends'):
180             pkg = st['Depends']
181         elif st.has_key('Suggests'):
182             pkg = st['Suggests']
183         else:
184 #            print 'Warning: Cannot determine name of prospective package ' \
185 #                    '... ignoring. Dump follows:'
186 #            print st
187             continue
188
189         # take care of pkg lists
190         for p in pkg.split(', '):
191             if not db.has_key(p):
192                 print 'Ignoring blend package "%s"' % p
193                 continue
194
195             info = {}
196
197             # blends info
198             info['tasks'] = [task]
199             if st.has_key('License'):
200                 info['license'] = st['License']
201             if st.has_key('Responsible'):
202                 info['responsible'] = st['Responsible']
203
204             # pkg description
205             if st.has_key('Pkg-Description'):
206                 descr = st['Pkg-Description'].split('\n')
207                 info['description'] = descr[0].strip()
208                 info['long_description'] = \
209                         u'\n'.join(descr[1:])
210
211                 # charge the basic property set
212                 db[p]['main']['description'] = info['description']
213                 db[p]['main']['long_description'] = info['long_description']
214             if st.has_key('WNPP'):
215                 db[p]['main']['debian_itp'] = st['WNPP']
216             if st.has_key('Pkg-URL'):
217                 db[p]['main']['other_pkg'] = st['Pkg-URL']
218             if st.has_key('Homepage'):
219                 db[p]['main']['homepage'] = st['Homepage']
220
221             # Publications
222             if st.has_key('Published-Title'):
223                 title = st['Published-Title']
224                 if title[-1] == '.':
225                     # trip trailing dot -- added later
226                     pub = {'title': title[:-1]}
227                 else:
228                     pub = {'title': title}
229                 if st.has_key('Published-Authors'):
230                     pub['authors'] = st['Published-Authors']
231                 if st.has_key('Published-Year'):
232                     pub['year'] = st['Published-Year']
233                 if st.has_key('Published-In'):
234                     pub['in'] = st['Published-In']
235                 if st.has_key('Published-URL'):
236                     pub['url'] = st['Published-URL']
237                 if st.has_key('Published-DOI'):
238                     pub['doi'] = st['Published-DOI']
239                     # need at least one URL
240                     if not pub.has_key('url'):
241                         pub['url'] = "http://dx.doi.org/%s" % st['Published-DOI']
242
243                 db[p]['main']['publication'] = pub
244
245             # Registration
246             if st.has_key('Registration'):
247                 db[p]['main']['registration'] = st['Registration']
248
249             # Remarks
250             if st.has_key('Remark'):
251                 # prepend a single space to make it look like a long description
252                 info['remark'] = convert_longdescr(' ' + st['Remark'])
253
254             # only store if there isn't something already
255             if not db[p].has_key('blends'):
256                 db[p]['blends'] = info
257             else:
258                 # just add this tasks name and id
259                 db[p]['blends']['tasks'].append(task)
260
261             # handle pkg name aliases
262             if p in cfg.options('blend package aliases'):
263                 src_entry = db[p].copy()
264                 # remove original entry
265                 del db[p]
266                 # copy the entry into all aliases
267                 for alias in cfg.get('blend package aliases', p).split():
268                     print "Aliasing %s to %s" % (p, alias)
269                     db[alias] = copy.deepcopy(src_entry)
270
271     return db
272
273
274 def get_releaseinfo(rurl):
275     cache = AptListsCache()
276     # root URL of the repository
277     baseurl = '/'.join(rurl.split('/')[:-1])
278     # get the release file from the cache
279     release_file = cache.get(rurl)
280
281     # create parser instance
282     rp = deb822.Release(release_file)
283
284     # architectures on this dist
285     archs = rp['Architectures'].split()
286     components = rp['Components'].split()
287     # compile a new codename that also considers the repository label
288     # to distinguish between official and unofficial repos.
289     label = rp['Label']
290     origin = rp['Origin']
291     codename = rp['Codename']
292     labelcode = '_'.join([rp['Label'], rp['Codename']])
293
294     # cleanup
295     release_file.close()
296
297     return {'baseurl': baseurl, 'archs': archs, 'components': components,
298             'codename': codename, 'label': label, 'labelcode': labelcode,
299             'origin': origin}
300
301
302 def build_pkgsurl(baseurl, component, arch):
303     return '/'.join([baseurl, component, 'binary-' + arch, 'Packages.bz2'])
304
305
306 def import_release(cfg, db, rurl):
307     cache = AptListsCache()
308
309     ri = get_releaseinfo(rurl)
310
311     # compile the list of Packages files to parse and parse them
312     for c in ri['components']:
313         for a in ri['archs']:
314             # compile packages URL
315             pkgsurl = build_pkgsurl(ri['baseurl'], c, a)
316
317             # retrieve from cache
318             packages_file = cache.get(pkgsurl)
319
320             # parse
321             for stanza in deb822.Packages.iter_paragraphs(packages_file):
322                 db = _store_pkg(cfg, db, stanza, ri['origin'], ri['codename'], c, ri['baseurl'])
323
324             # cleanup
325             packages_file.close()
326
327     return db
328
329 def _store_pkg(cfg, db, st, origin, codename, component, baseurl):
330     """
331     :Parameter:
332       st: Package section
333     """
334     pkg = st['Package']
335
336     # only care for known packages
337     if not db.has_key(pkg):
338 #        print 'Ignoring NeuroDebian package "%s"' % pkg
339         return db
340
341     distkey = (trans_codename(codename, cfg), 'neurodebian-' + codename)
342
343     if db[pkg].has_key(distkey):
344         info = db[pkg][distkey]
345     else:
346         info = {'architecture': []}
347
348     # fill in data
349     if not st['Architecture'] in info['architecture']:
350         info['architecture'].append(st['Architecture'])
351     info['maintainer'] = st['Maintainer']
352     if st.has_key('Homepage'):
353         info['homepage'] = st['Homepage']
354     info['version'] = st['Version']
355
356     # origin
357     info['distribution'] = origin
358     info['release'] = codename
359     info['component'] = component
360
361     # pool url
362     info['poolurl'] = '/'.join([os.path.dirname(st['Filename'])])
363
364     # pkg description
365     descr = st['Description'].replace('%', '%%').split('\n')
366     info['description'] = descr[0].strip()
367     info['long_description'] = u'\n'.join(descr[1:])
368
369     db[pkg][distkey] = info
370
371     # charge the basic property set
372     db[pkg]['main']['description'] = info['description']
373     db[pkg]['main']['long_description'] = info['long_description']
374     if st.has_key('Source'):
375         db[pkg]['main']['sv'] = "%s %s" % (st['Source'], st['Version'])
376     else:
377         db[pkg]['main']['sv'] = "%s %s" % (st['Package'], st['Version'])
378     if st.has_key('Homepage'):
379         db[pkg]['main']['homepage'] = st['Homepage']
380     if st.has_key('Recommends'):
381         db[pkg]['main']['recommends'] = st['Recommends']
382
383     return db
384
385
386 def trans_codename(codename, cfg):
387     """Translate a known codename into a release description.
388
389     Unknown codenames will simply be returned as is.
390     """
391     # if we know something, tell
392     if codename in cfg.options('release codenames'):
393         return cfg.get('release codenames', codename)
394     else:
395         return codename
396
397
398 def create_dir(path):
399     if os.path.exists(path):
400         return
401
402     ps = path.split(os.path.sep)
403
404     for i in range(1,len(ps) + 1):
405         p = os.path.sep.join(ps[:i])
406
407         if not os.path.exists(p):
408             os.mkdir(p)
409
410
411 def dde_get(url, fail=False):
412     # enforce delay to be friendly to DDE
413     time.sleep(3)
414     try:
415         data = json.read(urllib2.urlopen(url+"?t=json").read())['r']
416         print "SUCCESS:", url
417         return data
418     except urllib2.HTTPError, e:
419         print "NOINFO:", url, type(e)
420         return False
421     except urllib2.URLError, e:
422         print "URLERROR:", url, type(e)
423         if fail:
424             print "Permanant failure"
425             return False
426         print "Try again after 30 seconds..."
427         time.sleep(30)
428         return dde_get(url, fail=True)
429     except (StopIteration):
430         print "NOINFO:", url
431         return False
432     except json.ReadException, e:
433         print "UDD-DOWN?:", url, type(e)
434         return False
435
436
437 def nitrc_get(spec, fail=False):
438     nitrc_url = 'http://www.nitrc.org/export/site/projects.json.php'
439     try:
440         # change into this from python 2.6 on
441         #data = json.loads(urllib2.urlopen(nitrc_url + '?spec=%s' % spec).read())
442         data = json.read(urllib2.urlopen(nitrc_url + '?spec=%s' % spec).read())
443         print "NITRC-SUCCESS:", spec
444     except urllib2.HTTPError, e:
445         print "NITRC-NOINFO:", spec, type(e)
446         return False
447     except urllib2.URLError, e:
448         print "NITRC-URLERROR:", spec, type(e)
449         if fail:
450             print "Permanant failure"
451             return False
452         print "Try again after 30 seconds..."
453         time.sleep(30)
454         return nitrc_get(spec, fail=True)
455     return data
456
457
458 def parse_nitrc(data):
459     if data is False:
460         return None
461     # simplify -- there is only one project in the data
462     project = data['projects'][0]
463     nitrc_filtered = {'downloads': 0,
464                       'id': project['id']}
465     for pkg in project['packages']:
466         for release in pkg['releases']:
467             for file in release['files']:
468                 nitrc_filtered['downloads'] += file['download_count']
469     return nitrc_filtered
470
471
472 def import_nitrc(cfg, db):
473     for p in db.keys():
474         if not cfg.has_option("nitrc ids", p):
475             continue
476         nitrc_spec = cfg.get("nitrc ids", p)
477         nitrc_data = nitrc_get(nitrc_spec)
478         nitrc_excerpt = parse_nitrc(nitrc_data)
479         if not nitrc_excerpt is None:
480             db[p]['nitrc'] = nitrc_excerpt
481     return db
482
483
484 def import_dde(cfg, db):
485     query_url = cfg.get('dde', 'pkgquery_url')
486     for p in db.keys():
487         # get freshest
488         q = dde_get(query_url + "/packages/all/%s" % p)
489         if q:
490             # copy all stuff, while preserving non-overlapping information
491             for k, v in q.iteritems():
492                 db[p]['main'][k] = v
493             # get latest popcon info for debian and ubuntu
494             # cannot use origin field itself, since it is none for few packages
495             # i.e. python-nifti
496             origin = q['drc'].split()[0]
497             if origin == 'ubuntu':
498                 if q.has_key('popcon'):
499                     db[p]['main']['ubuntu_popcon'] = q['popcon']
500                 # if we have ubuntu, need to get debian
501                 q = dde_get(query_url + "/packages/prio-debian-sid/%s" % p)
502                 if q and q.has_key('popcon'):
503                     db[p]['main']['debian_popcon'] = q['popcon']
504             elif origin == 'debian':
505                 if q.has_key('popcon'):
506                     db[p]['main']['debian_popcon'] = q['popcon']
507                 # if we have debian, need to get ubuntu
508                 q = dde_get(query_url + "/packages/prio-ubuntu-natty/%s" % p)
509                 if q and q.has_key('popcon'):
510                     db[p]['main']['ubuntu_popcon'] = q['popcon']
511             else:
512                 print("Ignoring unkown origin '%s' for package '%s'." \
513                         % (origin, p))
514
515         # now get info for package from all releases in UDD
516         q = dde_get(query_url + "/dist/p:%s" % p)
517         if not q:
518             continue
519         # hold all info about this package per distribution release
520         info = {}
521         for cp in q:
522             distkey = (trans_codename(cp['release'], cfg),
523                        "%s-%s" % (cp['distribution'], cp['release']))
524             if not info.has_key(distkey):
525                 info[distkey] = cp
526                 # turn into a list to append others later
527                 info[distkey]['architecture'] = [info[distkey]['architecture']]
528             # accumulate data for multiple over archs
529             else:
530                 comp = apt.VersionCompare(cp['version'],
531                                           info[distkey]['version'])
532                 # found another arch for the same version
533                 if comp == 0:
534                     info[distkey]['architecture'].append(cp['architecture'])
535                 # found newer version, dump the old ones
536                 elif comp > 0:
537                     info[distkey] = cp
538                     # turn into a list to append others later
539                     info[distkey]['architecture'] = [info[distkey]['architecture']]
540                 # simply ignore older versions
541                 else:
542                     pass
543
544         # finally assign the new package data
545         for k, v in info.iteritems():
546             db[p][k] = v
547
548     return db
549
550
551 def convert_longdescr(ld):
552     ld = ld.replace('% ', '%% ')
553     ld = ld.split('\n')
554     for i, l in enumerate(ld):
555         if l == ' .':
556             ld[i] = ' #NEWLINEMARKER#'
557         # look for embedded lists
558         elif len(l) >=3 and l[:2] == '  ' and l[2] in '-*':
559             ld[i] = ' #NEWLINEMARKER# ' + l[2:]
560
561     ld = u' '.join([l[1:] for l in ld])
562     ld = ld.replace('#NEWLINEMARKER# ', '\n\n')
563     # cleanup any leftover (e.g. trailing markers)
564     ld = ld.replace('#NEWLINEMARKER#', '')
565     return ld
566
567
568 def generate_pkgpage(pkg, cfg, db, template, addenum_dir):
569     # local binding for ease of use
570     pkgdb = db[pkg]
571     # do nothing if there is not at least the very basic stuff
572     if not pkgdb['main'].has_key('description'):
573         return
574     title = '**%s** -- %s' % (pkg, pkgdb['main']['description'])
575     underline = '*' * (len(title) + 2)
576     title = '%s\n %s\n%s' % (underline, title, underline)
577
578     page = template.render(
579             pkg=pkg,
580             title=title,
581             long_description=convert_longdescr(pkgdb['main']['long_description']),
582             cfg=cfg,
583             db=pkgdb,
584             fulldb=db)
585     # the following can be replaced by something like
586     # {% include "sidebar.html" ignore missing %}
587     # in the template whenever jinja 2.2 becomes available
588     addenum = os.path.join(os.path.abspath(addenum_dir), '%s.rst' % pkg)
589     if os.path.exists(addenum):
590         page += '\n\n.. include:: %s\n' % addenum
591     return page
592
593
594 def store_db(db, filename):
595     pp = PrettyPrinter(indent=2)
596     f = codecs.open(filename, 'w', 'utf-8')
597     f.write(pp.pformat(db))
598     f.close()
599
600
601 def read_db(filename):
602     f = codecs.open(filename, 'r', 'utf-8')
603     db = eval(f.read())
604     return db
605
606 def write_sourceslist(jinja_env, cfg, outdir):
607     create_dir(outdir)
608     create_dir(os.path.join(outdir, '_static'))
609
610     repos = {}
611     for release in cfg.options('release codenames'):
612         if release == 'data':
613             # no seperate list for the data archive
614             continue
615         transrel = trans_codename(release, cfg)
616         repos[transrel] = []
617         for mirror in cfg.options('mirrors'):
618             listname = 'neurodebian.%s.%s.sources.list' % (release, mirror)
619             repos[transrel].append((mirror, listname))
620             lf = open(os.path.join(outdir, '_static', listname), 'w')
621             for rel in ('data', release):
622                 aptcfg = '%s %s main contrib non-free\n' % (cfg.get('mirrors', mirror),
623                                                           rel)
624                 lf.write('deb %s' % aptcfg)
625                 lf.write('#deb-src %s' % aptcfg)
626             lf.close()
627
628     srclist_template = jinja_env.get_template('sources_lists.rst')
629     sl = open(os.path.join(outdir, 'sources_lists'), 'w')
630     sl.write(srclist_template.render(repos=repos))
631     sl.close()
632
633
634 def write_pkgpages(jinja_env, cfg, db, outdir, addenum_dir):
635     create_dir(outdir)
636     create_dir(os.path.join(outdir, 'pkgs'))
637
638     # generate the TOC with all packages
639     toc_template = jinja_env.get_template('pkgs_toc.rst')
640     toc = codecs.open(os.path.join(outdir, 'pkgs.rst'), 'w', 'utf-8')
641     # this is a fragile test
642     toc.write(toc_template.render(
643         pkgs=[k for k in db.keys()
644                 if not ('Datasets', 'neurodebian-data') in db[k]]))
645     toc.close()
646     # and now only for dataset packages
647     toc_template = jinja_env.get_template('datasets_toc.rst')
648     toc = codecs.open(os.path.join(outdir, 'datasets.rst'), 'w', 'utf-8')
649     # this is a fragile test
650     toc.write(toc_template.render(
651         pkgs=[k for k in db.keys()
652                 if ('Datasets', 'neurodebian-data') in db[k]]))
653     toc.close()
654
655
656     # and now each individual package page
657     pkg_template = jinja_env.get_template('pkg.rst')
658     for p in db.keys():
659         page = generate_pkgpage(p, cfg, db, pkg_template, addenum_dir)
660         # when no page is available skip this package
661         if page is None:
662             continue
663         pf = codecs.open(os.path.join(outdir, 'pkgs', p + '.rst'), 'w', 'utf-8')
664         pf.write(page)
665         pf.close()
666
667
668 def prepOptParser(op):
669     # use module docstring for help output
670     op.usage = "%s [OPTIONS]\n\n" % sys.argv[0] + __doc__
671
672     op.add_option("--db",
673                   action="store", type="string", dest="db",
674                   default=None,
675                   help="Database file to read. Default: None")
676
677     op.add_option("--cfg",
678                   action="store", type="string", dest="cfg",
679                   default=None,
680                   help="Repository config file.")
681
682     op.add_option("-o", "--outdir",
683                   action="store", type="string", dest="outdir",
684                   default=None,
685                   help="Target directory for ReST output. Default: None")
686
687     op.add_option("-r", "--release-url",
688                   action="append", dest="release_urls",
689                   help="None")
690
691     op.add_option("--pkgaddenum", action="store", dest="addenum_dir",
692                   type="string", default=None, help="None")
693
694
695 def main():
696     op = OptionParser(version="%prog 0.0.2")
697     prepOptParser(op)
698
699     (opts, args) = op.parse_args()
700
701     if len(args) != 1:
702         print('There needs to be exactly one command')
703         sys.exit(1)
704
705     cmd = args[0]
706
707     if opts.cfg is None:
708         print("'--cfg' option is mandatory.")
709         sys.exit(1)
710     if opts.db is None:
711         print("'--db' option is mandatory.")
712         sys.exit(1)
713
714
715     cfg = SafeConfigParser()
716     cfg.read(opts.cfg)
717
718     # load existing db, unless renew is requested
719     if cmd == 'updatedb':
720         db = {}
721         if cfg.has_option('packages', 'select taskfiles'):
722             db = add_pkgfromtaskfile(db, cfg.get('packages',
723                                                  'select taskfiles').split())
724
725         # add additional package names from config file
726         if cfg.has_option('packages', 'select names'):
727             for p in cfg.get('packages', 'select names').split():
728                 if not db.has_key(p):
729                     db[p] = get_emptydbentry()
730
731         # get info from task files
732         if cfg.has_option('packages', 'prospective'):
733             for url in cfg.get('packages', 'prospective').split():
734                 db = import_blendstask(cfg, db, url)
735
736         # parse NeuroDebian repository
737         if cfg.has_option('neurodebian', 'releases'):
738             for rurl in cfg.get('neurodebian', 'releases').split():
739                 db = import_release(cfg, db, rurl)
740
741         # collect package information from DDE
742         db = import_dde(cfg, db)
743         # get info from NITRC
744         db = import_nitrc(cfg, db)
745         # store the new DB
746         store_db(db, opts.db)
747         # and be done
748         return
749
750     # load the db from file
751     db = read_db(opts.db)
752
753     # fire up jinja
754     jinja_env = Environment(loader=PackageLoader('neurodebian', 'templates'))
755
756     # generate package pages and TOC and write them to files
757     write_pkgpages(jinja_env, cfg, db, opts.outdir, opts.addenum_dir)
758
759     write_sourceslist(jinja_env, cfg, opts.outdir)
760
761 if __name__ == "__main__":
762     main()