| """ brain-dead simple parser for ini-style files. |
| (C) Ronny Pfannschmidt, Holger Krekel -- MIT licensed |
| """ |
| __version__ = "0.2.dev2" |
| |
| __all__ = ['IniConfig', 'ParseError'] |
| |
| COMMENTCHARS = "#;" |
| |
| class ParseError(Exception): |
| def __init__(self, path, lineno, msg): |
| Exception.__init__(self, path, lineno, msg) |
| self.path = path |
| self.lineno = lineno |
| self.msg = msg |
| |
| def __str__(self): |
| return "%s:%s: %s" %(self.path, self.lineno+1, self.msg) |
| |
| class SectionWrapper(object): |
| def __init__(self, config, name): |
| self.config = config |
| self.name = name |
| |
| def lineof(self, name): |
| return self.config.lineof(self.name, name) |
| |
| def get(self, key, default=None, convert=str): |
| return self.config.get(self.name, key, convert=convert, default=default) |
| |
| def __getitem__(self, key): |
| return self.config.sections[self.name][key] |
| |
| def __iter__(self): |
| section = self.config.sections.get(self.name, []) |
| def lineof(key): |
| return self.config.lineof(self.name, key) |
| for name in sorted(section, key=lineof): |
| yield name |
| |
| def items(self): |
| for name in self: |
| yield name, self[name] |
| |
| |
| class IniConfig(object): |
| def __init__(self, path, data=None): |
| self.path = str(path) # convenience |
| if data is None: |
| f = open(self.path) |
| try: |
| tokens = self._parse(iter(f)) |
| finally: |
| f.close() |
| else: |
| tokens = self._parse(data.splitlines(True)) |
| |
| self._sources = {} |
| self.sections = {} |
| |
| for lineno, section, name, value in tokens: |
| if section is None: |
| self._raise(lineno, 'no section header defined') |
| self._sources[section, name] = lineno |
| if name is None: |
| if section in self.sections: |
| self._raise(lineno, 'duplicate section %r'%(section, )) |
| self.sections[section] = {} |
| else: |
| if name in self.sections[section]: |
| self._raise(lineno, 'duplicate name %r'%(name, )) |
| self.sections[section][name] = value |
| |
| def _raise(self, lineno, msg): |
| raise ParseError(self.path, lineno, msg) |
| |
| def _parse(self, line_iter): |
| result = [] |
| section = None |
| for lineno, line in enumerate(line_iter): |
| name, data = self._parseline(line, lineno) |
| # new value |
| if name is not None and data is not None: |
| result.append((lineno, section, name, data)) |
| # new section |
| elif name is not None and data is None: |
| if not name: |
| self._raise(lineno, 'empty section name') |
| section = name |
| result.append((lineno, section, None, None)) |
| # continuation |
| elif name is None and data is not None: |
| if not result: |
| self._raise(lineno, 'unexpected value continuation') |
| last = result.pop() |
| last_name, last_data = last[-2:] |
| if last_name is None: |
| self._raise(lineno, 'unexpected value continuation') |
| |
| if last_data: |
| data = '%s\n%s' % (last_data, data) |
| result.append(last[:-1] + (data,)) |
| return result |
| |
| def _parseline(self, line, lineno): |
| # blank lines |
| if iscommentline(line): |
| line = "" |
| else: |
| line = line.rstrip() |
| if not line: |
| return None, None |
| # section |
| if line[0] == '[': |
| realline = line |
| for c in COMMENTCHARS: |
| line = line.split(c)[0].rstrip() |
| if line[-1] == "]": |
| return line[1:-1], None |
| return None, realline.strip() |
| # value |
| elif not line[0].isspace(): |
| try: |
| name, value = line.split('=', 1) |
| if ":" in name: |
| raise ValueError() |
| except ValueError: |
| try: |
| name, value = line.split(":", 1) |
| except ValueError: |
| self._raise(lineno, 'unexpected line: %r' % line) |
| return name.strip(), value.strip() |
| # continuation |
| else: |
| return None, line.strip() |
| |
| def lineof(self, section, name=None): |
| lineno = self._sources.get((section, name)) |
| if lineno is not None: |
| return lineno + 1 |
| |
| def get(self, section, name, default=None, convert=str): |
| try: |
| return convert(self.sections[section][name]) |
| except KeyError: |
| return default |
| |
| def __getitem__(self, name): |
| if name not in self.sections: |
| raise KeyError(name) |
| return SectionWrapper(self, name) |
| |
| def __iter__(self): |
| for name in sorted(self.sections, key=self.lineof): |
| yield SectionWrapper(self, name) |
| |
| def __contains__(self, arg): |
| return arg in self.sections |
| |
| def iscommentline(line): |
| c = line.lstrip()[:1] |
| return c in COMMENTCHARS |