Reorder redirects/release
[darcs-mirror-apt-repository-merge.git] / deb822.py
1 # vim: fileencoding=utf-8
2 #
3 # A python interface for various rfc822-like formatted files used by Debian
4 # (.changes, .dsc, Packages, Sources, etc)
5 #
6 # Copyright (C) 2005-2006  dann frazier <dannf@dannf.org>
7 # Copyright (C) 2006       John Wright <john@movingsucks.org>
8 # Copyright (C) 2006       Adeodato Simó <dato@net.com.org.es>
9 # Copyright (C) 2008       Stefano Zacchiroli <zack@upsilon.cc>
10 #
11 # This program is free software; you can redistribute it and/or
12 # modify it under the terms of the GNU General Public License
13 # as published by the Free Software Foundation, either version 2
14 # of the License, or (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with this program; if not, write to the Free Software
23 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
24
25
26 try:
27     import apt_pkg
28     _have_apt_pkg = True
29 except ImportError:
30     _have_apt_pkg = False
31
32 import new
33 import re
34 import sys
35 import StringIO
36 import UserDict
37
38 class Deb822Dict(object, UserDict.DictMixin):
39     # Subclassing UserDict.DictMixin because we're overriding so much dict
40     # functionality that subclassing dict requires overriding many more than
41     # the four methods that DictMixin requires.
42     """A dictionary-like object suitable for storing RFC822-like data.
43
44     Deb822Dict behaves like a normal dict, except:
45         - key lookup is case-insensitive
46         - key order is preserved
47         - if initialized with a _parsed parameter, it will pull values from
48           that dictionary-like object as needed (rather than making a copy).
49           The _parsed dict is expected to be able to handle case-insensitive
50           keys.
51
52     If _parsed is not None, an optional _fields parameter specifies which keys
53     in the _parsed dictionary are exposed.
54     """
55
56     # See the end of the file for the definition of _strI
57
58     def __init__(self, _dict=None, _parsed=None, _fields=None):
59         self.__dict = {}
60         self.__keys = []
61         self.__parsed = None
62
63         if _dict is not None:
64             # _dict may be a dict or a list of two-sized tuples
65             if hasattr(_dict, 'items'):
66                 items = _dict.items()
67             else:
68                 items = list(_dict)
69
70             try:
71                 for k, v in items:
72                     self[k] = v
73             except ValueError:
74                 this = len(self.__keys)
75                 len_ = len(items[this])
76                 raise ValueError('dictionary update sequence element #%d has '
77                     'length %d; 2 is required' % (this, len_))
78         
79         if _parsed is not None:
80             self.__parsed = _parsed
81             if _fields is None:
82                 self.__keys.extend([ _strI(k) for k in self.__parsed.keys() ])
83             else:
84                 self.__keys.extend([ _strI(f) for f in _fields if self.__parsed.has_key(f) ])
85
86         
87     ### BEGIN DictMixin methods
88
89     def __setitem__(self, key, value):
90         key = _strI(key)
91         if not key in self:
92             self.__keys.append(key)
93         self.__dict[key] = value
94         
95     def __getitem__(self, key):
96         key = _strI(key)
97         try:
98             return self.__dict[key]
99         except KeyError:
100             if self.__parsed is not None and key in self.__keys:
101                 return self.__parsed[key]
102             else:
103                 raise
104     
105     def __delitem__(self, key):
106         key = _strI(key)
107         del self.__dict[key]
108         self.__keys.remove(key)
109     
110     def keys(self):
111         return [str(key) for key in self.__keys]
112     
113     ### END DictMixin methods
114
115     def __repr__(self):
116         return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()])
117
118     def __eq__(self, other):
119         mykeys = self.keys(); mykeys.sort()
120         otherkeys = other.keys(); otherkeys.sort()
121         if not mykeys == otherkeys:
122             return False
123
124         for key in mykeys:
125             if self[key] != other[key]:
126                 return False
127
128         # If we got here, everything matched
129         return True
130
131     def copy(self):
132         # Use self.__class__ so this works as expected for subclasses
133         copy = self.__class__(self)
134         return copy
135
136     # TODO implement __str__() and make dump() use that?
137
138
139 class Deb822(Deb822Dict):
140
141     def __init__(self, sequence=None, fields=None, _parsed=None):
142         """Create a new Deb822 instance.
143
144         :param sequence: a string, or any any object that returns a line of
145             input each time, normally a file().  Alternately, sequence can
146             be a dict that contains the initial key-value pairs.
147
148         :param fields: if given, it is interpreted as a list of fields that
149             should be parsed (the rest will be discarded).
150
151         :param _parsed: internal parameter.
152         """
153
154         if hasattr(sequence, 'items'):
155             _dict = sequence
156             sequence = None
157         else:
158             _dict = None
159         Deb822Dict.__init__(self, _dict=_dict, _parsed=_parsed, _fields=fields)
160
161         if sequence is not None:
162             try:
163                 self._internal_parser(sequence, fields)
164             except EOFError:
165                 pass
166
167     def iter_paragraphs(cls, sequence, fields=None, shared_storage=True):
168         """Generator that yields a Deb822 object for each paragraph in sequence.
169
170         :param sequence: same as in __init__.
171
172         :param fields: likewise.
173
174         :param shared_storage: if sequence is a file(), apt_pkg will be used 
175             if available to parse the file, since it's much much faster. On the
176             other hand, yielded objects will share storage, so they can't be
177             kept across iterations. (Also, PGP signatures won't be stripped
178             with apt_pkg.) Set this parameter to False to disable using apt_pkg. 
179         """
180
181         # TODO Think about still using apt_pkg even if shared_storage is False,
182         # by somehow instructing the constructor to make copy of the data. (If
183         # this is still faster.)
184
185         if _have_apt_pkg and shared_storage and isinstance(sequence, file):
186             parser = apt_pkg.ParseTagFile(sequence)
187             while parser.Step() == 1:
188                 yield cls(fields=fields, _parsed=parser.Section)
189         else:
190             iterable = iter(sequence)
191             x = cls(iterable, fields)
192             while len(x) != 0:
193                 yield x
194                 x = cls(iterable, fields)
195
196     iter_paragraphs = classmethod(iter_paragraphs)
197
198     ###
199
200     def _internal_parser(self, sequence, fields=None):
201         single = re.compile("^(?P<key>\S+)\s*:\s*(?P<data>\S.*?)\s*$")
202         multi = re.compile("^(?P<key>\S+)\s*:\s*$")
203         multidata = re.compile("^\s(?P<data>.+?)\s*$")
204
205         wanted_field = lambda f: fields is None or f in fields
206
207         if isinstance(sequence, basestring):
208             sequence = sequence.splitlines()
209
210         curkey = None
211         content = ""
212         for line in self.gpg_stripped_paragraph(sequence):
213             m = single.match(line)
214             if m:
215                 if curkey:
216                     self[curkey] += content
217
218                 if not wanted_field(m.group('key')):
219                     curkey = None
220                     continue
221
222                 curkey = m.group('key')
223                 self[curkey] = m.group('data')
224                 content = ""
225                 continue
226
227             m = multi.match(line)
228             if m:
229                 if curkey:
230                     self[curkey] += content
231
232                 if not wanted_field(m.group('key')):
233                     curkey = None
234                     continue
235
236                 curkey = m.group('key')
237                 self[curkey] = ""
238                 content = ""
239                 continue
240
241             m = multidata.match(line)
242             if m:
243                 content += '\n' + line # XXX not m.group('data')?
244                 continue
245
246         if curkey:
247             self[curkey] += content
248
249     def __str__(self):
250         return self.dump()
251
252     # __repr__ is handled by Deb822Dict
253
254     def dump(self, fd=None):
255         """Dump the the contents in the original format
256
257         If fd is None, return a string.
258         """
259
260         if fd is None:
261             fd = StringIO.StringIO()
262             return_string = True
263         else:
264             return_string = False
265         for key, value in self.iteritems():
266             if not value or value[0] == '\n':
267                 # Avoid trailing whitespace after "Field:" if it's on its own
268                 # line or the value is empty
269                 # XXX Uh, really print value if value == '\n'?
270                 fd.write('%s:%s\n' % (key, value))
271             else:
272                 fd.write('%s: %s\n' % (key, value))
273         if return_string:
274             return fd.getvalue()
275
276     ###
277
278     def isSingleLine(self, s):
279         if s.count("\n"):
280             return False
281         else:
282             return True
283
284     def isMultiLine(self, s):
285         return not self.isSingleLine(s)
286
287     def _mergeFields(self, s1, s2):
288         if not s2:
289             return s1
290         if not s1:
291             return s2
292
293         if self.isSingleLine(s1) and self.isSingleLine(s2):
294             ## some fields are delimited by a single space, others
295             ## a comma followed by a space.  this heuristic assumes
296             ## that there are multiple items in one of the string fields
297             ## so that we can pick up on the delimiter being used
298             delim = ' '
299             if (s1 + s2).count(', '):
300                 delim = ', '
301
302             L = (s1 + delim + s2).split(delim)
303             L.sort()
304
305             prev = merged = L[0]
306
307             for item in L[1:]:
308                 ## skip duplicate entries
309                 if item == prev:
310                     continue
311                 merged = merged + delim + item
312                 prev = item
313             return merged
314
315         if self.isMultiLine(s1) and self.isMultiLine(s2):
316             for item in s2.splitlines(True):
317                 if item not in s1.splitlines(True):
318                     s1 = s1 + "\n" + item
319             return s1
320
321         raise ValueError
322
323     def mergeFields(self, key, d1, d2 = None):
324         ## this method can work in two ways - abstract that away
325         if d2 == None:
326             x1 = self
327             x2 = d1
328         else:
329             x1 = d1
330             x2 = d2
331
332         ## we only have to do work if both objects contain our key
333         ## otherwise, we just take the one that does, or raise an
334         ## exception if neither does
335         if key in x1 and key in x2:
336             merged = self._mergeFields(x1[key], x2[key])
337         elif key in x1:
338             merged = x1[key]
339         elif key in x2:
340             merged = x2[key]
341         else:
342             raise KeyError
343
344         ## back to the two different ways - if this method was called
345         ## upon an object, update that object in place.
346         ## return nothing in this case, to make the author notice a
347         ## problem if she assumes the object itself will not be modified
348         if d2 == None:
349             self[key] = merged
350             return None
351
352         return merged
353     ###
354
355     def gpg_stripped_paragraph(sequence):
356         lines = []
357         state = 'SAFE'
358         gpgre = re.compile(r'^-----(?P<action>BEGIN|END) PGP (?P<what>[^-]+)-----$')
359         blank_line = re.compile('^$')
360         first_line = True
361
362         for line in sequence:
363             line = line.strip('\r\n')
364
365             # skip initial blank lines, if any
366             if first_line:
367                 if blank_line.match(line):
368                     continue
369                 else:
370                     first_line = False
371
372             m = gpgre.match(line)
373
374             if not m:
375                 if state == 'SAFE':
376                     if not blank_line.match(line):
377                         lines.append(line)
378                     else:
379                         break
380                 elif state == 'SIGNED MESSAGE' and blank_line.match(line):
381                     state = 'SAFE'
382             elif m.group('action') == 'BEGIN':
383                 state = m.group('what')
384             elif m.group('action') == 'END':
385                 state = 'SAFE'
386
387         if len(lines):
388             return lines
389         else:
390             raise EOFError('only blank lines found in input')
391
392     gpg_stripped_paragraph = staticmethod(gpg_stripped_paragraph)
393
394 ###
395
396 class PkgRelation(object):
397     """Inter-package relationships
398
399     Structured representation of the relationships of a package to another,
400     i.e. of what can appear in a Deb882 field like Depends, Recommends,
401     Suggests, ... (see Debian Policy 7.1).
402     """
403
404     # XXX *NOT* a real dependency parser, and that is not even a goal here, we
405     # just parse as much as we need to split the various parts composing a
406     # dependency, checking their correctness wrt policy is out of scope
407     __dep_RE = re.compile( \
408             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*$')
409     __comma_sep_RE = re.compile(r'\s*,\s*')
410     __pipe_sep_RE = re.compile(r'\s*\|\s*')
411     __blank_sep_RE = re.compile(r'\s*')
412
413     @classmethod
414     def parse_relations(cls, raw):
415         """Parse a package relationship string (i.e. the value of a field like
416         Depends, Recommends, Build-Depends ...)
417         """
418         def parse_archs(raw):
419             # assumption: no space beween '!' and architecture name
420             archs = []
421             for arch in cls.__blank_sep_RE.split(raw.strip()):
422                 if len(arch) and arch[0] == '!':
423                     archs.append((False, arch[1:]))
424                 else:
425                     archs.append((True, arch))
426             return archs
427
428         def parse_rel(raw):
429             match = cls.__dep_RE.match(raw)
430             if match:
431                 parts = match.groupdict()
432                 d = { 'name': parts['name'] }
433                 if not (parts['relop'] is None or parts['version'] is None):
434                     d['version'] = (parts['relop'], parts['version'])
435                 else:
436                     d['version'] = None
437                 if parts['archs'] is None:
438                     d['arch'] = None
439                 else:
440                     d['arch'] = parse_archs(parts['archs'])
441                 return d
442             else:
443                 print >> sys.stderr, \
444                         'deb822.py: WARNING: cannot parse package' \
445                         ' relationship "%s", returning it raw' % raw
446                 return { 'name': raw, 'version': None, 'arch': None }
447
448         tl_deps = cls.__comma_sep_RE.split(raw.strip()) # top-level deps
449         cnf = map(cls.__pipe_sep_RE.split, tl_deps)
450         return map(lambda or_deps: map(parse_rel, or_deps), cnf)
451
452
453 class _lowercase_dict(dict):
454     """Dictionary wrapper which lowercase keys upon lookup."""
455
456     def __getitem__(self, key):
457         return dict.__getitem__(self, key.lower())
458
459
460 class _PkgRelationMixin(object):
461     """Package relationship mixin
462
463     Inheriting from this mixin you can extend a Deb882 object with attributes
464     letting you access inter-package relationship in a structured way, rather
465     than as strings. For example, while you can usually use pkg['depends'] to
466     obtain the Depends string of package pkg, mixing in with this class you
467     gain pkg.depends to access Depends as a Pkgrel instance
468
469     To use, subclass _PkgRelationMixin from a class with a _relationship_fields
470     attribute. It should be a list of field names for which structured access
471     is desired; for each of them a method wild be added to the inherited class.
472     The method name will be the lowercase version of field name; '-' will be
473     mangled as '_'. The method would return relationships in the same format of
474     the PkgRelation' relations property.
475
476     See Packages and Sources as examples.
477     """
478
479     def __init__(self, *args, **kwargs):
480         self.__relations = _lowercase_dict({})
481         self.__parsed_relations = False
482         for name in self._relationship_fields:
483             # To avoid reimplementing Deb822 key lookup logic we use a really
484             # simple dict subclass which just lowercase keys upon lookup. Since
485             # dictionary building happens only here, we ensure that all keys
486             # are in fact lowercase.
487             # With this trick we enable users to use the same key (i.e. field
488             # name) of Deb822 objects on the dictionary returned by the
489             # relations property.
490             keyname = name.lower()
491             if self.has_key(name):
492                 self.__relations[keyname] = None   # lazy value
493                     # all lazy values will be expanded before setting
494                     # __parsed_relations to True
495             else:
496                 self.__relations[keyname] = []
497
498     @property
499     def relations(self):
500         """Return a dictionary of inter-package relationships among the current
501         and other packages.
502
503         Dictionary keys depend on the package kind. Binary packages have keys
504         like 'depends', 'recommends', ... while source packages have keys like
505         'build-depends', 'build-depends-indep' and so on. See the Debian policy
506         for the comprehensive field list.
507
508         Dictionary values are package relationships returned as lists of lists
509         of dictionaries (see below for some examples).
510
511         The encoding of package relationships is as follows:
512         - the top-level lists corresponds to the comma-separated list of
513           Deb822, their components form a conjuction, i.e. they have to be
514           AND-ed together
515         - the inner lists corresponds to the pipe-separated list of Deb822,
516           their components form a disjunction, i.e. they have to be OR-ed
517           together
518         - member of the inner lists are dictionaries with the following keys:
519           - name:       package (or virtual package) name
520           - version:    A pair <operator, version> if the relationship is
521                         versioned, None otherwise. operator is one of "<<",
522                         "<=", "=", ">=", ">>"; version is the given version as
523                         a string.
524           - arch:       A list of pairs <polarity, architecture> if the
525                         relationship is architecture specific, None otherwise.
526                         Polarity is a boolean (false if the architecture is
527                         negated with "!", true otherwise), architecture the
528                         Debian archtiecture name as a string.
529
530         Examples:
531
532           "emacs | emacsen, make, debianutils (>= 1.7)"     becomes
533           [ [ {'name': 'emacs'}, {'name': 'emacsen'} ],
534             [ {'name': 'make'} ],
535             [ {'name': 'debianutils', 'version': ('>=', '1.7')} ] ]
536
537           "tcl8.4-dev, procps [!hurd-i386]"                 becomes
538           [ [ {'name': 'tcl8.4-dev'} ],
539             [ {'name': 'procps', 'arch': (false, 'hurd-i386')} ] ]
540         """
541         if not self.__parsed_relations:
542             lazy_rels = filter(lambda n: self.__relations[n] is None,
543                     self.__relations.keys())
544             for n in lazy_rels:
545                 self.__relations[n] = PkgRelation.parse_relations(self[n])
546             self.__parsed_relations = True
547         return self.__relations
548
549 class _multivalued(Deb822):
550     """A class with (R/W) support for multivalued fields.
551
552     To use, create a subclass with a _multivalued_fields attribute.  It should
553     be a dictionary with *lower-case* keys, with lists of human-readable
554     identifiers of the fields as the values.  Please see Dsc, Changes, and
555     PdiffIndex as examples.
556     """
557
558     def __init__(self, *args, **kwargs):
559         Deb822.__init__(self, *args, **kwargs)
560
561         for field, fields in self._multivalued_fields.items():
562             try:
563                 contents = self[field]
564             except KeyError:
565                 continue
566
567             if self.isMultiLine(contents):
568                 self[field] = []
569                 updater_method = self[field].append
570             else:
571                 self[field] = Deb822Dict()
572                 updater_method = self[field].update
573
574             for line in filter(None, contents.splitlines()):
575                 updater_method(Deb822Dict(zip(fields, line.split())))
576
577     def dump(self, fd=None):
578         """Dump the contents in the original format
579
580         If fd is None, return a string.
581         """
582         
583         if fd is None:
584             fd = StringIO.StringIO()
585             return_string = True
586         else:
587             return_string = False
588         for key in self.keys():
589             keyl = key.lower()
590             if keyl not in self._multivalued_fields:
591                 value = self[key]
592                 if not value or value[0] == '\n':
593                     # XXX Uh, really print value if value == '\n'?
594                     fd.write('%s:%s\n' % (key, value))
595                 else:
596                     fd.write('%s: %s\n' % (key, value))
597             else:
598                 fd.write(key + ":")
599                 if hasattr(self[key], 'keys'): # single-line
600                     array = [ self[key] ]
601                 else: # multi-line
602                     fd.write("\n")
603                     array = self[key]
604
605                 order = self._multivalued_fields[keyl]
606                 try:
607                     field_lengths = self._fixed_field_lengths
608                 except AttributeError:
609                     field_lengths = {}
610                 for item in array:
611                     for x in order:
612                         raw_value = str(item[x])
613                         try:
614                             length = field_lengths[keyl][x]
615                         except KeyError:
616                             value = raw_value
617                         else:
618                             value = (length - len(raw_value)) * " " + raw_value
619                         fd.write(" %s" % value)
620                     fd.write("\n")
621         if return_string:
622             return fd.getvalue()
623
624
625 ###
626
627 class Dsc(_multivalued):
628     _multivalued_fields = {
629         "files": [ "md5sum", "size", "name" ],
630         "checksums-sha1": ["sha1", "size", "name"],
631         "checksums-sha256": ["sha256", "size", "name"],
632     }
633
634
635 class Changes(_multivalued):
636     _multivalued_fields = {
637         "files": [ "md5sum", "size", "section", "priority", "name" ],
638         "checksums-sha1": ["sha1", "size", "name"],
639         "checksums-sha256": ["sha256", "size", "name"],
640     }
641
642     def get_pool_path(self):
643         """Return the path in the pool where the files would be installed"""
644     
645         # This is based on the section listed for the first file.  While
646         # it is possible, I think, for a package to provide files in multiple
647         # sections, I haven't seen it in practice.  In any case, this should
648         # probably detect such a situation and complain, or return a list...
649         
650         s = self['files'][0]['section']
651
652         try:
653             section, subsection = s.split('/')
654         except ValueError:
655             # main is implicit
656             section = 'main'
657
658         if self['source'].startswith('lib'):
659             subdir = self['source'][:4]
660         else:
661             subdir = self['source'][0]
662
663         return 'pool/%s/%s/%s' % (section, subdir, self['source'])
664
665
666 class PdiffIndex(_multivalued):
667     _multivalued_fields = {
668         "sha1-current": [ "SHA1", "size" ],
669         "sha1-history": [ "SHA1", "size", "date" ],
670         "sha1-patches": [ "SHA1", "size", "date" ],
671     }
672
673     @property
674     def _fixed_field_lengths(self):
675         fixed_field_lengths = {}
676         for key in self._multivalued_fields:
677             if hasattr(self[key], 'keys'):
678                 # Not multi-line -- don't need to compute the field length for
679                 # this one
680                 continue
681             length = self._get_size_field_length(key)
682             fixed_field_lengths[key] = {"size": length}
683         return fixed_field_lengths
684
685     def _get_size_field_length(self, key):
686         lengths = [len(str(item['size'])) for item in self[key]]
687         return max(lengths)
688
689
690 class Release(_multivalued):
691     """Represents a Release file
692
693     Set the size_field_behavior attribute to "dak" to make the size field
694     length only as long as the longest actual value.  The default,
695     "apt-ftparchive" makes the field 16 characters long regardless.
696     """
697     # FIXME: Add support for detecting the behavior of the input, if
698     # constructed from actual 822 text.
699
700     _multivalued_fields = {
701         "md5sum": [ "md5sum", "size", "name" ],
702         "sha1": [ "sha1", "size", "name" ],
703         "sha256": [ "sha256", "size", "name" ],
704     }
705
706     __size_field_behavior = "apt-ftparchive"
707     def set_size_field_behavior(self, value):
708         if value not in ["apt-ftparchive", "dak"]:
709             raise ValueError("size_field_behavior must be either "
710                              "'apt-ftparchive' or 'dak'")
711         else:
712             self.__size_field_behavior = value
713     size_field_behavior = property(lambda self: self.__size_field_behavior,
714                                    set_size_field_behavior)
715
716     @property
717     def _fixed_field_lengths(self):
718         fixed_field_lengths = {}
719         for key in self._multivalued_fields:
720             length = self._get_size_field_length(key)
721             fixed_field_lengths[key] = {"size": length}
722         return fixed_field_lengths
723
724     def _get_size_field_length(self, key):
725         if self.size_field_behavior == "apt-ftparchive":
726             return 16
727         elif self.size_field_behavior == "dak":
728             lengths = [len(str(item['size'])) for item in self[key]]
729             return max(lengths)
730
731
732 class Sources(Dsc, _PkgRelationMixin):
733     """Represent an APT source package list"""
734
735     _relationship_fields = [ 'build-depends', 'build-depends-indep',
736             'build-conflicts', 'build-conflicts-indep' ]
737
738     def __init__(self, *args, **kwargs):
739         Dsc.__init__(self, *args, **kwargs)
740         _PkgRelationMixin.__init__(self, *args, **kwargs)
741
742
743 class Packages(Deb822, _PkgRelationMixin):
744     """Represent an APT binary package list"""
745
746     _relationship_fields = [ 'depends', 'pre-depends', 'recommends',
747             'suggests', 'breaks', 'conflicts', 'provides', 'replaces',
748             'enhances' ]
749
750     def __init__(self, *args, **kwargs):
751         Deb822.__init__(self, *args, **kwargs)
752         _PkgRelationMixin.__init__(self, *args, **kwargs)
753
754 ###
755
756 class _CaseInsensitiveString(str):
757     """Case insensitive string.
758     """
759
760     def __init__(self, str_):
761         str.__init__(self, str_)
762         self.str_lower = str_.lower()
763         self.str_lower_hash = hash(self.str_lower)
764
765     def __hash__(self):
766         return self.str_lower_hash
767
768     def __eq__(self, other):
769         return str.__eq__(self.str_lower, other.lower())
770
771     def lower(self):
772         return self.str_lower
773
774 _strI = _CaseInsensitiveString