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