izzylib/IzzyLib/template.py

206 lines
4.8 KiB
Python

'''functions for web template management and rendering'''
import codecs, traceback, os, json, aiohttp
from os import listdir, makedirs
from os.path import isfile, isdir, getmtime, abspath
from jinja2 import Environment, FileSystemLoader, ChoiceLoader, select_autoescape
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 *
env = None
global_variables = {
'markdown': markdown,
'lighten': lighten,
'darken': darken,
'saturate': saturate,
'desaturate': desaturate,
'rgba': rgba
}
search_path = list()
build_path_pairs = dict()
def addSearchPath(path):
tplPath = abspath(path)
if tplPath not in search_path:
search_path.append(tplPath)
def delSearchPath(path):
tplPath = abspath(path)
if tplPath in search_path:
search_path.remove(tplPath)
def addBuildPath(name, source, destination):
src = abspath(source)
dest = abspath(destination)
if not isdir(src):
raise FileNotFoundError('Source path doesn\'t exist: {src}')
build_path_pairs.update({
name: {
'source': src,
'destination': dest
}
})
addSearchPath(dest)
def delBuildPath(name):
if not build_path_pairs.get(name):
raise ValueError(f'"{name}" not in build paths')
del build_path_pairs[src]
def getBuildPath(name=None):
template = build_path_pairs.get(name)
if name:
if template:
return template
else:
raise ValueError(f'"{name}" not in build paths')
return build_path_pairs
def addEnv(data):
if not isinstance(data, dict):
raise TypeError(f'environment data is not a dict')
global_variables.update(data)
def delEnv(var):
if not global_variables.get(var):
raise ValueError(f'"{var}" not in global variables')
del global_variables[var]
def setup():
global env
env = Environment(
loader=ChoiceLoader([FileSystemLoader(path) for path in search_path]),
autoescape=select_autoescape(['html', 'css']),
lstrip_blocks=True,
trim_blocks=True
)
def renderTemplate(tplfile, context, request=None, headers=dict(), cookies=dict(), **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 aiohttpTemplate(*args, **kwargs):
ctype = kwargs.get('content_type', 'text/html')
status = kwargs.get('status', 200)
html = renderTemplate(*args, **kwargs)
return aiohttp.web.Response(body=html, status=status, content_type=ctype)
def buildTemplates(name=None):
paths = getBuildPath(name)
for k, tplPaths in paths.items():
src = tplPaths['source']
dest = tplPaths['destination']
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 templateWatcher():
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=False)
observer.start()
return observer
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']