Conversion of User Guide to the SHH stack (#2781)
This commit is contained in:
0
guide/webapp/worker/__init__.py
Normal file
0
guide/webapp/worker/__init__.py
Normal file
14
guide/webapp/worker/config.py
Normal file
14
guide/webapp/worker/config.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from pathlib import Path
|
||||
|
||||
from msgspec import yaml
|
||||
from webapp.display.layouts.models import GeneralConfig, MenuItem
|
||||
|
||||
|
||||
def load_menu(path: Path) -> list[MenuItem]:
|
||||
loaded = yaml.decode(path.read_bytes(), type=dict[str, list[MenuItem]])
|
||||
return loaded["root"]
|
||||
|
||||
|
||||
def load_config(path: Path) -> GeneralConfig:
|
||||
loaded = yaml.decode(path.read_bytes(), type=GeneralConfig)
|
||||
return loaded
|
||||
73
guide/webapp/worker/factory.py
Normal file
73
guide/webapp/worker/factory.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from pathlib import Path
|
||||
|
||||
from webapp.display.layouts.models import MenuItem
|
||||
from webapp.display.page import Page, PageRenderer
|
||||
from webapp.endpoint.view import bp
|
||||
from webapp.worker.config import load_config, load_menu
|
||||
from webapp.worker.reload import setup_livereload
|
||||
from webapp.worker.style import setup_style
|
||||
|
||||
from sanic import Request, Sanic, html, redirect
|
||||
|
||||
|
||||
def _compile_sidebar_order(items: list[MenuItem]) -> list[str]:
|
||||
order = []
|
||||
for item in items:
|
||||
if item.path:
|
||||
order.append(item.path.removesuffix(".html") + ".md")
|
||||
if item.items:
|
||||
order.extend(_compile_sidebar_order(item.items))
|
||||
return order
|
||||
|
||||
|
||||
def create_app(root: Path) -> Sanic:
|
||||
app = Sanic("Documentation")
|
||||
app.config.PUBLIC_DIR = root / "public"
|
||||
app.config.CONTENT_DIR = root / "content"
|
||||
app.config.CONFIG_DIR = root / "config"
|
||||
app.config.STYLE_DIR = root / "style"
|
||||
app.config.NODE_MODULES_DIR = root / "node_modules"
|
||||
app.config.LANGUAGES = ["en"]
|
||||
app.config.SIDEBAR = load_menu(
|
||||
app.config.CONFIG_DIR / "en" / "sidebar.yaml"
|
||||
)
|
||||
app.config.NAVBAR = load_menu(app.config.CONFIG_DIR / "en" / "navbar.yaml")
|
||||
app.config.GENERAL = load_config(
|
||||
app.config.CONFIG_DIR / "en" / "general.yaml"
|
||||
)
|
||||
|
||||
setup_livereload(app)
|
||||
setup_style(app)
|
||||
app.blueprint(bp)
|
||||
|
||||
app.static("/assets/", app.config.PUBLIC_DIR / "assets")
|
||||
|
||||
@app.before_server_start
|
||||
async def setup(app: Sanic):
|
||||
app.ext.dependency(PageRenderer(base_title="TestApp"))
|
||||
page_order = _compile_sidebar_order(app.config.SIDEBAR)
|
||||
app.ctx.pages = Page.load_pages(app.config.CONTENT_DIR, page_order)
|
||||
app.ctx.get_page = Page.get
|
||||
|
||||
@app.get("/", name="root")
|
||||
@app.get("/index.html", name="index")
|
||||
async def index(request: Request):
|
||||
return redirect(request.app.url_for("page", language="en", path=""))
|
||||
|
||||
@app.get("/<language:str>", name="page-without-path")
|
||||
@app.get("/<language:str>/<path:path>")
|
||||
async def page(
|
||||
request: Request,
|
||||
page_renderer: PageRenderer,
|
||||
language: str,
|
||||
path: str = "",
|
||||
):
|
||||
return html(page_renderer.render(request, language, path))
|
||||
|
||||
@app.on_request
|
||||
async def set_language(request: Request):
|
||||
request.ctx.language = request.match_info.get(
|
||||
"language", Page.DEFAULT_LANGUAGE
|
||||
)
|
||||
|
||||
return app
|
||||
3795
guide/webapp/worker/livereload.js
Normal file
3795
guide/webapp/worker/livereload.js
Normal file
File diff suppressed because it is too large
Load Diff
115
guide/webapp/worker/reload.py
Normal file
115
guide/webapp/worker/reload.py
Normal file
@@ -0,0 +1,115 @@
|
||||
from asyncio import sleep
|
||||
from multiprocessing import Manager
|
||||
from pathlib import Path
|
||||
from queue import Empty, Queue
|
||||
from typing import Any
|
||||
|
||||
import ujson
|
||||
|
||||
from sanic import Request, Sanic, Websocket
|
||||
|
||||
|
||||
def setup_livereload(app: Sanic) -> None:
|
||||
@app.main_process_start
|
||||
async def main_process_start(app: Sanic):
|
||||
app.ctx.manager = Manager()
|
||||
app.shared_ctx.reload_queue = app.ctx.manager.Queue()
|
||||
|
||||
@app.main_process_ready
|
||||
async def main_process_ready(app: Sanic):
|
||||
app.manager.manage(
|
||||
"Livereload",
|
||||
_run_reload_server,
|
||||
{
|
||||
"reload_queue": app.shared_ctx.reload_queue,
|
||||
"debug": app.state.is_debug,
|
||||
"state": app.manager.worker_state,
|
||||
},
|
||||
)
|
||||
|
||||
@app.main_process_stop
|
||||
async def main_process_stop(app: Sanic):
|
||||
app.ctx.manager.shutdown()
|
||||
|
||||
@app.before_server_start
|
||||
async def before_server_start(app: Sanic):
|
||||
app.shared_ctx.reload_queue.put("reload")
|
||||
|
||||
@app.after_server_start
|
||||
async def after_server_start(app: Sanic):
|
||||
app.m.state["ready"] = True
|
||||
|
||||
@app.before_server_stop
|
||||
async def before_server_stop(app: Sanic):
|
||||
app.m.state["ready"] = False
|
||||
|
||||
|
||||
class Livereload:
|
||||
SERVER_NAME = "Reloader"
|
||||
HELLO = {
|
||||
"command": "hello",
|
||||
"protocols": [
|
||||
"http://livereload.com/protocols/official-7",
|
||||
],
|
||||
"serverName": SERVER_NAME,
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self, reload_queue: Queue, debug: bool, state: dict[str, Any]
|
||||
):
|
||||
self.reload_queue = reload_queue
|
||||
self.app = Sanic(self.SERVER_NAME)
|
||||
self.debug = debug
|
||||
self.state = state
|
||||
self.app.static(
|
||||
"/livereload.js", Path(__file__).parent / "livereload.js"
|
||||
)
|
||||
self.app.add_websocket_route(
|
||||
self.livereload_handler, "/livereload", name="livereload"
|
||||
)
|
||||
self.app.add_task(self._listen_to_queue())
|
||||
self.app.config.EVENT_AUTOREGISTER = True
|
||||
|
||||
def run(self):
|
||||
kwargs = {
|
||||
"debug": self.debug,
|
||||
"access_log": False,
|
||||
"single_process": True,
|
||||
"port": 35729,
|
||||
}
|
||||
self.app.run(**kwargs)
|
||||
|
||||
async def _listen_to_queue(self):
|
||||
while True:
|
||||
try:
|
||||
self.reload_queue.get_nowait()
|
||||
except Empty:
|
||||
await sleep(0.5)
|
||||
continue
|
||||
await self.app.dispatch("livereload.file.reload")
|
||||
|
||||
async def livereload_handler(self, request: Request, ws: Websocket):
|
||||
await ws.recv()
|
||||
await ws.send(ujson.dumps(self.HELLO))
|
||||
|
||||
while True:
|
||||
await request.app.event("livereload.file.reload")
|
||||
await self._wait_for_state()
|
||||
await ws.send(ujson.dumps({"command": "reload", "path": "..."}))
|
||||
|
||||
async def _wait_for_state(self):
|
||||
while True:
|
||||
states = [
|
||||
state.get("ready")
|
||||
for state in self.state.values()
|
||||
if state.get("server")
|
||||
]
|
||||
if all(states):
|
||||
await sleep(0.5)
|
||||
break
|
||||
|
||||
|
||||
def _run_reload_server(
|
||||
reload_queue: Queue, debug: bool, state: dict[str, Any]
|
||||
):
|
||||
Livereload(reload_queue, debug, state).run()
|
||||
28
guide/webapp/worker/style.py
Normal file
28
guide/webapp/worker/style.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# from scss.compiler import compile_string
|
||||
|
||||
from pygments.formatters import html
|
||||
from sass import compile as compile_scss
|
||||
from webapp.display.code_style import SanicCodeStyle
|
||||
|
||||
from sanic import Sanic
|
||||
|
||||
|
||||
def setup_style(app: Sanic) -> None:
|
||||
index = app.config.STYLE_DIR / "index.scss"
|
||||
style_output = app.config.PUBLIC_DIR / "assets" / "style.css"
|
||||
code_output = app.config.PUBLIC_DIR / "assets" / "code.css"
|
||||
|
||||
@app.before_server_start
|
||||
async def setup(app: Sanic):
|
||||
scss = compile_scss(
|
||||
string=index.read_text(),
|
||||
include_paths=[
|
||||
str(app.config.NODE_MODULES_DIR),
|
||||
str(app.config.STYLE_DIR),
|
||||
],
|
||||
)
|
||||
style_output.write_text(scss)
|
||||
formatter = html.HtmlFormatter(
|
||||
style=SanicCodeStyle, full=True, cssfile=code_output
|
||||
)
|
||||
code_output.write_text(formatter.get_style_defs())
|
||||
Reference in New Issue
Block a user