Use html5tagger for AutoIndex

This commit is contained in:
Adam Hopkins 2023-01-26 00:36:37 +02:00
parent fed2ef3527
commit 36e3cc9df7
No known key found for this signature in database
GPG Key ID: 9F85EE6C807303FB

View File

@ -8,9 +8,11 @@ from os import path
from pathlib import Path, PurePath from pathlib import Path, PurePath
from stat import S_ISDIR from stat import S_ISDIR
from time import time from time import time
from typing import Any, AnyStr, Callable, Dict, Optional, Tuple, Union from typing import Any, AnyStr, Callable, Dict, Iterable, Optional, Union
from urllib.parse import quote_plus from urllib.parse import quote_plus
from html5tagger import Document, E
from sanic.compat import Header, open_async, stat_async from sanic.compat import Header, open_async, stat_async
from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE
from sanic.helpers import Default, _default from sanic.helpers import Default, _default
@ -347,32 +349,20 @@ async def file_stream(
class AutoIndex: class AutoIndex:
INDEX_STYLE = """ STYLE = """
html { font-family: sans-serif } html { font-family: sans-serif; }
ul { padding: 0; list-style: none; } main { padding: 1rem; }
li { table { width: 100%; max-width: 1200px; }
display: flex; justify-content: space-between; td { font-family: monospace; }
font-family: monospace; td:last-child { text-align: right; }
span.icon { margin-right: 1rem; }
@media (prefers-color-scheme: dark) {
html { background: #111; color: #ccc; }
a { color: #ccc; }
a:visited { color: #777; }
} }
li > span { padding: 0.1rem 0.6rem; }
li > span:first-child { flex: 4; }
li > span:last-child { flex: 1; }
""" """
OUTPUT_HTML = ( TITLE = "📁 File browser"
"<!DOCTYPE html><html lang=en>"
"<meta charset=UTF-8><title>{title}</title>\n"
"<style>{style}</style>\n"
"<h1>{title}</h1>\n"
"{body}"
)
FILE_WRAPPER_HTML = "<ul>{first_line}{files}</ul>"
FILE_LINE_HTML = (
"<li>"
"<span>{icon} <a href={file_name}>{file_name}</a></span>"
"<span>{file_access}</span>"
"<span>{file_size}</span>"
"</li>"
)
def __init__( def __init__(
self, directory: Path, autoindex: bool, index_name: str self, directory: Path, autoindex: bool, index_name: str
@ -390,25 +380,43 @@ class AutoIndex:
return await file(index_file) return await file(index_file)
async def index(self): async def index(self):
return html( return html(self.render())
self.OUTPUT_HTML.format(
title="📁 File browser", def render(self) -> str:
style=self.INDEX_STYLE, doc = Document(title=self.TITLE, lang="en")
body=self._list_files(), doc.style(self.STYLE)
) with doc.main:
self._headline(doc)
self._file_table(doc)
return str(doc)
def _headline(self, doc: Document):
doc.h1(self.TITLE)
def _file_table(self, doc: Document):
with doc.table:
self._parent(doc)
for f in self._iter_files():
del f["priority"]
self._file_cell(doc, **f)
def _parent(self, doc: Document):
self._file_cell(doc, "📁", "..", "", "")
def _file_cell(
self,
doc: Document,
icon: str,
file_name: str,
file_access: str,
file_size: str,
):
first = E.span(icon, class_="icon").a(file_name, href=file_name)
doc.tr.td(first, width="65%").td(file_access).td(
file_size, width="15%"
) )
def _list_files(self) -> str: def _prepare_file(self, path: Path) -> Dict[str, Union[int, str]]:
prepared = [self._prepare_file(f) for f in self.directory.iterdir()]
files = "".join(itemgetter(2)(p) for p in sorted(prepared))
return self.FILE_WRAPPER_HTML.format(
files=files,
first_line=self.FILE_LINE_HTML.format(
icon="📁", file_name="..", file_access="", file_size=""
),
)
def _prepare_file(self, path: Path) -> Tuple[int, str, str]:
stat = path.stat() stat = path.stat()
modified = datetime.fromtimestamp(stat.st_mtime) modified = datetime.fromtimestamp(stat.st_mtime)
is_dir = S_ISDIR(stat.st_mode) is_dir = S_ISDIR(stat.st_mode)
@ -416,10 +424,14 @@ class AutoIndex:
file_name = path.name file_name = path.name
if is_dir: if is_dir:
file_name += "/" file_name += "/"
display = self.FILE_LINE_HTML.format( return {
icon=icon, "priority": is_dir * -1,
file_name=file_name, "file_name": file_name,
file_access=modified.isoformat(), "icon": icon,
file_size=stat.st_size, "file_access": modified.isoformat(),
) "file_size": stat.st_size,
return is_dir * -1, file_name, display }
def _iter_files(self) -> Iterable[Dict[str, Any]]:
prepared = [self._prepare_file(f) for f in self.directory.iterdir()]
return sorted(prepared, key=itemgetter("priority"))