1""" brain-dead simple parser for ini-style files. 2(C) Ronny Pfannschmidt, Holger Krekel -- MIT licensed 3""" 4__all__ = ['IniConfig', 'ParseError'] 5 6COMMENTCHARS = "#;" 7 8 9class ParseError(Exception): 10 def __init__(self, path, lineno, msg): 11 Exception.__init__(self, path, lineno, msg) 12 self.path = path 13 self.lineno = lineno 14 self.msg = msg 15 16 def __str__(self): 17 return "%s:%s: %s" % (self.path, self.lineno+1, self.msg) 18 19 20class SectionWrapper(object): 21 def __init__(self, config, name): 22 self.config = config 23 self.name = name 24 25 def lineof(self, name): 26 return self.config.lineof(self.name, name) 27 28 def get(self, key, default=None, convert=str): 29 return self.config.get(self.name, key, 30 convert=convert, default=default) 31 32 def __getitem__(self, key): 33 return self.config.sections[self.name][key] 34 35 def __iter__(self): 36 section = self.config.sections.get(self.name, []) 37 38 def lineof(key): 39 return self.config.lineof(self.name, key) 40 for name in sorted(section, key=lineof): 41 yield name 42 43 def items(self): 44 for name in self: 45 yield name, self[name] 46 47 48class IniConfig(object): 49 def __init__(self, path, data=None): 50 self.path = str(path) # convenience 51 if data is None: 52 f = open(self.path) 53 try: 54 tokens = self._parse(iter(f)) 55 finally: 56 f.close() 57 else: 58 tokens = self._parse(data.splitlines(True)) 59 60 self._sources = {} 61 self.sections = {} 62 63 for lineno, section, name, value in tokens: 64 if section is None: 65 self._raise(lineno, 'no section header defined') 66 self._sources[section, name] = lineno 67 if name is None: 68 if section in self.sections: 69 self._raise(lineno, 'duplicate section %r' % (section, )) 70 self.sections[section] = {} 71 else: 72 if name in self.sections[section]: 73 self._raise(lineno, 'duplicate name %r' % (name, )) 74 self.sections[section][name] = value 75 76 def _raise(self, lineno, msg): 77 raise ParseError(self.path, lineno, msg) 78 79 def _parse(self, line_iter): 80 result = [] 81 section = None 82 for lineno, line in enumerate(line_iter): 83 name, data = self._parseline(line, lineno) 84 # new value 85 if name is not None and data is not None: 86 result.append((lineno, section, name, data)) 87 # new section 88 elif name is not None and data is None: 89 if not name: 90 self._raise(lineno, 'empty section name') 91 section = name 92 result.append((lineno, section, None, None)) 93 # continuation 94 elif name is None and data is not None: 95 if not result: 96 self._raise(lineno, 'unexpected value continuation') 97 last = result.pop() 98 last_name, last_data = last[-2:] 99 if last_name is None: 100 self._raise(lineno, 'unexpected value continuation') 101 102 if last_data: 103 data = '%s\n%s' % (last_data, data) 104 result.append(last[:-1] + (data,)) 105 return result 106 107 def _parseline(self, line, lineno): 108 # blank lines 109 if iscommentline(line): 110 line = "" 111 else: 112 line = line.rstrip() 113 if not line: 114 return None, None 115 # section 116 if line[0] == '[': 117 realline = line 118 for c in COMMENTCHARS: 119 line = line.split(c)[0].rstrip() 120 if line[-1] == "]": 121 return line[1:-1], None 122 return None, realline.strip() 123 # value 124 elif not line[0].isspace(): 125 try: 126 name, value = line.split('=', 1) 127 if ":" in name: 128 raise ValueError() 129 except ValueError: 130 try: 131 name, value = line.split(":", 1) 132 except ValueError: 133 self._raise(lineno, 'unexpected line: %r' % line) 134 return name.strip(), value.strip() 135 # continuation 136 else: 137 return None, line.strip() 138 139 def lineof(self, section, name=None): 140 lineno = self._sources.get((section, name)) 141 if lineno is not None: 142 return lineno + 1 143 144 def get(self, section, name, default=None, convert=str): 145 try: 146 return convert(self.sections[section][name]) 147 except KeyError: 148 return default 149 150 def __getitem__(self, name): 151 if name not in self.sections: 152 raise KeyError(name) 153 return SectionWrapper(self, name) 154 155 def __iter__(self): 156 for name in sorted(self.sections, key=self.lineof): 157 yield SectionWrapper(self, name) 158 159 def __contains__(self, arg): 160 return arg in self.sections 161 162 163def iscommentline(line): 164 c = line.lstrip()[:1] 165 return c in COMMENTCHARS 166