Example #1
0
class ParseError(Exception):
    """An exception which is used to signal a parse failure.

    Attributes:

    filename - name of the file
    lineno - line number in the file
    msg - error message

    """

    def __init__(self, filename, lineno, msg):
        assert isinstance(int, lineno)
        self.filename = filename
        self.lineno = lineno
        self.msg = msg
        super(ParseError, self).__init__(self)

    def __str__(self):
        return self.msg

    def __repr__(self):
        return "ParseError(%r, %d, %r)" % (self.filename,
                                           self.lineno,
                                           self.msg)

    def print_out(self, file):
        """Writes a machine-parsable error message to file."""
        file.write("%s:%d: %s\n" % (self.filename, self.lineno, self.msg))
        file.flush()

    printOut = function_deprecated_by(print_out)
Example #2
0
class DB:
    """
    In-memory database mapping packages to tags and tags to packages.
    """
    def __init__(self):
        # type: () -> None
        self.db = {}  # type: PkgTagDbType
        self.rdb = {}  # type: TagPkgDbType

    def read(
            self,
            input_data,  # type: Iterator[Text]
            tag_filter=None,  # type: Optional[TagFilterType]
    ):
        # type: (...) -> None
        """
        Read the database from a file.

        Example::
            # Read the system Debtags database
            db.read(open("/var/lib/debtags/package-tags", "r"))
        """
        self.db, self.rdb = read_tag_database_both_ways(input_data, tag_filter)

    def qwrite(self, file):
        # type: (IO[bytes]) -> None
        """Quickly write the data to a pickled file"""
        pickle.dump(self.db, file)
        pickle.dump(self.rdb, file)

    def qread(self, file):
        # type: (IO[bytes]) -> None
        """Quickly read the data from a pickled file"""
        self.db = pickle.load(file)
        self.rdb = pickle.load(file)

    def insert(self, pkg, tags):
        # type: (str, Set[str]) -> None
        self.db[pkg] = tags.copy()
        for tag in tags:
            if tag in self.rdb:
                self.rdb[tag].add(pkg)
            else:
                self.rdb[tag] = set((pkg))

    def dump(self):
        # type: () -> None
        output(self.db)

    def dump_reverse(self):
        # type: () -> None
        output(self.rdb)

    dumpReverse = function_deprecated_by(dump_reverse)

    def reverse(self):
        # type: () -> DB
        "Return the reverse collection, sharing tagsets with this one"
        res = DB()
        res.db = self.rdb
        res.rdb = self.db
        return res

    def facet_collection(self):
        # type: () -> DB
        """
        Return a copy of this collection, but replaces the tag names
        with only their facets.
        """
        fcoll = DB()
        tofacet = re.compile(r"^([^:]+).+")
        for pkg, tags in self.iter_packages_tags():
            ftags = {tofacet.sub(r"\1", t) for t in tags}
            fcoll.insert(pkg, ftags)
        return fcoll

    facetCollection = function_deprecated_by(facet_collection)

    def copy(self):
        # type: () -> DB
        """
        Return a copy of this collection, with the tagsets copied as
        well.
        """
        res = DB()
        res.db = self.db.copy()
        res.rdb = self.rdb.copy()
        return res

    def reverse_copy(self):
        # type: () -> DB
        """
        Return the reverse collection, with a copy of the tagsets of
        this one.
        """
        res = DB()
        res.db = self.rdb.copy()
        res.rdb = self.db.copy()
        return res

    reverseCopy = function_deprecated_by(reverse_copy)

    def choose_packages(self, package_iter):
        # type: (Iterable[str]) -> DB
        """
        Return a collection with only the packages in package_iter,
        sharing tagsets with this one
        """
        res = DB()
        db = {}
        for pkg in package_iter:
            if pkg in self.db:
                db[pkg] = self.db[pkg]
        res.db = db
        res.rdb = reverse(db)
        return res

    choosePackages = function_deprecated_by(choose_packages)

    def choose_packages_copy(self, package_iter):
        # type: (Iterable[str]) -> DB
        """
        Return a collection with only the packages in package_iter,
        with a copy of the tagsets of this one
        """
        res = DB()
        db = {}
        for pkg in package_iter:
            db[pkg] = self.db[pkg]
        res.db = db
        res.rdb = reverse(db)
        return res

    choosePackagesCopy = function_deprecated_by(choose_packages_copy)

    def filter_packages(self, package_filter):
        # type: (PkgFilterType) -> DB
        """
        Return a collection with only those packages that match a
        filter, sharing tagsets with this one.  The filter will match
        on the package.
        """
        res = DB()
        db = {}
        for pkg in filter(package_filter, six.iterkeys(self.db)):
            db[pkg] = self.db[pkg]
        res.db = db
        res.rdb = reverse(db)
        return res

    filterPackages = function_deprecated_by(filter_packages)

    def filter_packages_copy(self, filter_data):
        # type: (PkgFilterType) -> DB
        """
        Return a collection with only those packages that match a
        filter, with a copy of the tagsets of this one.  The filter
        will match on the package.
        """
        res = DB()
        db = {}
        for pkg in filter(filter_data, six.iterkeys(self.db)):
            db[pkg] = self.db[pkg].copy()
        res.db = db
        res.rdb = reverse(db)
        return res

    filterPackagesCopy = function_deprecated_by(filter_packages_copy)

    def filter_packages_tags(self, package_tag_filter):
        # type: (PkgTagFilterType) -> DB
        """
        Return a collection with only those packages that match a
        filter, sharing tagsets with this one.  The filter will match
        on (package, tags).
        """
        res = DB()
        db = {}
        for pkg, _ in filter(package_tag_filter, six.iteritems(self.db)):
            db[pkg] = self.db[pkg]
        res.db = db
        res.rdb = reverse(db)
        return res

    filterPackagesTags = function_deprecated_by(filter_packages_tags)

    def filter_packages_tags_copy(self, package_tag_filter):
        # type: (PkgTagFilterType) -> DB
        """
        Return a collection with only those packages that match a
        filter, with a copy of the tagsets of this one.  The filter
        will match on (package, tags).
        """
        res = DB()
        db = {}
        for pkg, _ in filter(package_tag_filter, six.iteritems(self.db)):
            db[pkg] = self.db[pkg].copy()
        res.db = db
        res.rdb = reverse(db)
        return res

    filterPackagesTagsCopy = function_deprecated_by(filter_packages_tags_copy)

    def filter_tags(self, tag_filter):
        # type: (TagFilterType) -> DB
        """
        Return a collection with only those tags that match a
        filter, sharing package sets with this one.  The filter will match
        on the tag.
        """
        res = DB()
        rdb = {}
        for tag in filter(tag_filter, six.iterkeys(self.rdb)):
            rdb[tag] = self.rdb[tag]
        res.rdb = rdb
        res.db = reverse(rdb)
        return res

    filterTags = function_deprecated_by(filter_tags)

    def filter_tags_copy(self, tag_filter):
        # type: (TagFilterType) -> DB
        """
        Return a collection with only those tags that match a
        filter, with a copy of the package sets of this one.  The
        filter will match on the tag.
        """
        res = DB()
        rdb = {}
        for tag in filter(tag_filter, six.iterkeys(self.rdb)):
            rdb[tag] = self.rdb[tag].copy()
        res.rdb = rdb
        res.db = reverse(rdb)
        return res

    filterTagsCopy = function_deprecated_by(filter_tags_copy)

    def has_package(self, pkg):
        # type: (str) -> bool
        """Check if the collection contains the given package"""
        return pkg in self.db

    hasPackage = function_deprecated_by(has_package)

    def has_tag(self, tag):
        # type: (str) -> bool
        """Check if the collection contains packages tagged with tag"""
        return tag in self.rdb

    hasTag = function_deprecated_by(has_tag)

    def tags_of_package(self, pkg):
        # type: (str) -> Set[str]
        """Return the tag set of a package"""
        return self.db[pkg] if pkg in self.db else set()

    tagsOfPackage = function_deprecated_by(tags_of_package)

    def packages_of_tag(self, tag):
        # type: (str) -> Set[str]
        """Return the package set of a tag"""
        return self.rdb[tag] if tag in self.rdb else set()

    packagesOfTag = function_deprecated_by(packages_of_tag)

    def tags_of_packages(self, pkgs):
        # type: (Iterable[str]) -> Set[str]
        """Return the set of tags that have all the packages in ``pkgs``"""
        return set.union(*(self.tags_of_package(p) for p in pkgs))

    tagsOfPackages = function_deprecated_by(tags_of_packages)

    def packages_of_tags(self, tags):
        # type: (Iterable[str]) -> Set[str]
        """Return the set of packages that have all the tags in ``tags``"""
        return set.union(*(self.packages_of_tag(t) for t in tags))

    packagesOfTags = function_deprecated_by(packages_of_tags)

    def card(self, tag):
        # type: (str) -> int
        """
        Return the cardinality of a tag
        """
        return len(self.rdb[tag]) if tag in self.rdb else 0

    def discriminance(self, tag):
        # type: (str) -> int
        """
        Return the discriminance index if the tag.

        Th discriminance index of the tag is defined as the minimum
        number of packages that would be eliminated by selecting only
        those tagged with this tag or only those not tagged with this
        tag.
        """
        n = self.card(tag)
        tot = self.package_count()
        return min(n, tot - n)

    def iter_packages(self):
        # type: () -> Iterator[str]
        """Iterate over the packages"""
        return six.iterkeys(self.db)

    iterPackages = function_deprecated_by(iter_packages)

    def iter_tags(self):
        # type: () -> Iterator[str]
        """Iterate over the tags"""
        return six.iterkeys(self.rdb)

    iterTags = function_deprecated_by(iter_tags)

    def iter_packages_tags(self):
        # type: () -> Iterator[Tuple[str, Set[str]]]
        """Iterate over 2-tuples of (pkg, tags)"""
        return six.iteritems(self.db)

    iterPackagesTags = function_deprecated_by(iter_packages_tags)

    def iter_tags_packages(self):
        # type: () -> Iterator[Tuple[str, Set[str]]]
        """Iterate over 2-tuples of (tag, pkgs)"""
        return six.iteritems(self.rdb)

    iterTagsPackages = function_deprecated_by(iter_tags_packages)

    def package_count(self):
        # type: () -> int
        """Return the number of packages"""
        return len(self.db)

    packageCount = function_deprecated_by(package_count)

    def tag_count(self):
        # type: () -> int
        """Return the number of tags"""
        return len(self.rdb)

    tagCount = function_deprecated_by(tag_count)

    def ideal_tagset(self, tags):
        # type: (List[str]) -> Set[str]
        """
        Return an ideal selection of the top tags in a list of tags.

        Return the tagset made of the highest number of tags taken in
        consecutive sequence from the beginning of the given vector,
        that would intersect with the tagset of a comfortable amount
        of packages.

        Comfortable is defined in terms of how far it is from 7.
        """

        # TODO: the scoring function is quite ok, but may need more
        # tuning.  I also center it on 15 instead of 7 since we're
        # setting a starting point for the search, not a target point
        def score_fun(x):  # type: (float) -> float
            return float((x - 15) * (x - 15)) / x

        tagset = set()  # type: Set[str]
        min_score = 3.
        for i in range(len(tags)):
            pkgs = self.packages_of_tags(tags[:i + 1])
            card = len(pkgs)
            if card == 0:
                break
            score = score_fun(card)
            if score < min_score:
                min_score = score
                tagset = set(tags[:i + 1])

        # Return always at least the first tag
        if not tagset:
            return set(tags[:1])
        return tagset

    idealTagset = function_deprecated_by(ideal_tagset)

    def correlations(self):
        # type: () -> Iterator[Tuple[str, str, float]]
        """
        Generate the list of correlation as a tuple (hastag, hasalsotag, score).

        Every tuple will indicate that the tag 'hastag' tends to also
        have 'hasalsotag' with a score of 'score'.
        """
        for pivot in self.iter_tags():
            # pylint: disable=cell-var-from-loop
            with_ = self.filter_packages_tags(lambda pt: pivot in pt[1])
            without = self.filter_packages_tags(lambda pt: pivot not in pt[1])
            for tag in with_.iter_tags():
                if tag == pivot:
                    continue
                has = float(with_.card(tag)) / float(with_.package_count())
                hasnt = float(without.card(tag)) / float(
                    without.package_count())
                yield pivot, tag, has - hasnt
Example #3
0
    for line in input_data:
        # Is there a way to remove the last character of a line that does not
        # make a copy of the entire line?
        m = lre.match(line)
        if not m:
            continue

        pkgs = set(m.group(1).split(', '))
        if m.group(2):
            tags = set(m.group(2).split(', '))
        else:
            tags = set()
        yield pkgs, tags


parseTags = function_deprecated_by(parse_tags)


def read_tag_database(input_data):
    # type: (Iterator[Text]) -> PkgTagDbType
    """Read the tag database, returning a pkg->tags dictionary"""
    db = {}  # type: PkgTagDbType
    for pkgs, tags in parse_tags(input_data):
        # Create the tag set using the native set
        for p in pkgs:
            db[p] = tags.copy()
    return db


readTagDatabase = function_deprecated_by(read_tag_database)
Example #4
0
class PackageFile:
    """A Debian package file.

    Objects of this class can be used to read Debian's Source and
    Packages files."""

    re_field = re.compile(r'^([A-Za-z][A-Za-z0-9-_]+):(?:\s*(.*?))?\s*$')
    re_continuation = re.compile(r'^\s+(?:\.|(\S.*?)\s*)$')

    def __init__(self, name, file_obj=None):
        """Creates a new package file object.

        name - the name of the file the data comes from
        file_obj - an alternate data source; the default is to open the
                  file with the indicated name.
        """
        if file_obj is None:
            file_obj = open(name)
        self.name = name
        self.file = file_obj
        self.lineno = 0

    def __iter__(self):
        line = self.file.readline().decode()
        self.lineno += 1
        pkg = []
        while line:
            if line.strip(' \t') == '\n':
                if not pkg:
                    self.raise_syntax_error('expected package record')
                yield pkg
                pkg = []
                line = self.file.readline().decode()
                self.lineno += 1
                continue

            match = self.re_field.match(line)
            if not match:
                self.raise_syntax_error("expected package field")
            (name, contents) = match.groups()
            contents = contents or ''

            while True:
                line = self.file.readline().decode()
                self.lineno += 1
                match = self.re_continuation.match(line)
                if match:
                    (ncontents,) = match.groups()
                    if ncontents is None:
                        ncontents = ""
                    contents = "%s\n%s" % (contents, ncontents)
                else:
                    break
            pkg.append((name, contents))
        if pkg:
            yield pkg

    def raise_syntax_error(self, msg, lineno=None):
        if lineno is None:
            lineno = self.lineno
        raise ParseError(self.name, lineno, msg)

    raiseSyntaxError = function_deprecated_by(raise_syntax_error)
Example #5
0
            "etch",
            "lenny",
            "squeeze",
            "wheezy",
            "jessie",
            "stretch",
            "buster",
            "bullseye",
            "sid")
    for idx, rel in enumerate(rels):
        releases[rel] = Release(rel, idx)
    Release.releases = releases
    return releases


listReleases = function_deprecated_by(list_releases)

_release_list = list_releases()


def intern_release(name, releases=None):
    # type: (str, Optional[Any]) -> Any
    if releases is None:
        releases = _release_list
    return releases.get(name)


internRelease = function_deprecated_by(intern_release)


del listReleases
Example #6
0
from debian.deprecation import function_deprecated_by

def parse_tags(input):
	lre = re.compile(r"^(.+?)(?::?\s*|:\s+(.+?)\s*)$")
	for line in input:
		# Is there a way to remove the last character of a line that does not
		# make a copy of the entire line?
		m = lre.match(line)
		pkgs = set(m.group(1).split(', '))
		if m.group(2):
			tags = set(m.group(2).split(', '))
		else:
			tags = set()
		yield pkgs, tags

parseTags = function_deprecated_by(parse_tags)

def read_tag_database(input):
	"Read the tag database, returning a pkg->tags dictionary"
	db = {}
	for pkgs, tags in parse_tags(input):
		# Create the tag set using the native set
		for p in pkgs:
			db[p] = tags.copy()
	return db;

readTagDatabase = function_deprecated_by(read_tag_database)

def read_tag_database_reversed(input):
	"Read the tag database, returning a tag->pkgs dictionary"
	db = {}
Example #7
0
class Deb822(Deb822Dict):
    def __init__(self,
                 sequence=None,
                 fields=None,
                 _parsed=None,
                 encoding="utf-8"):
        """Create a new Deb822 instance.

        :param sequence: a string, or any any object that returns a line of
            input each time, normally a file.  Alternately, sequence can
            be a dict that contains the initial key-value pairs.

        :param fields: if given, it is interpreted as a list of fields that
            should be parsed (the rest will be discarded).

        :param _parsed: internal parameter.

        :param encoding: When parsing strings, interpret them in this encoding.
            (All values are given back as unicode objects, so an encoding is
            necessary in order to properly interpret the strings.)
        """

        if hasattr(sequence, 'items'):
            _dict = sequence
            sequence = None
        else:
            _dict = None
        Deb822Dict.__init__(self,
                            _dict=_dict,
                            _parsed=_parsed,
                            _fields=fields,
                            encoding=encoding)

        if sequence is not None:
            try:
                self._internal_parser(sequence, fields)
            except EOFError:
                pass

        self.gpg_info = None

    def iter_paragraphs(cls,
                        sequence,
                        fields=None,
                        use_apt_pkg=True,
                        shared_storage=False,
                        encoding="utf-8"):
        """Generator that yields a Deb822 object for each paragraph in sequence.

        :param sequence: same as in __init__.

        :param fields: likewise.

        :param use_apt_pkg: if sequence is a file, apt_pkg will be used
            if available to parse the file, since it's much much faster.  Set
            this parameter to False to disable using apt_pkg.
        :param shared_storage: not used, here for historical reasons.  Deb822
            objects never use shared storage anymore.
        :param encoding: Interpret the paragraphs in this encoding.
            (All values are given back as unicode objects, so an encoding is
            necessary in order to properly interpret the strings.)
        """

        if _have_apt_pkg and use_apt_pkg and _is_real_file(sequence):
            kwargs = {}
            if sys.version >= '3':
                # bytes=True is supported for both Python 2 and 3, but we
                # only actually need it for Python 3, so this saves us from
                # having to require a newer version of python-apt for Python
                # 2 as well.  This allows us to apply our own encoding
                # handling, which is more tolerant of mixed-encoding files.
                kwargs['bytes'] = True
            parser = apt_pkg.TagFile(sequence, **kwargs)
            for section in parser:
                paragraph = cls(fields=fields,
                                _parsed=TagSectionWrapper(section),
                                encoding=encoding)
                if paragraph:
                    yield paragraph

        else:
            if isinstance(sequence, six.string_types):
                sequence = sequence.splitlines()
            iterable = iter(sequence)
            x = cls(iterable, fields, encoding=encoding)
            while len(x) != 0:
                yield x
                x = cls(iterable, fields, encoding=encoding)

    iter_paragraphs = classmethod(iter_paragraphs)

    ###

    @staticmethod
    def _skip_useless_lines(sequence):
        """Yields only lines that do not begin with '#'.

        Also skips any blank lines at the beginning of the input.
        """
        at_beginning = True
        for line in sequence:
            # The bytes/str polymorphism required here to support Python 3
            # is unpleasant, but fortunately limited.  We need this because
            # at this point we might have been given either bytes or
            # Unicode, and we haven't yet got to the point where we can try
            # to decode a whole paragraph and detect its encoding.
            if isinstance(line, bytes):
                if line.startswith(b'#'):
                    continue
            else:
                if line.startswith('#'):
                    continue
            if at_beginning:
                if isinstance(line, bytes):
                    if not line.rstrip(b'\r\n'):
                        continue
                else:
                    if not line.rstrip('\r\n'):
                        continue
                at_beginning = False
            yield line

    def _internal_parser(self, sequence, fields=None):
        # The key is non-whitespace, non-colon characters before any colon.
        key_part = r"^(?P<key>[^: \t\n\r\f\v]+)\s*:\s*"
        single = re.compile(key_part + r"(?P<data>\S.*?)\s*$")
        multi = re.compile(key_part + r"$")
        multidata = re.compile(r"^\s(?P<data>.+?)\s*$")

        wanted_field = lambda f: fields is None or f in fields

        if isinstance(sequence, (six.string_types, bytes)):
            sequence = sequence.splitlines()

        curkey = None
        content = ""

        for line in self.gpg_stripped_paragraph(
                self._skip_useless_lines(sequence)):
            line = self._detect_encoding(line)

            m = single.match(line)
            if m:
                if curkey:
                    self[curkey] = content

                if not wanted_field(m.group('key')):
                    curkey = None
                    continue

                curkey = m.group('key')
                content = m.group('data')
                continue

            m = multi.match(line)
            if m:
                if curkey:
                    self[curkey] = content

                if not wanted_field(m.group('key')):
                    curkey = None
                    continue

                curkey = m.group('key')
                content = ""
                continue

            m = multidata.match(line)
            if m:
                content += '\n' + line  # XXX not m.group('data')?
                continue

        if curkey:
            self[curkey] = content

    def __str__(self):
        return self.dump()

    def __unicode__(self):
        return self.dump()

    if sys.version >= '3':

        def __bytes__(self):
            return self.dump().encode(self.encoding)

    # __repr__ is handled by Deb822Dict

    def get_as_string(self, key):
        """Return the self[key] as a string (or unicode)

        The default implementation just returns unicode(self[key]); however,
        this can be overridden in subclasses (e.g. _multivalued) that can take
        special values.
        """
        return six.text_type(self[key])

    def dump(self, fd=None, encoding=None):
        """Dump the the contents in the original format

        If fd is None, return a unicode object.

        If fd is not None, attempt to encode the output to the encoding the
        object was initialized with, or the value of the encoding argument if
        it is not None.  This will raise UnicodeEncodeError if the encoding
        can't support all the characters in the Deb822Dict values.
        """

        if fd is None:
            fd = StringIO()
            return_string = True
        else:
            return_string = False

        if encoding is None:
            # Use the encoding we've been using to decode strings with if none
            # was explicitly specified
            encoding = self.encoding

        for key in self:
            value = self.get_as_string(key)
            if not value or value[0] == '\n':
                # Avoid trailing whitespace after "Field:" if it's on its own
                # line or the value is empty.  We don't have to worry about the
                # case where value == '\n', since we ensure that is not the
                # case in __setitem__.
                entry = '%s:%s\n' % (key, value)
            else:
                entry = '%s: %s\n' % (key, value)
            if not return_string:
                fd.write(entry.encode(encoding))
            else:
                fd.write(entry)
        if return_string:
            return fd.getvalue()

    ###

    def is_single_line(self, s):
        if s.count("\n"):
            return False
        else:
            return True

    isSingleLine = function_deprecated_by(is_single_line)

    def is_multi_line(self, s):
        return not self.is_single_line(s)

    isMultiLine = function_deprecated_by(is_multi_line)

    def _merge_fields(self, s1, s2):
        if not s2:
            return s1
        if not s1:
            return s2

        if self.is_single_line(s1) and self.is_single_line(s2):
            ## some fields are delimited by a single space, others
            ## a comma followed by a space.  this heuristic assumes
            ## that there are multiple items in one of the string fields
            ## so that we can pick up on the delimiter being used
            delim = ' '
            if (s1 + s2).count(', '):
                delim = ', '

            L = sorted((s1 + delim + s2).split(delim))

            prev = merged = L[0]

            for item in L[1:]:
                ## skip duplicate entries
                if item == prev:
                    continue
                merged = merged + delim + item
                prev = item
            return merged

        if self.is_multi_line(s1) and self.is_multi_line(s2):
            for item in s2.splitlines(True):
                if item not in s1.splitlines(True):
                    s1 = s1 + "\n" + item
            return s1

        raise ValueError

    _mergeFields = function_deprecated_by(_merge_fields)

    def merge_fields(self, key, d1, d2=None):
        ## this method can work in two ways - abstract that away
        if d2 == None:
            x1 = self
            x2 = d1
        else:
            x1 = d1
            x2 = d2

        ## we only have to do work if both objects contain our key
        ## otherwise, we just take the one that does, or raise an
        ## exception if neither does
        if key in x1 and key in x2:
            merged = self._mergeFields(x1[key], x2[key])
        elif key in x1:
            merged = x1[key]
        elif key in x2:
            merged = x2[key]
        else:
            raise KeyError

        ## back to the two different ways - if this method was called
        ## upon an object, update that object in place.
        ## return nothing in this case, to make the author notice a
        ## problem if she assumes the object itself will not be modified
        if d2 == None:
            self[key] = merged
            return None

        return merged

    mergeFields = function_deprecated_by(merge_fields)

    def split_gpg_and_payload(sequence):
        """Return a (gpg_pre, payload, gpg_post) tuple

        Each element of the returned tuple is a list of lines (with trailing
        whitespace stripped).
        """

        gpg_pre_lines = []
        lines = []
        gpg_post_lines = []
        state = b'SAFE'
        gpgre = re.compile(
            br'^-----(?P<action>BEGIN|END) PGP (?P<what>[^-]+)-----$')
        blank_line = re.compile(b'^$')
        first_line = True

        for line in sequence:
            # Some consumers of this method require bytes (encoding
            # detection and signature checking).  However, we might have
            # been given a file opened in text mode, in which case it's
            # simplest to encode to bytes.
            if sys.version >= '3' and isinstance(line, str):
                line = line.encode()

            line = line.strip(b'\r\n')

            # skip initial blank lines, if any
            if first_line:
                if blank_line.match(line):
                    continue
                else:
                    first_line = False

            m = gpgre.match(line)

            if not m:
                if state == b'SAFE':
                    if not blank_line.match(line):
                        lines.append(line)
                    else:
                        if not gpg_pre_lines:
                            # There's no gpg signature, so we should stop at
                            # this blank line
                            break
                elif state == b'SIGNED MESSAGE':
                    if blank_line.match(line):
                        state = b'SAFE'
                    else:
                        gpg_pre_lines.append(line)
                elif state == b'SIGNATURE':
                    gpg_post_lines.append(line)
            else:
                if m.group('action') == b'BEGIN':
                    state = m.group('what')
                elif m.group('action') == b'END':
                    gpg_post_lines.append(line)
                    break
                if not blank_line.match(line):
                    if not lines:
                        gpg_pre_lines.append(line)
                    else:
                        gpg_post_lines.append(line)

        if len(lines):
            return (gpg_pre_lines, lines, gpg_post_lines)
        else:
            raise EOFError('only blank lines found in input')

    split_gpg_and_payload = staticmethod(split_gpg_and_payload)

    def gpg_stripped_paragraph(cls, sequence):
        return cls.split_gpg_and_payload(sequence)[1]

    gpg_stripped_paragraph = classmethod(gpg_stripped_paragraph)

    def get_gpg_info(self, keyrings=None):
        """Return a GpgInfo object with GPG signature information

        This method will raise ValueError if the signature is not available
        (e.g. the original text cannot be found).

        :param keyrings: list of keyrings to use (see GpgInfo.from_sequence)
        """

        # raw_text is saved (as a string) only for Changes and Dsc (see
        # _gpg_multivalued.__init__) which is small compared to Packages or
        # Sources which contain no signature
        if not hasattr(self, 'raw_text'):
            raise ValueError("original text cannot be found")

        if self.gpg_info is None:
            self.gpg_info = GpgInfo.from_sequence(self.raw_text,
                                                  keyrings=keyrings)

        return self.gpg_info

    def validate_input(self, key, value):
        """Raise ValueError if value is not a valid value for key

        Subclasses that do interesting things for different keys may wish to
        override this method.
        """

        # The value cannot end in a newline (if it did, dumping the object
        # would result in multiple stanzas)
        if value.endswith('\n'):
            raise ValueError("value must not end in '\\n'")

        # Make sure there are no blank lines (actually, the first one is
        # allowed to be blank, but no others), and each subsequent line starts
        # with whitespace
        for line in value.splitlines()[1:]:
            if not line:
                raise ValueError("value must not have blank lines")
            if not line[0].isspace():
                raise ValueError("each line must start with whitespace")

    def __setitem__(self, key, value):
        self.validate_input(key, value)
        Deb822Dict.__setitem__(self, key, value)
class PackageFile:
    """A Debian package file.

    Objects of this class can be used to read Debian's Source and
    Packages files."""

    re_field = re.compile(r'^([A-Za-z][A-Za-z0-9-_]+):(?:\s*(.*?))?\s*$')
    re_continuation = re.compile(r'^\s+(?:\.|(\S.*?)\s*)$')

    def __init__(self,
                 name,              # type: str
                 file_obj=None,     # type: Optional[Union[TextIO, BinaryIO]]
                 encoding="utf-8",  # type: str
                 ):
        # type: (...) -> None
        """Creates a new package file object.

        name - the name of the file the data comes from
        file_obj - an alternate data source; the default is to open the
                  file with the indicated name.
        """
        if file_obj is None:
            file_obj = open(name, 'rb')
        self.name = name
        self.file = file_obj
        self.lineno = 0
        self.encoding = encoding

    def __iter__(self):
        # type: () -> Generator[List[Tuple[str, str]], None, None]
        line = self._aux_read_line()
        self.lineno += 1
        pkg = []   # type: List[Tuple[str, str]]
        while line:
            if line.strip(' \t') == '\n':
                if not pkg:
                    self.raise_syntax_error('expected package record')
                yield pkg
                pkg = []
                line = self._aux_read_line()
                self.lineno += 1
                continue

            match = self.re_field.match(line)  # type: Optional[Match[str]]
            if not match:
                self.raise_syntax_error("expected package field")
            (name, contents) = match.groups()
            contents = contents or ''

            while True:
                line = self._aux_read_line()
                self.lineno += 1
                match = self.re_continuation.match(line)
                if match:
                    (ncontents,) = match.groups()
                    if ncontents is None:
                        ncontents = ""
                    contents = "%s\n%s" % (contents, ncontents)
                else:
                    break
            pkg.append((name, contents))
        if pkg:
            yield pkg

    def _aux_read_line(self):
        # type: () -> str
        # Not always readline returns a byte object, also str
        # can be returned (i.e: StringIO)
        line = self.file.readline()
        if isinstance(line, bytes):
            return line.decode(self.encoding)
        return line

    def raise_syntax_error(self, msg, lineno=None):
        # type: (str, Optional[int]) -> NoReturn
        if lineno is None:
            lineno = self.lineno
        raise ParseError(self.name, lineno, msg)

    raiseSyntaxError = function_deprecated_by(raise_syntax_error)
    def __gt__(self, other):
        return self._order > other._order
    def __hash__(self):
        return hash(self._order)

class Release(PseudoEnum): pass

def list_releases():
    releases = {}
    rels = ("potato", "woody", "sarge", "etch", "lenny", "sid")
    for r in range(len(rels)):
        releases[rels[r]] = Release(rels[r], r)
    Release.releases = releases
    return releases

listReleases = function_deprecated_by(list_releases)

def intern_release(name, releases=list_releases()):
    return releases.get(name)

internRelease = function_deprecated_by(intern_release)

del listReleases
del list_releases

def read_lines_sha1(lines):
    m = hashlib.sha1()
    for l in lines:
        if isinstance(l, bytes):
            m.update(l)
        else: