Templating.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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. self._st_mtime = 0
  116. def _getitem(self, key):
  117. if not key.startswith("include:"):
  118. raise KeyError(key)
  119. filename = key[len("include:"):]
  120. if os.path.isabs(filename):
  121. with open(filename, 'r') as f:
  122. stat = os.fstat(f.fileno())
  123. if stat.st_mtime > self._st_mtime:
  124. self._st_mtime = stat.st_mtime
  125. return f.read()
  126. for elt in self._includePath:
  127. fullname = os.path.join(elt, filename)
  128. if os.path.exists(fullname):
  129. with open(fullname, 'r') as f:
  130. stat = os.fstat(f.fileno())
  131. if stat.st_mtime > self._st_mtime:
  132. self._st_mtime = stat.st_mtime
  133. return f.read()
  134. raise KeyError(key)
  135. def getUpdateTime(self):
  136. return self._st_mtime
  137. class _BetterTemplate(string.Template):
  138. idpattern = r'[a-z0-9:_/\.\-]+'
  139. def __init__(self, template):
  140. string.Template.__init__(self, template)
  141. class _FindVarsHelper:
  142. def __init__(self, dflts):
  143. self._dflts = dflts
  144. self._vars = set()
  145. def __getitem__(self, var):
  146. self._vars.add(var)
  147. try:
  148. return self._dflts[var]
  149. except KeyError:
  150. return ""
  151. class Template:
  152. MAX_ITERATIONS = 32
  153. def __init__(self, pattern, includePath=(".",)):
  154. self._pat = pattern
  155. self._includePath = includePath
  156. def freevars(self, defaults=None):
  157. if defaults is None:
  158. defaults = {}
  159. d = _FindVarsHelper(defaults)
  160. self.format(d)
  161. return d._vars
  162. def format(self, values):
  163. values = IncluderDict(values, self._includePath)
  164. orig_val = self._pat
  165. nIterations = 0
  166. while True:
  167. v = _BetterTemplate(orig_val).substitute(values)
  168. if v == orig_val:
  169. return v
  170. orig_val = v
  171. nIterations += 1
  172. if nIterations > self.MAX_ITERATIONS:
  173. raise ValueError("Too many iterations in expanding template!")
  174. if __name__ == '__main__':
  175. import sys
  176. if len(sys.argv) == 1:
  177. import doctest
  178. doctest.testmod()
  179. print "done"
  180. else:
  181. for fn in sys.argv[1:]:
  182. with open(fn, 'r') as f:
  183. t = Template(f.read())
  184. print fn, t.freevars()