206 lines
4.8 KiB
Python
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']
|