Mercurial > hgrepos > Python > libs > ConfigMix
changeset 16:f85dc4677c01
Implemented the real configuration dictionary with attribute access or
variable substitution
| author | Franz Glasner <hg@dom66.de> |
|---|---|
| date | Thu, 10 Mar 2016 09:39:35 +0100 |
| parents | 0b1292e920af |
| children | 94b5e94fae44 |
| files | configmix/compat.py configmix/config.py |
| diffstat | 2 files changed, 177 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/configmix/compat.py Wed Mar 09 15:35:46 2016 +0100 +++ b/configmix/compat.py Thu Mar 10 09:39:35 2016 +0100 @@ -11,7 +11,8 @@ __all__ = ["PY2", "text_to_native_os_str", - "native_os_str_to_text"] + "native_os_str_to_text", + "u"] PY2 = sys.version_info[0] <= 2 @@ -31,6 +32,13 @@ def native_os_str_to_text(s, encoding=None): return s.decode(encoding or _OS_ENCODING) + + def u(s, encoding="utf-8"): + if isinstance(s, unicode): + return s + else: + return s.decode(encoding) + else: def text_to_native_os_str(s, encoding=None): @@ -39,3 +47,9 @@ def native_os_str_to_text(s, encoding=None): return s + + def u(s, encoding="utf-8"): + if isinstance(s, str): + return s + else: + return s.decode(encoding)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/configmix/config.py Thu Mar 10 09:39:35 2016 +0100 @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +r"""The unified configuration dictionary with attribute support +or variable substitution. + +""" + +from __future__ import division, absolute_import, print_function + +try: + from collections import OrderedDict as ConfigurationBase +except ImportError: + try: + from ordereddict import OrderedDict as ConfigurationBase + except ImportError: + ConfigurationBase = dict + +from .variables import lookup_varns, lookup_filter +from .compat import u + + +__all__ = ["Configuration"] + + +_MARKER = object() + + +class AttributeDict(ConfigurationBase): + + def __getattr__(self, name): + try: + v = self[name] + except KeyError: + raise AttributeError("%s has no attribute %r" % (type(self), name)) + else: + # Wrap a dict into another dict with attribute access support + if isinstance(v, dict): + return AttributeDict(v) + else: + return v + + +class Configuration(AttributeDict): + + def getvar(self, varname, default=_MARKER): + """Get a variable of the form ``[[ns1.]ns2.]name`` - including + variables from other namespaces. + + No variable expansion is done and no filters are applied. + """ + varns, varname = self._split_ns(varname) + try: + if not varns: + lookupfn = self._lookupvar + else: + lookupfn = lookup_varns(varns) + varvalue = lookupfn(varname) + except KeyError: + if default is _MARKER: + raise KeyError("Variable %r not found" % varname) + else: + return default + else: + return varvalue + + def getvar_s(self, varname, default=_MARKER): + """Get a variable - incliding variables from other namespaces. + + Variables will be substituted recursively in the result. + """ + try: + obj = self.getvar(varname) + return self.substitute_variables_in_obj(obj) + except KeyError: + if default is _MARKER: + raise + else: + return default + + def _split_ns(self, s): + nameparts = s.split(':', 1) + if len(nameparts) == 1: + return (None, s, ) + else: + return (nameparts[0], nameparts[1], ) + + def _split_filters(self, s): + nameparts = s.split('|') + if len(nameparts) == 1: + return (s, [], ) + else: + return (nameparts[0].rstrip(), nameparts[1:], ) + + def _lookupvar(self, key, default=_MARKER): + """Lookup a variable. + + If no default is given an unexisting `key` raises a `KeyError` + else `default` is returned. + """ + parts = key.split('.') + try: + v = self[parts[0]] + for p in parts[1:]: + v = v[p] + except KeyError: + if default is _MARKER: + raise KeyError("Configuration variable %r not found" % key) + else: + return default + return v + + # Speed + _TEXTTYPE = type(u("")) + + def substitute_variables_in_obj(self, obj): + """Recursively expand variables in the object tree `obj`.""" + if isinstance(obj, self._TEXTTYPE): + # a string - really replace the value + return self.expand_variable(obj) + elif isinstance(obj, list): + return [self.substitute_variables_in_obj(i) for i in obj] + elif isinstance(obj, tuple): + tmp = [self.substitute_variables_in_obj(i) for i in obj] + return type(obj)(tmp) + elif isinstance(obj, dict): + newdict = type(obj)() + for k in obj: + newdict[k] = self.substitute_variables_in_obj(obj[k]) + return newdict + elif isinstance(obj, set): + newset = type(obj)() + for i in obj: + newset.add(self.substitute_variables_in_obj(i)) + else: + return obj + + # Speed + _STARTTOK = u(b"{{") + _ENDTOK = u(b"}}") + + def expand_variable(self, s): + """Expand variables in a single string""" + start = s.find(self._STARTTOK, 0) + while start != -1: + end = s.find(self._ENDTOK, start) + if end < 0: + return s + varname, filters = self._split_filters(s[start+2:end]) + varvalue = self._apply_filters(filters, self.getvar_s(varname)) + s = u(b"{0}{1}{2}").format(s[:start], varvalue, s[end+2:]) + # don't re-evaluate because `self.getvar_s()` expands already + start = s.find(self._STARTTOK, start + len(varvalue)) + return s + + def _apply_filters(self, filters, value): + for name in filters: + try: + filterfn = lookup_filter(name) + except KeyError: + raise NameError("Filter %r not found" % name) + else: + value = filterfn(self, value) + return value
