/silverlining/tmplconfig.py
Python | 233 lines | 181 code | 46 blank | 6 comment | 49 complexity | e1bfbc93713c410c4f4e54b64f18b92a MD5 | raw file
1"""Template-based configuration file""" 2 3import os 4from UserDict import DictMixin 5from initools.configparser import ConfigParser, NoOptionError 6from tempita import Template 7 8__all__ = ['Configuration', 'asbool', 'lines'] 9 10 11class TmplParser(ConfigParser): 12 """Parser customized for use with this configuration""" 13 global_section = True 14 case_sensitive = True 15 section_case_sensitive = True 16 inherit_defaults = False 17 extendable = True 18 ignore_missing_files = False 19 20 21class EnvironWrapper(DictMixin): 22 """Handily wraps os.environ allowing attribute access""" 23 24 def __init__(self, environ): 25 self.environ = environ 26 27 def __getitem__(self, item): 28 return self.environ[item] 29 30 def __setitem__(self, item, value): 31 self.environ[item] = value 32 33 def __delitem__(self, item): 34 del self.environ[item] 35 36 def keys(self): 37 return self.environ.keys() 38 39 def __contains__(self, item): 40 return item in self.environ 41 42 def __getattr__(self, attr): 43 return self.get(attr) 44 45 def __repr__(self): 46 if self.environ is os.environ: 47 return '<EnvironWrapper for os.environ>' 48 else: 49 return '<EnvironWrapper for %r>' % self.environ 50 51 def __str__(self): 52 lines = [] 53 for name, value in sorted(self.environ.items()): 54 lines.append('%s=%r' % (name, value)) 55 return '\n'.join(lines) 56 57 58class NoDefault: 59 pass 60 61 62class Configuration(DictMixin): 63 """Handy configuration object that does template expansion""" 64 65 def __init__(self, filenames=None): 66 self._parser = TmplParser() 67 if filenames: 68 self.parse_files(filenames) 69 self._ns = dict( 70 config=self, 71 environ=EnvironWrapper(os.environ)) 72 self._sections = {} 73 74 def set_variable(self, name, value=NoDefault): 75 if value is NoDefault: 76 if name in self._ns: 77 del self._ns[name] 78 else: 79 self._ns[name] = value 80 81 def parse_files(self, filenames): 82 if isinstance(filenames, basestring): 83 filenames = [filenames] 84 self._parser.read(filenames) 85 86 def __getitem__(self, item): 87 if item in self._sections: 88 return self._sections[item] 89 if self._parser.has_section(item): 90 section = self._sections[item] = _Section(self, item) 91 return section 92 else: 93 raise KeyError('No section [%s]' % item) 94 95 def keys(self): 96 return self._parser.sections() 97 98 def sections(self, prefix=None): 99 result = {} 100 if prefix is None: 101 for name, sec in self.items(): 102 result[name] = sec 103 else: 104 for name, sec in self.items(): 105 if name.startswith(prefix): 106 name = name[len(prefix):] 107 result[name] = sec 108 return result 109 110 def __contains__(self, item): 111 return self._parser.has_section(item) 112 113 def __getattr__(self, item): 114 try: 115 return self[item] 116 except KeyError: 117 raise AttributeError('No section %s' % item) 118 119 def __str__(self): 120 return unicode(self).encode('utf8') 121 122 def __unicode__(self, section_name=None): 123 lines = [] 124 p = self._parser 125 cur_filename = None 126 for sec in p._section_order: 127 if section_name and sec != section_name: 128 continue 129 ## FIXME: there's a problem with [DEFAULT] here: 130 sec_obj = self[sec] 131 comment = p._section_comments.get(sec) 132 if comment: 133 lines.append(_add_hash(comment)) 134 lines.append('[%s]' % p._pre_normalized_sections[sec]) 135 ops = p._section_key_order[sec] 136 for op in ops: 137 filename = p.setting_location(sec, op)[0] 138 if filename != cur_filename: 139 lines.append('# From %s:' % filename) 140 cur_filename = filename 141 comment = p._key_comments.get((sec, op)) 142 if comment: 143 lines.append(_add_hash(comment)) 144 value = sec_obj[op] 145 value_lines= value.splitlines() 146 lines.append('%s = %s' % (p._pre_normalized_keys[(sec, op)], value_lines[0])) 147 lines.extend(' %s' % v for v in value_lines[1:]) 148 try: 149 rendered = getattr(sec_obj, op) 150 except Exception, e: 151 lines.append('# %s rendering error: %s' % (op, e)) 152 if rendered != value: 153 rendered_lines = rendered.splitlines() 154 lines.append('# %s rendered: %s' % (op, rendered_lines[0])) 155 lines.extend('# %s' % l for l in rendered_lines[1:]) 156 return '\n'.join(lines) 157 158 159class _Section(DictMixin): 160 """Object to represent one section""" 161 162 def __init__(self, config, section_name): 163 self._config = config 164 self._section_name = section_name 165 self._ns = self._config._ns.copy() 166 self._ns['section'] = self 167 168 def set_variable(self, name, value=NoDefault): 169 if value is NoDefault: 170 if name in self._ns: 171 del self._ns[name] 172 else: 173 self._ns[name] = value 174 175 def __getitem__(self, item): 176 try: 177 return self._config._parser.get(self._section_name, item) 178 except NoOptionError: 179 raise KeyError('No option [%s] %s' % (self._section_name, item)) 180 181 def keys(self): 182 return self._config._parser.options(self._section_name) 183 184 def __contains__(self, item): 185 return self._config._parser.has_option(self._section_name, item) 186 187 def __getattr__(self, item): 188 try: 189 value = self[item] 190 except KeyError, e: 191 raise AttributeError(str(e)) 192 filename, line_number = self._config._parser.setting_location( 193 self._section_name, item) 194 name = '[%s] %s (in %s:%s)' % (self._section_name, item, filename, line_number) 195 tmpl = Template(value, name=name) 196 return tmpl.substitute(self._ns) 197 198 def __repr__(self): 199 return 'Configuration()[%r]' % (self._section_name) 200 201 def __unicode__(self): 202 return self._config.__unicode__(self._section_name) 203 204 def __str__(self): 205 return self._config.__unicode__(self._section_name).encode('utf8') 206 207 208def asbool(obj): 209 if isinstance(obj, (str, unicode)): 210 obj = obj.strip().lower() 211 if obj in ['true', 'yes', 'on', 'y', 't', '1']: 212 return True 213 elif obj in ['false', 'no', 'off', 'n', 'f', '0']: 214 return False 215 else: 216 raise ValueError( 217 "String is not true/false: %r" % obj) 218 return bool(obj) 219 220 221def lines(obj): 222 if not obj: 223 return [] 224 if isinstance(obj, basestring): 225 obj = obj.splitlines() 226 return [ 227 l.strip() for l in obj 228 if l.strip() and not l.strip().startswith('#')] 229 return obj 230 231 232def _add_hash(comment): 233 return '\n'.join('#'+l for l in comment.splitlines())