templating.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. #!/usr/bin/python
  2. #
  3. # Copyright 2011 Nick Mathewson, Michael Stone
  4. #
  5. # You may do anything with this work that copyright law would normally
  6. # restrict, so long as you retain the above notice(s) and this license
  7. # in all redistributed copies and derived works. There is no warranty.
  8. """
  9. >>> base = Environ(foo=99, bar=600)
  10. >>> derived1 = Environ(parent=base, bar=700, quux=32)
  11. >>> base["foo"]
  12. 99
  13. >>> sorted(base.keys())
  14. ['bar', 'foo']
  15. >>> derived1["foo"]
  16. 99
  17. >>> base["bar"]
  18. 600
  19. >>> derived1["bar"]
  20. 700
  21. >>> derived1["quux"]
  22. 32
  23. >>> sorted(derived1.keys())
  24. ['bar', 'foo', 'quux']
  25. >>> class Specialized(Environ):
  26. ... def __init__(self, p=None, **kw):
  27. ... Environ.__init__(self, p, **kw)
  28. ... self._n_calls = 0
  29. ... def _get_expensive_value(self, me):
  30. ... self._n_calls += 1
  31. ... return "Let's pretend this is hard to compute"
  32. ...
  33. >>> s = Specialized(base, quux="hi")
  34. >>> s["quux"]
  35. 'hi'
  36. >>> s['expensive_value']
  37. "Let's pretend this is hard to compute"
  38. >>> s['expensive_value']
  39. "Let's pretend this is hard to compute"
  40. >>> s._n_calls
  41. 1
  42. >>> sorted(s.keys())
  43. ['bar', 'expensive_value', 'foo', 'quux']
  44. >>> bt = _BetterTemplate("Testing ${hello}, $goodbye$$, $foo , ${a:b:c}")
  45. >>> bt.safe_substitute({'a:b:c': "4"}, hello=1, goodbye=2, foo=3)
  46. 'Testing 1, 2$, 3 , 4'
  47. >>> t = Template("${include:/dev/null} $hi_there")
  48. >>> sorted(t.freevars())
  49. ['hi_there']
  50. >>> t.format(dict(hi_there=99))
  51. ' 99'
  52. >>> t2 = Template("X$${include:$fname} $bar $baz")
  53. >>> t2.format(dict(fname="/dev/null", bar=33, baz="$foo", foo=1337))
  54. 'X 33 1337'
  55. >>> sorted(t2.freevars({'fname':"/dev/null"}))
  56. ['bar', 'baz', 'fname']
  57. """
  58. from __future__ import with_statement
  59. import string
  60. import os
  61. import re
  62. #class _KeyError(KeyError):
  63. # pass
  64. _KeyError = KeyError
  65. class _DictWrapper:
  66. def __init__(self, parent=None):
  67. self._parent = parent
  68. def __getitem__(self, key):
  69. try:
  70. return self._getitem(key)
  71. except KeyError:
  72. pass
  73. if self._parent is None:
  74. raise _KeyError(key)
  75. try:
  76. return self._parent[key]
  77. except KeyError:
  78. raise _KeyError(key)
  79. class Environ(_DictWrapper):
  80. def __init__(self, parent=None, **kw):
  81. _DictWrapper.__init__(self, parent)
  82. self._dict = kw
  83. self._cache = {}
  84. def _getitem(self, key):
  85. try:
  86. return self._dict[key]
  87. except KeyError:
  88. pass
  89. try:
  90. return self._cache[key]
  91. except KeyError:
  92. pass
  93. fn = getattr(self, "_get_%s"%key, None)
  94. if fn is not None:
  95. try:
  96. self._cache[key] = rv = fn(self)
  97. return rv
  98. except _KeyError:
  99. raise KeyError(key)
  100. raise KeyError(key)
  101. def __setitem__(self, key, val):
  102. self._dict[key] = val
  103. def keys(self):
  104. s = set()
  105. s.update(self._dict.keys())
  106. s.update(self._cache.keys())
  107. if self._parent is not None:
  108. s.update(self._parent.keys())
  109. s.update(name[5:] for name in dir(self) if name.startswith("_get_"))
  110. return s
  111. class IncluderDict(_DictWrapper):
  112. def __init__(self, parent, includePath=(".",)):
  113. _DictWrapper.__init__(self, parent)
  114. self._includePath = includePath
  115. def _getitem(self, key):
  116. if not key.startswith("include:"):
  117. raise KeyError(key)
  118. filename = key[len("include:"):]
  119. if os.path.isabs(filename):
  120. with open(filename, 'r') as f:
  121. return f.read()
  122. for elt in self._includePath:
  123. fullname = os.path.join(elt, filename)
  124. if os.path.exists(fullname):
  125. with open(fullname, 'r') as f:
  126. return f.read()
  127. raise KeyError(key)
  128. class _BetterTemplate(string.Template):
  129. idpattern = r'[a-z0-9:_/\.\-]+'
  130. def __init__(self, template):
  131. string.Template.__init__(self, template)
  132. class _FindVarsHelper:
  133. def __init__(self, dflts):
  134. self._dflts = dflts
  135. self._vars = set()
  136. def __getitem__(self, var):
  137. self._vars.add(var)
  138. try:
  139. return self._dflts[var]
  140. except KeyError:
  141. return ""
  142. class Template:
  143. MAX_ITERATIONS = 32
  144. def __init__(self, pattern, includePath=(".",)):
  145. self._pat = pattern
  146. self._includePath = includePath
  147. def freevars(self, defaults=None):
  148. if defaults is None:
  149. defaults = {}
  150. d = _FindVarsHelper(defaults)
  151. self.format(d)
  152. return d._vars
  153. def format(self, values):
  154. values = IncluderDict(values, self._includePath)
  155. orig_val = self._pat
  156. nIterations = 0
  157. while True:
  158. v = _BetterTemplate(orig_val).substitute(values)
  159. if v == orig_val:
  160. return v
  161. orig_val = v
  162. nIterations += 1
  163. if nIterations > self.MAX_ITERATIONS:
  164. raise ValueError("Too many iterations in expanding template!")
  165. if __name__ == '__main__':
  166. import sys
  167. if len(sys.argv) == 1:
  168. import doctest
  169. doctest.testmod()
  170. print "done"
  171. else:
  172. for fn in sys.argv[1:]:
  173. with open(fn, 'r') as f:
  174. t = Template(f.read())
  175. print fn, t.freevars()