chore: rename project coven → hack-house ⛧
Rebrand the Rust client crate (coven/ → hh/, package+binary "hack-house"), README, CLI strings, and branch (coven → hack-house). Gitea repo renamed cmd-chat → hack-house to match. Crypto/server logic unchanged; selftest + golden-vector test still green, binary is now `hack-house`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from html5tagger import HTML, Builder, Document
|
||||
|
||||
from sanic import __version__ as VERSION
|
||||
from sanic.application.logo import SVG_LOGO_SIMPLE
|
||||
from sanic.pages.css import CSS
|
||||
|
||||
|
||||
class BasePage(ABC, metaclass=CSS): # no cov
|
||||
"""Base page for Sanic pages."""
|
||||
|
||||
TITLE = "Sanic"
|
||||
HEADING = None
|
||||
CSS: str
|
||||
doc: Builder
|
||||
|
||||
def __init__(self, debug: bool = True) -> None:
|
||||
self.debug = debug
|
||||
|
||||
@property
|
||||
def style(self) -> str:
|
||||
"""Returns the CSS for the page.
|
||||
|
||||
Returns:
|
||||
str: The CSS for the page.
|
||||
"""
|
||||
return self.CSS
|
||||
|
||||
def render(self) -> str:
|
||||
"""Renders the page.
|
||||
|
||||
Returns:
|
||||
str: The rendered page.
|
||||
"""
|
||||
self.doc = Document(self.TITLE, lang="en", id="sanic")
|
||||
self._head()
|
||||
self._body()
|
||||
self._foot()
|
||||
return str(self.doc)
|
||||
|
||||
def _head(self) -> None:
|
||||
self.doc.style(HTML(self.style))
|
||||
with self.doc.header:
|
||||
self.doc.div(self.HEADING or self.TITLE)
|
||||
|
||||
def _foot(self) -> None:
|
||||
with self.doc.footer:
|
||||
self.doc.div("powered by")
|
||||
with self.doc.div:
|
||||
self._sanic_logo()
|
||||
if self.debug:
|
||||
self.doc.div(f"Version {VERSION}")
|
||||
with self.doc.div:
|
||||
for idx, (title, href) in enumerate(
|
||||
(
|
||||
("Docs", "https://sanic.dev"),
|
||||
("Help", "https://sanic.dev/en/help.html"),
|
||||
("GitHub", "https://github.com/sanic-org/sanic"),
|
||||
)
|
||||
):
|
||||
if idx > 0:
|
||||
self.doc(" | ")
|
||||
self.doc.a(
|
||||
title,
|
||||
href=href,
|
||||
target="_blank",
|
||||
referrerpolicy="no-referrer",
|
||||
)
|
||||
self.doc.div("DEBUG mode")
|
||||
|
||||
@abstractmethod
|
||||
def _body(self) -> None: ...
|
||||
|
||||
def _sanic_logo(self) -> None:
|
||||
self.doc.a(
|
||||
HTML(SVG_LOGO_SIMPLE),
|
||||
href="https://sanic.dev",
|
||||
target="_blank",
|
||||
referrerpolicy="no-referrer",
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
from abc import ABCMeta
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CURRENT_DIR = Path(__file__).parent
|
||||
|
||||
|
||||
def _extract_style(maybe_style: str | None, name: str) -> str:
|
||||
if maybe_style is not None:
|
||||
maybe_path = Path(maybe_style)
|
||||
if maybe_path.exists():
|
||||
return maybe_path.read_text(encoding="UTF-8")
|
||||
return maybe_style
|
||||
maybe_path = CURRENT_DIR / "styles" / f"{name}.css"
|
||||
if maybe_path.exists():
|
||||
return maybe_path.read_text(encoding="UTF-8")
|
||||
return ""
|
||||
|
||||
|
||||
class CSS(ABCMeta):
|
||||
"""Cascade stylesheets, i.e. combine all ancestor styles"""
|
||||
|
||||
def __new__(cls, name, bases, attrs):
|
||||
Page = super().__new__(cls, name, bases, attrs)
|
||||
# Use a locally defined STYLE or the one from styles directory
|
||||
Page.STYLE = _extract_style(attrs.get("STYLE_FILE"), name)
|
||||
Page.STYLE += attrs.get("STYLE_APPEND", "")
|
||||
# Combine with all ancestor styles
|
||||
Page.CSS = "".join(
|
||||
Class.STYLE
|
||||
for Class in reversed(Page.__mro__)
|
||||
if type(Class) is CSS
|
||||
)
|
||||
return Page
|
||||
@@ -0,0 +1,63 @@
|
||||
from collections.abc import Iterable
|
||||
from typing import TypedDict
|
||||
|
||||
from html5tagger import E
|
||||
|
||||
from .base import BasePage
|
||||
|
||||
|
||||
class FileInfo(TypedDict):
|
||||
"""Type for file info."""
|
||||
|
||||
icon: str
|
||||
file_name: str
|
||||
file_access: str
|
||||
file_size: str
|
||||
|
||||
|
||||
class DirectoryPage(BasePage): # no cov
|
||||
"""Page for viewing a directory."""
|
||||
|
||||
TITLE = "Directory Viewer"
|
||||
|
||||
def __init__(
|
||||
self, files: Iterable[FileInfo], url: str, debug: bool
|
||||
) -> None:
|
||||
super().__init__(debug)
|
||||
self.files = files
|
||||
self.url = url
|
||||
|
||||
def _body(self) -> None:
|
||||
with self.doc.main:
|
||||
self._headline()
|
||||
files = list(self.files)
|
||||
if files:
|
||||
self._file_table(files)
|
||||
else:
|
||||
self.doc.p("The folder is empty.")
|
||||
|
||||
def _headline(self):
|
||||
"""Implement a heading with the current path, combined with
|
||||
breadcrumb links"""
|
||||
with self.doc.h1(id="breadcrumbs"):
|
||||
p = self.url.split("/")[:-1]
|
||||
|
||||
for i, part in enumerate(p):
|
||||
path = "/".join(p[: i + 1]) + "/"
|
||||
with self.doc.a(href=path):
|
||||
self.doc.span(part, class_="dir").span("/", class_="sep")
|
||||
|
||||
def _file_table(self, files: Iterable[FileInfo]):
|
||||
with self.doc.table(class_="autoindex container"):
|
||||
for f in files:
|
||||
self._file_row(**f)
|
||||
|
||||
def _file_row(
|
||||
self,
|
||||
icon: str,
|
||||
file_name: str,
|
||||
file_access: str,
|
||||
file_size: str,
|
||||
):
|
||||
first = E.span(icon, class_="icon").a(file_name, href=file_name)
|
||||
self.doc.tr.td(first).td(file_size).td(file_access)
|
||||
@@ -0,0 +1,112 @@
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
import tracerite.html
|
||||
|
||||
from html5tagger import E
|
||||
from tracerite import html_traceback, inspector
|
||||
|
||||
from sanic.request import Request
|
||||
|
||||
from .base import BasePage
|
||||
|
||||
|
||||
# Avoid showing the request in the traceback variable inspectors
|
||||
inspector.blacklist_types += (Request,)
|
||||
|
||||
ENDUSER_TEXT = """\
|
||||
We're sorry, but it looks like something went wrong. Please try refreshing \
|
||||
the page or navigating back to the homepage. If the issue persists, our \
|
||||
technical team is working to resolve it as soon as possible. We apologize \
|
||||
for the inconvenience and appreciate your patience.\
|
||||
"""
|
||||
|
||||
|
||||
class ErrorPage(BasePage):
|
||||
"""Page for displaying an error."""
|
||||
|
||||
STYLE_APPEND = tracerite.html.style
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
debug: bool,
|
||||
title: str,
|
||||
text: str,
|
||||
request: Request,
|
||||
exc: Exception,
|
||||
) -> None:
|
||||
super().__init__(debug)
|
||||
name = request.app.name.replace("_", " ").strip()
|
||||
if name.islower():
|
||||
name = name.title()
|
||||
self.TITLE = f"Application {name} cannot handle your request"
|
||||
self.HEADING = E("Application ").strong(name)(
|
||||
" cannot handle your request"
|
||||
)
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.request = request
|
||||
self.exc = exc
|
||||
self.details_open = not getattr(exc, "quiet", False)
|
||||
|
||||
def _head(self) -> None:
|
||||
self.doc._script(tracerite.html.javascript)
|
||||
super()._head()
|
||||
|
||||
def _body(self) -> None:
|
||||
debug = self.request.app.debug
|
||||
route_name = self.request.name or "[route not found]"
|
||||
with self.doc.main:
|
||||
self.doc.h1(f"⚠️ {self.title}").p(self.text)
|
||||
# Show context details if available on the exception
|
||||
context = getattr(self.exc, "context", None)
|
||||
if context:
|
||||
self._key_value_table(
|
||||
"Issue context", "exception-context", context
|
||||
)
|
||||
|
||||
if not debug:
|
||||
with self.doc.div(id="enduser"):
|
||||
self.doc.p(ENDUSER_TEXT).p.a("Front Page", href="/")
|
||||
return
|
||||
# Show additional details in debug mode,
|
||||
# open by default for 500 errors
|
||||
with self.doc.details(open=self.details_open, class_="smalltext"):
|
||||
# Show extra details if available on the exception
|
||||
extra = getattr(self.exc, "extra", None)
|
||||
if extra:
|
||||
self._key_value_table(
|
||||
"Issue extra data", "exception-extra", extra
|
||||
)
|
||||
|
||||
self.doc.summary(
|
||||
"Details for developers (Sanic debug mode only)"
|
||||
)
|
||||
if self.exc:
|
||||
with self.doc.div(class_="exception-wrapper"):
|
||||
self.doc.h2(f"Exception in {route_name}:")
|
||||
self.doc(
|
||||
html_traceback(self.exc, include_js_css=False)
|
||||
)
|
||||
|
||||
self._key_value_table(
|
||||
f"{self.request.method} {self.request.path}",
|
||||
"request-headers",
|
||||
self.request.headers,
|
||||
)
|
||||
|
||||
def _key_value_table(
|
||||
self, title: str, table_id: str, data: Mapping[str, Any]
|
||||
) -> None:
|
||||
with self.doc.div(class_="key-value-display"):
|
||||
self.doc.h2(title)
|
||||
with self.doc.dl(id=table_id, class_="key-value-table smalltext"):
|
||||
for key, value in data.items():
|
||||
# Reading values may cause a new exception, so suppress it
|
||||
try:
|
||||
value = str(value)
|
||||
except Exception:
|
||||
value = E.em("Unable to display value")
|
||||
self.doc.dt.span(key, class_="nobr key").span(": ").dd(
|
||||
value
|
||||
)
|
||||
@@ -0,0 +1,146 @@
|
||||
/** BasePage **/
|
||||
|
||||
:root {
|
||||
--sanic: #ff0d68;
|
||||
--sanic-yellow: #FFE900;
|
||||
--sanic-background: #efeced;
|
||||
--sanic-text: #121010;
|
||||
--sanic-text-lighter: #756169;
|
||||
--sanic-link: #ff0d68;
|
||||
--sanic-block-background: #f7f4f6;
|
||||
--sanic-block-text: #000;
|
||||
--sanic-block-alt-text: #6b6468;
|
||||
--sanic-header-background: #272325;
|
||||
--sanic-header-border: #fff;
|
||||
--sanic-header-text: #fff;
|
||||
--sanic-highlight-background: var(--sanic-yellow);
|
||||
--sanic-highlight-text: var(--sanic-text);
|
||||
--sanic-tab-background: #f7f4f6;
|
||||
--sanic-tab-shadow: #f7f6f6;
|
||||
--sanic-tab-text: #222021;
|
||||
--sanic-tracerite-var: var(--sanic-text);
|
||||
--sanic-tracerite-val: #ff0d68;
|
||||
--sanic-tracerite-type: #6d6a6b;
|
||||
}
|
||||
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--sanic-text: #f7f4f6;
|
||||
--sanic-background: #121010;
|
||||
--sanic-block-background: #0f0d0e;
|
||||
--sanic-block-text: #f7f4f6;
|
||||
--sanic-header-background: #030203;
|
||||
--sanic-header-border: #000;
|
||||
--sanic-highlight-text: var(--sanic-background);
|
||||
--sanic-tab-background: #292728;
|
||||
--sanic-tab-shadow: #0f0d0e;
|
||||
--sanic-tab-text: #aea7ab;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
font: 16px sans-serif;
|
||||
background: var(--sanic-background);
|
||||
color: var(--sanic-text);
|
||||
scrollbar-gutter: stable;
|
||||
overflow: hidden auto;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 125%;
|
||||
}
|
||||
|
||||
body>* {
|
||||
padding: 1rem 2vw;
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
body>* {
|
||||
padding: 0.5rem 1.5vw;
|
||||
}
|
||||
|
||||
html {
|
||||
/* Scale everything by rem of 6px-16px by viewport width */
|
||||
font-size: calc(6px + 10 * 100vw / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
/* Make sure the footer is closer to bottom */
|
||||
min-height: 70vh;
|
||||
/* Generous padding for readability */
|
||||
padding: 1rem 2.5rem;
|
||||
}
|
||||
|
||||
.smalltext {
|
||||
font-size: 1.0rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
min-width: 600px;
|
||||
max-width: 1600px;
|
||||
}
|
||||
|
||||
header {
|
||||
background: var(--sanic-header-background);
|
||||
color: var(--sanic-header-text);
|
||||
border-bottom: 1px solid var(--sanic-header-border);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 0.8rem;
|
||||
margin: 2rem;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--sanic-link);
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:focus {
|
||||
text-decoration: underline;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
span.icon {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
#logo-simple {
|
||||
height: 1.75rem;
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
#logo-simple path:last-child {
|
||||
fill: #e1e1e1;
|
||||
}
|
||||
}
|
||||
|
||||
#sanic pre,
|
||||
#sanic code {
|
||||
font-family: "Fira Code",
|
||||
"Source Code Pro",
|
||||
Menlo,
|
||||
Meslo,
|
||||
Monaco,
|
||||
Consolas,
|
||||
Lucida Console,
|
||||
monospace;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/** DirectoryPage **/
|
||||
#breadcrumbs>a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#breadcrumbs>a .dir {
|
||||
padding: 0 0.25em;
|
||||
}
|
||||
|
||||
#breadcrumbs>a:first-child:hover::before,
|
||||
#breadcrumbs>a .dir:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#breadcrumbs>a:first-child::before {
|
||||
content: "🏠";
|
||||
}
|
||||
|
||||
#breadcrumbs>a:last-child {
|
||||
color: #ff0d68;
|
||||
}
|
||||
|
||||
main a {
|
||||
color: inherit;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table.autoindex {
|
||||
width: 100%;
|
||||
font-family: monospace;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
table.autoindex tr {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
table.autoindex tr:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
table.autoindex td {
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
table.autoindex td:first-child {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
table.autoindex td:nth-child(2) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
table.autoindex td:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
table.autoindex tr:hover {
|
||||
background-color: #222;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/** ErrorPage **/
|
||||
#enduser {
|
||||
max-width: 30em;
|
||||
margin: 5em auto 5em auto;
|
||||
text-align: justify;
|
||||
/*text-justify: both;*/
|
||||
}
|
||||
|
||||
#enduser a {
|
||||
color: var(--sanic-blue);
|
||||
}
|
||||
|
||||
#enduser p:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
summary {
|
||||
margin-top: 3em;
|
||||
color: var(--sanic-text-lighter);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tracerite {
|
||||
--tracerite-var: var(--sanic-tracerite-var);
|
||||
--tracerite-val: var(--sanic-tracerite-val);
|
||||
--tracerite-type: var(--sanic-tracerite-type);
|
||||
--tracerite-exception: var(--sanic);
|
||||
--tracerite-highlight: var(--sanic-yellow);
|
||||
--tracerite-tab: var(--sanic-tab-background);
|
||||
--tracerite-tab-text: var(--sanic-tab-text);
|
||||
}
|
||||
|
||||
.tracerite>h3 {
|
||||
margin: 0.5rem 0 !important;
|
||||
}
|
||||
|
||||
#sanic .tracerite .traceback-labels button {
|
||||
font-size: 0.8rem;
|
||||
line-height: 120%;
|
||||
background: var(--tracerite-tab);
|
||||
color: var(--tracerite-tab-text);
|
||||
transition: 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tracerite .traceback-labels {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.tracerite .traceback-labels button:hover {
|
||||
filter: contrast(150%) brightness(120%) drop-shadow(0 -0 2px var(--sanic-tab-shadow));
|
||||
}
|
||||
|
||||
#sanic .tracerite .tracerite-tooltip::before {
|
||||
bottom: 1.75em;
|
||||
}
|
||||
|
||||
#sanic .tracerite .traceback-details mark span {
|
||||
background: var(--sanic-highlight-background);
|
||||
color: var(--sanic-highlight-text);
|
||||
}
|
||||
|
||||
header {
|
||||
background: var(--sanic-header-background);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.3rem;
|
||||
color: var(--sanic-text);
|
||||
}
|
||||
|
||||
.key-value-display,
|
||||
.exception-wrapper {
|
||||
padding: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.key-value-display {
|
||||
background-color: var(--sanic-block-background);
|
||||
color: var(--sanic-block-text);
|
||||
}
|
||||
|
||||
.key-value-display h2 {
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
|
||||
dl.key-value-table {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 5fr;
|
||||
grid-gap: .3em;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
dl.key-value-table * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
dl.key-value-table dt {
|
||||
color: var(--sanic-block-alt-text);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
dl.key-value-table dd {
|
||||
/* Better breaking for cookies header and such */
|
||||
word-break: break-all;
|
||||
}
|
||||
Reference in New Issue
Block a user