# -*- coding: utf-8 -*- import contextlib import csv import datetime import hashlib import io import os import re import subprocess as SP import sys import tempfile from functools import partial import panflute as pf from dateutil.parser import parse as dateparse from jinja2 import Environment, PackageLoader, Template, select_autoescape from panflute import * def remove_pound(elem, doc): if type(elem) == Str: return Str(elem.text.lstrip("#")) def fix_color(elem, doc): if type(elem) == MetaMap: for k in elem.content: if k.endswith("-color"): elem[k] = elem[k].walk(remove_pound) def update_date(elem, doc): if type(elem) == MetaMap: datefmt = doc.get_metadata("datefmt", "%Y-%m-%d") today = datetime.date.today().strftime(datefmt) date = dateparse(doc.get_metadata("date", today)).date() elem["date"] = MetaInlines(Str(date.strftime(datefmt))) return elem def csv_table(elem, doc): if type(elem) == Para and len(elem.content) == 1 and type(elem.content[0]) == Image: elem = elem.content[0] ext = os.path.splitext(elem.url)[1][1:] if ext == "csv": caption = elem.content has_header = elem.attributes.get("has-header", "false").lower() == "true" with open(elem.url) as f: reader = csv.reader(f) body = [] for row in reader: cells = [TableCell(Plain(Str(x))) for x in row] body.append(TableRow(*cells)) header = body.pop(0) if has_header else None ret = Table(*body, header=header, caption=caption) return ret def code_refs(elem, doc): if type(elem) == Cite: label = elem.content[0] if type(label) == Str: label = label.text filename = re.findall(r"^\[@lst:(.*)\]$", label) or [None] if filename[0] in doc.inc_files: return [ RawInline( "\\hyperref[{}]{{{}}}".format(filename[0], filename[0]), format="tex", ) ] def include_code(elem, doc): if type(elem) == CodeBlock: if "include" in elem.attributes: filepath = elem.attributes.pop("include") filename = os.path.split(filepath)[-1] try: elem.text += elem.text + open(filepath, encoding="utf-8").read() elem.attributes["caption"] = filename doc.inc_files.append(filename) except Exception as e: elem.text += "Error: {}".format(e) return [RawBlock("\\label{{{}}}".format(filename), format="tex"), elem] def py_eval(options, data, element, doc): out_buffer = io.StringIO() with contextlib.redirect_stdout(out_buffer): exec(data, doc.pyenv) out_buffer.seek(0) return convert_text(out_buffer.read()) def jinja_py_filt(doc, file): env = {} code = open(file, encoding="utf-8").read() exec(code, env) return env["main"](doc) def prepare(doc): doc.inc_files = [] doc.env = Environment() doc.pyenv = {} filters = {"py": partial(jinja_py_filt, doc)} doc.env.filters.update(filters) def process_templates(elem, doc): if type(elem) == CodeBlock: if elem.classes == ["@"]: args = {"meta": doc.get_metadata()} return convert_text(doc.env.from_string(elem.text).render(args)) def yaml_filt(elem, doc): tags = {"eval": py_eval} return yaml_filter(elem, doc, tags=tags, strict_yaml=True) def checkboxes(elem, doc): if type(elem) in [Para, Plain]: val = re.findall(r"^\[([xX]|\ )\] (.*)$", stringify(elem)) if val: val = val[0][0].lower() == "x" else: return elem marker = { True: RawInline("$\\boxtimes$", format="latex"), False: RawInline("$\\square$", format="latex"), }[val] cont = [] if val: cont += elem.content[2:] else: cont += elem.content[4:] return Plain(marker, Space, *cont) return elem def main(doc=None): f = [ process_templates, update_date, csv_table, include_code, fix_color, code_refs, yaml_filt, checkboxes, ] return run_filters(f, prepare=prepare, doc=doc) if __name__ == "__main__": main()