Templating.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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. #class _KeyError(KeyError):
  62. # pass
  63. _KeyError = KeyError
  64. class _DictWrapper(object):
  65. def __init__(self, parent=None):
  66. self._parent = parent
  67. def __getitem__(self, key):
  68. try:
  69. return self._getitem(key)
  70. except KeyError:
  71. pass
  72. if self._parent is None:
  73. raise _KeyError(key)
  74. try:
  75. return self._parent[key]
  76. except KeyError:
  77. raise _KeyError(key)
  78. class Environ(_DictWrapper):
  79. def __init__(self, parent=None, **kw):
  80. _DictWrapper.__init__(self, parent)
  81. self._dict = kw
  82. self._cache = {}
  83. def _getitem(self, key):
  84. try:
  85. return self._dict[key]
  86. except KeyError:
  87. pass
  88. try:
  89. return self._cache[key]
  90. except KeyError:
  91. pass
  92. fn = getattr(self, "_get_%s"%key, None)
  93. if fn is not None:
  94. try:
  95. self._cache[key] = rv = fn(self)
  96. return rv
  97. except _KeyError:
  98. raise KeyError(key)
  99. raise KeyError(key)
  100. def __setitem__(self, key, val):
  101. self._dict[key] = val
  102. def keys(self):
  103. s = set()
  104. s.update(self._dict.keys())
  105. s.update(self._cache.keys())
  106. if self._parent is not None:
  107. s.update(self._parent.keys())
  108. s.update(name[5:] for name in dir(self) if name.startswith("_get_"))
  109. return s
  110. class IncluderDict(_DictWrapper):
  111. def __init__(self, parent, includePath=(".",)):
  112. _DictWrapper.__init__(self, parent)
  113. self._includePath = includePath
  114. self._st_mtime = 0
  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. stat = os.fstat(f.fileno())
  122. if stat.st_mtime > self._st_mtime:
  123. self._st_mtime = stat.st_mtime
  124. return f.read()
  125. for elt in self._includePath:
  126. fullname = os.path.join(elt, filename)
  127. if os.path.exists(fullname):
  128. with open(fullname, 'r') as f:
  129. stat = os.fstat(f.fileno())
  130. if stat.st_mtime > self._st_mtime:
  131. self._st_mtime = stat.st_mtime
  132. return f.read()
  133. raise KeyError(key)
  134. def getUpdateTime(self):
  135. return self._st_mtime
  136. class _BetterTemplate(string.Template):
  137. idpattern = r'[a-z0-9:_/\.\-]+'
  138. def __init__(self, template):
  139. string.Template.__init__(self, template)
  140. class _FindVarsHelper(object):
  141. def __init__(self, dflts):
  142. self._dflts = dflts
  143. self._vars = set()
  144. def __getitem__(self, var):
  145. self._vars.add(var)
  146. try:
  147. return self._dflts[var]
  148. except KeyError:
  149. return ""
  150. class Template(object):
  151. MAX_ITERATIONS = 32
  152. def __init__(self, pattern, includePath=(".",)):
  153. self._pat = pattern
  154. self._includePath = includePath
  155. def freevars(self, defaults=None):
  156. if defaults is None:
  157. defaults = {}
  158. d = _FindVarsHelper(defaults)
  159. self.format(d)
  160. return d._vars
  161. def format(self, values):
  162. values = IncluderDict(values, self._includePath)
  163. orig_val = self._pat
  164. nIterations = 0
  165. while True:
  166. v = _BetterTemplate(orig_val).substitute(values)
  167. if v == orig_val:
  168. return v
  169. orig_val = v
  170. nIterations += 1
  171. if nIterations > self.MAX_ITERATIONS:
  172. raise ValueError("Too many iterations in expanding template!")
  173. if __name__ == '__main__':
  174. import sys
  175. if len(sys.argv) == 1:
  176. import doctest
  177. doctest.testmod()
  178. print "done"
  179. else:
  180. for fn in sys.argv[1:]:
  181. with open(fn, 'r') as f:
  182. t = Template(f.read())
  183. print fn, t.freevars()