/uliweb/contrib/template/tags.py
Python | 286 lines | 261 code | 9 blank | 16 comment | 11 complexity | fd9993c0425434832bd02e7391b1aac7 MD5 | raw file
- import os
- import re
- from uliweb.utils.common import log
- from uliweb.core.template import *
- from uliweb import functions
- import warnings
- import inspect
-
- r_links = re.compile('<link\s+.*?\s*href\s*=\s*"?(.*?)["\s>]|<script\s+.*?\s*src\s*=\s*"?(.*?)["\s>]', re.I)
- r_head = re.compile('(?i)<head>(.*?)</head>', re.DOTALL)
- r_top = re.compile('<!--\s*toplinks\s*-->')
- r_bottom = re.compile('<!--\s*bottomlinks\s*-->')
-
- #used to remember static files combine infos
- __static_combine__ = None
- __static_mapping__ = {}
- __saved_template_plugins_modules__ = {}
- __use_cached__ = {}
-
- class UseModuleNotFound(Exception): pass
- class TemplateDefineError(Exception): pass
-
- def link(env, links, to='toplinks', **kwargs):
- if not isinstance(links, (tuple, list)):
- links = [links]
- if kwargs:
- new_links = []
- for x in links:
- kw = {'value':x}
- kw.update(kwargs)
- new_links.append(kw)
- links = new_links
- if to == 'toplinks':
- env['toplinks'].extend(links)
- else:
- env['bottomlinks'].extend(links)
-
- def htmlmerge(text, env):
- m = HtmlMerge(text, env)
- return m()
-
- def use(env, plugin, *args, **kwargs):
- toplinks, bottomlinks = find(plugin, *args, **kwargs)
- env['toplinks'].extend(toplinks)
- env['bottomlinks'].extend(bottomlinks)
-
- def find(plugin, *args, **kwargs):
- from uliweb.core.SimpleFrame import get_app_dir
- from uliweb import application as app, settings
- from uliweb.utils.common import is_pyfile_exist
-
- key = (plugin, repr(args) + repr(sorted(kwargs.items())))
- if key in __use_cached__:
- return __use_cached__[key]
-
- if plugin in __saved_template_plugins_modules__:
- mod = __saved_template_plugins_modules__[plugin]
- else:
- #add settings support, only support simple situation
- #so for complex cases you should still write module
- #format just like:
- #
- #[TEMPLATE_USE]
- #name = {
- # 'toplinks':[
- # 'myapp/jquery.myapp.{version}.min.js',
- # ],
- # 'depends':[xxxx],
- # 'config':{'version':'UI_CONFIG/test'},
- # 'default':{'version':'1.2.0'},
- #}
- #
- mod = None
- c = settings.get_var('TEMPLATE_USE/'+plugin)
- if c:
- config = c.pop('config', {})
- default = c.pop('default', {})
- #evaluate config value
- config = dict([(k, settings.get_var(v, default.get(k, ''))) for k, v in config.items()])
- #merge passed arguments
- config.update(kwargs)
- for t in ['toplinks', 'bottomlinks']:
- if t in c:
- c[t] = [x.format(**config) for x in c[t]]
- mod = c
- else:
- for p in reversed(app.apps):
- if not is_pyfile_exist(os.path.join(get_app_dir(p), 'template_plugins'), plugin):
- continue
- module = '.'.join([p, 'template_plugins', plugin])
- try:
- mod = __import__(module, fromlist=['*'])
- break
- except ImportError as e:
- log.exception(e)
- mod = None
- if mod:
- __saved_template_plugins_modules__[plugin] = mod
- else:
- log.error("Can't find the [%s] template plugin, please check if you've installed special app already" % plugin)
- raise UseModuleNotFound("Can't find the %s template plugin, check if you've installed special app already" % plugin)
-
- #mod maybe an dict
- if isinstance(mod, dict):
- v = mod
- else:
- v = None
- call = getattr(mod, 'call', None)
- call.__name__ = call.__module__
- if call:
- para = inspect.getargspec(call)[0]
- #test if the funtion is defined as old style
- if ['app', 'var', 'env'] == para[:3]:
- warnings.simplefilter('default')
- warnings.warn("Tmplate plugs call function(%s) should be defined"
- " as call(*args, **kwargs) not need (app, var, env) any more" % call.__module__,
- DeprecationWarning)
- v = call(app, {}, {}, *args, **kwargs)
-
- else:
- v = call(*args, **kwargs)
-
- toplinks = []
- bottomlinks = []
- if v:
- if 'depends' in v:
- for _t in v['depends']:
- if isinstance(_t, str):
- t, b = find(_t)
- else:
- d, kw = _t
- t, b = find(d, **kw)
- toplinks.extend(t)
- bottomlinks.extend(b)
- if 'toplinks' in v:
- links = v['toplinks']
- if not isinstance(links, (tuple, list)):
- links = [links]
- toplinks.extend(links)
- if 'bottomlinks' in v:
- links = v['bottomlinks']
- if not isinstance(links, (tuple, list)):
- links = [links]
- bottomlinks.extend(links)
- if 'depends_after' in v:
- for _t in v['depends_after']:
- if isinstance(_t, str):
- t, b = use(env, _t)
- else:
- d, kw = _t
- t, b = use(env, d, **kw)
- toplinks.extend(t)
- bottomlinks.extend(b)
-
- __use_cached__[key] = toplinks, bottomlinks
- return toplinks, bottomlinks
-
- class HtmlMerge(object):
- def __init__(self, text, links):
- self.text = text
- self.links = links
- self.init()
-
- def init(self):
- global __static_combine__, __static_mapping__
- from . import init_static_combine
-
- if __static_combine__ is None:
- __static_combine__ = init_static_combine()
- for k, v in __static_combine__.items():
- for x in v:
- __static_mapping__[x] = k
-
- def __call__(self):
- result = self.assemble(self._clean_collection([]))
- #cal links first, if no toplinks or bottomlinks be found, then
- #do nothing, otherwise find the head, and calculate the position
- #of toplinks and bottomlinks
- if result['toplinks'] or result['bottomlinks'] or result['headlinks']:
- links = []
- b = r_head.search(self.text)
- if b:
- start, end = b.span()
- head = b.group()
- for v in r_links.findall(head):
- link = v[0] or v[1]
- links.append(link)
- else:
- head = ''
- start, end = 0, 0
- result = self.assemble(self._clean_collection(links))
- if result['toplinks'] or result['bottomlinks']:
- top = result['toplinks'] or ''
- bottom = result['bottomlinks'] or ''
- top_start, bottom_start = self.cal_position(self.text, top, bottom,
- len(head), start)
-
- if top and bottom:
- if bottom_start < top_start:
- raise TemplateDefineError("Template <!-- bottomlinks --> shouldn't be defined before <!-- toplinks -->")
- return self.text[:top_start] + top + self.text[top_start:bottom_start] + bottom + self.text[bottom_start:]
- elif top:
- return self.text[:top_start] + top + self.text[top_start:]
- elif bottom:
- return self.text[:bottom_start] + bottom + self.text[bottom_start:]
-
- return self.text
-
- def _clean_collection(self, existlinks):
- from uliweb.utils.sorteddict import SortedDict
-
- r = {'toplinks':SortedDict(), 'bottomlinks':SortedDict(), 'headlinks':SortedDict()}
- #process links, link could be (order, link) or link
- for _type in ['toplinks', 'bottomlinks', 'headlinks']:
- t = self.links.get(_type, [])
- for link in t:
- #link will also be template string
- if '{{' in link and '}}' in link:
- #link = template(link, self.env)
- raise TemplateDefineError("Can't support tag {{}} in links")
-
- #process static combine
- if isinstance(link, dict):
- link_key = link.get('value')
- link_value = link.copy()
- link_value.pop('value')
- else:
- link_key = link
- link_value = {}
- new_link = __static_mapping__.get(link_key, link_key)
- if new_link.endswith('.js') or new_link.endswith('.css'):
- _link = functions.url_for_static(new_link)
- else:
- _link = new_link
- if not new_link in r[_type] and not _link in existlinks:
- link_value['link'] = _link
- r[_type][new_link] = link_value
- existlinks.append(_link)
- return r
-
- def cal_position(self, text, has_toplinks, has_bottomlinks, head_len, head_start):
- """
- Calculate the position of toplinks and bottomlinks, if there is not
- toplinks and bottomlinks then toplinks position will be the position after <head>
- and if there is no bottomlinks, the bottomlinks position will be the
- position before </head>.
- """
- if head_len == 0:
- top_start = top_end = bottom_start = bottom_end = 0
- else:
- top_start = top_end = head_start + 6
- bottom_start = bottom_end = head_start + head_len - 7
-
- if has_toplinks:
- t = r_top.search(text)
- if t:
- top_start, top_end = t.span()
-
- if has_bottomlinks:
- t = r_bottom.search(text)
- if t:
- bottom_start, bottom_end = t.span()
-
- return top_end, bottom_end
-
- def assemble(self, links):
- from uliweb.core.html import to_attrs
-
- toplinks = ['']
- bottomlinks = ['']
- for _type, result in [('toplinks', toplinks), ('bottomlinks', bottomlinks)]:
- for link, kw in links[_type].items():
- _link = kw.pop('link', link)
- if kw:
- attrs = to_attrs(kw)
- else:
- attrs = ''
- if link.endswith('.js'):
- result.append('<script type="text/javascript" src="%s"%s></script>' % (_link, attrs))
- elif link.endswith('.css'):
- result.append('<link rel="stylesheet" type="text/css" href="%s"%s/>' % (_link, attrs))
- else:
- result.append(link)
-
- return {'toplinks':'\n'.join(toplinks), 'bottomlinks':'\n'.join(bottomlinks)}
-