izzylib/IzzyLib/template.py

234 lines
5.3 KiB
Python

'''functions for web template management and rendering'''
import codecs, traceback, os, json
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 *
framework = 'sanic'
try:
import sanic
except:
logging.debug('Cannot find Sanic')
try:
import aiohttp
except:
logging.debug('Cannot find aioHTTP')
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 not isdir(tplPath):
raise FileNotFoundError(f'Cannot find template directory: {tplPath}')
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(f'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(fwork='sanic'):
global env
global framework
framework = fwork
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 sendResponse(template, request, context=dict(), status=200, ctype='text/html', headers=dict(), **kwargs):
context['request'] = request
html = renderTemplate(template, context, **kwargs)
if framework == 'sanic':
return sanic.response.text(html, status=status, headers=headers, content_type=ctype)
elif framework == 'aiohttp':
return aiohttp.web.Response(body=html, status=status, headers=headers, content_type=ctype)
else:
logging.error('Please install aiohttp or sanic. Response not sent.')
# delete me later
aiohttpTemplate = sendResponse
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=True)
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', 'sendResponse', 'buildTemplates', 'templateWatcher']