PageRenderTime 31ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/silverlining/tmplconfig.py

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