PageRenderTime 254ms CodeModel.GetById 90ms app.highlight 52ms RepoModel.GetById 109ms app.codeStats 0ms

/silverlining/tmplconfig.py

https://bitbucket.org/ianb/silverlining/
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())