138 lines
3.5 KiB
Python
138 lines
3.5 KiB
Python
'''functions for web template management and rendering'''
|
|
import codecs, traceback, os, json, xml
|
|
|
|
from os import listdir, makedirs
|
|
from os.path import isfile, isdir, getmtime, abspath
|
|
|
|
from jinja2 import Environment, FileSystemLoader, ChoiceLoader, select_autoescape, Markup
|
|
from hamlish_jinja import HamlishExtension
|
|
from xml.dom import minidom
|
|
|
|
try:
|
|
from sanic import response as Response
|
|
|
|
except ModuleNotFoundError:
|
|
Response = None
|
|
|
|
from . import logging
|
|
from .color import *
|
|
from .misc import Path, DotDict
|
|
|
|
|
|
class Template(Environment):
|
|
def __init__(self, search=[], global_vars={}, context=None, autoescape=True):
|
|
self.autoescape = autoescape
|
|
self.search = []
|
|
self.func_context = context
|
|
|
|
for path in search:
|
|
self.__add_search_path(path)
|
|
|
|
super().__init__(
|
|
loader=ChoiceLoader([FileSystemLoader(path) for path in self.search]),
|
|
extensions=[HamlishExtension],
|
|
autoescape=self.autoescape,
|
|
lstrip_blocks=True,
|
|
trim_blocks=True
|
|
)
|
|
|
|
self.hamlish_file_extensions=('.haml',)
|
|
self.hamlish_enable_div_shortcut=True
|
|
self.hamlish_mode = 'indented'
|
|
|
|
self.globals.update({
|
|
'markup': Markup,
|
|
'cleanhtml': lambda text: ''.join(xml.etree.ElementTree.fromstring(text).itertext()),
|
|
'lighten': lighten,
|
|
'darken': darken,
|
|
'saturate': saturate,
|
|
'desaturate': desaturate,
|
|
'rgba': rgba
|
|
})
|
|
|
|
self.globals.update(global_vars)
|
|
|
|
|
|
def __add_search_path(self, path):
|
|
tpl_path = Path(path)
|
|
|
|
if not tpl_path.exists():
|
|
raise FileNotFoundError('Cannot find search path:', tpl_path.str())
|
|
|
|
if tpl_path.str() not in self.search:
|
|
self.search.append(tpl_path.str())
|
|
|
|
def setContext(self, context):
|
|
if not hasattr(context, '__call__'):
|
|
logging.error('Context is not callable')
|
|
return
|
|
|
|
if not isinstance(context({}, {}), dict):
|
|
logging.error('Context does not return a dict or dict-like object')
|
|
return
|
|
|
|
self.func_context = context
|
|
|
|
|
|
def addEnv(self, k, v):
|
|
self.globals[k] = v
|
|
|
|
|
|
def delEnv(self, var):
|
|
if not self.globals.get(var):
|
|
raise ValueError(f'"{var}" not in global variables')
|
|
|
|
del self.var[var]
|
|
|
|
|
|
def updateEnv(self, data):
|
|
if not isinstance(data, dict):
|
|
raise ValueError(f'Environment data not a dict')
|
|
|
|
self.globals.update(data)
|
|
|
|
|
|
def addFilter(self, funct, name=None):
|
|
name = funct.__name__ if not name else name
|
|
self.filters[name] = funct
|
|
|
|
|
|
def delFilter(self, name):
|
|
if not self.filters.get(name):
|
|
raise valueError(f'"{name}" not in global filters')
|
|
|
|
del self.filters[name]
|
|
|
|
|
|
def updateFilter(self, data):
|
|
if not isinstance(context, dict):
|
|
raise ValueError(f'Filter data not a dict')
|
|
|
|
self.filters.update(data)
|
|
|
|
|
|
def render(self, tplfile, context={}, headers={}, cookies={}, request=None, pprint=False, **kwargs):
|
|
if not isinstance(context, dict):
|
|
raise TypeError(f'context for {tplfile} not a dict: {type(context)} {context}')
|
|
|
|
context['request'] = request if request else {'headers': headers, 'cookies': cookies}
|
|
|
|
if self.func_context:
|
|
context.update(self.func_context(DotDict(context), DotDict(self.globals)))
|
|
|
|
result = self.get_template(tplfile).render(context)
|
|
|
|
if pprint and any(map(tplfile.endswith, ['haml', 'html', 'xml'])):
|
|
return minidom.parseString(result).toprettyxml(indent=" ")
|
|
|
|
else:
|
|
return result
|
|
|
|
|
|
def response(self, request, tpl, ctype='text/html', status=200, **kwargs):
|
|
if not Response:
|
|
raise ModuleNotFoundError('Sanic is not installed')
|
|
|
|
html = self.render(tpl, request=request, **kwargs)
|
|
return Response.HTTPResponse(body=html, status=status, content_type=ctype, headers=kwargs.get('headers', {}))
|