diff options
Diffstat (limited to 'lib/templating.py')
-rw-r--r-- | lib/templating.py | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/lib/templating.py b/lib/templating.py new file mode 100644 index 0000000..1fe3465 --- /dev/null +++ b/lib/templating.py @@ -0,0 +1,205 @@ +#!/usr/bin/python +# +# Copyright 2011 Nick Mathewson, Michael Stone +# +# You may do anything with this work that copyright law would normally +# restrict, so long as you retain the above notice(s) and this license +# in all redistributed copies and derived works. There is no warranty. + +""" +>>> base = Environ(foo=99, bar=600) +>>> derived1 = Environ(parent=base, bar=700, quux=32) +>>> base["foo"] +99 +>>> sorted(base.keys()) +['bar', 'foo'] +>>> derived1["foo"] +99 +>>> base["bar"] +600 +>>> derived1["bar"] +700 +>>> derived1["quux"] +32 +>>> sorted(derived1.keys()) +['bar', 'foo', 'quux'] +>>> class Specialized(Environ): +... def __init__(self, p=None, **kw): +... Environ.__init__(self, p, **kw) +... self._n_calls = 0 +... def _get_expensive_value(self, me): +... self._n_calls += 1 +... return "Let's pretend this is hard to compute" +... +>>> s = Specialized(base, quux="hi") +>>> s["quux"] +'hi' +>>> s['expensive_value'] +"Let's pretend this is hard to compute" +>>> s['expensive_value'] +"Let's pretend this is hard to compute" +>>> s._n_calls +1 +>>> sorted(s.keys()) +['bar', 'expensive_value', 'foo', 'quux'] + +>>> bt = _BetterTemplate("Testing ${hello}, $goodbye$$, $foo , ${a:b:c}") +>>> bt.safe_substitute({'a:b:c': "4"}, hello=1, goodbye=2, foo=3) +'Testing 1, 2$, 3 , 4' + +>>> t = Template("${include:/dev/null} $hi_there") +>>> sorted(t.freevars()) +['hi_there'] +>>> t.format(dict(hi_there=99)) +' 99' +>>> t2 = Template("X$${include:$fname} $bar $baz") +>>> t2.format(dict(fname="/dev/null", bar=33, baz="$foo", foo=1337)) +'X 33 1337' +>>> sorted(t2.freevars({'fname':"/dev/null"})) +['bar', 'baz', 'fname'] + +""" + +from __future__ import with_statement + +import string +import os +import re + +#class _KeyError(KeyError): +# pass + +_KeyError = KeyError + +class _DictWrapper: + def __init__(self, parent=None): + self._parent = parent + + def __getitem__(self, key): + try: + return self._getitem(key) + except KeyError: + pass + if self._parent is None: + raise _KeyError(key) + + try: + return self._parent[key] + except KeyError: + raise _KeyError(key) + +class Environ(_DictWrapper): + def __init__(self, parent=None, **kw): + _DictWrapper.__init__(self, parent) + self._dict = kw + self._cache = {} + + def _getitem(self, key): + try: + return self._dict[key] + except KeyError: + pass + try: + return self._cache[key] + except KeyError: + pass + fn = getattr(self, "_get_%s"%key, None) + if fn is not None: + try: + self._cache[key] = rv = fn(self) + return rv + except _KeyError: + raise KeyError(key) + raise KeyError(key) + + def __setitem__(self, key, val): + self._dict[key] = val + + def keys(self): + s = set() + s.update(self._dict.keys()) + s.update(self._cache.keys()) + if self._parent is not None: + s.update(self._parent.keys()) + s.update(name[5:] for name in dir(self) if name.startswith("_get_")) + return s + +class IncluderDict(_DictWrapper): + def __init__(self, parent, includePath=(".",)): + _DictWrapper.__init__(self, parent) + self._includePath = includePath + + def _getitem(self, key): + if not key.startswith("include:"): + raise KeyError(key) + + filename = key[len("include:"):] + if os.path.isabs(filename): + with open(filename, 'r') as f: + return f.read() + + for elt in self._includePath: + fullname = os.path.join(elt, filename) + if os.path.exists(fullname): + with open(fullname, 'r') as f: + return f.read() + + raise KeyError(key) + +class _BetterTemplate(string.Template): + + idpattern = r'[a-z0-9:_/\.\-]+' + + def __init__(self, template): + string.Template.__init__(self, template) + +class _FindVarsHelper: + def __init__(self, dflts): + self._dflts = dflts + self._vars = set() + def __getitem__(self, var): + self._vars.add(var) + try: + return self._dflts[var] + except KeyError: + return "" + +class Template: + MAX_ITERATIONS = 32 + + def __init__(self, pattern, includePath=(".",)): + self._pat = pattern + self._includePath = includePath + + def freevars(self, defaults=None): + if defaults is None: + defaults = {} + d = _FindVarsHelper(defaults) + self.format(d) + return d._vars + + def format(self, values): + values = IncluderDict(values, self._includePath) + orig_val = self._pat + nIterations = 0 + while True: + v = _BetterTemplate(orig_val).substitute(values) + if v == orig_val: + return v + orig_val = v + nIterations += 1 + if nIterations > self.MAX_ITERATIONS: + raise ValueError("Too many iterations in expanding template!") + +if __name__ == '__main__': + import sys + if len(sys.argv) == 1: + import doctest + doctest.testmod() + print "done" + else: + for fn in sys.argv[1:]: + with open(fn, 'r') as f: + t = Template(f.read()) + print fn, t.freevars() + |