~ubuntu-branches/debian/jessie/python-debian/jessie

« back to all changes in this revision

Viewing changes to debian_bundle/deb822.py

  • Committer: Bazaar Package Importer
  • Author(s): John Wright, Filippo Giunchedi, Stefano Zacchiroli, John Wright
  • Date: 2008-11-05 16:03:40 UTC
  • mfrom: (2.1.1 sid)
  • Revision ID: james.westby@ubuntu.com-20081105160340-62n7m2hf8h9j4vw2
Tags: 0.1.12
[ Filippo Giunchedi ]
* Add initial support to deb822 for gpg signature checking (Closes: #485829)
* Suggest gpgv for signature checking

[ Stefano Zacchiroli ]
* deb822: add support for pretty printing structured inter-package
  relationships (and relevant tests)
* debfile: add support for .tar.bz2 parts contained in .deb packages, and
  relative regression test. Thanks to Cameron Dale and Tom Parker.
  (Closes: #462859).

[ John Wright ]
* deb822: allow the use of unicode objects in __getitem__
* changelog: fix the "topline" regular expression to match the one in dpkg's
  Dpkg/Changelog/Debian.pm so that it allows '.' and '+' in the distribution
  field
* deb822: Add a use_apt_pkg parameter to Deb822.iter_paragraphs.  Now,
  callers can set use_apt_pkg=True and shared_storage=False (now the default)
  in order to take advantage of apt_pkg's speed and still be able to use
  values across iterations.  (Closes: #504413)

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
# (.changes, .dsc, Packages, Sources, etc)
5
5
#
6
6
# Copyright (C) 2005-2006  dann frazier <dannf@dannf.org>
7
 
# Copyright (C) 2006       John Wright <john@movingsucks.org>
 
7
# Copyright (C) 2006-2008  John Wright <john@johnwright.org>
8
8
# Copyright (C) 2006       Adeodato Simó <dato@net.com.org.es>
 
9
# Copyright (C) 2008       Stefano Zacchiroli <zack@upsilon.cc>
9
10
#
10
11
# This program is free software; you can redistribute it and/or
11
12
# modify it under the terms of the GNU General Public License
28
29
except ImportError:
29
30
    _have_apt_pkg = False
30
31
 
 
32
import new
31
33
import re
 
34
import string
 
35
import sys
32
36
import StringIO
33
37
import UserDict
34
38
 
 
39
class OrderedSet(object):
 
40
    """A set-like object that preserves order when iterating over it
 
41
 
 
42
    We use this to keep track of keys in Deb822Dict, because it's much faster
 
43
    to look up if a key is in a set than in a list.
 
44
    """
 
45
 
 
46
    def __init__(self, iterable=[]):
 
47
        self.__set = set()
 
48
        self.__order = []
 
49
        for item in iterable:
 
50
            self.add(item)
 
51
 
 
52
    def add(self, item):
 
53
        if item not in self:
 
54
            # set.add will raise TypeError if something's unhashable, so we
 
55
            # don't have to handle that ourselves
 
56
            self.__set.add(item)
 
57
            self.__order.append(item)
 
58
 
 
59
    def remove(self, item):
 
60
        # set.remove will raise KeyError, so we don't need to handle that
 
61
        # ourselves
 
62
        self.__set.remove(item)
 
63
        self.__order.remove(item)
 
64
 
 
65
    def __iter__(self):
 
66
        # Return an iterator of items in the order they were added
 
67
        return iter(self.__order)
 
68
 
 
69
    def __contains__(self, item):
 
70
        # This is what makes OrderedSet faster than using a list to keep track
 
71
        # of keys.  Lookup in a set is O(1) instead of O(n) for a list.
 
72
        return item in self.__set
 
73
 
 
74
    ### list-like methods
 
75
    append = add
 
76
 
 
77
    def extend(self, iterable):
 
78
        for item in iterable:
 
79
            self.add(item)
 
80
    ###
 
81
 
35
82
class Deb822Dict(object, UserDict.DictMixin):
36
83
    # Subclassing UserDict.DictMixin because we're overriding so much dict
37
84
    # functionality that subclassing dict requires overriding many more than
54
101
 
55
102
    def __init__(self, _dict=None, _parsed=None, _fields=None):
56
103
        self.__dict = {}
57
 
        self.__keys = []
 
104
        self.__keys = OrderedSet()
58
105
        self.__parsed = None
59
106
 
60
107
        if _dict is not None:
79
126
                self.__keys.extend([ _strI(k) for k in self.__parsed.keys() ])
80
127
            else:
81
128
                self.__keys.extend([ _strI(f) for f in _fields if self.__parsed.has_key(f) ])
82
 
 
83
129
        
84
130
    ### BEGIN DictMixin methods
85
131
 
86
132
    def __setitem__(self, key, value):
87
133
        key = _strI(key)
88
 
        if not key in self:
89
 
            self.__keys.append(key)
 
134
        self.__keys.add(key)
90
135
        self.__dict[key] = value
91
136
        
92
137
    def __getitem__(self, key):
98
143
                return self.__parsed[key]
99
144
            else:
100
145
                raise
101
 
    
 
146
 
102
147
    def __delitem__(self, key):
103
148
        key = _strI(key)
104
149
        del self.__dict[key]
105
150
        self.__keys.remove(key)
 
151
 
 
152
    def has_key(self, key):
 
153
        key = _strI(key)
 
154
        return key in self.__keys
106
155
    
107
156
    def keys(self):
108
157
        return [str(key) for key in self.__keys]
161
210
            except EOFError:
162
211
                pass
163
212
 
164
 
    def iter_paragraphs(cls, sequence, fields=None, shared_storage=True):
 
213
        self.gpg_info = None
 
214
 
 
215
    def iter_paragraphs(cls, sequence, fields=None, use_apt_pkg=True,
 
216
                        shared_storage=False):
165
217
        """Generator that yields a Deb822 object for each paragraph in sequence.
166
218
 
167
219
        :param sequence: same as in __init__.
168
220
 
169
221
        :param fields: likewise.
170
222
 
171
 
        :param shared_storage: if sequence is a file(), apt_pkg will be used 
172
 
            if available to parse the file, since it's much much faster. On the
173
 
            other hand, yielded objects will share storage, so they can't be
174
 
            kept across iterations. (Also, PGP signatures won't be stripped
175
 
            with apt_pkg.) Set this parameter to False to disable using apt_pkg. 
 
223
        :param use_apt_pkg: if sequence is a file(), apt_pkg will be used 
 
224
            if available to parse the file, since it's much much faster.  Set
 
225
            this parameter to False to disable using apt_pkg.
 
226
        :param shared_storage: if sequence is a file(), use_apt_pkg is True,
 
227
            and shared_storage is True, yielded objects will share storage, so
 
228
            they can't be kept across iterations.  (Also, PGP signatures won't
 
229
            be stripped.)  By default, this parameter is False, causing a copy
 
230
            of the parsed data to be made through each iteration.  Except for
 
231
            with raw Deb822 paragraphs (as opposed to _multivalued subclasses),
 
232
            the speed gained by setting shared_storage=True is marginal.  This
 
233
            parameter has no effect if use_apt_pkg is False or apt_pkg is not
 
234
            available.
176
235
        """
177
236
 
178
 
        # TODO Think about still using apt_pkg even if shared_storage is False,
179
 
        # by somehow instructing the constructor to make copy of the data. (If
180
 
        # this is still faster.)
181
 
 
182
 
        if _have_apt_pkg and shared_storage and isinstance(sequence, file):
 
237
        if _have_apt_pkg and use_apt_pkg and isinstance(sequence, file):
183
238
            parser = apt_pkg.ParseTagFile(sequence)
184
239
            while parser.Step() == 1:
185
 
                yield cls(fields=fields, _parsed=parser.Section)
 
240
                if shared_storage:
 
241
                    parsed = parser.Section
 
242
                else:
 
243
                    # Since parser.Section doesn't have an items method, we
 
244
                    # need to imitate that method here and make a Deb822Dict
 
245
                    # from the result in order to preserve order.
 
246
                    items = [(key, parser.Section[key])
 
247
                             for key in parser.Section.keys()]
 
248
                    parsed = Deb822Dict(items)
 
249
                yield cls(fields=fields, _parsed=parsed)
 
250
 
186
251
        else:
187
252
            iterable = iter(sequence)
188
253
            x = cls(iterable, fields)
349
414
        return merged
350
415
    ###
351
416
 
352
 
    def gpg_stripped_paragraph(sequence):
 
417
    def split_gpg_and_payload(sequence):
 
418
        """Return a (gpg_pre, payload, gpg_post) tuple
 
419
        
 
420
        Each element of the returned tuple is a list of lines (with trailing
 
421
        whitespace stripped).
 
422
        """
 
423
 
 
424
        gpg_pre_lines = []
353
425
        lines = []
 
426
        gpg_post_lines = []
354
427
        state = 'SAFE'
355
428
        gpgre = re.compile(r'^-----(?P<action>BEGIN|END) PGP (?P<what>[^-]+)-----$')
356
429
        blank_line = re.compile('^$')
373
446
                    if not blank_line.match(line):
374
447
                        lines.append(line)
375
448
                    else:
376
 
                        break
377
 
                elif state == 'SIGNED MESSAGE' and blank_line.match(line):
378
 
                    state = 'SAFE'
379
 
            elif m.group('action') == 'BEGIN':
380
 
                state = m.group('what')
381
 
            elif m.group('action') == 'END':
382
 
                state = 'SAFE'
 
449
                        if not gpg_pre_lines:
 
450
                            # There's no gpg signature, so we should stop at
 
451
                            # this blank line
 
452
                            break
 
453
                elif state == 'SIGNED MESSAGE':
 
454
                    if blank_line.match(line):
 
455
                        state = 'SAFE'
 
456
                    else:
 
457
                        gpg_pre_lines.append(line)
 
458
                elif state == 'SIGNATURE':
 
459
                    gpg_post_lines.append(line)
 
460
            else:
 
461
                if m.group('action') == 'BEGIN':
 
462
                    state = m.group('what')
 
463
                elif m.group('action') == 'END':
 
464
                    gpg_post_lines.append(line)
 
465
                    break
 
466
                if not blank_line.match(line):
 
467
                    if not lines:
 
468
                        gpg_pre_lines.append(line)
 
469
                    else:
 
470
                        gpg_post_lines.append(line)
383
471
 
384
472
        if len(lines):
385
 
            return lines
 
473
            return (gpg_pre_lines, lines, gpg_post_lines)
386
474
        else:
387
475
            raise EOFError('only blank lines found in input')
388
476
 
389
 
    gpg_stripped_paragraph = staticmethod(gpg_stripped_paragraph)
390
 
 
391
 
###
 
477
    split_gpg_and_payload = staticmethod(split_gpg_and_payload)
 
478
 
 
479
    def gpg_stripped_paragraph(cls, sequence):
 
480
        return cls.split_gpg_and_payload(sequence)[1]
 
481
 
 
482
    gpg_stripped_paragraph = classmethod(gpg_stripped_paragraph)
 
483
 
 
484
    def get_gpg_info(self):
 
485
        """Return a GpgInfo object with GPG signature information
 
486
 
 
487
        This method will raise ValueError if the signature is not available
 
488
        (e.g. the original text cannot be found)"""
 
489
 
 
490
        # raw_text is saved (as a string) only for Changes and Dsc (see
 
491
        # _gpg_multivalued.__init__) which is small compared to Packages or
 
492
        # Sources which contain no signature
 
493
        if not hasattr(self, 'raw_text'):
 
494
            raise ValueError, "original text cannot be found"
 
495
 
 
496
        if self.gpg_info is None:
 
497
            self.gpg_info = GpgInfo.from_sequence(self.raw_text)
 
498
 
 
499
        return self.gpg_info
 
500
 
 
501
###
 
502
 
 
503
# XXX check what happens if input contains more that one signature
 
504
class GpgInfo(dict):
 
505
    """A wrapper around gnupg parsable output obtained via --status-fd
 
506
 
 
507
    This class is really a dictionary containing parsed output from gnupg plus
 
508
    some methods to make sense of the data.
 
509
    Keys are keywords and values are arguments suitably splitted.
 
510
    See /usr/share/doc/gnupg/DETAILS.gz"""
 
511
 
 
512
    # keys with format "key keyid uid"
 
513
    uidkeys = ('GOODSIG', 'EXPSIG', 'EXPKEYSIG', 'REVKEYSIG', 'BADSIG')
 
514
 
 
515
    def valid(self):
 
516
        """Is the signature valid?"""
 
517
        return self.has_key('GOODSIG') or self.has_key('VALIDSIG')
 
518
    
 
519
# XXX implement as a property?
 
520
# XXX handle utf-8 %-encoding
 
521
    def uid(self):
 
522
        """Return the primary ID of the signee key, None is not available"""
 
523
        pass
 
524
 
 
525
    @staticmethod
 
526
    def from_output(out, err=None):
 
527
        """Create a new GpgInfo object from gpg(v) --status-fd output (out) and
 
528
        optionally collect stderr as well (err).
 
529
        
 
530
        Both out and err can be lines in newline-terminated sequence or regular strings."""
 
531
 
 
532
        n = GpgInfo()
 
533
 
 
534
        if isinstance(out, basestring):
 
535
            out = out.split('\n')
 
536
        if isinstance(err, basestring):
 
537
            err = err.split('\n')
 
538
 
 
539
        n.out = out
 
540
        n.err = err
 
541
        
 
542
        header = '[GNUPG:] '
 
543
        for l in out:
 
544
            if not l.startswith(header):
 
545
                continue
 
546
 
 
547
            l = l[len(header):]
 
548
            l = l.strip('\n')
 
549
 
 
550
            # str.partition() would be better, 2.5 only though
 
551
            s = l.find(' ')
 
552
            key = l[:s]
 
553
            if key in GpgInfo.uidkeys:
 
554
                # value is "keyid UID", don't split UID
 
555
                value = l[s+1:].split(' ', 1)
 
556
            else:
 
557
                value = l[s+1:].split(' ')
 
558
 
 
559
            n[key] = value
 
560
        return n 
 
561
 
 
562
# XXX how to handle sequences of lines? file() returns \n-terminated
 
563
    @staticmethod
 
564
    def from_sequence(sequence, keyrings=['/usr/share/keyrings/debian-keyring.gpg'],
 
565
            executable=["/usr/bin/gpgv"]):
 
566
        """Create a new GpgInfo object from the given sequence.
 
567
 
 
568
        Sequence is a sequence of lines or a string
 
569
        executable is a list of args for subprocess.Popen, the first element being the gpg executable"""
 
570
 
 
571
        # XXX check for gpg as well and use --verify accordingly?
 
572
        args = executable
 
573
        #args.extend(["--status-fd", "1", "--no-default-keyring"])
 
574
        args.extend(["--status-fd", "1"])
 
575
        import os
 
576
        [args.extend(["--keyring", k]) for k in keyrings if os.path.isfile(k) and os.access(k, os.R_OK)]
 
577
        
 
578
        if "--keyring" not in args:
 
579
            raise IOError, "cannot access none of given keyrings"
 
580
 
 
581
        import subprocess
 
582
        p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
583
        # XXX what to do with exit code?
 
584
 
 
585
        if isinstance(sequence, basestring):
 
586
            (out, err) = p.communicate(sequence)
 
587
        else:
 
588
            (out, err) = p.communicate("\n".join(sequence))
 
589
 
 
590
        return GpgInfo.from_output(out, err)
 
591
 
 
592
    @staticmethod
 
593
    def from_file(target, *args):
 
594
        """Create a new GpgInfo object from the given file, calls from_sequence(file(target), *args)"""
 
595
        return from_sequence(file(target), *args)
 
596
    
 
597
###
 
598
 
 
599
class PkgRelation(object):
 
600
    """Inter-package relationships
 
601
 
 
602
    Structured representation of the relationships of a package to another,
 
603
    i.e. of what can appear in a Deb882 field like Depends, Recommends,
 
604
    Suggests, ... (see Debian Policy 7.1).
 
605
    """
 
606
 
 
607
    # XXX *NOT* a real dependency parser, and that is not even a goal here, we
 
608
    # just parse as much as we need to split the various parts composing a
 
609
    # dependency, checking their correctness wrt policy is out of scope
 
610
    __dep_RE = re.compile( \
 
611
            r'^\s*(?P<name>[a-zA-Z0-9.+\-]{2,})(\s*\(\s*(?P<relop>[>=<]+)\s*(?P<version>[0-9a-zA-Z:\-+~.]+)\s*\))?(\s*\[(?P<archs>[\s!\w\-]+)\])?\s*$')
 
612
    __comma_sep_RE = re.compile(r'\s*,\s*')
 
613
    __pipe_sep_RE = re.compile(r'\s*\|\s*')
 
614
    __blank_sep_RE = re.compile(r'\s*')
 
615
 
 
616
    @classmethod
 
617
    def parse_relations(cls, raw):
 
618
        """Parse a package relationship string (i.e. the value of a field like
 
619
        Depends, Recommends, Build-Depends ...)
 
620
        """
 
621
        def parse_archs(raw):
 
622
            # assumption: no space beween '!' and architecture name
 
623
            archs = []
 
624
            for arch in cls.__blank_sep_RE.split(raw.strip()):
 
625
                if len(arch) and arch[0] == '!':
 
626
                    archs.append((False, arch[1:]))
 
627
                else:
 
628
                    archs.append((True, arch))
 
629
            return archs
 
630
 
 
631
        def parse_rel(raw):
 
632
            match = cls.__dep_RE.match(raw)
 
633
            if match:
 
634
                parts = match.groupdict()
 
635
                d = { 'name': parts['name'] }
 
636
                if not (parts['relop'] is None or parts['version'] is None):
 
637
                    d['version'] = (parts['relop'], parts['version'])
 
638
                else:
 
639
                    d['version'] = None
 
640
                if parts['archs'] is None:
 
641
                    d['arch'] = None
 
642
                else:
 
643
                    d['arch'] = parse_archs(parts['archs'])
 
644
                return d
 
645
            else:
 
646
                print >> sys.stderr, \
 
647
                        'deb822.py: WARNING: cannot parse package' \
 
648
                        ' relationship "%s", returning it raw' % raw
 
649
                return { 'name': raw, 'version': None, 'arch': None }
 
650
 
 
651
        tl_deps = cls.__comma_sep_RE.split(raw.strip()) # top-level deps
 
652
        cnf = map(cls.__pipe_sep_RE.split, tl_deps)
 
653
        return map(lambda or_deps: map(parse_rel, or_deps), cnf)
 
654
 
 
655
    @staticmethod
 
656
    def str(rels):
 
657
        """Format to string structured inter-package relationships
 
658
        
 
659
        Perform the inverse operation of parse_relations, returning a string
 
660
        suitable to be written in a package stanza.
 
661
        """
 
662
        def pp_arch(arch_spec):
 
663
            (excl, arch) = arch_spec
 
664
            if excl:
 
665
                return arch
 
666
            else:
 
667
                return '!' + arch
 
668
 
 
669
        def pp_atomic_dep(dep):
 
670
            s = dep['name']
 
671
            if dep.has_key('version') and dep['version'] is not None:
 
672
                s += ' (%s %s)' % dep['version']
 
673
            if dep.has_key('arch') and dep['arch'] is not None:
 
674
                s += ' [%s]' % string.join(map(pp_arch, dep['arch']))
 
675
            return s
 
676
 
 
677
        pp_or_dep = lambda deps: string.join(map(pp_atomic_dep, deps), ' | ')
 
678
        return string.join(map(pp_or_dep, rels), ', ')
 
679
 
 
680
 
 
681
class _lowercase_dict(dict):
 
682
    """Dictionary wrapper which lowercase keys upon lookup."""
 
683
 
 
684
    def __getitem__(self, key):
 
685
        return dict.__getitem__(self, key.lower())
 
686
 
 
687
 
 
688
class _PkgRelationMixin(object):
 
689
    """Package relationship mixin
 
690
 
 
691
    Inheriting from this mixin you can extend a Deb882 object with attributes
 
692
    letting you access inter-package relationship in a structured way, rather
 
693
    than as strings. For example, while you can usually use pkg['depends'] to
 
694
    obtain the Depends string of package pkg, mixing in with this class you
 
695
    gain pkg.depends to access Depends as a Pkgrel instance
 
696
 
 
697
    To use, subclass _PkgRelationMixin from a class with a _relationship_fields
 
698
    attribute. It should be a list of field names for which structured access
 
699
    is desired; for each of them a method wild be added to the inherited class.
 
700
    The method name will be the lowercase version of field name; '-' will be
 
701
    mangled as '_'. The method would return relationships in the same format of
 
702
    the PkgRelation' relations property.
 
703
 
 
704
    See Packages and Sources as examples.
 
705
    """
 
706
 
 
707
    def __init__(self, *args, **kwargs):
 
708
        self.__relations = _lowercase_dict({})
 
709
        self.__parsed_relations = False
 
710
        for name in self._relationship_fields:
 
711
            # To avoid reimplementing Deb822 key lookup logic we use a really
 
712
            # simple dict subclass which just lowercase keys upon lookup. Since
 
713
            # dictionary building happens only here, we ensure that all keys
 
714
            # are in fact lowercase.
 
715
            # With this trick we enable users to use the same key (i.e. field
 
716
            # name) of Deb822 objects on the dictionary returned by the
 
717
            # relations property.
 
718
            keyname = name.lower()
 
719
            if self.has_key(name):
 
720
                self.__relations[keyname] = None   # lazy value
 
721
                    # all lazy values will be expanded before setting
 
722
                    # __parsed_relations to True
 
723
            else:
 
724
                self.__relations[keyname] = []
 
725
 
 
726
    @property
 
727
    def relations(self):
 
728
        """Return a dictionary of inter-package relationships among the current
 
729
        and other packages.
 
730
 
 
731
        Dictionary keys depend on the package kind. Binary packages have keys
 
732
        like 'depends', 'recommends', ... while source packages have keys like
 
733
        'build-depends', 'build-depends-indep' and so on. See the Debian policy
 
734
        for the comprehensive field list.
 
735
 
 
736
        Dictionary values are package relationships returned as lists of lists
 
737
        of dictionaries (see below for some examples).
 
738
 
 
739
        The encoding of package relationships is as follows:
 
740
        - the top-level lists corresponds to the comma-separated list of
 
741
          Deb822, their components form a conjuction, i.e. they have to be
 
742
          AND-ed together
 
743
        - the inner lists corresponds to the pipe-separated list of Deb822,
 
744
          their components form a disjunction, i.e. they have to be OR-ed
 
745
          together
 
746
        - member of the inner lists are dictionaries with the following keys:
 
747
          - name:       package (or virtual package) name
 
748
          - version:    A pair <operator, version> if the relationship is
 
749
                        versioned, None otherwise. operator is one of "<<",
 
750
                        "<=", "=", ">=", ">>"; version is the given version as
 
751
                        a string.
 
752
          - arch:       A list of pairs <polarity, architecture> if the
 
753
                        relationship is architecture specific, None otherwise.
 
754
                        Polarity is a boolean (false if the architecture is
 
755
                        negated with "!", true otherwise), architecture the
 
756
                        Debian archtiecture name as a string.
 
757
 
 
758
        Examples:
 
759
 
 
760
          "emacs | emacsen, make, debianutils (>= 1.7)"     becomes
 
761
          [ [ {'name': 'emacs'}, {'name': 'emacsen'} ],
 
762
            [ {'name': 'make'} ],
 
763
            [ {'name': 'debianutils', 'version': ('>=', '1.7')} ] ]
 
764
 
 
765
          "tcl8.4-dev, procps [!hurd-i386]"                 becomes
 
766
          [ [ {'name': 'tcl8.4-dev'} ],
 
767
            [ {'name': 'procps', 'arch': (false, 'hurd-i386')} ] ]
 
768
        """
 
769
        if not self.__parsed_relations:
 
770
            lazy_rels = filter(lambda n: self.__relations[n] is None,
 
771
                    self.__relations.keys())
 
772
            for n in lazy_rels:
 
773
                self.__relations[n] = PkgRelation.parse_relations(self[n])
 
774
            self.__parsed_relations = True
 
775
        return self.__relations
392
776
 
393
777
class _multivalued(Deb822):
394
778
    """A class with (R/W) support for multivalued fields.
395
 
    
 
779
 
396
780
    To use, create a subclass with a _multivalued_fields attribute.  It should
397
781
    be a dictionary with *lower-case* keys, with lists of human-readable
398
782
    identifiers of the fields as the values.  Please see Dsc, Changes, and
399
783
    PdiffIndex as examples.
400
784
    """
401
 
    
402
785
 
403
786
    def __init__(self, *args, **kwargs):
404
787
        Deb822.__init__(self, *args, **kwargs)
469
852
 
470
853
###
471
854
 
472
 
class Dsc(_multivalued):
 
855
 
 
856
class _gpg_multivalued(_multivalued):
 
857
    """A _multivalued class that can support gpg signed objects
 
858
 
 
859
    This class's feature is that it stores the raw text before parsing so that
 
860
    gpg can verify the signature.  Use it just like you would use the
 
861
    _multivalued class.
 
862
 
 
863
    This class only stores raw text if it is given a raw string, or if it
 
864
    detects a gpg signature when given a file or sequence of lines (see
 
865
    Deb822.split_gpg_and_payload for details).
 
866
    """
 
867
 
 
868
    def __init__(self, *args, **kwargs):
 
869
        try:
 
870
            sequence = args[0]
 
871
        except IndexError:
 
872
            sequence = kwargs.get("sequence", None)
 
873
 
 
874
        if sequence is not None:
 
875
            if isinstance(sequence, basestring):
 
876
                self.raw_text = sequence
 
877
            elif hasattr(sequence, "items"):
 
878
                # sequence is actually a dict(-like) object, so we don't have
 
879
                # the raw text.
 
880
                pass
 
881
            else:
 
882
                try:
 
883
                    gpg_pre_lines, lines, gpg_post_lines = \
 
884
                            self.split_gpg_and_payload(sequence)
 
885
                except EOFError:
 
886
                    # Empty input
 
887
                    gpg_pre_lines = lines = gpg_post_lines = []
 
888
                if gpg_pre_lines and gpg_post_lines:
 
889
                    raw_text = StringIO.StringIO()
 
890
                    raw_text.write("\n".join(gpg_pre_lines))
 
891
                    raw_text.write("\n\n")
 
892
                    raw_text.write("\n".join(lines))
 
893
                    raw_text.write("\n\n")
 
894
                    raw_text.write("\n".join(gpg_post_lines))
 
895
                    self.raw_text = raw_text.getvalue()
 
896
                try:
 
897
                    args = list(args)
 
898
                    args[0] = lines
 
899
                except IndexError:
 
900
                    kwargs["sequence"] = lines
 
901
 
 
902
        _multivalued.__init__(self, *args, **kwargs)
 
903
 
 
904
 
 
905
class Dsc(_gpg_multivalued):
473
906
    _multivalued_fields = {
474
907
        "files": [ "md5sum", "size", "name" ],
475
908
        "checksums-sha1": ["sha1", "size", "name"],
477
910
    }
478
911
 
479
912
 
480
 
class Changes(_multivalued):
 
913
class Changes(_gpg_multivalued):
481
914
    _multivalued_fields = {
482
915
        "files": [ "md5sum", "size", "section", "priority", "name" ],
483
 
        "checksums-sha1": ["sha1", "size", "section", "priority", "name"],
484
 
        "checksums-sha256": ["sha256", "size", "section", "priority", "name"],
 
916
        "checksums-sha1": ["sha1", "size", "name"],
 
917
        "checksums-sha256": ["sha256", "size", "name"],
485
918
    }
486
919
 
487
920
    def get_pool_path(self):
573
1006
            lengths = [len(str(item['size'])) for item in self[key]]
574
1007
            return max(lengths)
575
1008
 
576
 
Sources = Dsc
577
 
Packages = Deb822
 
1009
 
 
1010
class Sources(Dsc, _PkgRelationMixin):
 
1011
    """Represent an APT source package list"""
 
1012
 
 
1013
    _relationship_fields = [ 'build-depends', 'build-depends-indep',
 
1014
            'build-conflicts', 'build-conflicts-indep' ]
 
1015
 
 
1016
    def __init__(self, *args, **kwargs):
 
1017
        Dsc.__init__(self, *args, **kwargs)
 
1018
        _PkgRelationMixin.__init__(self, *args, **kwargs)
 
1019
 
 
1020
 
 
1021
class Packages(Deb822, _PkgRelationMixin):
 
1022
    """Represent an APT binary package list"""
 
1023
 
 
1024
    _relationship_fields = [ 'depends', 'pre-depends', 'recommends',
 
1025
            'suggests', 'breaks', 'conflicts', 'provides', 'replaces',
 
1026
            'enhances' ]
 
1027
 
 
1028
    def __init__(self, *args, **kwargs):
 
1029
        Deb822.__init__(self, *args, **kwargs)
 
1030
        _PkgRelationMixin.__init__(self, *args, **kwargs)
578
1031
 
579
1032
###
580
1033
 
591
1044
        return self.str_lower_hash
592
1045
 
593
1046
    def __eq__(self, other):
594
 
        return str.__eq__(self.str_lower, other.lower())
 
1047
        return self.str_lower == other.lower()
595
1048
 
596
1049
    def lower(self):
597
1050
        return self.str_lower