0001"""
0002A parser that keeps lots of information around, so the file can be
0003reconstructed almost exactly like it originally was entered.  Also, if
0004there are errors with values, they can be tracked back to a file and
0005line number.
0006"""
0007
0008from iniparser import INIParser, ParseError
0009
0010class ConversionError(Exception):
0011    pass
0012
0013def canonical_name(name):
0014    return name.lower().replace(' ', '').replace('_', '')
0015
0016class LazyINIParser(INIParser):
0017
0018    def __init__(self, allow_empty_sections=False):
0019        self.allow_empty_sections = allow_empty_sections
0020        INIParser.__init__(self)
0021
0022    def reset(self):
0023        self.configuration = Configuration()
0024        self.last_comment = []
0025
0026    def add_comment(self, line):
0027        if line.startswith(' '):
0028            line = line[1:]
0029        self.last_comment.append(line)
0030
0031    def assignment(self, name, content):
0032        item = Item(section=self.section,
0033                    name=name,
0034                    content=content,
0035                    comment='\n'.join(self.last_comment),
0036                    filename=self.filename,
0037                    lineno=self.start_lineno)
0038        self.last_comment = []
0039        if not self.section:
0040            self.new_section('')
0041        self.section.add_item(item)
0042
0043    def new_section(self, section):
0044        if not section and not self.allow_empty_sections:
0045            self.error_no_section_name()
0046        self.section = Section(
0047            name=section.strip(),
0048            comment='\n'.join(self.last_comment))
0049        self.configuration.add_section(self.section)
0050        self.last_comment = []
0051
0052class Configuration(object):
0053
0054    def __init__(self):
0055        self.sections = []
0056        self.sections_by_name = {}
0057
0058    def add_section(self, section):
0059        self.sections.append(section)
0060        # @@: I shouldn't be doing this here, I'll do it in lazyloader
0061        #names = self.split_names(section.name)
0062        #d = self.sections_by_name
0063        #for name in names[:-1]:
0064        #    d = d.setdefault(name, {})
0065        #d.setdefault(names[-1], []).append(section)
0066
0067    def split_names(self, name):
0068        names = []
0069        while 1:
0070            dot_pos = name.find('.')
0071            paren_pos = name.find('(')
0072            if dot_pos == -1 and paren_pos == -1:
0073                next = canonical_name(name)
0074                if next:
0075                    names.append(next)
0076                return names
0077            if (dot_pos == -1 or (dot_pos > paren_pos
0078                                  and paren_pos != -1)):
0079                next = canonical_name(name[:paren_pos])
0080                if next:
0081                    names.append(next)
0082                name = name[paren_pos+1:]
0083                next_pos = name.find(')')
0084                assert next_pos != -1, (
0085                    "Bad section name, ) expected: %r" % name)
0086                names.append(name[:next_pos])
0087                name = name[next_pos+1:]
0088            else:
0089                assert dot_pos != -1
0090                assert paren_pos == -1 or dot_pos < paren_pos
0091                next = canonical_name(name[:dot_pos])
0092                assert next, (
0093                    "Empty name")
0094                names.append(next)
0095                name = name[dot_pos+1:]
0096
0097    def source(self):
0098        return '\n\n'.join([s.source() for s in self.sections])
0099
0100class Section(object):
0101
0102    def __init__(self, name, comment):
0103        self.name = name
0104        self.comment = comment
0105        self.items = []
0106        self.canonical = {}
0107
0108    def add_item(self, item):
0109        self.items.append(item)
0110        self.canonical.setdefault(
0111            canonical_name(item.name), []).append(item)
0112
0113    def __repr__(self):
0114        return '<%s name=%r>' % (self.__class__.__name__, self.name)
0115
0116    def source(self):
0117        s = ''
0118        if self.comment:
0119            s += '\n'.join(['# ' + l
0120                            for l in self.comment.splitlines()]) + '\n'
0121        s += '[%s]\n' % self.name
0122        s += ''.join([i.source() for i in self.items])
0123        return s
0124
0125
0126class Item(object):
0127
0128    def __init__(self, section, name, content, comment,
0129                 filename, lineno):
0130        self.section = section
0131        self.name = name
0132        self.content = content
0133        self.comment = comment
0134        self.filename = filename
0135        self.lineno = lineno
0136
0137    def value(self, name, converter=None, catch_all_exceptions=False):
0138        if catch_all_exceptions:
0139            ExcClass = Exception
0140        else:
0141            ExcClass = ConversionError
0142        if converter is not None:
0143            try:
0144                return converter(self.content)
0145            except ExcClass, e:
0146                msg = str(e)
0147                raise ParseError(
0148                    msg,
0149                    filename=self.filename,
0150                    lineno=self.lineno,
0151                    column=None)
0152        else:
0153            return self.content
0154
0155    def __str__(self):
0156        return self.content
0157
0158    def __repr__(self):
0159        return '<%s name=%r; value=%r>' % (
0160            self.__class__.__name__, self.name, self.content)
0161
0162    def source(self):
0163        s = ''
0164        if self.comment:
0165            s += '\n'.join(['# ' + l
0166                            for l in self.comment.splitlines()]) + '\n'
0167        s += '%s = %s\n' % (self.name, self.content)
0168        return s