[Blends-commit] [Git][blends-team/blends][experimental] Create and use python3-blends package
Ole Streicher
gitlab at salsa.debian.org
Tue Apr 10 13:12:24 UTC 2018
Ole Streicher pushed to branch experimental at Debian Blends Team / blends
Commits:
fb15ffe0 by Ole Streicher at 2018-04-10T15:10:50+02:00
Create and use python3-blends package
- - - - -
10 changed files:
- + blends.py
- debian/control
- + debian/python3-blends.doc-base
- + debian/python3-blends.docs
- + debian/python3-blends.pyinstall
- debian/rules
- devtools/blend-gen-control
- + sphinxdoc/Makefile
- + sphinxdoc/conf.py
- + sphinxdoc/index.rst
Changes:
=====================================
blends.py
=====================================
--- /dev/null
+++ b/blends.py
@@ -0,0 +1,793 @@
+'''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(release, packages, **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))
+
+ Package = collections.namedtuple('Package',
+ ('name', 'versions',))
+ Version = collections.namedtuple('Version',
+ ('package', 'architecture', 'version'))
+
+ stmt = '''SELECT packages.package,
+ packages.provides,
+ packages.architecture,
+ packages.version
+ FROM packages, releases
+ WHERE packages.release=releases.release
+ AND (releases.release=%s OR releases.role=%s)
+ AND (packages.package IN %s OR packages.provides in %s);'''
+
+ with psycopg2.connect(**db_args) as conn:
+ cursor = conn.cursor()
+ cursor.execute(stmt, (release, release, pkgtuple, pkgtuple))
+
+ cache = dict()
+ for package, provides, arch, version in cursor:
+ p = cache.setdefault(package, Package(package, []))
+ p.versions.append(Version(p, arch, version))
+ if provides:
+ pp = cache.setdefault(provides, Package(package, []))
+ pp.versions.append(Version(p, arch, version))
+
+ return cache
=====================================
debian/control
=====================================
--- a/debian/control
+++ b/debian/control
@@ -8,11 +8,15 @@ Uploaders: Petter Reinholdtsen <pere at debian.org>,
Section: devel
Priority: optional
Build-Depends: debhelper (>= 10)
-Build-Depends-Indep: xmlto,
- dblatex,
- w3m,
+Build-Depends-Indep: dblatex,
dh-python,
- python3
+ python3-all,
+ python3-apt,
+ python3-debian,
+ python3-pytest,
+ python3-sphinx,
+ w3m,
+ xmlto
Standards-Version: 4.1.3
Vcs-Browser: https://salsa.debian.org/blends-team/blends
Vcs-Git: https://salsa.debian.org/blends-team/blends.git
@@ -20,10 +24,12 @@ Vcs-Git: https://salsa.debian.org/blends-team/blends.git
Package: blends-dev
Architecture: all
Depends: debconf,
+ debhelper (>= 9),
make | build-essential,
- debhelper (>= 9),python3,
- ${misc:Depends},
- ${python3:Depends}
+ python3,
+ python3-apt,
+ python3-blends,
+ ${misc:Depends}
Suggests: blends-doc
Recommends: python3-psycopg2
Description: Debian Pure Blends common files for developing metapackages
@@ -36,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
@@ -52,8 +58,8 @@ Package: blends-doc
Architecture: all
Section: doc
Depends: ${misc:Depends}
-Suggests: www-browser,
- postscript-viewer
+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
@@ -70,8 +76,8 @@ Package: blends-tasks
Architecture: all
Section: misc
Priority: important
-Depends: ${misc:Depends},
- tasksel
+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
@@ -80,3 +86,18 @@ Description: Debian Pure Blends tasks for new installations
.
The package is intended to be installed in the base system. Later
(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/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 $@ --with python3
+ 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/blend-gen-control
=====================================
--- a/devtools/blend-gen-control
+++ b/devtools/blend-gen-control
@@ -1,915 +1,132 @@
#!/usr/bin/python3
-'''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 sys
+import argparse
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(l[1:] if l != ' .' else ''
- for l 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
+from blends import Blend, aptcache, uddcache
- def update(self, cache):
- '''Update from cache
+default_release = "testing"
- This adds the available versions to all dependencies. It updates
- descriptions, summaries etc. available to all BaseDependencies.
+parser = argparse.ArgumentParser()
- :param cache: ``apt.Cache`` like object
+parser.add_argument("-d", "--dir", dest="dir", type=str,
+ default=".",
+ help="Base directory of the tasks source package"
+ + " (default: current directory)")
- Instead of using ``update()``, also the ``+=`` operator can be used:
+parser.add_argument("-r", "--release", dest="release", type=str,
+ default=default_release,
+ help="Target release, eg: stable, testing etc."
+ + " (default: {})".format(default_release))
- >>> 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
+parser.add_argument("-S", '--supress-empty', dest="suppressempty",
+ action="store_true", default=False,
+ help="suppress tasks without any recommended package")
- def __iadd__(self, cache):
- self.update(cache)
- return self
-
- def fix_dependencies(self):
- '''Fix the dependencies according to available packages
+parser.add_argument("-a", '--wanted', dest="wanted",
+ action="store_true", default=False,
+ help="print all wanted packages")
- This lowers all unavailable ``recommended`` dependencies to
- ``suggested``.
+parser.add_argument("-m", '--missing', dest="missing",
+ action="store_true", default=False,
+ help="print missing packages")
- >>> 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
+parser.add_argument("-c", '--control', dest="gencontrol",
+ action="store_true", default=False,
+ help="Create new debian/control file.")
- @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)
-
+parser.add_argument("-t", '--taskdesc', dest="taskdesc",
+ action="store_true", default=False,
+ help="Generate task descriptions and package list for task")
-class Dependency(list):
- '''Represent an Or-group of dependencies.
+parser.add_argument("-u", '--udebs', dest="udebs",
+ action="store_true", default=False,
+ help="Modify tasks desc file in case the blend uses udebs")
- Example:
+parser.add_argument("-F", '--upgrade-task-format', dest="upgrade_tasks",
+ action="store_true", default=False,
+ help="Upgrade tasks files to the latest format version")
- >>> with open('education') as fp:
- ... task = Task('debian-astro', 'education', fp)
- >>> dep = task.recommends[0]
- >>> print(dep.rawstr)
- celestia-gnome | celestia-glut
- '''
+parser.add_argument("-U", "--udd", dest="udd",
+ action="store_true", default=False,
+ help="Query UDD instead of apt (needs python3-psycopg2)")
- 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
+parser.add_argument("--udd-host", dest="udd_host",
+ default="udd-mirror.debian.net",
+ help="UDD host name (default: udd-mirror.debian.net)")
- @property
- def rawstr(self):
- '''String represenation of the Or-group of dependencies.
+parser.add_argument("--udd-user", dest="udd_user",
+ default="udd-mirror",
+ help="UDD user (default: udd-mirror)")
- 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)
+parser.add_argument("--udd-password", dest="udd_password",
+ default="udd-mirror",
+ help="UDD password (default: udd-mirror)")
- @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))
+parser.add_argument("--udd-database", dest="udd_database",
+ default="udd",
+ help="UDD database name (default: udd)")
+args = parser.parse_args()
-class BaseDependency:
- '''A single dependency.
+if args.release == "current":
+ args.release = None
- Example:
+blend = Blend(basedir=args.dir)
- >>> 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
- '''
+# 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)
- 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 = []
+if args.udd:
+ blend += uddcache(args.release, blend.all, 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'])
- def _get_from_target_versions(self, key):
- for v in self.target_versions:
- if v.package.name == self.name:
- return getattr(v, key)
+missing = blend.fix_dependencies()
- @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(l[1:] if l != ' .' else ''
- for l 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(release, packages, **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))
-
- Package = collections.namedtuple('Package',
- ('name', 'versions',))
- Version = collections.namedtuple('Version',
- ('package', 'architecture', 'version'))
-
- stmt = '''SELECT packages.package,
- packages.provides,
- packages.architecture,
- packages.version
- FROM packages, releases
- WHERE packages.release=releases.release
- AND (releases.release=%s OR releases.role=%s)
- AND (packages.package IN %s OR packages.provides in %s);'''
-
- with psycopg2.connect(**db_args) as conn:
- cursor = conn.cursor()
- cursor.execute(stmt, (release, release, pkgtuple, pkgtuple))
-
- cache = dict()
- for package, provides, arch, version in cursor:
- p = cache.setdefault(package, Package(package, []))
- p.versions.append(Version(p, arch, version))
- if provides:
- pp = cache.setdefault(provides, Package(package, []))
- pp.versions.append(Version(p, arch, version))
-
- return cache
-
-
-if __name__ == '__main__':
- import sys
- import argparse
-
- default_release = "testing"
-
- parser = argparse.ArgumentParser()
-
- parser.add_argument("-d", "--dir", dest="dir", type=str,
- default=".",
- help="Base directory of the tasks source package"
- + " (default: current directory)")
-
- 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)")
-
- 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
+if args.suppressempty:
for task in blend.tasks[:]:
- if not task.is_metapackage:
+ if len(task.recommends) == 0 and len(task.dependencies) > 0:
blend.tasks.remove(task)
- if args.udd:
- blend += uddcache(args.release, blend.all, host=args.udd_host,
- user=args.udd_user, password=args.udd_password,
- database=args.udd_database)
+if missing and args.missing:
+ missing = sorted(set(d.name for d in missing))
+ print('Missing {} packages downgraded to `suggests`:\n '
+ .format(len(missing))
+ + '\n '.join(missing))
+
+if args.wanted:
+ wanted = sorted(set(d.name for d in itertools.chain(*(
+ itertools.chain(*(t.recommends + t.dependencies))
+ for t in blend.tasks))))
+ 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)
+
+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)
+
+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:
- blend += aptcache(args.release, [ args.dir, '/etc/blends' ])
-
- missing = blend.fix_dependencies()
-
- if args.suppressempty:
- for task in blend.tasks[:]:
- if len(task.recommends) == 0 and len(task.dependencies) > 0:
- blend.tasks.remove(task)
-
- if missing and args.missing:
- missing = sorted(set(d.name for d in missing))
- print('Missing {} packages downgraded to `suggests`:\n '
- .format(len(missing))
- + '\n '.join(missing))
-
- if args.wanted:
- wanted = sorted(set(d.name for d in itertools.chain(*(
- itertools.chain(*(t.recommends + t.dependencies))
- for t in blend.tasks))))
- 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)
-
- 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)
-
- 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 `-U` flag.')
-
+ 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 `-U` flag.')
=====================================
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/commit/fb15ffe0e7c17fbdcafa9173b9562b4c6d1d0bce
---
View it on GitLab: https://salsa.debian.org/blends-team/blends/commit/fb15ffe0e7c17fbdcafa9173b9562b4c6d1d0bce
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.alioth.debian.org/pipermail/blends-commit/attachments/20180410/5c69dc86/attachment-0001.html>
More information about the Blends-commit
mailing list