uncia/uncia/Lib/IzzyLib/template.py
2020-10-10 23:51:55 -04:00

191 lines
4.9 KiB
Python

'''functions for web template management and rendering'''
import codecs, traceback, os, json, aiohttp, xml
from os import listdir, makedirs
from os.path import isfile, isdir, getmtime, abspath
from jinja2 import Environment, FileSystemLoader, ChoiceLoader, select_autoescape, Markup
from sanic import response as Response
from hamlpy.hamlpy import Compiler
from markdown import markdown
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from . import logging
from .color import *
class Template(Environment):
def __init__(self, build={}, search=[], global_vars={}, autoescape=None):
self.autoescape = ['html', 'css'] if not autoescape else autoescape
self.search = []
self.build = {}
for source, dest in build.items():
self.__addBuildPath(source, dest)
for path in search:
self.__addSearchPath(path)
self.var = {
'markdown': markdown,
'markup': Markup,
'cleanhtml': remove_tags,
'lighten': lighten,
'darken': darken,
'saturate': saturate,
'desaturate': desaturate,
'rgba': rgba
}
self.var.update(global_vars)
super().__init__(
loader=ChoiceLoader([FileSystemLoader(path) for path in self.search]),
autoescape=select_autoescape(self.autoescape),
lstrip_blocks=True,
trim_blocks=True
)
def __addSearchPath(self, path):
tplPath = abspath(str(path))
if tplPath not in self.search:
self.search.append(tplPath)
def __addBuildPath(self, source, destination):
src = abspath(str(source))
dest = abspath(str(destination))
if not isdir(src):
raise FileNotFoundError('Source path doesn\'t exist: {src}')
self.build[src] = dest
self.__addSearchPath(dest)
def addEnv(self, k, v):
self.var[k] = v
def delEnv(self, var):
if not self.var.get(var):
raise ValueError(f'"{var}" not in global variables')
del self.var[var]
def render(self, tplfile, context, request=None, headers={}, cookies={}, **kwargs):
if not isinstance(context, dict):
raise TypeError(f'context for {tplfile} not a dict')
data = global_variables.copy()
data['request'] = request if request else {'headers': headers, 'cookies': cookies}
data.update(context)
return env.get_template(tplfile).render(data)
def response(self, *args, ctype='text/html', status=200, headers={}, **kwargs):
html = self.render(*args, **kwargs)
return Response.HTTPResponse(body=html, status=status, content_type=ctype, headers=headers)
def buildTemplates(self, src=None):
paths = {src: self.search.get(src)} if src else self.search
for src, dest in paths.items():
timefile = f'{dest}/times.json'
updated = False
if not isdir(f'{dest}'):
makedirs(f'{dest}')
if isfile(timefile):
try:
times = json.load(open(timefile))
except:
times = {}
else:
times = {}
for filename in listdir(src):
fullPath = f'{src}/{filename}'
modtime = getmtime(fullPath)
base, ext = filename.split('.', 1)
if ext != 'haml':
pass
elif base not in times or times.get(base) != modtime:
updated = True
logging.verbose(f"Template '{filename}' was changed. Building...")
try:
destination = f'{dest}/{base}.html'
haml_lines = codecs.open(fullPath, 'r', encoding='utf-8').read().splitlines()
compiler = Compiler()
output = compiler.process_lines(haml_lines)
outfile = codecs.open(destination, 'w', encoding='utf-8')
outfile.write(output)
logging.info(f"Template '{filename}' has been built")
except Exception as e:
'''I'm actually not sure what sort of errors can happen here, so generic catch-all for now'''
traceback.print_exc()
logging.error(f'Failed to build {filename}: {e}')
times[base] = modtime
if updated:
with open(timefile, 'w') as filename:
filename.write(json.dumps(times))
def remove_tags(self, text):
return ''.join(xml.etree.ElementTree.fromstring(text).itertext())
def setupWatcher(self):
watchPaths = [path['source'] for k, path in build_path_pairs.items()]
logging.info('Starting template watcher')
observer = Observer()
for tplpath in watchPaths:
logging.debug(f'Watching template dir for changes: {tplpath}')
observer.schedule(templateWatchHandler(), tplpath, recursive=True)
self.watcher = observer
def startWatcher(self):
if not self.watcher:
self.setupWatcher()
self.watcher.start()
def stopWatcher(self, destroy=False):
self.watcher.stop()
if destroy:
self.watcher = None
class TemplateWatchHandler(FileSystemEventHandler):
def on_any_event(self, event):
filename, ext = os.path.splitext(os.path.relpath(event.src_path))
if event.event_type in ['modified', 'created'] and ext[1:] == 'haml':
logging.info('Rebuilding templates')
buildTemplates()
__all__ = ['addSearchPath', 'delSearchPath', 'addBuildPath', 'delSearchPath', 'addEnv', 'delEnv', 'setup', 'renderTemplate', 'aiohttp', 'buildTemplates', 'templateWatcher']