diff --git a/Server/MarkdownPreviewer/server.py b/Server/MarkdownPreviewer/server.py index 2fbd571..5c53db3 100644 --- a/Server/MarkdownPreviewer/server.py +++ b/Server/MarkdownPreviewer/server.py @@ -10,6 +10,8 @@ import re import tempfile import subprocess import os +import hashlib +from pathlib import Path import time from datetime import datetime @@ -17,87 +19,9 @@ from datetime import datetime from .render_pipeline import RenderPipeline, MultiCallback, CallbackClass from .pandoc import Pandoc, Div, Span, Header, Attr, Plain, PString, Inline, Image -blockquote_re = re.compile('!\\[([a-z]+)\\]') - -panel_content = Attr("", classes=["panel-content"]) -header_attr = Attr("", classes=["panel-title"]) -warning_icon = Attr("", classes=["fas", "fa-exclamation-triangle", "panel-icon"]) -information_icon = Attr("", classes=["fas", "fa-info-circle", "panel-icon"]) -header_title_attr = Attr("", classes=["panel-header"]) -outer_attr = Attr("", classes=["panel", "panel-warning"]) -outer_info_attr = Attr("", classes=["panel", "panel-info"]) - -def blockquote_filter(block): - - data = block['c'] - - if data[0]['t'] != 'Para': - return None - - paragraph_content = data[0]['c'] - if paragraph_content[0]['t'] != 'Str': - return None - - string_content = paragraph_content[0]['c'] - - m = blockquote_re.match(string_content) - - if m is None: - return None - - return m.group(1) - -def create_warning(content): - - internal_content = content['c'] - - internal_content[0]['c'].pop(0) - internal_content[0]['c'].pop(0) - - content_div = Div(panel_content, internal_content).toJson() - label = PString("Warning").toJson() - header = Header(header_attr, 3, [label]).toJson() - icon = Plain(Span(warning_icon, []).toJson()).toJson() - - header_div = Div(header_title_attr, - [ - icon, - header - ]).toJson() - - outer_div = Div(outer_attr, - [ - header_div, - content_div - ]).toJson() - - return outer_div - -def create_information(content): - - internal_content = content['c'] - - internal_content[0]['c'].pop(0) - internal_content[0]['c'].pop(0) - - content_div = Div(panel_content, internal_content).toJson() - label = PString("Information").toJson() - header = Header(header_attr, 3, [label]).toJson() - icon = Plain(Span(information_icon, []).toJson()).toJson() - - header_div = Div(header_title_attr, - [ - icon, - header - ]).toJson() - - outer_div = Div(outer_info_attr, - [ - header_div, - content_div - ]).toJson() - - return outer_div +from .admonition import blockquote_filter, create_warning, create_information, Theorem, Proof +from .penrose import Penrose +from .image import ImageFilter def parse_title(content): if content['t'] == 'MetaString': @@ -138,108 +62,6 @@ def parse_date(content): Publisher.publish("date", data) -class Theorem(CallbackClass): - def __init__(self): - self.counter = 1 - - def __call__(self, content): - - internal_content = content['c'] - - internal_content[0]['c'].pop(0) - internal_content[0]['c'].pop(0) - - outer_div_attr = Attr("", classes=["theorem"]) - inner_div_attr = Attr("", classes=["theorem-content"]) - bold_attr = Attr("", classes=["theorem-title"]) - - span_attr = Attr("") - - theorem_string = "Theorem {}. ".format(self.counter) - - title_content = Inline("Emph", [PString(theorem_string).toJson()]).toJson() - title = Span(span_attr, [title_content]).toJson() - - internal_content[0]['c'].insert(0, title) - - content_div = Div(inner_div_attr, internal_content).toJson() - outer_div = Div(outer_div_attr, [content_div]).toJson() - - self.counter += 1 - - return outer_div - - def clear(self): - self.counter = 1 - -class Proof(CallbackClass): - def __init__(self): - self.counter = 1 - - def clear(self): - self.counter = 1 - - def __call__(self, content): - internal_content = content['c'] - - internal_content[0]['c'].pop(0) - internal_content[0]['c'].pop(0) - - outer_div_attr = Attr("", classes=["proof"]) - inner_div_attr = Attr("", classes=["proof-content"]) - span_attr = Attr("", classes=["proof-title"]) - qed_attr = Attr("", classes=["proof-qed"]) - - proof_string = "Proof {}. ".format(self.counter) - - title_content = Span(span_attr, [PString(proof_string).toJson()]).toJson() - title = Inline("Emph", [title_content]).toJson() - - internal_content[0]['c'].insert(0, title) - qed_string = Plain(PString("◻").toJson()).toJson() - qed = Div(qed_attr, [qed_string]).toJson() - - inner_div = Div(inner_div_attr, internal_content).toJson() - - outer_div = Div(outer_div_attr, [inner_div, qed]).toJson() - - return outer_div - -class Penrose(CallbackClass): - def __init__(self, base_path): - self.data_path = base_path + "/data/penrose/" - - def run(self, input_file_name, domain, style): - - domain_path = self.data_path + domain + ".domain" - style_path = self.data_path + domain + ".style" - - return subprocess.run( - ["roger", "trio", input_file_name, domain_path, style_path, '--path', "/"], - text=True, - capture_output=True) - - def __call__(self, content): - handle, file_path = tempfile.mkstemp(suffix=".substance", text=True) - text = content['c'][1] - - with os.fdopen(handle, 'w') as f: - f.write(text) - - data = self.run(file_path, "set", "set") - - handle, file_path = tempfile.mkstemp(suffix=".svg", text=True) - - with os.fdopen(handle, 'w') as f: - f.write(data.stdout) - - img_attr = Attr("") - - new_content = Image(img_attr, [{'t' : 'Str', 'c' : 'Penrose'}], "/generated/{}".format(file_path[5:])).toJson() - wrapper = Plain(new_content).toJson() - - return wrapper - class Publisher: topics = dict() @@ -346,13 +168,45 @@ def on_stdin(fd, pipeline): else: Publisher.publish(key, value) +class RuntimeImageHandler(StaticFileHandler): + + base_path = None + + @classmethod + def set_base_path(cls, path): + p = Path(path) + if not p.is_dir(): + cls.base_path = p.parent + else: + cls.base_path = p + + @classmethod + def get_absolute_path(cls, root, path): + if cls.base_path: + p = (cls.base_path / Path(path)).resolve() + else: + p = (Path(root) / Path(path)).resolve() + + return str(p) + + def validate_absolute_path(self, root, absolute_path): + p = Path(absolute_path) + + if not p.exists(): + raise HTTPError(404) + if not p.isfile(): + raise HTTPError(403, "%s is not a file", self.path) + + return absolute_path + def route_handler(base_path, loader): return [ (r"/", MainBodyHandler, {"loader" : loader}), (r"/ws", PushPull), (r"/css/(.*)", StaticFileHandler, {"path" : r"{}/data/css".format(base_path)}), (r"/lib/(.*)", StaticFileHandler, {"path" : r"{}/data/lib".format(base_path)}), - (r"/generated/(.*)", StaticFileHandler, {"path" : r"/tmp"}) + (r"/generated/(.*)", StaticFileHandler, {"path" : r"/tmp"}), + (r"/(.*)", RuntimeImageHandler, {"path": r""}) ] def codeblock_filter(block): @@ -398,5 +252,7 @@ async def main(base_path): Publisher.subscribe("PushPull", "course", PushPull.update_course) Publisher.subscribe("PushPull", "date", PushPull.update_date) Publisher.subscribe("PushPull", "scroll", PushPull.update_scroll) + Publisher.subscribe("MainBodyHandler", "base", MainBodyHandler.update_title) + Publisher.subscribe("RuntimeImageHandler", "base", RuntimeImageHandler.set_base_path) application.listen(8888) await asyncio.Event().wait()