from dataclasses import dataclass from typing import Any, Optional import datetime import re import os import jinja_markdown from flask import ( Flask, send_from_directory, render_template, make_response, url_for, render_template_string ) import sass from livereload import Server # Importing performs monkeypatching import xml_lexer # NOQA: F401 import ini_lexer # NOQA: F401 app = Flask(__name__) extensions = app.jinja_options.setdefault('extensions', []) extensions.append('jinja2_highlight.HighlightExtension') extensions.append('jinja_markdown.MarkdownExtension') jinja_markdown.EXTENSIONS.append("mdx_spantables") jinja_markdown.EXTENSIONS.append("toc") HTAG = re.compile(r"]*id=\"([^\"]+)\"[^>]*>([^<]*){part.name}' f'ASSY IDSEGA {part.id}' f'Description{part.description}' '' ) return f'{id_}' def generate_xrpc_list(): output = "" def generate_toc(base, name, route, start=1): parts = route.strip("/").split("/") toc = CONTENTS for i in PAGES_BASE.split("/")[1:]: toc = toc[i] if parts == [""]: parts = [] for i in parts: if i in toc: toc = toc[i] if isinstance(toc, tuple): toc = toc[1] if not isinstance(toc, dict): return "" else: return "" def walk(toc, path, start=1): unordered = len(toc) == 1 and 0 in toc out = f'<{"u" if unordered else "o"}l start="{start}">' for url in toc: if isinstance(toc[url], tuple): name, children = toc[url] elif isinstance(toc[url], str): name, children = toc[url], -1 else: raise ValueError out += "
  • " if isinstance(url, str): fqu = f"{ROOT}/{path}" if not url.startswith("#"): fqu += "/" fqu += url while "//" in fqu: fqu = fqu.replace("//", "/") if not fqu.endswith((".html", "/")) and "#" not in fqu: fqu += "/" out += f'{name}' else: out += name out += "
  • " if children == -1: continue if children is None: filename = "/".join((TEMPLATES, PAGES_BASE, path, url)) while "//" in filename: filename = filename.replace("//", "/") if url == "": filename += "index.html" if "." not in filename: filename += "/index.html" with open(filename) as page: headers = HTAG.findall(page.read()) children = {} for level, anchor, text in headers: if level in TOC_HTAG_LEVELS: children[f"#{anchor}"] = text if not children: children = None if children is not None: out += walk(children, f"{path}/{url}" if isinstance(url, str) else path) out += f'' return out return walk(toc, route, start) def generate_footer_links(base, name, route): parts = route.strip("/").split("/") if not parts: return {} toc = CONTENTS path = [] for i in parts[:-1]: if i in toc: path.append(i) toc = toc[i] if isinstance(toc, tuple): toc = toc[1] if not isinstance(toc, dict): toc = None break elif toc == CONTENTS: toc = toc[PAGES_BASE.partition("/")[2]] else: toc = None break if toc == CONTENTS and len(parts) == 1: assert toc is not None toc = toc[PAGES_BASE.partition("/")[2]] if toc is None: siblings = None us_idx = -1 parent = "" else: siblings = [i for i in toc.keys() if isinstance(i, str)] try: us_idx = siblings.index(parts[-1]) except ValueError: us_idx = -1 parent = ROOT + "/" + "/".join(parts[:-1]) if not parent.endswith("/"): parent += "/" links: dict[str, Any] = dict(footer_previous="", footer_crumbs=[], footer_current="", footer_next="") if siblings and us_idx > 0: links["footer_previous"] = parent + siblings[us_idx - 1] if parts: built = ROOT + "/" for i in parts[:-1]: built += f"{i}" if not built.endswith((".html", "/")): built += "/" links["footer_crumbs"].append((built, i)) links["footer_current"] = parts[-1] if siblings and us_idx < len(siblings) - 1 and us_idx != -1: links["footer_next"] = parent + siblings[us_idx + 1] return links def ioctl(original): original = eval(original) # Unsafe as hell def CTL_CODE(DeviceType, Function, Method, Access): return ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) deviceType = original >> 16 access = (original >> 14) & 0x3 function = (original >> 2) & 0xfff method = original & 0x3 assert hex(CTL_CODE(deviceType, function, method, access)) == hex(original) deviceType = hex(deviceType) if deviceType == "0x9c40": deviceType = "FILE_DEVICE_SEGA" function = hex(function) method = "METHOD_" + ["BUFFERED", "IN_DIRECT", "OUT_DIRECT", "NEITHER"][method] access = ["FILE_ANY_ACCESS", "FILE_READ_ACCESS", "FILE_WRITE_ACCESS"][access] return (f"CTL_CODE({deviceType}, {function}, {method}, {access})") @app.route("/tango.css") def tango(): return send_from_directory(".", "tango.css") @app.route("/headers.js") def header_script(): return send_from_directory(".", "headers.js") def install_static(): for i in STATIC: for base, _, files in os.walk(i): for name in files: def handler_factory(base, name): def handler(): return send_from_directory(base, name) return handler local_base = base.replace("\\", "/").strip(".").strip("/") route = local_base + "/" + name if not route.startswith("/"): route = "/" + route app.add_url_rule(route, route, handler_factory(base, name)) def handler_factory_html(base, name, route): def handler(): return render_template( os.path.join(base, name).strip("/").replace("\\", "/"), HOST=HOST, ROOT=ROOT, CANONICAL=ROOT + route, **generate_footer_links(base, name, route), generate_xrpc_list=generate_xrpc_list, generate_toc=lambda start=1: generate_toc(base, name, route, start), relative=lambda path: os.path.join(base, path).strip("/").replace("\\", "/"), part=part, ioctl=ioctl, ) return handler def handler_factory_markdown(base, name, route): md_name = name[:-5] + ".md" md_path = os.path.join(base, md_name).strip("/").replace("\\", "/") title = "Markdown Page" with open(os.path.join(TEMPLATES, md_path)) as md_f: for line in md_f: line = line.strip() if line.startswith("#") and not line.startswith("##"): title = line[1:].strip() template = ( f"{{% extends \"sega.html\" %}}{{% block title %}}{title}{{% endblock %}}" f"{{% block body %}}" f"{{% markdown %}}{{% include \"{md_path}\" %}}{{% endmarkdown %}}" f"{{% endblock %}}" ) def handler(): return render_template_string( template, HOST=HOST, ROOT=ROOT, CANONICAL=ROOT + route, **generate_footer_links(base, name, route), generate_xrpc_list=generate_xrpc_list, generate_toc=lambda start=1: generate_toc(base, name, route, start), relative=lambda path: os.path.join(base, path).strip("/").replace("\\", "/"), part=part, ioctl=ioctl, ) return handler def install_pages(): for base, _, files in os.walk(TEMPLATES + "/" + PAGES_BASE): if ".git" in base: continue if base.startswith(TEMPLATES): base = base[len(TEMPLATES):] for name in files: handler_factory = None if name.endswith(".html"): handler_factory = handler_factory_html elif name.endswith(".md") and not name.startswith("~"): handler_factory = handler_factory_markdown name = name[:-3] + ".html" if handler_factory is not None: local_base = base.replace("\\", "/").strip(".").strip("/") if local_base.startswith(PAGES_BASE): local_base = local_base[len(PAGES_BASE):] if name.endswith(".md"): route = local_base + "/" + name[:-3] + ".html" else: route = local_base + "/" + name if route.endswith("/index.html"): route = route[:-10] if not route.startswith("/"): route = "/" + route app.add_url_rule(route, route, handler_factory(base, name, route)) @app.route("/sitemap.xml") def sitemap(): host_base = HOST + ROOT links = [] for rule in app.url_map.iter_rules(): if (not rule.methods or "GET" in rule.methods) and len(rule.arguments) == 0: url = url_for(rule.endpoint, **(rule.defaults or {})) if not url.endswith(("/", ".html", ".png")): continue path = rule.endpoint if path.endswith("/"): path += "index.html" path = os.path.join(TEMPLATES, PAGES_BASE, path.lstrip("/")) if os.path.exists(path): mod_time = os.path.getmtime(path) mod_time = datetime.datetime.fromtimestamp(mod_time).strftime("%Y-%m-%dT%H:%M:%SZ") else: mod_time = None links.append((host_base + url, mod_time)) response = make_response(render_template("sitemap.xml", urls=links[::-1])) response.headers["Content-Type"] = "application/xml" return response def compile_sass(): with open("static/main.css", "w") as main_css: main_css.write(sass.compile(filename="main.scss", output_style="compressed")) # type: ignore install_static() install_pages() compile_sass() def run_dev(): app.config['TEMPLATES_AUTO_RELOAD'] = True app.config['DEBUG'] = True # app.run(debug=True, port=3000, host="0.0.0.0") server = Server(app.wsgi_app) server.watch(".") server.watch("main.scss", func=compile_sass) server.watch("styles", func=compile_sass) server.serve(port=3000) if __name__ == '__main__': run_dev()