123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- #!/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
- self._st_mtime = 0
- 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:
- stat = os.fstat(f.fileno())
- if stat.st_mtime > self._st_mtime:
- self._st_mtime = stat.st_mtime
- 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:
- stat = os.fstat(f.fileno())
- if stat.st_mtime > self._st_mtime:
- self._st_mtime = stat.st_mtime
- return f.read()
- raise KeyError(key)
- def getUpdateTime(self):
- return self._st_mtime
- 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()
|