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
0061
0062
0063
0064
0065
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