docs/docs.py

397 lines
12 KiB
Python
Raw Normal View History

2022-04-24 22:35:53 +00:00
from dataclasses import dataclass
2021-12-29 01:18:06 +00:00
import datetime
2021-12-28 22:29:33 +00:00
import re
2021-12-28 20:54:12 +00:00
import os
2023-03-27 19:47:33 +00:00
import jinja_markdown
2022-04-11 18:27:15 +00:00
from flask import Flask, send_from_directory, render_template, make_response, url_for
2021-12-29 01:18:06 +00:00
from livereload import Server
2022-04-11 18:27:15 +00:00
# Importing performs monkeypatching
import xml_lexer # NOQA: F401
2022-06-27 22:09:04 +00:00
import ini_lexer # NOQA: F401
2021-12-29 01:18:06 +00:00
2021-12-28 22:29:33 +00:00
2021-12-28 20:54:12 +00:00
app = Flask(__name__)
2023-03-27 19:47:33 +00:00
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")
2021-12-28 22:29:33 +00:00
2022-04-11 18:27:15 +00:00
HTAG = re.compile(r"<h(\d)[^>]*id=\"([^\"]+)\"[^>]*>([^<]*)</h")
TOC_HTAG_LEVELS = {"1", "2"}
HOST = "https://bsnk.me"
2021-12-28 22:29:33 +00:00
TEMPLATES = "templates"
PAGES_BASE = "pages"
2022-06-13 04:48:39 +00:00
STATIC = ["images", "static", "mice"]
2021-12-28 22:29:33 +00:00
ROOT = os.environ.get("EA_ROOT", "")
2022-04-11 18:27:15 +00:00
EAMUSE_CONTENTS = {
"getting_started.html": ("Getting started and following along", {
0: ("A quick one-stop shop for getting setup with the tools you'll want on hand if you want to investigate "
+ "things for yourself.")
}),
"transport.html": ("Transport layer", None),
"packet.html": ("The inner packet structure", None),
"protocol.html": ("Communication protocol details", {
0: ("There are a crazy number of sub pages here, so just go check the contents there.")
}),
"server.html": "Let's write a server",
0: ("Misc pages", {
"cardid.html": ("Parsing and converting card IDs", ())
}),
}
SEGA_CONTENTS = {
"intro.html": ("Introduction to RingEdge 2", ()),
2022-11-18 12:08:48 +00:00
"hardware": ("Hardware", {
2022-04-24 22:35:53 +00:00
"jvs.html": "JVS",
2022-05-19 00:50:12 +00:00
"touch.html": "Touchscreen",
2022-11-18 12:08:48 +00:00
}),
"software": ("Software", {
"pcp": ("PCP", {"libpcp.html": "libpcp"}),
2022-06-13 04:48:39 +00:00
"drivers": ("Device drivers", None),
2023-02-17 09:37:51 +00:00
"security": ("Security", {
"alphadvd.html": "AlphaDVD",
}),
2022-06-27 22:09:04 +00:00
"groovemaster.html": "GrooveMaster.ini",
2022-04-11 18:27:15 +00:00
}),
2022-12-09 10:55:32 +00:00
"manual": ("Manual", {
2022-12-09 15:01:46 +00:00
"errors.html": "Error Codes",
"keychip.html": "Keychip Modding",
2022-12-09 10:55:32 +00:00
}),
2022-11-18 04:49:39 +00:00
# "network": ("Networking", {
# "allnet.html": "ALL.Net"
# }),
2022-11-27 22:29:36 +00:00
"misc": ("Misc", {
"partition.html": "SEGA Partition Structure"
}),
2022-04-11 18:27:15 +00:00
}
CONTENTS = {
"": EAMUSE_CONTENTS,
"sega": SEGA_CONTENTS
}
2021-12-28 22:29:33 +00:00
2022-04-24 22:35:53 +00:00
@dataclass
class Part:
id: str
name: str
description: str = None
page: str = None
PARTS = {
2022-05-19 00:50:12 +00:00
"838-14971": Part("838-14971", "Aime NFC daughter board", "NFC RW BD TN32MSEC003S"),
2022-06-27 22:09:04 +00:00
"838-15221": Part("838-15221", "Serial I/F BD Touchpanel Gunze",
"The interface board for the touchscreen on MaiMai versions pre-DX.")
2022-04-24 22:35:53 +00:00
}
2022-06-27 22:09:04 +00:00
def part(id_):
if (part := PARTS.get(id_)):
return (
f'<span class="part" tabindex="0">{part.name}<span>'
f'<span>ASSY ID</span><span>SEGA {part.id}</span>'
f'<span>Description</span><span>{part.description}</span>'
'</span></span>'
)
return f'<span class="part">{id_}</span>'
2022-04-24 22:35:53 +00:00
2021-12-28 22:29:33 +00:00
def generate_xrpc_list():
output = "<ul>"
proto = TEMPLATES + "/" + PAGES_BASE + "/proto"
for base, _, files in os.walk(proto):
prefix = base[len(proto):].replace("\\", "/").strip("/")
if prefix:
prefix = prefix.replace("/", ".") + "."
for i in files:
2023-03-27 19:47:33 +00:00
if i.startswith("~"):
continue
2021-12-28 22:29:33 +00:00
delim = "_" if prefix else "."
href = f"{ROOT}/proto{base[len(proto):]}/{i}"
output += f"<li><code><a href=\"{href}\">"
output += prefix + i.replace(".html", delim + "%s")
output += "</code></a></li>"
with open(os.path.join(base, i)) as f:
headers = re.findall('<h2 id="([^"]*?)">', f.read())
output += "<ul>"
for j in headers:
output += f"<li><code><a href=\"{href}#{j}\">"
output += prefix + i.replace(".html", delim + j)
output += "</code></a></li>"
output += "</ul>"
return output + "</ul>"
2021-12-28 20:54:12 +00:00
2022-04-11 18:27:15 +00:00
def generate_toc(base, name, route, start=1):
parts = route.strip("/").split("/")
toc = CONTENTS
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
out += "<li>"
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'<a href="{fqu}">{name}</a>'
else:
out += name
out += "</li>"
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"
2022-04-25 00:47:34 +00:00
if "." not in filename:
filename += "/index.html"
2022-04-11 18:27:15 +00:00
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'</{"u" if unordered else "o"}l>'
return out
return walk(toc, route, start)
def generate_footer(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[""]
else:
toc = None
break
if toc == CONTENTS and len(parts) == 1:
toc = toc[""]
if toc is None:
siblings = None
else:
siblings = [i for i in toc.keys() if isinstance(i, str)]
try:
us_idx = siblings.index(parts[-1])
except ValueError:
us_idx = -1
2022-04-11 18:49:43 +00:00
parent = ROOT + "/" + "/".join(parts[:-1])
2022-04-11 18:27:15 +00:00
if not parent.endswith("/"):
parent += "/"
footer = "<footer><span>"
if siblings and us_idx > 0:
footer += f'<a href="{parent}{siblings[us_idx - 1]}">Previous page</a>'
footer += "</span><span>"
if parts:
crumbs = []
2022-04-11 18:49:43 +00:00
built = ROOT + "/"
2022-04-11 18:27:15 +00:00
for i in parts[:-1]:
built += f"{i}"
if not built.endswith((".html", "/")):
built += "/"
crumbs.append(f'<a href="{built}">{i}</a>')
crumbs.append(parts[-1])
footer += "/".join(crumbs)
footer += "</span><span>"
if siblings and us_idx < len(siblings) - 1 and us_idx != -1:
footer += f'<a href="{parent}{siblings[us_idx + 1]}">Next page</a>'
footer += "</span></footer>"
return footer
2022-04-25 00:47:34 +00:00
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})")
2021-12-28 20:54:12 +00:00
@app.route("/styles.css")
def styles():
return send_from_directory(".", "styles.css")
2022-04-11 18:27:15 +00:00
2021-12-28 22:29:33 +00:00
@app.route("/tango.css")
def tango():
return send_from_directory(".", "tango.css")
2022-04-11 18:27:15 +00:00
2021-12-29 01:55:43 +00:00
@app.route("/headers.js")
def header_script():
return send_from_directory(".", "headers.js")
2021-12-28 20:54:12 +00:00
2021-12-28 22:29:33 +00:00
for i in STATIC:
for base, _, files in os.walk(i):
for name in files:
def handler(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
2021-12-28 20:54:12 +00:00
2021-12-28 22:29:33 +00:00
handler = handler(base, name)
handler.__name__ == route
app.add_url_rule(route, route, handler)
for base, _, files in os.walk(TEMPLATES + "/" + PAGES_BASE):
2021-12-28 20:54:12 +00:00
if ".git" in base:
continue
if base.startswith(TEMPLATES):
base = base[len(TEMPLATES):]
for name in files:
if name.endswith(".html"):
2022-04-11 18:27:15 +00:00
def handler(base, name, route):
2021-12-28 20:54:12 +00:00
def handler():
2021-12-28 22:29:33 +00:00
return render_template(
os.path.join(base, name).strip("/").replace("\\", "/"),
ROOT=ROOT,
2022-11-18 12:08:48 +00:00
CANONICAL=ROOT + route,
2022-04-11 18:27:15 +00:00
generate_xrpc_list=generate_xrpc_list,
generate_toc=lambda start=1: generate_toc(base, name, route, start),
generate_footer=lambda: generate_footer(base, name, route),
2023-03-27 19:47:33 +00:00
relative=lambda path: os.path.join(base, path).strip("/").replace("\\", "/"),
2022-04-24 22:35:53 +00:00
part=part,
2022-04-25 00:47:34 +00:00
ioctl=ioctl,
2021-12-28 22:29:33 +00:00
)
2021-12-28 20:54:12 +00:00
return handler
local_base = base.replace("\\", "/").strip(".").strip("/")
if local_base.startswith(PAGES_BASE):
local_base = local_base[len(PAGES_BASE):]
route = local_base + "/" + name
if route.endswith("/index.html"):
route = route[:-10]
if not route.startswith("/"):
route = "/" + route
2022-04-11 18:27:15 +00:00
handler = handler(base, name, route)
2021-12-28 20:54:12 +00:00
handler.__name__ == route
app.add_url_rule(route, route, handler)
2021-12-29 01:18:06 +00:00
@app.route("/sitemap.xml")
def sitemap():
2022-04-11 18:27:15 +00:00
host_base = HOST + ROOT
2021-12-29 01:18:06 +00:00
links = []
for rule in app.url_map.iter_rules():
if "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))
2021-12-29 01:23:43 +00:00
response = make_response(render_template("sitemap.xml", urls=links[::-1]))
2021-12-29 01:18:06 +00:00
response.headers["Content-Type"] = "application/xml"
return response
2021-12-28 20:54:12 +00:00
2022-04-11 18:27:15 +00:00
2022-06-27 22:09:04 +00:00
def run_dev():
2021-12-28 20:54:12 +00:00
app.config['TEMPLATES_AUTO_RELOAD'] = True
app.config['DEBUG'] = True
2021-12-29 01:41:21 +00:00
# app.run(debug=True, port=3000, host="0.0.0.0")
2021-12-28 22:29:33 +00:00
2021-12-29 01:41:21 +00:00
server = Server(app.wsgi_app)
server.watch(".")
2022-04-11 18:27:15 +00:00
server.serve(port=3000)
2022-06-27 22:09:04 +00:00
if __name__ == '__main__':
run_dev()