[Blends-commit] [Git][blends-team/blends][master] 40 commits: Replace blend-gen-control with a Python implementation

Ole Streicher gitlab at salsa.debian.org
Fri Apr 13 19:51:40 BST 2018


Ole Streicher pushed to branch master at Debian Blends Team / blends


Commits:
3add4f13 by Ole Streicher at 2018-03-24T16:54:34+01:00
Replace blend-gen-control with a Python implementation

- - - - -
35533d90 by Ole Streicher at 2018-03-24T16:54:34+01:00
Update changelog for 0.6.101 release

- - - - -
f4c0f012 by Ole Streicher at 2018-03-28T08:46:02+02:00
Fix some problems, and refactorization of blend-gen-control

 * Don't lower Recommends to Suggests on format 1.0
 * Don't allow backslashes in Recommends/Suggests/Depends in format 1.1

- - - - -
3120dc33 by Ole Streicher at 2018-03-28T08:52:24+02:00
Update changelog for 0.6.102 release

- - - - -
6caa8085 by Andreas Tille at 2018-03-28T16:23:53+02:00
Try hard to get lintian error "blends-dev: python-script-but-no-python-dep usr/share/blends-dev/blend-gen-control" solved - no idea why this failed.

- - - - -
da90c543 by Andreas Tille at 2018-03-28T16:24:41+02:00
ignore false lintian warning binary-package-depends-on-toolchain-package

- - - - -
a43a2bac by Andreas Tille at 2018-03-28T16:49:06+02:00
cme fix dpkg-control

- - - - -
61c13e4c by Andreas Tille at 2018-03-28T17:12:12+02:00
Take over tasks_diff from blends-gsoc to create dependency_data

- - - - -
eee797ae by Andreas Tille at 2018-03-28T19:58:29+02:00
Fix path of dependency_data for changelog creation

- - - - -
4fae955d by Andreas Tille at 2018-03-28T20:25:49+02:00
Use Deb822List to evaluate taskprefix rather than the old error prone method.

- - - - -
c611dc44 by Andreas Tille at 2018-03-29T08:44:07+02:00
Sort tasks alphabethically in changelog

- - - - -
dd6e6fe9 by Andreas Tille at 2018-03-29T08:45:14+02:00
Spacing

- - - - -
0fb78e41 by Ole Streicher at 2018-03-29T21:33:17+02:00
Add UDD query options to blend-gen-control

- - - - -
24faa7f3 by Ole Streicher at 2018-03-29T21:35:14+02:00
Update changelog for 0.6.103 release

- - - - -
ed526453 by Ole Streicher at 2018-03-29T21:41:18+02:00
Add python3 dep, and use python3 instead of python3-all in build-dep

- - - - -
4879c3f3 by Ole Streicher at 2018-03-29T21:42:10+02:00
Remove trailing whitespaces in d/changelog

- - - - -
3df36e29 by Ole Streicher at 2018-03-29T21:43:50+02:00
Update d/copyright of blends-gen-control (rewritten from scratch)

- - - - -
de5e5a03 by Andreas Tille at 2018-04-03T15:51:37+02:00
Do not mention subversion any more

- - - - -
e2c1bc3f by Andreas Tille at 2018-04-03T16:04:29+02:00
s/alioth/salsa/

- - - - -
3228dff2 by Andreas Tille at 2018-04-03T16:56:27+02:00
Description how Blends relevant data are gathered and stored

- - - - -
204bedb2 by Ole Streicher at 2018-04-09T16:43:14+02:00
Fix help for --taskdesc option

- - - - -
67e426ba by Ole Streicher at 2018-04-10T12:54:21+02:00
Fix documentation of lowering priorities

- - - - -
876a6be3 by Ole Streicher at 2018-04-10T13:15:49+02:00
Recommend python3-psycopg2 and note it in the help

- - - - -
fb15ffe0 by Ole Streicher at 2018-04-10T15:10:50+02:00
Create and use python3-blends package

- - - - -
fc9fb8db by Andreas Tille at 2018-04-10T16:08:47+02:00
Standards-Version: 4.1.4

- - - - -
10de8200 by Andreas Tille at 2018-04-10T16:31:34+02:00
Use blends.py

- - - - -
dc6f7bc9 by Ole Streicher at 2018-04-10T17:00:51+02:00
blends-gen-control: read defaults from GENCONTROL_OPTS env var

- - - - -
e39204f2 by Ole Streicher at 2018-04-10T17:51:27+02:00
Fix suppression of empty tasks

- - - - -
b2bd2dff by Ole Streicher at 2018-04-10T21:33:51+02:00
Restrict UDD query to "main" component

- - - - -
61fcc481 by Ole Streicher at 2018-04-11T08:50:20+02:00
Move distribution name fix to blends-dev Makefile

The idea is to have a consistent and transparent behaviour between udd
and apt update methods.

- - - - -
1a649bf1 by Ole Streicher at 2018-04-11T11:40:40+02:00
Oops. Forgot to reset distribution for 'unstable'

- - - - -
41c7deb4 by Ole Streicher at 2018-04-12T14:55:43+02:00
blends.py: Normalize "provides" column in UDD query

- - - - -
1c01f7f7 by Ole Streicher at 2018-04-12T16:32:35+02:00
blends.py: Handle versionized provides

Since some time, provides may be versionized. They are handled here by
just ignoring them.

- - - - -
af3096a3 by Ole Streicher at 2018-04-13T09:27:57+02:00
blends.py: escape backslashes in sql query

- - - - -
76f535f5 by Ole Streicher at 2018-04-13T09:28:39+02:00
blend-gen-control: help output fix

- - - - -
96643c65 by Ole Streicher at 2018-04-13T12:58:51+02:00
Remove empty GENCONTROL_OPTS definition from blends-dev/Makefile

- - - - -
dc702a0a by Ole Streicher at 2018-04-13T17:35:21+02:00
Improve program output

- - - - -
43ca1b83 by Ole Streicher at 2018-04-13T17:37:14+02:00
Fix file name in tasks gen stdout

- - - - -
d15041a4 by Ole Streicher at 2018-04-13T20:48:07+02:00
Merge branch 'experimental'

- - - - -
d79e49e0 by Ole Streicher at 2018-04-13T20:48:24+02:00
Update changelog for 0.7.0 release

- - - - -


17 changed files:

- + blends.py
- + debian/blends-dev.lintian-overrides
- debian/changelog
- debian/control
- debian/copyright
- + debian/python3-blends.doc-base
- + debian/python3-blends.docs
- + debian/python3-blends.pyinstall
- debian/rules
- devtools/Makefile
- devtools/blend-gen-control
- + devtools/tasks_diff
- − sources.list.UNRELEASED
- sources.list.unstable
- + sphinxdoc/Makefile
- + sphinxdoc/conf.py
- + sphinxdoc/index.rst


Changes:

=====================================
blends.py
=====================================
--- /dev/null
+++ b/blends.py
@@ -0,0 +1,836 @@
+'''A module to handle Debian Pure Blends tasks, modelled after apt.package.
+
+The examples use the following sample tasks file:
+
+>>> sample_task = """Format: https://blends.debian.org/blends/1.1
+... Task: Education
+... Install: true
+... Description: Educational astronomy applications
+...  Various applications that can be used to teach astronomy.
+...  .
+...  This is however incomplete.
+...
+... Recommends: celestia-gnome | celestia-glut, starplot
+...
+... Recommends: gravit
+... WNPP: 743379
+... Homepage: http://gravit.slowchop.com/
+... Pkg-Description: Visually stunning gravity simulator
+...  Gravit is a free, visually stunning gravity simulator.
+...  .
+...  You can spend endless time experimenting with various
+...  configurations of simulated universes.
+... Why: Useful package
+... Remark: Entered Debian in 2014
+...
+... Suggests: sunclock, xtide
+... """
+>>> with open('education', 'w') as fp:
+...     nbytes = fp.write(sample_task)
+'''
+import io
+import os
+import itertools
+import re
+import shutil
+from debian.deb822 import Deb822
+
+
+class Blend:
+    '''Representation of a Debian Pure Blend.
+    '''
+    def __init__(self, basedir='.'):
+        with open(os.path.join(basedir, 'debian', 'control.stub'),
+                  encoding="UTF-8") as fp:
+            self.control_stub = Deb822List(Deb822.iter_paragraphs(fp))
+
+        self.name = self.control_stub[0]['Source']
+        '''Full (package) name of the blend (``debian-astro``)'''
+
+        self.short_name = self.name.split('-', 1)[-1]
+        '''Short name of the blend (``astro``)'''
+
+        self.title = 'Debian ' + self.short_name.capitalize()
+        '''Blends title (``Debian Astro``)'''
+
+        base_deps = ["${misc:Depends}"]
+
+        self.prefix = self.short_name
+        '''Prefix for tasks (``astro``)'''
+
+        for pkg in self.control_stub[1:]:
+            p = pkg['Package'].split('-', 1)
+            if len(p) > 1 and p[1] == 'tasks':
+                self.prefix = p[0]
+                base_deps.append("{Package} (= ${{source:Version}})"
+                                 .format(**pkg))
+                break
+
+        try:
+            with open(os.path.join(basedir, 'config', 'control'),
+                      encoding="UTF-8") as fp:
+                self.control_stub.append(Deb822(fp))
+                base_deps.append("{}-config (= ${{source:Version}})"
+                                 .format(self.prefix))
+        except IOError:
+            pass
+
+        self.tasks = []
+        '''``Task`` list'''
+        for name in sorted(filter(lambda n: n[-1] != '~',
+                                  os.listdir(os.path.join(basedir, 'tasks')))):
+            with open(os.path.join(basedir, 'tasks', name),
+                      encoding="UTF-8") as fp:
+                task = Task(self, name, fp, base_deps=base_deps)
+            self.tasks.append(task)
+
+    def update(self, cache):
+        '''Update from cache
+
+        :param cache: ``apt.Cache`` like object
+
+        This adds the available versions to all dependencies. It
+        updates descriptions, summaries etc. available to all
+        BaseDependencies in all tasks.
+
+        Instead of using ``update()``, also the ``+=`` operator can be used.
+
+        '''
+        for task in self.tasks:
+            task += cache
+
+    def __iadd__(self, cache):
+        self.update(cache)
+        return self
+
+    @property
+    def all(self):
+        '''All Base Dependencies of this task
+        '''
+        return list(itertools.chain(*(t.all for t in self.tasks)))
+
+    def fix_dependencies(self):
+        '''Fix the dependencies according to available packages
+
+        This lowers all unavailable ``recommended`` dependencies to
+        ``suggested``.
+        '''
+        missing = []
+        for task in self.tasks:
+            missing += task.fix_dependencies()
+        return missing
+
+    def gen_control(self):
+        '''Return the task as list of ``Deb822`` objects suitable for
+        ``debian/control``
+        '''
+        tasks = list(filter(lambda task: task.is_metapackage, self.tasks))
+
+        # Create the special 'all' task recommending all tasks that
+        # shall be installed by default
+        all_task = Task(
+            self, "all",
+            '''Description: Default selection of tasks for {task.title}
+ This package is part of the {task.title} Pure Blend and installs all
+ tasks for a default installation of this blend.'''.format(task=self),
+            base_deps=['${misc:Depends}'])
+        for task in tasks:
+            if task.install:
+                all_task.recommends.append(Dependency("Recommends",
+                                                      task.package_name))
+            else:
+                all_task.suggests.append(Dependency("Suggests",
+                                                    task.package_name))
+        if len(all_task.recommends) > 0:
+            tasks.insert(0, all_task)
+
+        return Deb822List(self.control_stub
+                          + [task.gen_control() for task in tasks])
+
+    def gen_task_desc(self, udeb=False):
+        '''Return the task as list of ``Deb822`` objects suitable for
+        ``blends-task.desc``
+        '''
+        tasks = list(filter(lambda task: task.is_metapackage and task.is_leaf,
+                            self.tasks))
+
+        header = [Deb822({
+            'Task': self.name,
+            'Relevance':  '7',
+            'Section': self.name,
+            'Description': '{} Pure Blend\n .'.format(self.title),
+        })] if not udeb else []
+        return Deb822List(header + [task.gen_task_desc(udeb)
+                                    for task in tasks])
+
+
+class Task:
+    '''Representation of a Blends task. Modelled after apt.package.Version.
+
+    The Version class contains all information related to a
+    specific package version of a blends task.
+
+    :param blend: ``Blend`` object, or Blend name
+
+    :param name: Name of the task
+
+    :param sequence: ``str`` or ``file`` containing the ``Deb822``
+                     description of the task
+
+    :param base_deps: List of dependencies to add to the task (``str``)
+
+    When the header does not contain a line
+
+    ``Format: https://blends.debian.org/blends/1.1``
+
+    then the ``Depends`` priorities will be lowered to ``Recommends``
+    when read.
+
+    Example:
+
+    >>> with open('education') as fp:
+    ...     task = Task('debian-astro', 'education', fp)
+    >>> print(task.name)
+    education
+    >>> print(task.package_name)
+    astro-education
+    >>> print(task.description)
+    Various applications that can be used to teach astronomy.
+    <BLANKLINE>
+    This is however incomplete.
+    >>> print(task.summary)
+    Educational astronomy applications
+    >>> print(task.section)
+    metapackages
+    >>> print(task.architecture)
+    all
+    >>> for p in task.all:
+    ...     print(p.name)
+    celestia-gnome
+    celestia-glut
+    starplot
+    gravit
+    sunclock
+    xtide
+
+    '''
+    def __init__(self, blend, name, sequence, base_deps=None):
+        if isinstance(blend, str):
+            self.blend = blend
+            '''Blend name'''
+
+            self.prefix = blend[len('debian-'):] \
+                if blend.startswith('debian-') else blend
+            '''Metapackage prefix'''
+        else:
+            self.blend = blend.name
+            self.prefix = blend.prefix
+
+        self.name = name
+        '''Task name'''
+
+        self.content = Deb822List(Deb822.iter_paragraphs(sequence))
+        '''Deb822List content of the task'''
+
+        self.header = self.content[0]
+        '''Deb822 header'''
+
+        self.base_deps = base_deps or []
+        '''Base dependencies'''
+
+        # Check for the format version, and upgrade if not actual
+        self.format_upgraded = False
+        '''``True`` if the format was upgraded from an older version'''
+
+        if 'Format' in self.header:
+            self.format_version = self.header['Format'].strip() \
+                .rsplit('/', 1)[-1]
+        else:
+            self.format_version = '1'
+        if self.format_version.split('.') < ['1', '1']:
+            self.content = Task.upgrade_from_1_0(self.content)
+            self.format_upgraded = True
+
+        # Create package dependencies
+        dep_types = ["Depends", "Recommends", "Suggests"]
+        dep_attrs = ["dependencies", "recommends", "suggests"]
+        for dep_type, dep_attr in zip(dep_types, dep_attrs):
+            setattr(self, dep_attr, list(itertools.chain(
+                *(list(Dependency(dep_type, s.strip(), par)
+                       for s in par.get(dep_type, '').split(",") if s)
+                  for par in self.content[1:]))))
+        self.enhances = [
+            Dependency('Enhances', s.strip(), self.header)
+            for s in self.header.get('Enhances', '').split(",") if s
+        ]
+
+    @property
+    def install(self):
+        '''``True`` if the task is installed as a default package
+        '''
+        return self.header.get("Install") == "true"
+
+    @property
+    def index(self):
+        '''``True`` if the task shall appear in the tasks index in the
+        web senitel
+        '''
+        return self.header.get("index", "true") == "true"
+
+    @property
+    def is_leaf(self):
+        return self.header.get("leaf", "true") == "true"
+
+    @property
+    def is_metapackage(self):
+        '''``True`` if the tasks has a Debian metapackage
+        '''
+        return self.header.get("metapackage", "true") == "true"
+
+    @property
+    def package_name(self):
+        return '{task.prefix}-{task.name}'.format(task=self)
+
+    @property
+    def description(self):
+        '''Return the formatted long description.
+        '''
+        desc = self.header.get("Pkg-Description",
+                               self.header.get("Description"))
+        if not desc:
+            return None
+        else:
+            return "\n".join(line[1:] if line != ' .' else ''
+                             for line in desc.split("\n")[1:])
+
+    @property
+    def summary(self):
+        '''Return the short description (one line summary).
+        '''
+        desc = self.header.get("Pkg-Description",
+                               self.header.get("Description"))
+        return desc.split('\n')[0] if desc else None
+
+    @property
+    def section(self):
+        '''Return the section of the package.
+        '''
+        return 'metapackages'
+
+    @property
+    def architecture(self):
+        '''Return the architecture of the package version.
+        '''
+        return self.header.get('Architecture', 'all')
+
+    @property
+    def tests(self):
+        '''Return all tests for this task when included in tasksel
+        '''
+        tests = dict((key.split('-', 1)[1], value)
+                     for key, value in self.header.items()
+                     if key.startswith('Test-'))
+        if self.install:
+            tests['new-install'] = 'mark show'
+        return tests
+
+    @property
+    def all(self):
+        '''All Base Dependencies of this task
+        '''
+        return list(itertools.chain(
+            *itertools.chain(self.dependencies,
+                             self.recommends,
+                             self.suggests)))
+
+    def gen_control(self):
+        '''Return the task as ``Deb822`` object suitable for ``debian/control``
+
+        >>> with open('education') as fp:
+        ...     task = Task('debian-astro', 'education', fp)
+        >>> print(task.gen_control().dump())
+        Package: astro-education
+        Section: metapackages
+        Architecture: all
+        Recommends: celestia-gnome | celestia-glut,
+                    gravit,
+                    starplot
+        Suggests: sunclock,
+                  xtide
+        Description: Educational astronomy applications
+         Various applications that can be used to teach astronomy.
+         .
+         This is however incomplete.
+        <BLANKLINE>
+        '''
+        d = Deb822()
+        d['Package'] = self.package_name
+        d['Section'] = self.section
+        d['Architecture'] = self.architecture
+        if self.dependencies or self.base_deps:
+            d['Depends'] = ",\n         ".join(sorted(
+                self.base_deps
+                + list(set(d.rawstr for d in self.dependencies))
+            ))
+        if self.recommends:
+            d['Recommends'] = ",\n            ".join(sorted(
+                set(d.rawstr for d in self.recommends)
+            ))
+        if self.suggests:
+            d['Suggests'] = ",\n          ".join(sorted(
+                set(d.rawstr for d in self.suggests)
+            ))
+        d['Description'] = self.summary + '\n ' + \
+            "\n ".join(self.description.replace("\n\n", "\n.\n").split("\n"))
+        return d
+
+    def gen_task_desc(self, udeb=False):
+        '''Return the task as ``Deb822`` object suitable for ``blends-task.desc``.
+
+        :parameter udeb: if ``True``, generate ```blends-task.desc``
+                         suitable for udebs
+
+        >>> with open('education') as fp:
+        ...     task = Task('debian-astro', 'education', fp)
+        >>> print(task.gen_task_desc().dump())
+        Task: astro-education
+        Parent: debian-astro
+        Section: debian-astro
+        Description: Educational astronomy applications
+         Various applications that can be used to teach astronomy.
+         .
+         This is however incomplete.
+        Test-new-install: mark show
+        Key:
+         astro-education
+        <BLANKLINE>
+        >>> print(task.gen_task_desc(udeb=True).dump())
+        Task: astro-education
+        Section: debian-astro
+        Description: Educational astronomy applications
+         Various applications that can be used to teach astronomy.
+         .
+         This is however incomplete.
+        Relevance: 10
+        Test-new-install: mark show
+        Key:
+         astro-education
+        Packages: list
+         celestia-glut
+         celestia-gnome
+         gravit
+         starplot
+        <BLANKLINE>
+
+        '''
+        d = Deb822()
+        d['Task'] = self.package_name
+        if not udeb:
+            d['Parent'] = self.blend
+        d['Section'] = self.blend
+        d['Description'] = self.summary + '\n ' + \
+            "\n ".join(self.description.replace("\n\n", "\n.\n").split("\n"))
+        if udeb:
+            d['Relevance'] = '10'
+        if self.enhances:
+            d['Enhances'] = ', '.join(sorted(d.name for d in itertools.chain(
+                *self.enhances)))
+        for key, value in self.tests.items():
+            d['Test-' + key] = value
+        d['Key'] = '\n {}'.format(self.package_name)
+        if udeb:
+            d['Packages'] = 'list\n ' + \
+                          '\n '.join(sorted(d.name for d in itertools.chain(
+                              *(self.recommends + self.dependencies))))
+        return d
+
+    def update(self, cache):
+        '''Update from cache
+
+        This adds the available versions to all dependencies. It updates
+        descriptions, summaries etc. available to all BaseDependencies.
+
+        :param cache: ``apt.Cache`` like object
+
+        Instead of using ``update()``, also the ``+=`` operator can be used:
+
+        >>> import apt
+        >>> with open('education') as fp:
+        ...     task = Task('debian-astro', 'education', fp)
+        >>> dep = task.recommends[1][0]
+        >>> print(dep.name + ": ", dep.summary)
+        starplot:  None
+        >>> task += apt.Cache()
+        >>> print(dep.name + ": ", dep.summary)
+        starplot:  3-dimensional perspective star map viewer
+        '''
+        for dep in self.all:
+            pkg = cache.get(dep.name)
+            if pkg is not None:
+                dep.target_versions += pkg.versions
+            if hasattr(cache, 'get_providing_packages'):
+                for pkg in cache.get_providing_packages(dep.name):
+                    dep.target_versions += pkg.versions
+
+    def __iadd__(self, cache):
+        self.update(cache)
+        return self
+
+    def fix_dependencies(self):
+        '''Fix the dependencies according to available packages
+
+        This lowers all unavailable ``recommended`` dependencies to
+        ``suggested``.
+
+        >>> import apt
+        >>> with open('education') as fp:
+        ...     task = Task('debian-astro', 'education', fp)
+        >>> for dep in task.recommends:
+        ...     print(dep.rawstr)
+        celestia-gnome | celestia-glut
+        starplot
+        gravit
+        >>> for dep in task.suggests:
+        ...     print(dep.rawstr)
+        sunclock
+        xtide
+        >>> task += apt.Cache()
+        >>> missing = task.fix_dependencies()
+        >>> for dep in task.recommends:
+        ...     print(dep.rawstr)
+        starplot
+        gravit
+        >>> for dep in task.suggests:
+        ...     print(dep.rawstr)
+        sunclock
+        xtide
+        celestia-gnome | celestia-glut
+        '''
+        missing = list()
+        for recommended in self.recommends[:]:
+            suggested = Dependency("Suggests")
+            for dep in recommended[:]:
+                if len(dep.target_versions) == 0:
+                    recommended.remove(dep)
+                    suggested.append(dep)
+                    missing.append(dep)
+            if len(recommended) == 0:
+                self.recommends.remove(recommended)
+            if len(suggested) > 0:
+                self.suggests.append(suggested)
+        return missing
+
+    @staticmethod
+    def upgrade_from_1_0(content):
+        header = [("Format", "https://blends.debian.org/blends/1.1")]
+        header += list(filter(lambda x: x[0] != "Format", content[0].items()))
+        res = [dict(header)]
+        for p in content[1:]:
+            q = []
+            for key, value in p.items():
+                if key == 'Depends' and 'Recommends' not in p:
+                    key = 'Recommends'
+                # Remove backslashes, which are not DEB822 compliant
+                value = re.sub(r'\s*\\', '', value)
+                q.append((key, value))
+            res.append(dict(q))
+        return Deb822List(res)
+
+
+class Dependency(list):
+    '''Represent an Or-group of dependencies.
+
+    Example:
+
+    >>> with open('education') as fp:
+    ...     task = Task('debian-astro', 'education', fp)
+    >>> dep = task.recommends[0]
+    >>> print(dep.rawstr)
+    celestia-gnome | celestia-glut
+    '''
+
+    def __init__(self, rawtype, s=None, content=None):
+        super(Dependency, self).__init__(BaseDependency(bs.strip(), content)
+                                         for bs in (s.split("|") if s else []))
+        self.rawtype = rawtype
+        '''The type of the dependencies in the Or-group'''
+
+    @property
+    def or_dependencies(self):
+        return self
+
+    @property
+    def rawstr(self):
+        '''String represenation of the Or-group of dependencies.
+
+        Returns the string representation of the Or-group of
+        dependencies as it would be written in the ``debian/control``
+        file.  The string representation does not include the type of
+        the Or-group of dependencies.
+        '''
+        return ' | '.join(bd.rawstr for bd in self)
+
+    @property
+    def target_versions(self):
+        '''A list of all Version objects which satisfy this Or-group of deps.
+        '''
+        return list(itertools.chain(bd.target_versions for bd in self))
+
+
+class BaseDependency:
+    '''A single dependency.
+
+    Example:
+
+    >>> with open('education') as fp:
+    ...     task = Task('debian-astro', 'education', fp)
+    >>> dep = task.recommends[2][0]
+    >>> print(dep.rawstr)
+    gravit
+    >>> print(dep.wnpp)
+    743379
+    >>> print(dep.homepage)
+    http://gravit.slowchop.com/
+    >>> print(dep.description)
+    Gravit is a free, visually stunning gravity simulator.
+    <BLANKLINE>
+    You can spend endless time experimenting with various
+    configurations of simulated universes.
+    >>> print(dep.summary)
+    Visually stunning gravity simulator
+    '''
+
+    def __init__(self, s, content=None):
+        r = re.compile(r'([a-z0-9][a-z0-9+-\.]+)')
+        m = r.match(s)
+        if m is None or m.string != s:
+            raise ValueError('"{}" is not a valid package name'.format(s))
+        self.name = s
+        self.content = content or dict()
+        self.target_versions = []
+
+    def _get_from_target_versions(self, key):
+        for v in self.target_versions:
+            if v.package.name == self.name:
+                return getattr(v, key)
+
+    @property
+    def rawstr(self):
+        '''String represenation of the dependency.
+
+        Returns the string representation of the dependency as it
+        would be written in the ``debian/control`` file.  The string
+        representation does not include the type of the dependency.
+        '''
+        return self.name
+
+    @property
+    def wnpp(self):
+        '''The WNPP bug number, if available, or None
+        '''
+        return self.content.get("WNPP")
+
+    @property
+    def homepage(self):
+        '''Return the homepage for the package.
+        '''
+        return self._get_from_target_versions("homepage") or \
+            self.content.get("Homepage")
+
+    @property
+    def description(self):
+        '''Return the formatted long description.
+        '''
+        desc = self._get_from_target_versions("description")
+        if desc is not None:
+            return desc
+        desc = self.content.get("Pkg-Description",
+                                self.content.get("Description"))
+        if desc:
+            return "\n".join(line[1:] if line != ' .' else ''
+                             for line in desc.split("\n")[1:])
+
+    @property
+    def summary(self):
+        '''Return the short description (one line summary).
+        '''
+        summary = self._get_from_target_versions("summary")
+        if summary:
+            return summary
+
+        desc = self.content.get("Pkg-Description",
+                                self.content.get("Description"))
+        if desc:
+            return desc.split('\n')[0]
+
+    @property
+    def why(self):
+        return self.content.get("Why")
+
+    @property
+    def remark(self):
+        return self.content.get("Remark")
+
+
+class Deb822List(list):
+    '''A list of ``Deb822`` paragraphs
+    '''
+    def __init__(self, paragraphs):
+        list.__init__(self, (p if isinstance(p, Deb822) else Deb822(p)
+                             for p in paragraphs))
+
+    def dump(self, fd=None, encoding=None, text_mode=False):
+        '''Dump the the contents in the original format
+
+        If ``fd`` is ``None``, returns a ``str`` object. Otherwise,
+        ``fd`` is assumed to be a ``file``-like object, and this
+        method will write the data to it instead of returning an
+        ``str`` object.
+
+        If ``fd`` is not ``None`` and ``text_mode`` is ``False``, the
+        data will be encoded to a byte string before writing to the
+        file.  The encoding used is chosen via the encoding parameter;
+        None means to use the encoding the object was initialized with
+        (utf-8 by default).  This will raise ``UnicodeEncodeError`` if
+        the encoding can't support all the characters in the
+        ``Deb822Dict`` values.
+
+        '''
+        if fd is None:
+            fd = io.StringIO()
+            return_string = True
+        else:
+            return_string = False
+
+        for p in self:
+            p.dump(fd, encoding, text_mode)
+            fd.write("\n")
+
+        if return_string:
+            return fd.getvalue()
+
+
+def aptcache(release=None, srcdirs=['/etc/blends']):
+    '''Open and update a (temporary) apt cache for the specified distribution.
+
+    :param release: Distribution name
+
+    :param srcdirs: List of directories to search for
+        ``sources.list.<<release>>``
+
+    If the distribution is not given, use the system's cache without update.
+    '''
+    import tempfile
+    import apt
+
+    if release is None:
+        return apt.Cache()
+    rootdir = tempfile.mkdtemp()
+    try:
+        os.makedirs(os.path.join(rootdir, 'etc', 'apt'))
+        shutil.copytree('/etc/apt/trusted.gpg.d',
+                        os.path.join(rootdir, 'etc', 'apt', 'trusted.gpg.d'))
+        for src_dir in srcdirs:
+            sources_list = os.path.join(src_dir,
+                                        'sources.list.{}'.format(release))
+            if os.path.exists(sources_list):
+                shutil.copy(sources_list,
+                            os.path.join(rootdir, 'etc/apt/sources.list'))
+                break
+        else:
+            raise OSError("sources.list not found in " + str(srcdirs))
+        cache = apt.Cache(rootdir=rootdir, memonly=True)
+        cache.update()
+        cache.open()
+    finally:
+        shutil.rmtree(rootdir)
+    return cache
+
+
+def uddcache(packages, release, components=['main'], **db_args):
+    '''Create a ``dict`` from UDD that is roughly modelled after ``apt.Cache``.
+
+    The ``dict`` just resolves the version number and archs for the packages.
+    For performance reasons, an initial package list needs to be given.
+
+    :param release: Distribution name
+    :param packages: Initial package list
+    :param db_args: UDD connection parameters
+
+    ``Provided`` dependencies are integrated in the returned ``dict``.
+    '''
+    import collections
+    import psycopg2
+
+    pkgtuple = tuple(set(p.name for p in packages))
+
+    componenttuple = tuple(components)
+
+    Package = collections.namedtuple('Package',
+                                     ('name', 'versions',))
+    Version = collections.namedtuple('Version',
+                                     ('package', 'architecture', 'version'))
+
+    # To make the SELECT statements easier, we create a virtual view
+    # of the "packages" table that is restricted to the release and
+    # the component(s)
+    pkg_view_stmt = '''
+    CREATE TEMPORARY VIEW pkg
+    AS SELECT packages.package,
+           packages.version,
+           packages.architecture,
+           packages.provides
+    FROM packages, releases
+    WHERE packages.release=releases.release
+        AND (releases.release=%s  OR releases.role=%s)
+        AND packages.component IN %s;
+    '''
+
+    # Since the "provides" are in a comma separated list, we create a
+    # normalized view
+    provides_view_stmt = '''
+    CREATE TEMPORARY VIEW provides
+    AS SELECT DISTINCT
+        package,
+        version,
+        architecture,
+        regexp_split_to_table(regexp_replace(provides,
+                                             E'\\\\s*\\\\([^)]*\\\\)',
+                                             '', 'g'),
+                              E',\\\\s*') AS provides
+    FROM pkg;
+    '''
+
+    # Query all packages that have one of the specified names either as
+    # package name, or as one of the provides
+    query_stmt = '''
+    SELECT package,
+           version,
+           architecture,
+           provides
+    FROM pkg
+    WHERE package IN %s
+    UNION
+    SELECT package,
+           version,
+           architecture,
+           provides
+    FROM provides
+    WHERE provides IN %s;
+    '''
+
+    with psycopg2.connect(**db_args) as conn:
+        cursor = conn.cursor()
+        cursor.execute(pkg_view_stmt, (release, release, componenttuple))
+        cursor.execute(provides_view_stmt)
+        cursor.execute(query_stmt, (pkgtuple, pkgtuple))
+
+        cache = dict()
+        for package, version, arch, provides in cursor:
+            p = cache.setdefault(package, Package(package, []))
+            p.versions.append(Version(p, arch, version))
+            if provides:
+                for prv in provides.split(','):
+                    pp = cache.setdefault(prv, Package(package, []))
+                    pp.versions.append(Version(p, arch, version))
+        return cache


=====================================
debian/blends-dev.lintian-overrides
=====================================
--- /dev/null
+++ b/debian/blends-dev.lintian-overrides
@@ -0,0 +1,2 @@
+# Dependency on debhelper is intended
+blends-dev: binary-package-depends-on-toolchain-package depends: debhelper (>= 9)


=====================================
debian/changelog
=====================================
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,11 +1,61 @@
-blends (0.6.101) UNRELEASED; urgency=medium
+blends (0.7.0) unstable; urgency=low
 
+  [ Andreas Tille ]
+  * Do not mention subversion any more
+  * s/alioth/salsa/
+  * Description how Blends relevant data are gathered and stored
+  * Standards-Version: 4.1.4
+
+  [ Ole Streicher ]
+  * Switch back to unstable
+  * Create and use python3-blends package
+  * Move distribution name fix to blends-dev Makefile
+  * blends.py: Several UDD query fixes
+  * blends-gen-control: read defaults from GENCONTROL_OPTS env var
+  * blend-gen-control: Improve program output
+
+ -- Ole Streicher <olebole at debian.org>  Fri, 13 Apr 2018 20:40:52 +0200
+
+blends (0.6.103) experimental; urgency=low
+
+  [ Andreas Tille ]
+  * d/control:
+     - Build-Depends: dh-python, python3-all
+     - blends-dev: Depends: ${python3:Depends}
+  * d/rules: --with python3
+  * ignore false lintian warning binary-package-depends-on-toolchain-package
+  * cme fix dpkg-control
+  * Take over tasks_diff from blends-gsoc to create dependency_data
+  * Use Deb822List to evaluate taskprefix in tasks_diff
+
+  [ Ole Streicher ]
+  * Add UDD query options to blend-gen-control
+
+ -- Ole Streicher <olebole at debian.org>  Thu, 29 Mar 2018 21:33:29 +0200
+
+blends (0.6.102) experimental; urgency=low
+
+  * blend-gen-control: Bugfixes, slight refactorization, documentation
+     - Don't lower Recommends to Suggests in format 1.0
+     - Fail on backslashes in dependency lists
+
+ -- Ole Streicher <olebole at debian.org>  Wed, 28 Mar 2018 08:49:42 +0200
+
+blends (0.6.101) experimental; urgency=low
+
+  [ Andreas Tille ]
   * Moved to Salsa
-  * Degrade packages from contrib/non-free to Suggests even when enforcing
-    strict depends
-    Closes: #891188
 
- -- Andreas Tille <tille at debian.org>  Fri, 02 Mar 2018 14:44:16 +0100
+  [ Ole Streicher ]
+  * Switch to experimental
+  * Replace blend-gen-control with a Python implementation
+    - Degrade packages from contrib/non-free to Suggests even when enforcing
+      strict depends. Closes: #891188
+    - Do not drop virtual packages in alternatives. Closes: #785678
+    - Use Python deb822 to parse tasks files.
+    - Recognize Format field in tasks file header. Closes: #825161
+
+ -- Ole Streicher <olebole at debian.org>  Sat, 24 Mar 2018 10:41:20 +0100
 
 blends (0.6.100) unstable; urgency=medium
 
@@ -422,7 +472,7 @@ blends (0.6.3) unstable; urgency=low
 
   * Section for cdd-common misc instead of devel
   * Updated mailing list address of Debian Pure Blends team
-  * devtools/blends-gen-control: 
+  * devtools/blends-gen-control:
      - If a task lists not a single package which is available in
        the target distribution the metapackage will be suppressed
        if option '-S' is set; there is one exception which is needed
@@ -709,7 +759,7 @@ cdd (0.3.10) unstable; urgency=low
   * Otavio Salvador
     - cdd-gen-control:
       o Add -D suport to cdd-gen-control to degrade dependencies to
-        recommends; 
+        recommends;
       o Add support for architecture dependent packages;
       o Add fallback to architecture independent packages so allow
         backward compatibility;


=====================================
debian/control
=====================================
--- a/debian/control
+++ b/debian/control
@@ -4,25 +4,34 @@ Uploaders: Petter Reinholdtsen <pere at debian.org>,
            Andreas Tille <tille at debian.org>,
            Jonas Smedegaard <dr at jones.dk>,
            Ole Streicher <olebole at debian.org>,
-           Mike Gabriel <sunweaver at debian.org>,
+           Mike Gabriel <sunweaver at debian.org>
 Section: devel
 Priority: optional
 Build-Depends: debhelper (>= 10)
-Build-Depends-Indep: xmlto,
-                     dblatex,
-                     w3m
-Standards-Version: 4.0.0
+Build-Depends-Indep: dblatex,
+                     dh-python,
+                     python3-all,
+                     python3-apt,
+                     python3-debian,
+                     python3-pytest,
+                     python3-sphinx,
+                     w3m,
+                     xmlto
+Standards-Version: 4.1.4
 Vcs-Browser: https://salsa.debian.org/blends-team/blends
 Vcs-Git: https://salsa.debian.org/blends-team/blends.git
 
 Package: blends-dev
 Architecture: all
 Depends: debconf,
-         make | build-essential,
          debhelper (>= 9),
+         make | build-essential,
+         python3,
+         python3-apt,
+         python3-blends,
          ${misc:Depends}
 Suggests: blends-doc
-Replaces: cdd-dev
+Recommends: python3-psycopg2
 Description: Debian Pure Blends common files for developing metapackages
  This package makes life easier when packaging metapackages.  Perhaps
  this will also encourage other people to build metapackages if there are
@@ -33,8 +42,8 @@ Package: blends-common
 Architecture: all
 Section: misc
 Depends: adduser,
-         menu,
          debconf,
+         menu,
          ${misc:Depends}
 Suggests: blends-doc
 Description: Debian Pure Blends common package
@@ -49,9 +58,8 @@ Package: blends-doc
 Architecture: all
 Section: doc
 Depends: ${misc:Depends}
-Suggests: www-browser,
-          postscript-viewer
-Replaces: cdd-doc
+Suggests: postscript-viewer,
+          www-browser
 Description: Debian Pure Blends documentation
  This paper is intended to people who are interested in the philosophy
  of Debian Pure Blends and the technique which is used to
@@ -66,10 +74,10 @@ Description: Debian Pure Blends documentation
 
 Package: blends-tasks
 Architecture: all
-Priority: important
 Section: misc
-Depends: ${misc:Depends},
-         tasksel
+Priority: important
+Depends: tasksel,
+         ${misc:Depends}
 Description: Debian Pure Blends tasks for new installations
  This package installs a choice of a default installation for each
  Debian Pure Blend when run from the Debian installer. The
@@ -77,4 +85,19 @@ Description: Debian Pure Blends tasks for new installations
  invocation of tasksel enables the choice of individual tasks.
  .
  The package is intended to be installed in the base system. Later
- (un)installation is harmless, but has no effect.
\ No newline at end of file
+ (un)installation is harmless, but has no effect.
+
+Package: python3-blends
+Architecture: all
+Section: python
+Depends: python3-debian,
+         ${misc:Depends},
+         ${python3:Depends},
+         ${sphinxdoc:Depends}
+Suggests: python3-psycopg2,
+          python3-apt
+Description: Python 3 module for Debian Pure Blends support
+ This package installs a module to handle Debian Pure Blends tasks.
+ It reads the tasks description from unpacked Blend Metapackages
+ sources. It is directly possible to create the debian/control file
+ and the tasks definition files from it.


=====================================
debian/copyright
=====================================
--- a/debian/copyright
+++ b/debian/copyright
@@ -1,12 +1,11 @@
-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
 
 Files: *
 Copyright: © 2003-2012 Andreas Tille <tille at debian.org>
 License: GPL-2+
 
 Files: devtools/blend-gen-control
-Copyright: © 2003-2007 Petter Reinholdtsen <pere at debian.org>
-           © 2007-2012 Andreas Tille <tille at debian.org>
+Copyright: © 2018 Ole Streicher <olebole at debian.org>
 License: GPL-2+
 
 Files: share/blends/*


=====================================
debian/python3-blends.doc-base
=====================================
--- /dev/null
+++ b/debian/python3-blends.doc-base
@@ -0,0 +1,10 @@
+Document: python-blends
+Title: Debian Pure Blends Python package
+Author: Ole Streicher
+Abstract: This document describes the Debian Pure Blends
+ Python package API.
+Section: Debian
+
+Format: HTML
+Index: /usr/share/doc/python3-blends/index.html
+Files: /usr/share/doc/python3-blends/*.html


=====================================
debian/python3-blends.docs
=====================================
--- /dev/null
+++ b/debian/python3-blends.docs
@@ -0,0 +1 @@
+sphinxdoc/_build/html/*


=====================================
debian/python3-blends.pyinstall
=====================================
--- /dev/null
+++ b/debian/python3-blends.pyinstall
@@ -0,0 +1 @@
+blends.py


=====================================
debian/rules
=====================================
--- a/debian/rules
+++ b/debian/rules
@@ -8,12 +8,19 @@ include /usr/share/dpkg/default.mk
 DISTDIR := $(DEB_SOURCE)-$(DEB_VERSION)
 
 %:
-	dh $@
+	dh $@ --with python3,sphinxdoc
 
 override_dh_auto_build:
-	cd doc; $(MAKE) html; $(MAKE) txt; $(MAKE) pdf
+	$(MAKE) -C doc html txt pdf
+	$(MAKE) -C sphinxdoc html
 	dh_auto_build
 
+override_dh_auto_test:
+ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
+	pytest-3 --doctest-modules
+	rm -f education # clean up test remnants
+endif
+
 override_dh_installchangelogs:
 	for pkgnews in $(DEB_SOURCE)-common $(DEB_SOURCE)-dev ; do \
 	    cp -a debian/$$pkgnews.NEWS.Debian debian/$$pkgnews/usr/share/doc/$$pkgnews/NEWS.Debian ; \
@@ -21,7 +28,8 @@ override_dh_installchangelogs:
 	dh_installchangelogs
 
 override_dh_auto_clean:
-	cd doc; $(MAKE) clean
+	$(MAKE) -C doc clean
+	$(MAKE) -C sphinxdoc clean
 	dh_auto_clean
 
 override_dh_compress:


=====================================
devtools/Makefile
=====================================
--- a/devtools/Makefile
+++ b/devtools/Makefile
@@ -9,32 +9,92 @@
 # TARGET_DIST is one of stable, sarge, etch, unstable, or any other available
 # sources.list file available
 TARGET_DIST := $(shell head -1 debian/changelog |awk '{print $$3}'|tr -d ';')
+
+# using unstable as target distribution for the meta package dependencies
+# does actually not sound reasonable.  The idea is to enable a smooth transition
+# to testing for all meta packages and thus here testing is used as target
+# distribution.
+ifeq "$(TARGET_DIST)" "unstable"
+	TARGET_DIST := "testing"
+endif
+
+# It is a good practice to use UNRELEASED in the changelog as target
+# distribution for not yet finished packages and blends-dev should
+# also work in this case.
+ifeq "$(TARGET_DIST)" "UNRELEASED"
+	TARGET_DIST := "unstable"
+endif
+
 BLEND := $(shell /usr/share/blends-dev/blend-get-names blendname)
+VERSION  := $(shell dpkg-parsechangelog -ldebian/changelog | grep Version: | cut -f2 -d' ' | cut -f1 -d- )
 GENCONTROL := /usr/share/blends-dev/blend-gen-control
-GENCONTROL_OPTS := $(shell grep -q -E '^GENCONTROL_DEPENDS\s*=\s*(T|t)(R|r)(U|u)(E|e)\s*$$' Makefile || echo "-D")
+TASKSDIFF := /usr/share/blends-dev/tasks_diff
+DEPENDENCIES_DATA := dependency_data
 TASKSELOPTS := $(shell grep TASKSELOPTS Makefile | cut -d '=' -f2)
 
 # Verify whether config/control exists, if yes, add it to the depends of debian/control
 CONFIGCONTROL := $(shell if [ -d config -a -e config/control ] ; then echo config/control; fi)
 
+#get two latest releases
+RELEASES   := $(shell grep '^$(BLEND)' debian/changelog | head -2 | awk '{print $$2}' | tr -d '[(,)]')
+LATEST    := $(shell echo "$(RELEASES)" | cut -d ' ' -f1)
+PREVIOUS   := $(shell echo $(RELEASES) | cut -d ' ' -f2)
+
+ifneq "$(LATEST)" "$(PREVIOUS)"
+	LINEEND   := $(shell lineend=`grep '^$(BLEND)' debian/changelog -n | sed -n 2p | cut -d ':' -f1`; echo "$$lineend - 3" | bc)
+	LINESTART  := $(shell linestart=`grep "* start of automatic changelog entry *" debian/changelog -n | head -1 | awk '{print $$1}' | tr -d ':'`; if [ -z "$$linestart" ]; then echo "0"; else echo "$$linestart - 1" | bc; fi)
+
+	ISGREATER := $(shell expr $(LINESTART) \> $(LINEEND))
+
+	ifeq "$(LINESTART)" "0"
+		LINESTART := $(LINEEND)
+	else ifeq "$(ISGREATER)" "1"
+		LINESTART := $(LINEEND)
+	endif
+endif
+
 all: $(BLEND)-tasks.desc debian/control
 
 debian/control: debian/control.stub debian/changelog tasks/* $(CONFIGCONTROL)
-	(export LC_ALL=C;\
-	 echo "# This file is autogenerated via "make -f debian/rules dist".  Do not edit!"; \
-	 cat debian/control.stub; \
-	 test -f config/control && ( cat config/control; echo ) ; \
-	$(GENCONTROL) -s $(TARGET_DIST) -S $(GENCONTROL_OPTS) -c -m -i -A) > $@.new && mv $@.new $@
+	$(GENCONTROL) -r $(TARGET_DIST) -S -c -m
 
 tasksel: $(BLEND)-tasks.desc
 $(BLEND)-tasks.desc: tasks/* debian/changelog
-	LC_ALL=C $(GENCONTROL) -s $(TARGET_DIST) $(TASKSELOPTS) -S -t -A > $(BLEND)-tasks.desc.new && mv $(BLEND)-tasks.desc.new $(BLEND)-tasks.desc
+	$(GENCONTROL) $(TASKSELOPTS) -r $(TARGET_DIST) -S -t
+
+dependency_data:
+	if [ ! -d $(DEPENDENCIES_DATA) ]; then \
+		echo "$(DEPENDENCIES_DATA) directory does not exist, creating it now."; \
+		mkdir $(DEPENDENCIES_DATA); \
+	fi
+
+statusdump: dependency_data
+	$(TASKSDIFF) --status-dump --tasks .  --output $(DEPENDENCIES_DATA)/$(BLEND)_$(VERSION).json
+
+#update changelog with dependencies changes
+changelogentry: debian/changelog statusdump
+ifneq "$(LATEST)" "$(PREVIOUS)"
+	if [ ! -f $(DEPENDENCIES_DATA)/$(BLEND)_$(LATEST).json ]; then \
+		echo "$(DEPENDENCIES_DATA)/$(BLEND)_$(LATEST).json does not exist, can not generate changelog dependencies-changes entry"; \
+		exit -1; \
+	fi
+	if [ ! -f $(DEPENDENCIES_DATA)/$(BLEND)_$(PREVIOUS).json ]; then \
+		echo "$(DEPENDENCIES_DATA)/$(BLEND)_$(PREVIOUS).json does not exist, can not generate changelog dependencies-changes entry"; \
+		exit -1; \
+	fi
+
+	(sed $(LINESTART)q debian/changelog; \
+	 $(TASKSDIFF) --compare $(DEPENDENCIES_DATA)/$(BLEND)_$(LATEST).json,$(DEPENDENCIES_DATA)/$(BLEND)_$(PREVIOUS).json | sed 's/^/  /'; \
+	 sed -n '$(LINEEND),$$p' debian/changelog; ) > debian/changelog.new && mv debian/changelog.new debian/changelog
+else
+	echo "It is the first release, skip the changelog entry"
+endif
 
 packages.txt: tasks/*
-	$(GENCONTROL) -s $(TARGET_DIST) -a > packages.txt.$$$$ && mv packages.txt.$$$$ packages.txt
+	$(GENCONTROL) -r $(TARGET_DIST) -a > packages.txt.$$$$ && mv packages.txt.$$$$ packages.txt
 
 avoidpackages.txt: tasks/* sources.list.$(TARGET_DIST)
-	$(GENCONTROL) -s $(TARGET_DIST) -e > avoidpackages.txt.$$$$ && mv avoidpackages.txt.$$$$ avoidpackages.txt
+	$(GENCONTROL) -r $(TARGET_DIST) -e > avoidpackages.txt.$$$$ && mv avoidpackages.txt.$$$$ avoidpackages.txt
 
 by_vote:
 	rm -f by_vote


=====================================
devtools/blend-gen-control
=====================================
--- a/devtools/blend-gen-control
+++ b/devtools/blend-gen-control
@@ -1,812 +1,149 @@
-#!/usr/bin/perl
-#
-# Authors:
-#       Petter Reinholdtsen <pere at hungry.com>
-#       Andreas Tille <tille at debian.org>
-# Date:   2001-08-23
-#
-# Generate the control file used by the Blend task package.
+#!/usr/bin/python3
 
-use warnings;
-use strict;
+import os
+import sys
+import argparse
+import itertools
 
-use Getopt::Std;
-use File::Path;
+from blends import Blend, aptcache, uddcache
 
-use vars qw(%opts %available %excluded %included @wanted %missing
-            @tasks $debug $suppressempty);
-my @arch = qw(alpha arm i386 ia64 m68k mips mipsel powerpc s390 sparc hppa);
+default_release = "testing"
 
-my $debug           = 0;
-my $nodepends       = 0;
-my $ignoreapterrors = 0;
-my $suppressempty   = 0;
+parser = argparse.ArgumentParser()
 
-my %taskinfo = ();
-my $tasksdir = "tasks" ;
-my $taskcontrolfile = "tasks.ctl" ;
+parser.add_argument("-d", "--dir", dest="dir", type=str,
+                    default=".",
+                    help="Base directory of the tasks source package"
+                    + " (default: current directory)")
 
-my $aptsourcesdefaultlocation = "/etc/blends";
-my $aptsources = $aptsourcesdefaultlocation . "/sources.list";
-my $blend_dev_dir = "/usr/share/blends-dev";
-
-my %commondepends ;
-$commondepends{"adduser"}    = "0" ;
-$commondepends{"debconf"}    = "1.2" ;
-$commondepends{"menu"}       = "2.1.14" ;
-
-my $CommonPackage = "" ;
-my $prefix        = "test-" ;
-my $blendname       = "" ;
-my $blendshortname  = "" ;
-my $blendtitle  = "" ;
-my $tasksname     = "" ;
-my $hasconfig     = 0 ;
-
-sub usage() {
-    print STDERR << "EOF";
-blend-gen-control help screen
-usage: $0 [options]
-
-   -a               : print wanted packages
-   -A               : ignore APT errors
-   -c               : create new debian/control file
-   -d               : print debug information
-   -D               : lower all Depends: to Recommends:
-   -e               : print excluded packages
-   -h               : print this help screen and exit
-   -i               :
-   -m               : print missing packages
-   -s <sourcefile>  : specify which sources.list file to use
-   -S               : suppress tasks without any recommended package
-   -t               : print task descriptions and package list for task
-   -u               : modify tasks desc file in case the blend uses udebs
-
-example: $0 -s sources.list.etch -D -c -m -i
-EOF
-}
-
-getopts("cdaemis:StuDhA", \%opts);
-
-usage() and exit if $opts{'h'};
-
-my $aptsourcesinput = $opts{'s'} if ($opts{'s'});
-$aptsources = $aptsourcesinput ;
-if ( $aptsources !~ m%^/% && $aptsources !~ /^sources\.list\./ ) {
-    my $cwd ;
-    chomp($cwd = `pwd`) ;
-    $aptsources = $cwd . "/sources.list." . $aptsources ;
-}
-if ( ! -e $aptsources ) {
-    $aptsources = $aptsourcesdefaultlocation . "/sources.list." . $aptsourcesinput;
-    if ( ! -e $aptsources ) {
-        die "Apt sources.list $aptsources not found.\n" ;
-    }
-}
-
-$debug           = 1 if ($opts{'d'});
-$nodepends       = 1 if ($opts{'D'});
-$ignoreapterrors = 1 if ($opts{'A'});
-$suppressempty   = 1 if ($opts{'S'});
-
-blend_init();
-
-# print "$prefix ; $blendshortname ; $blendname ; $tasksname \n";
-
-load_available_packages();
-
-load_tasks();
-
-# An ordered list of Blend tasks, in priority order of which are
-# most needed on the CD. Only leaf tasks need be listed.
-my @priorityorder = get_priorities("priority-high", 1);
-my @medpriorder   = get_priorities("priority-med", 0);
-
-# print "high: @priorityorder\nmed: @medpriorder\n" ;
-
-if ($opts{'c'}) {
-    gen_control();
-} elsif ($opts{'e'}) {
-    print_excluded_packages();
-} elsif ($opts{'a'}) {
-    print_all_packages();
-} elsif ($opts{'t'}) {
-    print_task_desc();
-} else {
-    print_available_packages();
-}
-print_missing_packages() if ($opts{'m'});
-
-sub apt {
-    my $op = shift;
-
-    my $aptdir  = "../tmp/apt";
-    # FIXME: For propper apt configuration see
-    #        https://lists.debian.org/debian-mentors/2014/11/msg00032.html
-    #        https://lists.debian.org/debian-mentors/2014/11/msg00033.html
-    # For the moment to do only minimal changes in freeze time we set
-    #        Dir::Etc::sourceparts=none
-    # to prevent including random sources from users sources.list.d
-    my @aptopts = ("Dir::Etc::sourcelist=$aptsources",
-                   "Dir::Etc::sourceparts=none",
-                   "Dir::State=$aptdir/state",
-                   "Dir::Cache=$aptdir/cache",
-                   "Dir::State::Status=/dev/null",
-                   "Debug::NoLocking=true",
-                   "APT::Get::AllowUnauthenticated=true");
-
-    # Stupid apt-get and apt-cache do not understand the same arguments!
-    # I have to map them to different formats to get both working.
-
-    if ("update" eq $op) {
-        mkpath "$aptdir/state/lists/partial";
-        mkpath "$aptdir/cache/archives/partial";
-
-        my $aptget   = "apt-get --assume-yes -o " . join(" -o ", @aptopts);
-
-        print STDERR "aptget: $aptget\n" if $debug;
-        if (system("$aptget update 1>&2")) {
-            print STDERR "error: updating apt package lists failed\n";
-            exit 1 unless $ignoreapterrors;
-        }
-    } elsif ("apt-cache" eq "$op") {
-        my $aptcache = "apt-cache -o=" . join(" -o=", @aptopts);
-        print STDERR "aptcache: $aptcache\n" if $debug;
-        return $aptcache;
-    }
-}
-
-sub sort_uniq {
-    my $seen = shift;
-    my @list;
-    for my $entry (sort @_) {
-        push @list,$entry unless $seen->{$entry};
-        $seen->{$entry} = 1;
-    }
-    return @list;
-}
-
-sub uniq {
-    my $seen = shift;
-    my @list;
-    for my $entry (@_) {
-        push @list,$entry unless $seen->{$entry};
-        $seen->{$entry} = 1;
-    }
-    return @list;
-}
-
-sub gen_control {
-    my $task;
-
-    my @recommends;
-    my @suggests;
-    for $task (sort keys %taskinfo) {
-        next if (exists $taskinfo{$task}{'Metapackage'} &&
-                        $taskinfo{$task}{'Metapackage'} eq 'false');
-        next if ( $suppressempty && $taskinfo{$task}{'haspackages'} == 0 );
-        if (exists $taskinfo{$task}{'Install'} &&
-            $taskinfo{$task}{'Install'} eq 'true') {
-            push (@recommends, $task)
-        } else {
-            push (@suggests, $task)
-        }
-    }
-    if (@recommends) {
-        print "Package: " . $prefix . "all\n";
-        print "Section: metapackages\n" ;
-        print "Architecture: all\n";
-        print("Recommends: ", join(",\n ", @recommends),"\n");
-        print("Suggests: ", join(",\n ", @suggests),"\n") if @suggests;
-        print "Description: Default selection of tasks for $blendtitle\n";
-        print " This package is part of the $blendtitle Pure Blend and installs all\n";
-        print " tasks for a default installation of this blend.\n\n";
-    }
-
-    for $task (sort keys %taskinfo) {
-        next if (exists $taskinfo{$task}{'Metapackage'} &&
-                        $taskinfo{$task}{'Metapackage'} eq 'false');
-
-        print STDERR "$task: $taskinfo{$task}{'haspackages'}\n" if $debug;
-        # if no package was found in the target distribution suppress this task at all
-        if ( $suppressempty && $taskinfo{$task}{'haspackages'} == 0 ) {
-            print STDERR "The metapackage $task will not be created because $taskinfo{$task}{'haspackages'} dependant are in the pool and suppressempty was set ($suppressempty)\n" if $debug;
-            next ;
-        }
-        print "Package: $task\n";
-
-        # metapackages should not be Section misc -> see #720199
-        if (defined $taskinfo{$task}{'Section'} && $taskinfo{$task}{'Section'} ne 'misc') {
-            print "Section: $taskinfo{$task}{'Section'}\n" ;
-        } else {
-            print "Section: metapackages\n" ;
-        }
-        my $header;
-        for $header (qw(Architecture Priority)) {
-            print "$header: $taskinfo{$task}{$header}\n"
-                if (defined $taskinfo{$task}{$header});
-        }
-        my %seenlist;
-        if ($nodepends) {
-                # degrade dependencies to recommends
-                if ( $tasksname ) {
-                    print "Depends: $tasksname";
-                    if ( $tasksname =~ /-tasks$/ ) {
-                        print ' (= ${source:Version}), ${misc:Depends}';
-                    }
-                    if ( $hasconfig ) {
-                        print ', ' . $prefix . 'config (= ${source:Version})';
-                    }
-                    print "\n" ;
-                }
-                # Use common %seenlist, as it is no use listing
-                # packages both in recommends and suggest
-                my @list;
-                for $header (qw(Depends Recommends)) {
-                    push (@list, @{$taskinfo{$task}{$header}})
-                        if defined $taskinfo{$task}{$header};
-                }
-                my ($pkglist, $missinglist) = process_pkglist(join(",", at list));
-                my (@recommends, @suggests);
-                push @recommends, @{$pkglist};
-                push @suggests, @{$missinglist};
-                push(@suggests, @{$taskinfo{$task}{Suggests}})
-                    if defined $taskinfo{$task}{Suggests};
-                my @recommends_sorted = sort_uniq(\%seenlist, @recommends);
-                my @suggests_sorted = sort_uniq(\%seenlist, @suggests);
-                print("Recommends: ",
-                      join(",\n ", @recommends_sorted),"\n")
-                        if @recommends_sorted;
-                print("Suggests: ",
-                      join(",\n ", @suggests_sorted),"\n")
-                    if @suggests_sorted;
-        }
-        else {
-                my $pkglist;
-                my $missinglist;
-                my $pkgcandidate;
-                if (defined $taskinfo{$task}{Depends})
-                {
-                    ($pkglist, $missinglist) = process_pkglist(join(",",@{$taskinfo{$task}{Depends}}));
-                }
-                # make sure that $missinglist will not remain empty if there are no Depends defined
-                if (defined $taskinfo{$task}{Recommends})
-                {
-                    ($pkglist, $missinglist) = process_pkglist(join(",",@{$taskinfo{$task}{Recommends}}));
-                }
-
-                my (@depends, @recommends, @suggests);
-
-                push @depends, $tasksname.' (= ${source:Version})';
-                push @depends, '${misc:Depends}';
-                if ( defined $pkglist ) {
-                    for $pkgcandidate (@{$pkglist}) {
-                        unless ( grep( /^$pkgcandidate$/, $missinglist ) ) { push @depends, $pkgcandidate };
-                    }
-                }
-                if ( defined $taskinfo{$task}{Recommends} ) {
-                    for $pkgcandidate (@{$taskinfo{$task}{Recommends}}) {
-                        unless ( grep( /^$pkgcandidate$/, $missinglist ) ) { push @recommends, $pkgcandidate };
-                    }
-                }
-
-                push @suggests, @{$missinglist}
-                    if defined $missinglist;
-                push @suggests, @{$taskinfo{$task}{Suggests}}
-                    if defined $taskinfo{$task}{Suggests};
-
-                my @depends_sorted = sort_uniq(\%seenlist, @depends);
-                my @recommends_sorted = sort_uniq(\%seenlist, @recommends);
-                my @suggests_sorted = sort_uniq(\%seenlist, @suggests);
-
-                print("Depends: ",
-                      join(",\n ", @depends_sorted),"\n")
-                        if @depends_sorted;
-                print("Recommends: ",
-                      join(",\n ", @recommends_sorted),"\n")
-                        if @recommends_sorted;
-                print("Suggests: ",
-                      join(",\n ", @suggests_sorted),"\n")
-                    if @suggests_sorted;
-        }
-
-        # Description Description-long
-        print "Description: $taskinfo{$task}{Description}\n";
-        print "$taskinfo{$task}{'Description-long'}"; # Already contain newline
-
-        print "\n";
-    }
-}
-
-# List all depends, recommends and suggests packages as task packages.
-# Optionally, list depends as key packages, and the rest as task
-# packages.
-# Enable to list all dependencies as key packages
-my $task_depends_are_keys = 0;
-sub print_task_desc {
-        if (! $opts{'u'} ) {
-            print "Task: $blendname\n";
-            print "Relevance: 7\n";
-            print "Section: $blendname\n";
-#        print "Key: \n";
-#        print " $blendshortname-tasks\n";
-            print "Description: $blendtitle Pure Blend\n";
-            print " .\n"; # no long description available
-            print "\n";
-        }
-        foreach my $task (sort keys %taskinfo) {
-                next if (exists $taskinfo{$task}{'Leaf'} &&
-                        $taskinfo{$task}{'Leaf'} eq 'false');
-                next if (exists $taskinfo{$task}{'Metapackage'} &&
-                         $taskinfo{$task}{'Metapackage'} eq 'false');
-                my $header;
-                if ( $suppressempty && $taskinfo{$task}{'haspackages'} == 0 ) {
-                    # Check for Test-always-lang header.  If this field exists the
-                    # task should be created even if there are no explicite dependencies
-                    # This is a request of Debian Edu (see the thread at
-                    # http://lists.debian.org/debian-blends/2009/04/msg00008.html
-                    my $foundflag = 0;
-                    for $header (keys %{$taskinfo{$task}}) {
-                        if ($header =~ m/Test-always-lang/) {
-                            print STDERR "Print empty task $task because Test-always-lang is set\n" if $debug;
-                            $foundflag = 1;
-                            last;
-                        }
-                    }
-                    if ( $foundflag == 0 ) {
-                        print STDERR "The metapackage $task will not be created because $taskinfo{$task}{'haspackages'} dependant are in the pool and suppressempty was set ($suppressempty)\n" if $debug;
-                        next ;
-                    }
-                }
-                print "Task: $task\n";
-                if (! $opts{'u'}) {
-                    print "Parent: $blendname\n";
-                }
-                print "Section: $blendname\n";
-                print "Description: $taskinfo{$task}{Description}\n";
-                print "$taskinfo{$task}{'Description-long'}"; # Already contain newline
-                if ($opts{'u'}) {
-                    print "Relevance: 10\n";
-                }
-                print "Enhances: $taskinfo{$task}{Enhances}\n"
-                    if exists $taskinfo{$task}{Enhances};
-                if (exists $taskinfo{$task}{'Install'} &&
-                    $taskinfo{$task}{'Install'} eq 'true') {
-                        if (! $opts{'u'}) {
-                            print "Test-new-install: mark show\n";
-                        }
-                }
-                for $header (keys %{$taskinfo{$task}}) {
-                    if ($header =~ m/test-.+/i) {
-                        print "$header: $taskinfo{$task}{$header}\n";
-                    }
-                }
-                unless (exists $taskinfo{$task}{'Metapackage'} &&
-                        $taskinfo{$task}{'Metapackage'} eq 'false') {
-                    # No use listing a metapackage as a key package, if no metapackage exist.
-                    print "Key: \n";
-                    print " $task\n";
-                }
-                my %seen;
-                $seen{$task} = 1;
-                if ($task_depends_are_keys) {
-                    foreach my $package (task_packages($task, "Depends")) {
-                        print " $package\n" unless $seen{$package};
-                        $seen{$package} = 1;
-                    }
-                }
-
-                if ($opts{'u'}) {
-                    print "Packages: list\n";
-                    for my $header (qw(Depends Recommends)) {
-                        foreach my $package (task_packages($task, $header, 1)) {
-                            print " $package\n" unless $seen{$package};
-                            $seen{$package} = 1;
-                        }
-                    }
-                }
-                print "\n";
-        }
-}
-
-sub select_alternative {
-    my $pkglist = shift;
-    return $pkglist;
-}
-
-sub task_packages {
-        my ($task, $header, $includealldeps) = @_;
-        my @packages = $task;
-        foreach my $package (@{$taskinfo{$task}{$header}}) {
-                if ($package=~/\|/) {
-                        # Tasksel doesn't allow boolean or-ing of
-                        # dependencies. Just take the first one that is
-                        # available.
-                        my $ok=0;
-                        foreach my $alternative (split(' | ', $package)) {
-                                if (! exists $taskinfo{$alternative} &&
-                                    ! exists $available{$alternative}) {
-                                        if (! exists $missing{$alternative}) {
-                                                $missing{$alternative} = 1;
-                                        }
-                                }
-                                else {
-                                        print STDERR "task_packages: choosing $alternative from $package\n" if $debug;
-                                        $package=$alternative;
-                                        $ok=1;
-                                        last;
-                                }
-                        }
-                        if (! $ok) {
-                                next;
-                        }
-                }
-                if (exists $taskinfo{$package}) {
-                        # Add packages from task recursively, since
-                        # tasksel does not support dependent tasks of
-                        # the type used by Blend
-                        if (defined $includealldeps && $includealldeps) {
-                                for my $h (qw(Depends Recommends)) {
-
-                                        push(@packages, $package,
-                                             task_packages($package, $h, 1));
-                            }
-                        } else {
-                                push(@packages, $package,
-                                     task_packages($package, $header));
-                        }
-                }
-                else {
-                        push @packages, $package;
-                }
-        }
-        return @packages;  ### FIXME!: insert sort here to enable sorted list inside tasks control file (tested on 2014-11-03 but do not touch in freeze time)
-}
-
-#
-# Check the APT cache, and find the packages currently available.
-#
-sub load_available_packages
-{
-    apt("update");
-    my $aptcache = apt("apt-cache");
-    open(APT, "$aptcache dump |") || die "Unable to start apt-cache";
-    my $pkg;
-    while (<APT>) {
-        chomp;
-        if (/^Package: (.+)$/) {
-            $pkg = $1;
-            print STDERR "Found pkg '$pkg'\n" if $debug;
-        }
-        if (/^\s+Version:\s+(.+)/) {
-            print STDERR " pkg $pkg = ver $1\n" if $debug;
-#           print "C: $pkg $available{$pkg} lt $1\n" if ( exists $available{$pkg});
-            $available{$pkg} = $1 if ( ! exists $available{$pkg} ||
-                                       $available{$pkg} lt $1 );
-        }
-    }
-}
-
-#
-# Load all tasks
-#
-sub load_tasks {
-    my $taskfile;
-
-    # First document their existence, so they can depend on each other.
-    for $taskfile (<tasks/*>) {
-        next if (($taskfile eq "tasks/CVS") || ($taskfile eq "tasks/.svn"));
-        next if ($taskfile =~ m/~$/);
-
-        my $curpkg = $taskfile;
-        $curpkg =~ s%tasks/%$prefix%;
-        $available{$curpkg} = "n/a";
-
-        push(@tasks, "$taskfile:$curpkg");
-    }
-
-    # Next, load their content.
-    my $foo;
-    for $foo (@tasks) {
-        my ($taskfile, $curpkg) = $foo =~ m/^(.+):(.+)$/;
-        next if ("tasks/CVS" eq $taskfile);
-
-        load_task($taskfile, $curpkg);
-    }
-}
-
-sub process_pkglist {
-    my $pkgstring = shift;
-    my @pkglist = ();
-    my @missinglist = ();
-    my $packages;
-    for $packages (split(/\s*,\s*/, $pkgstring)) {
-        print "E: double comma?: $_\n" if ($packages =~ /^\s*$/ && $debug);
-        my $package;
-        my @alternates=split(/\s*\|\s*/, $packages);
-        my $alternatecount=0;
-        for $package (@alternates) {
-            print STDERR "Loading pkg '$package'\n" if $debug;
-            if ($package =~ /[A-Z]/) {
-                print STDERR "Packages may not contain upper case letters (policy 5.6.7) $package. Name will be turned into ";
-                $package = lc($package);
-                print STDERR "$package\n";
-            }
-            if ($package =~ /^-(.+)$/) {
-                $excluded{$1} = 1;
-            } elsif ( !exists $available{$package} ) {
-                if ( !exists $missing{$package}) {
-                    $missing{$package} = 1;
-                }
-                push(@missinglist, $package);
-            } else {
-                if ($alternatecount == 0) {
-                    #push(@pkglist, $package) if (! exists $pkglist[$package]);
-                    push(@pkglist, $package);
-                }
-                else {
-                    $pkglist[-1].=" | $package";
-                }
-                $alternatecount++;
-
-                if ( ! $included{$package} ) {
-                    push(@wanted, $package);
-                    $included{$package} = 1;
-                }
-            }
-        }
-    }
-    return (\@pkglist, \@missinglist);
-}
-
-sub load_task {
-    my ($taskfile, $curpkg) = @_;
-    open(TASKFILE, "<$taskfile") || die "Unable to open $taskfile";
-    my $line;
-
-    $taskinfo{$curpkg} = ();
-
-    print STDERR "Loading task $curpkg\n" if $debug;
-
-    my $haspackages = 0;
-    while (<TASKFILE>) {
-        chomp;
-        next if (m/^\#/); # Skip comments
-        $line = $_;
-
-        # Append multi-line
-        while ($line =~ /\\$/) {
-            $line =~ s/\s*\\//;
-            $_ = <TASKFILE>;
-            chomp;
-            $line .= $_;
-        }
-        # Remove trailing space
-        $line =~ s/\s+$//;
-
-
-        $_ = $line;
-        for my $header (qw(Section Architecture Priority Leaf Enhances Metapackage Install)) {
-            $taskinfo{$curpkg}{$header} = $1 if (m/^$header:\s+(.+)$/);
-        }
-        $taskinfo{$curpkg}{$1} = $2 if (m/^(test-.+):\s+(.+)$/i);
-
-        if (m/^Description:\s+(.+)$/) {
-            $taskinfo{$curpkg}{'Description'} = $1;
-            $taskinfo{$curpkg}{'Description-long'} = "";
-            while (<TASKFILE>) {
-                # End of description, pass next line to pattern matching
-                last if (m/^\S+/ || m/^\s*$/);
-
-                $taskinfo{$curpkg}{'Description-long'} .= $_;
-            }
-        }
-
-        next unless defined $_;
-
-        my $header;
-        for $header (qw(Depends Recommends Suggests)) {
-            last if !defined $_;
-
-            my $pkgs = '';
-
-            # first package comes after header ('<header>: <package>')
-            if (m/^$header:\s+(.+)$/ && $1 !~ /^\s*$/) {
-                $pkgs .= $1;
-
-                # remove blanks near kommas and pipe symbols
-                $pkgs =~ s/\s*,\s*/,/g;
-                $pkgs =~ s/\s*\|\s*/|/g;
-
-                # reliable whitespace and backslash cleanup
-                $pkgs =~ s/^\s*([a-z0-9\_\.\|+,-]+)\s*(|\\\s*)$/$1/;
-            }
-
-            # or no package in the header line ('<header>':<LF>)
-            if (($pkgs) || (m/^$header:\s*$/)) {
-                while (<TASKFILE>) {
-                    last if (m/^\S+/ || m/^\s*$/);
-
-                    # remove blanks near kommas and pipe symbols
-                    $_ =~ s/\s*,\s*/,/g;
-                    $_ =~ s/\s*\|\s*/|/g;
-
-                    # reliable whitespace and backslash cleanup around
-                    # package name
-                    $_ =~ s/^\s*([a-zA-Z0-9\_\.\|+,-]+)\s*(|\\\s*)$/$1/;
-                    $pkgs .= $_;
-                }
-
-                # strip trailing komma after last package
-                $pkgs =~ s/\s*,$//;
-
-                $taskinfo{$curpkg}{$header} = ()
-                    if (! exists $taskinfo{$curpkg}{$header});
-                my ($pkglist, $missinglist) = process_pkglist($pkgs);
-                push(@{$taskinfo{$curpkg}{$header}}, @{$pkglist});
-
-                $haspackages += $#{$taskinfo{$curpkg}{$header}} + 1;
-                print STDERR "$curpkg $header:", @{$taskinfo{$curpkg}{$header}}, "($haspackages)\n" if $debug;
-                # Avoid missing packages in Depends lists, allow them
-                # in the two others.  Insert missing depends in
-                # suggests list.
-                if (@{$missinglist}) {
-                    print STDERR "$curpkg: missing = ", @{$missinglist}, "\n" if $debug;
-                    if ("Depends" eq $header) {
-                        push(@{$taskinfo{$curpkg}{'Suggests'}}, @{$missinglist});
-                    } else {
-                        push(@{$taskinfo{$curpkg}{$header}}, @{$missinglist});
-                    }
-                }
-            }
-        }
-
-        next unless defined $_;
-
-        if (/^Avoid:\s+(.+)$/) {
-            my @pkgs = split(/\s*,\s*/, $1);
-            my $packages;
-            for $packages (@pkgs) {
-                my $package;
-                for $package (split(/\s*\|\s*/, $packages)) {
-                    $excluded{$package} = 1;
-                }
-            }
-        }
-
-        if (/^Ignore:\s+(.+)$/) {
-            my @pkgs = split(/\s*,\s*/, $1);
-            my $packages;
-            for $packages (@pkgs) {
-                my $package;
-                for $package (split(/\s*\|\s*/, $packages)) {
-                    # Remove explanations, ie the paranteses at the end.
-                    $package =~ s/\s*\([^\)]*\)\s*$//;
-                    $missing{$package} = 1;
-                }
-            }
-        }
-    }
-    close(TASKFILE);
-    unless ( $taskinfo{$curpkg}{'Architecture'} ) { $taskinfo{$curpkg}{'Architecture'} = "all" ; }
-    $taskinfo{$curpkg}{'haspackages'} = $haspackages;
-    print STDERR "$curpkg: haspackages = ", $taskinfo{$curpkg}{'haspackages'}, "\n" if $debug;
-}
-
-sub print_excluded_packages {
-    print join("\n", sort keys %excluded),"\n";
-}
-
-sub print_available_packages {
-    print join("\n", @wanted),"\n";
-}
-
-sub print_all_pkgs_tasks {
-    my ($seenref, $headerlistref, @tasks) = @_;
-
-    my @headers;
-    if ( $headerlistref ) {
-      @headers = @{$headerlistref};
-    } else {
-      @headers = qw(Depends Recommends Suggests)
-    }
-
-    for my $header (@headers) {
-        print STDERR "  Processing $header\n" if $debug;
-        my %seentask;
-        for my $task (@tasks) {
-            next if $seentask{$task};
-            $seentask{$task} = 1;
-
-            print "# printing $header in $task\n";
-            print STDERR "   Printing $task\n" if $debug;
-
-            # Pick the first available if there are alternatives
-            my @pkgs = uniq($seenref, task_packages($task, $header), $task);
-            print join("\n", @pkgs), "\n" if @pkgs;
-        }
-    }
-}
-
-sub print_all_packages {
-    print STDERR "Printing all packages\n" if $debug;
-#    print join("\n", @wanted, keys %missing),"\n";
-
-    print "# First process the high priority tasks\n";
-    my %seenlist;
-    print_all_pkgs_tasks(\%seenlist, [qw(Depends Recommends)], @priorityorder );
-
-    print "# Next, medium priority tasks tasks\n";
-    print_all_pkgs_tasks(\%seenlist, [qw(Depends Recommends)], @medpriorder );
-
-    print "# Next process all the others, in alphabetic order\n";
-    print_all_pkgs_tasks(\%seenlist, undef, sort keys %taskinfo);
-
-    print "# And last, the alternatives we dropped above\n";
-    print join("\n", uniq(\%seenlist, @wanted, sort keys %missing)),"\n";
-}
-
-sub print_missing_packages {
-    if (%missing) {
-        print STDERR "Missing or avoided packages:\n";
-        my $package;
-        for $package (sort keys %missing) {
-            if (exists $available{$package}) {
-                print STDERR "  $package ($available{$package} available)\n";
-            } else {
-                print STDERR "  $package\n";
-            }
-        }
-        exit 1 unless $opts{'i'};
-    }
-}
-
-## Additions by Andreas Tille
-
-sub get_priorities {
-    my ($prio, $default) = @_;
-    my @list = () ;
-
-    # if there is no taskcontrolfile every task has the same priority
-    if ( ! stat($taskcontrolfile) ) {
-        if ( ! $default ) { return (); }
-        print STDERR "No task control file found - setting all tasks priority high.\n" if $debug;
-        opendir(DIR, $tasksdir) || die("No tasks directory found.");
-        @list = grep { !/^\./ } readdir(DIR);
-        closedir DIR;
-        return @list;
-    }
-    # read taskcontrolfile and find priorities
-    print STDERR "Reading task control file.\n" if $debug;
-    open(PRIO,$taskcontrolfile) || die("Unable to read task control file.");
-    while (<PRIO>) {
-        chomp ;
-        if ( $_=~/^$prio\s*:\s*([-\w]+)/) {
-            push @list,$1;
-        }
-    }
-    close PRIO;
-
-    return @list;
-}
-
-sub blend_init {
-    # initialise blend name and other basic stuff
-    unless  ( -d "debian" ) {
-        mkdir("debian") || die "mkdir debian: $!";
-    }
-
-    unless ( -e "debian/control.stub" ) {
-        print STDERR "No template debian/control.stub.  Use test prefix.\n" ;
-    } else {
-        chomp($prefix = `$blend_dev_dir/blend-get-names metapackageprefix`) ;
-        $prefix = $prefix . "-" ;
-        $tasksname    = $prefix . "tasks";
-        chomp($blendshortname = `$blend_dev_dir/blend-get-names blendshortname`);
-        chomp($blendname      = `$blend_dev_dir/blend-get-names blendname`);
-        chomp($blendtitle     = `$blend_dev_dir/blend-get-names blendtitle`);
-    }
-    if  ( -d "config" && -e "config/control" ) {
-        $hasconfig = 1;
-    }
-}
+parser.add_argument("-r", "--release", dest="release", type=str,
+                    default=default_release,
+                    help="Target release, eg: stable, testing etc."
+                    + " (default: {})".format(default_release))
+
+parser.add_argument("-S", '--supress-empty', dest="suppressempty",
+                    action="store_true", default=False,
+                    help="suppress tasks without any recommended package")
+
+parser.add_argument("-a", '--wanted', dest="wanted",
+                    action="store_true", default=False,
+                    help="print all wanted packages")
+
+parser.add_argument("-m", '--missing', dest="missing",
+                    action="store_true", default=False,
+                    help="print missing packages")
+
+parser.add_argument("-c", '--control', dest="gencontrol",
+                    action="store_true", default=False,
+                    help="Create new debian/control file.")
+
+parser.add_argument("-t", '--taskdesc', dest="taskdesc",
+                    action="store_true", default=False,
+                    help="Generate task descriptions and package list for task")
+
+parser.add_argument("-u", '--udebs', dest="udebs",
+                    action="store_true", default=False,
+                    help="Modify tasks desc file in case the blend uses udebs")
+
+parser.add_argument("-F", '--upgrade-task-format', dest="upgrade_tasks",
+                    action="store_true", default=False,
+                    help="Upgrade tasks files to the latest format version")
+
+parser.add_argument("-U", "--udd", dest="udd",
+                    action="store_true", default=False,
+                    help="Query UDD instead of apt (needs python3-psycopg2)")
+
+parser.add_argument("--udd-host", dest="udd_host",
+                    default="udd-mirror.debian.net",
+                    help="UDD host name (default: udd-mirror.debian.net)")
+
+parser.add_argument("--udd-user", dest="udd_user",
+                    default="udd-mirror",
+                    help="UDD user (default: udd-mirror)")
+
+parser.add_argument("--udd-password", dest="udd_password",
+                    default="udd-mirror",
+                    help="UDD password (default: udd-mirror)")
+
+parser.add_argument("--udd-database", dest="udd_database",
+                    default="udd",
+                    help="UDD database name (default: udd)")
+
+if 'GENCONTROL_OPTS' in os.environ:
+    opts = parser.parse_args(os.environ['GENCONTROL_OPTS'].split())
+    parser.set_defaults(**opts.__dict__)
+
+args = parser.parse_args()
+
+if args.release == "current":
+    args.release = None
+
+blend = Blend(basedir=args.dir)
+
+# For better performance, remove all tasks that will not create metapackages
+for task in blend.tasks[:]:
+    if not task.is_metapackage:
+        blend.tasks.remove(task)
+
+if args.udd:
+    blend += uddcache(blend.all, args.release, host=args.udd_host,
+                      user=args.udd_user, password=args.udd_password,
+                      database=args.udd_database)
+else:
+    blend += aptcache(args.release, [args.dir, '/etc/blends'])
+
+missing = sorted(set(d.name for d in blend.fix_dependencies()))
+wanted = sorted(set(d.name for d in itertools.chain(*(
+    itertools.chain(*(t.recommends + t.dependencies))
+    for t in blend.tasks))))
+
+if args.suppressempty:
+    for task in blend.tasks[:]:
+        if len(task.recommends) == 0 and len(task.dependencies) == 0:
+            blend.tasks.remove(task)
+
+if missing:
+    if args.missing:
+        print('Downgraded {} missing packages to Suggests:\n  '
+              .format(len(missing))
+              + '\n  '.join(missing))
+    else:
+        print('Downgraded {} missing packages to Suggests'
+              .format(len(missing)))
+
+if args.wanted:
+    print("Total {} packages in Depends or Recommend:\n  "
+          .format(len(wanted))
+          + '\n  '.join(wanted))
+
+if args.gencontrol:
+    with open(os.path.join(args.dir, 'debian', 'control'), 'w') as fp:
+        fp.write('# This file is autogenerated. Do not edit!\n')
+        blend.gen_control().dump(fp, text_mode=True)
+        print("Generated {} for {} with {} using {} Depends+Recommends"
+              .format('debian/control',
+                      args.release,
+                      'udd' if args.udd else 'apt',
+                      len(wanted)))
+
+if args.taskdesc:
+    with open(os.path.join(args.dir, '{}-tasks.desc'.format(blend.name)), 'w') as fp:
+        blend.gen_task_desc(udeb=args.udebs).dump(fp, text_mode=True)
+        print("Generated {} for {} with {} using {} Depends+Recommends"
+              .format(blend.name + '-tasks.desc',
+                      args.release,
+                      'udd' if args.udd else 'apt',
+                      len(wanted)))
+
+upgraded_tasks = list(filter(lambda task: task.format_upgraded, blend.tasks))
+if upgraded_tasks:
+    if args.upgrade_tasks:
+        print('Upgrading {} tasks from format version {}'
+              .format(len(upgraded_tasks), upgraded_tasks[0].format_version))
+        for task in upgraded_tasks:
+            with open(os.path.join(args.dir, 'tasks', task.name), 'w') as fp:
+                task.content.dump(fp, text_mode=True)
+    else:
+        print('Warning: {} tasks use the old format {}'
+              .format(len(upgraded_tasks), upgraded_tasks[0].format_version))
+        print('Please consider upgrading the task files with the `-F` flag.')


=====================================
devtools/tasks_diff
=====================================
--- /dev/null
+++ b/devtools/tasks_diff
@@ -0,0 +1,248 @@
+#!/usr/bin/python3
+
+# Copyright 2013: Emmanouil Kiagias <e.kiagias at gmail.com>
+# Converted via 2to3 by Andreas Tille <tille at debian.org>
+# License: GPL
+
+"""
+no  documentation for the moment
+"""
+
+import os
+import re
+import sys
+import json
+import pprint
+import logging
+import argparse
+import subprocess
+from debian import deb822
+
+# I shamelessly copied class Deb822List into a file blends_helper.py to test using it here
+# That's so hackish that I do not even commit this - needs further discussion
+from blends import Deb822List
+from debian.deb822 import Deb822
+
+#with this we distinguish the start of automatic entry in the changelog so we
+#can replace the entry if needed
+START_FLAG = "* start of automatic changelog entry *"
+
+def clean_up_packages(packages):
+	logger = logging.getLogger(__name__)
+	# Hack: Debian Edu tasks files are using '\' at EOL which is broken
+	#       in RFC 822 files, but blend-gen-control from blends-dev relies
+	#       on this.  So remove this stuff here for the Moment
+	pkgs = re.sub('\\\\\n\s+', '', packages)
+
+	# Remove versions from versioned depends
+	pkgs = re.sub(' *\([ ><=\.0-9]+\) *', '', pkgs)
+
+	# temporary strip spaces from alternatives ('|') to enable erroneous space handling as it was done before
+	pkgs = re.sub('\s*\|\s*', '|', pkgs)
+
+	# turn alternatives ('|') into real depends for this purpose
+	# because we are finally interested in all alternatives
+	pkgslist = pkgs.split(',')
+	# Collect all dependencies in one line first,
+	# create an object for each later
+	pkgs_in_one_line = []
+	for depl in pkgslist:
+		dl = depl.strip()
+		if dl != '': # avoid confusion when ',' is at end of line
+			if re.search('\s', dl):
+				#logger.error("Blend %s task %s: Syntax error '%s'" % (blend, task, dl))
+				# trying to fix the syntax error after issuing error message
+				dlspaces = re.sub('\s+', ',', dl).split(',')
+				for dls in dlspaces:
+					pkgs_in_one_line.append(dls.strip())
+					#logger.info("Blend %s task %s: Found '%s' package inside broken syntax string - please fix task file anyway" % (blend, task, dls.strip()))
+			else:
+				# in case we have to deal with a set of alternatives
+				if re.search('\|', dl):
+					#for da in dl.split('|'):
+					#  deps_in_one_line.append(da)
+					dl = re.sub('\|', ' | ', dl)
+				pkgs_in_one_line.append(dl)
+				# self.inject_package_alternatives(blend, task, strength, dl)
+
+	return pkgs_in_one_line
+
+def load_task(path_to_task):
+	"""
+	parses a task file and return a dictionary containing all its package headers elements
+	(depends, suggests etc)
+	"""
+	ftask = open(path_to_task, 'r')
+	task = os.path.basename(path_to_task)
+	taskinfo = {}
+
+	for header in ["depends", "suggests", "recommends", "ignore", "avoid"]:
+		taskinfo[header] = []
+
+	for paragraph in deb822.Sources.iter_paragraphs(ftask, shared_storage=False):
+		if "depends" in paragraph:
+			taskinfo["depends"] += clean_up_packages(paragraph["depends"])
+
+		if "suggests" in paragraph:
+			taskinfo["suggests"] += clean_up_packages(paragraph["suggests"])
+
+		if "recommends" in paragraph:
+			taskinfo["recommends"] += clean_up_packages(paragraph["recommends"])
+
+		if "ignore" in paragraph:
+			taskinfo["ignore"] += clean_up_packages(paragraph["ignore"])
+
+		if "avoid" in paragraph:
+			taskinfo["avoid"] += clean_up_packages(paragraph["avoid"])
+
+	return task, taskinfo
+
+def compare_tasks(tasks, tasks_compare, taskprefix):
+	"""
+	This function will dump in stdout the package differences between
+	the given tasks1 and tasks2
+	"""
+
+	first_print = True
+
+	for task in sorted(tasks):
+		if not task in tasks_compare:
+			continue
+		
+		task_first = True 
+		first_add = True
+		for header in ["depends", "recommends", "suggests", "ignore", "avoid"]:
+			added =  set(tasks[task][header]) - set(tasks_compare[task][header])
+			if added:
+				if first_print:
+					print(START_FLAG, "\n")
+					print("* Changes in metapackage dependencies")
+					first_print = False
+				if task_first:
+					print(" -{0}-{1}".format(taskprefix,task))
+					task_first = False
+				if first_add:
+					print("  added:")
+					first_add = False
+				print("    {0}: ".format(header.capitalize()), end=' ')
+				print(", ".join(added))
+		
+		first_remove = True
+		for header in ["depends", "recommends", "suggests", "ignore", "avoid"]:
+			removed =  set(tasks_compare[task][header]) - set(tasks[task][header])
+			if removed:
+				if first_print:
+					print(START_FLAG, "\n")
+					print("* Changes in metapackage dependencies")
+					first_print = False
+				if task_first:
+					print(" -{0}-{1}".format(taskprefix,task))
+					task_first = False
+				if first_remove:
+					print("  removed:")
+					first_remove = False
+				print("    {0}: ".format(header.capitalize()), end=' ')
+				print(", ".join(removed))
+
+	removed_tasks =  set(tasks_compare.keys()) - set(tasks.keys())
+	added_tasks =  set(tasks.keys()) - set(tasks_compare.keys())
+	if added_tasks:
+		if first_print:
+			print(START_FLAG, "\n")
+			print("* Changes in metapackage dependencies")
+			first_print = False
+		print("* New metapackages:")
+		for newtask in added_tasks:
+			print(" -{0}-{1}".format(taskprefix, newtask))
+
+	if removed_tasks:
+		if first_print:
+			print(START_FLAG, "\n")
+			print("* Changes in metapackage dependencies")
+			first_print = False
+		print("* Removed metapackages:")
+		for removedtask in removed_tasks:
+			print(" - {0}-{1}".format(taskprefix, removedtask))
+
+def load_tasks(tasks_path):
+	tasks = {}
+
+	for taskpath in tasks_path:
+		taskname, taskinfo = load_task(taskpath)
+		tasks[taskname] = taskinfo
+
+	return tasks
+
+if __name__ == "__main__":
+	blend_dev_dir = "/usr/share/blends-dev/"
+	default_json = "tasks.json"
+
+	##TODO add proper epilog giving example usage
+	parser = argparse.ArgumentParser(epilog="")
+	
+	parser.add_argument("-t", "--tasks", dest="tasks", type=str,
+	                    help="Path to task files", default=".")
+	parser.add_argument("-s", "--status-dump", dest="statusdump", action="store_true",
+						help="Dump dependencies status into a json file")
+	parser.add_argument("-o", "--output", dest="output", type=str, default=default_json,
+						help="Output file where to store the dependencies json file(when -s/--status-dump is provided)")
+	parser.add_argument("-c", "--compare", dest="compare", type=str,
+						help="Provide two comma separated(without spaces)  paths to json files to be compared")
+	parser.add_argument("-d", "--debug", dest="debug", action="store_true", default=False,
+	                    help="Print debug information")
+	#parse the command line arguments
+	args = parser.parse_args()
+
+	if args.debug:
+	    logging.basicConfig(level=logging.DEBUG)
+	else:
+	    logging.basicConfig()
+	logger = logging.getLogger(__name__)
+
+	#load the taskprefix
+	with open(os.path.join('debian', 'control.stub'), encoding="UTF-8") as fp:
+	    control_stub = Deb822List(Deb822.iter_paragraphs(fp))
+	taskprefix = control_stub[0]['Source'].split('-', 1)[-1]
+
+	if not args.statusdump and not args.compare:
+		logger.error("At least -s/--statusdump or -c/--compare argument must be provided")
+		sys.exit(-1)
+
+	path_to_tasks = os.path.join(args.tasks, "tasks")
+	if not os.path.isdir(path_to_tasks):
+		logger.error("tasks directory could not be found in given path. aborting...")
+		sys.exit(-1)
+
+	logger.debug("Reading task files from directory {0}".format(path_to_tasks))
+	tasks = [ os.path.join(path_to_tasks, fold) for fold in os.listdir(path_to_tasks) if not fold.startswith('.') ]
+	giventasks = load_tasks(tasks)
+
+	if args.statusdump:
+		logger.debug("Status dump was selected")
+
+		with open(args.output, "w") as fout:
+			logger.debug("Dumping json dependencies file into {0}".format(args.output))
+			json.dump(giventasks, fout)
+
+		sys.exit(0)
+
+	if args.compare:
+		if not ',' in args.compare:
+			logger.error("For --compare two comma separated paths to json files should be provided.")
+			sys.exit(-1)
+
+		latest, previous = [ x.strip() for x in args.compare.split(',') ]
+
+		if not os.path.isfile(previous) or not os.path.isfile(latest):
+			logger.error("Please provide existing json files to be compared.")
+			sys.exit(-1)
+
+		logger.debug("Comparing json files:")
+		logger.debug("{0} with {1}".format(latest, previous))
+
+		latest_tasks = json.load(open(latest))
+		previous_tasks = json.load(open(previous))
+
+		logger.debug("Comparing releases...")
+		compare_tasks(latest_tasks, previous_tasks, taskprefix)
+


=====================================
sources.list.UNRELEASED deleted
=====================================
--- a/sources.list.UNRELEASED
+++ /dev/null
@@ -1,5 +0,0 @@
-# For testing purposes this sources.list might be useful.  It is a
-# good practice to use UNRELEASED in the changelog as target distribution
-# for not yet finished packages and blends-dev should also work in this
-# case
-deb http://ftp.debian.org/debian unstable main


=====================================
sources.list.unstable
=====================================
--- a/sources.list.unstable
+++ b/sources.list.unstable
@@ -1,7 +1 @@
-# using unstable as target distribution for the meta package dependencies
-# does actually not sound reasonable.  The idea is to enable a smooth transition
-# to testing for all meta packages and thus here testing is used as target
-# distribution.  You are free to provide your own source.list.unstable
-# in the source of your meta package building code to force unstable as
-# target or alternatively you could change this file (/etc/blends/sources.list.unstable).
-deb http://ftp.debian.org/debian testing main
+deb http://ftp.debian.org/debian unstable main


=====================================
sphinxdoc/Makefile
=====================================
--- /dev/null
+++ b/sphinxdoc/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+SPHINXPROJ    = blends
+SOURCEDIR     = .
+BUILDDIR      = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file


=====================================
sphinxdoc/conf.py
=====================================
--- /dev/null
+++ b/sphinxdoc/conf.py
@@ -0,0 +1,174 @@
+# -*- coding: utf-8 -*-
+#
+# blends documentation build configuration file, created by
+# sphinx-quickstart on Tue Mar 20 22:23:34 2018.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+import sys
+sys.path.insert(0, os.path.abspath('..'))
+import blends
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = ['sphinx.ext.autodoc',
+    'sphinx.ext.doctest',
+    'sphinx.ext.intersphinx']
+
+autodoc_member_order = 'bysource'
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'blends'
+copyright = u'2018, Debian Blends Team'
+author = u'Debian Blends Team'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = u'0.7'
+# The full version, including alpha/beta/rc tags.
+release = u'0.7'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This patterns also effect to html_static_path and html_extra_path
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'alabaster'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# This is required for the alabaster theme
+# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
+html_sidebars = {
+    '**': [
+        'relations.html',  # needs 'show_related': True theme option to display
+        'searchbox.html',
+    ]
+}
+
+
+# -- Options for HTMLHelp output ------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'blendsdoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+    # The paper size ('letterpaper' or 'a4paper').
+    #
+    # 'papersize': 'letterpaper',
+
+    # The font size ('10pt', '11pt' or '12pt').
+    #
+    # 'pointsize': '10pt',
+
+    # Additional stuff for the LaTeX preamble.
+    #
+    # 'preamble': '',
+
+    # Latex figure (float) alignment
+    #
+    # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [
+    (master_doc, 'blends.tex', u'blends Documentation',
+     u'Debian Blends Team', 'manual'),
+]
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    (master_doc, 'blends', u'blends Documentation',
+     [author], 1)
+]
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+    (master_doc, 'blends', u'blends Documentation',
+     author, 'blends', 'One line description of project.',
+     'Miscellaneous'),
+]
+
+
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'https://docs.python.org/': None}


=====================================
sphinxdoc/index.rst
=====================================
--- /dev/null
+++ b/sphinxdoc/index.rst
@@ -0,0 +1,9 @@
+Python module debian.blends
+===========================
+
+.. automodule:: blends
+   :members:
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Contents:



View it on GitLab: https://salsa.debian.org/blends-team/blends/compare/9c630f3d4a2facae7d75e44f905ebb195b897dda...d79e49e0ed0f1be5f1b875c2aaf668fa6610c122

---
View it on GitLab: https://salsa.debian.org/blends-team/blends/compare/9c630f3d4a2facae7d75e44f905ebb195b897dda...d79e49e0ed0f1be5f1b875c2aaf668fa6610c122
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/blends-commit/attachments/20180413/ca2842c3/attachment-0001.html>


More information about the Blends-commit mailing list