PageRenderTime 61ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/uliweb/contrib/template/tags.py

https://gitlab.com/18runt88/uliweb
Python | 286 lines | 261 code | 9 blank | 16 comment | 11 complexity | fd9993c0425434832bd02e7391b1aac7 MD5 | raw file
  1. import os
  2. import re
  3. from uliweb.utils.common import log
  4. from uliweb.core.template import *
  5. from uliweb import functions
  6. import warnings
  7. import inspect
  8. r_links = re.compile('<link\s+.*?\s*href\s*=\s*"?(.*?)["\s>]|<script\s+.*?\s*src\s*=\s*"?(.*?)["\s>]', re.I)
  9. r_head = re.compile('(?i)<head>(.*?)</head>', re.DOTALL)
  10. r_top = re.compile('<!--\s*toplinks\s*-->')
  11. r_bottom = re.compile('<!--\s*bottomlinks\s*-->')
  12. #used to remember static files combine infos
  13. __static_combine__ = None
  14. __static_mapping__ = {}
  15. __saved_template_plugins_modules__ = {}
  16. __use_cached__ = {}
  17. class UseModuleNotFound(Exception): pass
  18. class TemplateDefineError(Exception): pass
  19. def link(env, links, to='toplinks', **kwargs):
  20. if not isinstance(links, (tuple, list)):
  21. links = [links]
  22. if kwargs:
  23. new_links = []
  24. for x in links:
  25. kw = {'value':x}
  26. kw.update(kwargs)
  27. new_links.append(kw)
  28. links = new_links
  29. if to == 'toplinks':
  30. env['toplinks'].extend(links)
  31. else:
  32. env['bottomlinks'].extend(links)
  33. def htmlmerge(text, env):
  34. m = HtmlMerge(text, env)
  35. return m()
  36. def use(env, plugin, *args, **kwargs):
  37. toplinks, bottomlinks = find(plugin, *args, **kwargs)
  38. env['toplinks'].extend(toplinks)
  39. env['bottomlinks'].extend(bottomlinks)
  40. def find(plugin, *args, **kwargs):
  41. from uliweb.core.SimpleFrame import get_app_dir
  42. from uliweb import application as app, settings
  43. from uliweb.utils.common import is_pyfile_exist
  44. key = (plugin, repr(args) + repr(sorted(kwargs.items())))
  45. if key in __use_cached__:
  46. return __use_cached__[key]
  47. if plugin in __saved_template_plugins_modules__:
  48. mod = __saved_template_plugins_modules__[plugin]
  49. else:
  50. #add settings support, only support simple situation
  51. #so for complex cases you should still write module
  52. #format just like:
  53. #
  54. #[TEMPLATE_USE]
  55. #name = {
  56. # 'toplinks':[
  57. # 'myapp/jquery.myapp.{version}.min.js',
  58. # ],
  59. # 'depends':[xxxx],
  60. # 'config':{'version':'UI_CONFIG/test'},
  61. # 'default':{'version':'1.2.0'},
  62. #}
  63. #
  64. mod = None
  65. c = settings.get_var('TEMPLATE_USE/'+plugin)
  66. if c:
  67. config = c.pop('config', {})
  68. default = c.pop('default', {})
  69. #evaluate config value
  70. config = dict([(k, settings.get_var(v, default.get(k, ''))) for k, v in config.items()])
  71. #merge passed arguments
  72. config.update(kwargs)
  73. for t in ['toplinks', 'bottomlinks']:
  74. if t in c:
  75. c[t] = [x.format(**config) for x in c[t]]
  76. mod = c
  77. else:
  78. for p in reversed(app.apps):
  79. if not is_pyfile_exist(os.path.join(get_app_dir(p), 'template_plugins'), plugin):
  80. continue
  81. module = '.'.join([p, 'template_plugins', plugin])
  82. try:
  83. mod = __import__(module, fromlist=['*'])
  84. break
  85. except ImportError as e:
  86. log.exception(e)
  87. mod = None
  88. if mod:
  89. __saved_template_plugins_modules__[plugin] = mod
  90. else:
  91. log.error("Can't find the [%s] template plugin, please check if you've installed special app already" % plugin)
  92. raise UseModuleNotFound("Can't find the %s template plugin, check if you've installed special app already" % plugin)
  93. #mod maybe an dict
  94. if isinstance(mod, dict):
  95. v = mod
  96. else:
  97. v = None
  98. call = getattr(mod, 'call', None)
  99. call.__name__ = call.__module__
  100. if call:
  101. para = inspect.getargspec(call)[0]
  102. #test if the funtion is defined as old style
  103. if ['app', 'var', 'env'] == para[:3]:
  104. warnings.simplefilter('default')
  105. warnings.warn("Tmplate plugs call function(%s) should be defined"
  106. " as call(*args, **kwargs) not need (app, var, env) any more" % call.__module__,
  107. DeprecationWarning)
  108. v = call(app, {}, {}, *args, **kwargs)
  109. else:
  110. v = call(*args, **kwargs)
  111. toplinks = []
  112. bottomlinks = []
  113. if v:
  114. if 'depends' in v:
  115. for _t in v['depends']:
  116. if isinstance(_t, str):
  117. t, b = find(_t)
  118. else:
  119. d, kw = _t
  120. t, b = find(d, **kw)
  121. toplinks.extend(t)
  122. bottomlinks.extend(b)
  123. if 'toplinks' in v:
  124. links = v['toplinks']
  125. if not isinstance(links, (tuple, list)):
  126. links = [links]
  127. toplinks.extend(links)
  128. if 'bottomlinks' in v:
  129. links = v['bottomlinks']
  130. if not isinstance(links, (tuple, list)):
  131. links = [links]
  132. bottomlinks.extend(links)
  133. if 'depends_after' in v:
  134. for _t in v['depends_after']:
  135. if isinstance(_t, str):
  136. t, b = use(env, _t)
  137. else:
  138. d, kw = _t
  139. t, b = use(env, d, **kw)
  140. toplinks.extend(t)
  141. bottomlinks.extend(b)
  142. __use_cached__[key] = toplinks, bottomlinks
  143. return toplinks, bottomlinks
  144. class HtmlMerge(object):
  145. def __init__(self, text, links):
  146. self.text = text
  147. self.links = links
  148. self.init()
  149. def init(self):
  150. global __static_combine__, __static_mapping__
  151. from . import init_static_combine
  152. if __static_combine__ is None:
  153. __static_combine__ = init_static_combine()
  154. for k, v in __static_combine__.items():
  155. for x in v:
  156. __static_mapping__[x] = k
  157. def __call__(self):
  158. result = self.assemble(self._clean_collection([]))
  159. #cal links first, if no toplinks or bottomlinks be found, then
  160. #do nothing, otherwise find the head, and calculate the position
  161. #of toplinks and bottomlinks
  162. if result['toplinks'] or result['bottomlinks'] or result['headlinks']:
  163. links = []
  164. b = r_head.search(self.text)
  165. if b:
  166. start, end = b.span()
  167. head = b.group()
  168. for v in r_links.findall(head):
  169. link = v[0] or v[1]
  170. links.append(link)
  171. else:
  172. head = ''
  173. start, end = 0, 0
  174. result = self.assemble(self._clean_collection(links))
  175. if result['toplinks'] or result['bottomlinks']:
  176. top = result['toplinks'] or ''
  177. bottom = result['bottomlinks'] or ''
  178. top_start, bottom_start = self.cal_position(self.text, top, bottom,
  179. len(head), start)
  180. if top and bottom:
  181. if bottom_start < top_start:
  182. raise TemplateDefineError("Template <!-- bottomlinks --> shouldn't be defined before <!-- toplinks -->")
  183. return self.text[:top_start] + top + self.text[top_start:bottom_start] + bottom + self.text[bottom_start:]
  184. elif top:
  185. return self.text[:top_start] + top + self.text[top_start:]
  186. elif bottom:
  187. return self.text[:bottom_start] + bottom + self.text[bottom_start:]
  188. return self.text
  189. def _clean_collection(self, existlinks):
  190. from uliweb.utils.sorteddict import SortedDict
  191. r = {'toplinks':SortedDict(), 'bottomlinks':SortedDict(), 'headlinks':SortedDict()}
  192. #process links, link could be (order, link) or link
  193. for _type in ['toplinks', 'bottomlinks', 'headlinks']:
  194. t = self.links.get(_type, [])
  195. for link in t:
  196. #link will also be template string
  197. if '{{' in link and '}}' in link:
  198. #link = template(link, self.env)
  199. raise TemplateDefineError("Can't support tag {{}} in links")
  200. #process static combine
  201. if isinstance(link, dict):
  202. link_key = link.get('value')
  203. link_value = link.copy()
  204. link_value.pop('value')
  205. else:
  206. link_key = link
  207. link_value = {}
  208. new_link = __static_mapping__.get(link_key, link_key)
  209. if new_link.endswith('.js') or new_link.endswith('.css'):
  210. _link = functions.url_for_static(new_link)
  211. else:
  212. _link = new_link
  213. if not new_link in r[_type] and not _link in existlinks:
  214. link_value['link'] = _link
  215. r[_type][new_link] = link_value
  216. existlinks.append(_link)
  217. return r
  218. def cal_position(self, text, has_toplinks, has_bottomlinks, head_len, head_start):
  219. """
  220. Calculate the position of toplinks and bottomlinks, if there is not
  221. toplinks and bottomlinks then toplinks position will be the position after <head>
  222. and if there is no bottomlinks, the bottomlinks position will be the
  223. position before </head>.
  224. """
  225. if head_len == 0:
  226. top_start = top_end = bottom_start = bottom_end = 0
  227. else:
  228. top_start = top_end = head_start + 6
  229. bottom_start = bottom_end = head_start + head_len - 7
  230. if has_toplinks:
  231. t = r_top.search(text)
  232. if t:
  233. top_start, top_end = t.span()
  234. if has_bottomlinks:
  235. t = r_bottom.search(text)
  236. if t:
  237. bottom_start, bottom_end = t.span()
  238. return top_end, bottom_end
  239. def assemble(self, links):
  240. from uliweb.core.html import to_attrs
  241. toplinks = ['']
  242. bottomlinks = ['']
  243. for _type, result in [('toplinks', toplinks), ('bottomlinks', bottomlinks)]:
  244. for link, kw in links[_type].items():
  245. _link = kw.pop('link', link)
  246. if kw:
  247. attrs = to_attrs(kw)
  248. else:
  249. attrs = ''
  250. if link.endswith('.js'):
  251. result.append('<script type="text/javascript" src="%s"%s></script>' % (_link, attrs))
  252. elif link.endswith('.css'):
  253. result.append('<link rel="stylesheet" type="text/css" href="%s"%s/>' % (_link, attrs))
  254. else:
  255. result.append(link)
  256. return {'toplinks':'\n'.join(toplinks), 'bottomlinks':'\n'.join(bottomlinks)}