added hot reload and Gevent to uwsgi server
This commit is contained in:
0
src/md2html/__init__.py
Normal file
0
src/md2html/__init__.py
Normal file
92
src/md2html/file_watch.py
Normal file
92
src/md2html/file_watch.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from threading import Lock
|
||||
from typing import Optional, Callable
|
||||
from os import getcwd
|
||||
from watchdog.events import PatternMatchingEventHandler, FileSystemEvent, \
|
||||
FileCreatedEvent, FileModifiedEvent, FileClosedEvent, FileMovedEvent
|
||||
from watchdog.observers import Observer
|
||||
import logging
|
||||
from gevent.event import Event
|
||||
|
||||
|
||||
class Subscription:
|
||||
|
||||
def __init__(self, unsubscribe: Callable[['Subscription'], None]):
|
||||
self._unsubscribe_callback = unsubscribe
|
||||
self._event: Event = Event()
|
||||
|
||||
def unsubscribe(self) -> None:
|
||||
self._unsubscribe_callback(self)
|
||||
|
||||
def wait(self, tout: float) -> bool:
|
||||
return self._event.wait(tout)
|
||||
|
||||
def notify(self) -> None:
|
||||
self._event.set()
|
||||
|
||||
def reset(self) -> None:
|
||||
self._event.clear()
|
||||
|
||||
|
||||
class FileWatcher(PatternMatchingEventHandler):
|
||||
def __init__(self, path):
|
||||
super().__init__(patterns=['*.md'],
|
||||
ignore_patterns=None,
|
||||
ignore_directories=False,
|
||||
case_sensitive=True)
|
||||
self.subscriptions: dict[str, set[Subscription]] = dict()
|
||||
self.observer: Observer = Observer()
|
||||
self.observer.schedule(self, path=path, recursive=True)
|
||||
self.observer.start()
|
||||
self.logger = logging.getLogger(FileWatcher.__name__)
|
||||
self._lock = Lock()
|
||||
|
||||
def subscribe(self, path: str) -> Subscription:
|
||||
subscriptions = self.subscriptions
|
||||
subscriptions_per_path = subscriptions.setdefault(path, set())
|
||||
|
||||
def unsubscribe_callback(subscription):
|
||||
with self._lock:
|
||||
subscriptions_per_path.remove(subscription)
|
||||
|
||||
result = Subscription(unsubscribe_callback)
|
||||
subscriptions_per_path.add(result)
|
||||
return result
|
||||
|
||||
def stop(self) -> None:
|
||||
self.observer.stop()
|
||||
self.observer.join()
|
||||
|
||||
def on_any_event(self, event: FileSystemEvent) -> None:
|
||||
what = "directory" if event.is_directory else "file"
|
||||
|
||||
def notify_subscriptions(path):
|
||||
with self._lock:
|
||||
subscriptions = self.subscriptions
|
||||
subscriptions_per_path = subscriptions.get(path, None)
|
||||
if subscriptions_per_path:
|
||||
for s in subscriptions_per_path:
|
||||
s.notify()
|
||||
|
||||
if isinstance(event, FileClosedEvent):
|
||||
self.logger.debug("Closed %s: %s", what, event.src_path)
|
||||
# update_subscriptions()
|
||||
elif isinstance(event, FileMovedEvent):
|
||||
self.logger.debug("Moved %s: %s to %s", what, event.src_path, event.dest_path)
|
||||
notify_subscriptions(event.dest_path)
|
||||
elif isinstance(event, FileCreatedEvent):
|
||||
self.logger.debug("Created %s: %s", what, event.src_path)
|
||||
notify_subscriptions(event.src_path)
|
||||
elif isinstance(event, FileModifiedEvent):
|
||||
self.logger.debug("Modified %s: %s", what, event.src_path)
|
||||
notify_subscriptions(event.src_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s [%(threadName)s] (%(name)s) %(levelname)s %(message)s'
|
||||
)
|
||||
watcher = FileWatcher(getcwd())
|
||||
watcher.observer.join()
|
||||
|
41
src/md2html/md2html.py
Normal file
41
src/md2html/md2html.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import sys
|
||||
from os.path import dirname, join
|
||||
from time import time
|
||||
from typing import Optional
|
||||
|
||||
import markdown
|
||||
|
||||
STATIC_RESOURCES: set[str] = {
|
||||
'/github-markdown.css',
|
||||
'/custom.css',
|
||||
'/hot-reload.js',
|
||||
'/pygment.css',
|
||||
}
|
||||
STATIC_CACHE: dict[str, tuple[str, float]] = {}
|
||||
|
||||
MARDOWN_EXTENSIONS = ['extra', 'smarty', 'tables', 'codehilite']
|
||||
|
||||
def load_from_cache(path) -> tuple[str, float]:
|
||||
global STATIC_CACHE
|
||||
if path not in STATIC_CACHE:
|
||||
with open(join(dirname(__file__), 'static') + path, 'r') as static_file:
|
||||
STATIC_CACHE[path] = (static_file.read(), time())
|
||||
return STATIC_CACHE[path]
|
||||
|
||||
|
||||
def compile_html(mdfile=None,
|
||||
extensions: Optional[list[str]] = None,
|
||||
raw: bool = False) -> str:
|
||||
with mdfile and open(mdfile, 'r') or sys.stdin as instream:
|
||||
html = markdown.markdown(instream.read(), extensions=extensions, output_format='html')
|
||||
if raw:
|
||||
doc = html
|
||||
else:
|
||||
css = ' <style>% s\n%s\n%s\n </style>' % (
|
||||
load_from_cache('/github-markdown.css')[0],
|
||||
load_from_cache('/pygment.css')[0],
|
||||
load_from_cache('/custom.css')[0],
|
||||
)
|
||||
script = '<script src="/hot-reload.js", type="text/javascript" defer="true"></script>'
|
||||
doc = load_from_cache('/template.html')[0].format(content=html, script=script, css=css)
|
||||
return doc
|
225
src/md2html/server.py
Normal file
225
src/md2html/server.py
Normal file
@@ -0,0 +1,225 @@
|
||||
import logging
|
||||
from os import getcwd, listdir
|
||||
from os.path import exists, splitext, isfile, join, relpath, isdir, basename, getmtime, dirname
|
||||
from mimetypes import init as mimeinit, guess_type
|
||||
import hashlib
|
||||
from .md2html import compile_html, load_from_cache, STATIC_RESOURCES, MARDOWN_EXTENSIONS
|
||||
from shutil import which
|
||||
from subprocess import check_output
|
||||
from io import BytesIO
|
||||
from typing import Callable, TYPE_CHECKING, BinaryIO, Optional
|
||||
from .file_watch import FileWatcher
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _typeshed import StrOrBytesPath
|
||||
|
||||
mimeinit()
|
||||
|
||||
cwd: 'StrOrBytesPath' = getcwd()
|
||||
|
||||
|
||||
def has_extension(filepath, extension):
|
||||
_, ext = splitext(filepath)
|
||||
return ext == extension
|
||||
|
||||
|
||||
def is_markdown(filepath):
|
||||
return has_extension(filepath, ".md")
|
||||
|
||||
|
||||
def is_dotfile(filepath):
|
||||
return has_extension(filepath, ".dot")
|
||||
|
||||
|
||||
class Server:
|
||||
|
||||
def __init__(self, root_dir: 'StrOrBytesPath' = getcwd()):
|
||||
self.root_dir = root_dir
|
||||
self.cache = dict['StrOrBytesPath', tuple[str, float]]()
|
||||
self.file_watcher = FileWatcher(cwd)
|
||||
self.logger = logging.getLogger(Server.__name__)
|
||||
|
||||
def handle_request(self, method: str, url_path: str, etag: Optional[str], query_string: Optional[str], start_response):
|
||||
if method != 'GET':
|
||||
start_response('405', [])
|
||||
return []
|
||||
path: 'StrOrBytesPath' = join(self.root_dir, relpath(url_path, '/'))
|
||||
if url_path in STATIC_RESOURCES:
|
||||
content, mtime = load_from_cache(url_path)
|
||||
content = content.encode()
|
||||
etag, digest = self.compute_etag_and_digest(
|
||||
etag,
|
||||
url_path,
|
||||
lambda: BytesIO(content),
|
||||
lambda: mtime
|
||||
)
|
||||
if etag and etag == digest:
|
||||
return self.not_modified(start_response, digest, ('Cache-Control', 'must-revalidate, max-age=86400'))
|
||||
elif content:
|
||||
mime_type = guess_type(basename(url_path))[0] or 'application/octet-stream'
|
||||
start_response('200 OK', [
|
||||
('Content-Type', f'{mime_type}; charset=UTF-8'),
|
||||
('Etag', 'W/"%s"' % digest),
|
||||
('Cache-Control', 'must-revalidate, max-age=86400'),
|
||||
])
|
||||
return content
|
||||
elif exists(path):
|
||||
if isfile(path):
|
||||
etag, digest = self.compute_etag_and_digest(
|
||||
etag,
|
||||
path,
|
||||
lambda: open(path, 'rb'),
|
||||
lambda: getmtime(path)
|
||||
)
|
||||
if etag and etag == digest:
|
||||
if is_markdown(path) and query_string == 'reload':
|
||||
subscription = self.file_watcher.subscribe(path)
|
||||
try:
|
||||
has_changed = subscription.wait(30)
|
||||
if has_changed:
|
||||
_, digest = self.compute_etag_and_digest(
|
||||
etag,
|
||||
path,
|
||||
lambda: open(path, 'rb'),
|
||||
lambda: getmtime(path)
|
||||
)
|
||||
if etag != digest:
|
||||
body = compile_html(path,
|
||||
MARDOWN_EXTENSIONS,
|
||||
raw=True).encode()
|
||||
start_response('200 OK', [('Content-Type', 'text/html; charset=UTF-8'),
|
||||
('Etag', 'W/"%s"' % digest),
|
||||
('Cache-Control', 'no-cache'),
|
||||
])
|
||||
return [body]
|
||||
finally:
|
||||
subscription.unsubscribe()
|
||||
return self.not_modified(start_response, digest)
|
||||
elif is_markdown(path):
|
||||
raw = query_string == 'reload'
|
||||
body = compile_html(path, MARDOWN_EXTENSIONS, raw=raw).encode()
|
||||
start_response('200 OK', [('Content-Type', 'text/html; charset=UTF-8'),
|
||||
('Etag', 'W/"%s"' % digest),
|
||||
('Cache-Control', 'no-cache'),
|
||||
])
|
||||
return [body]
|
||||
elif is_dotfile(path) and which("dot"):
|
||||
body = check_output(['dot', '-Tsvg', basename(path)], cwd=dirname(path))
|
||||
start_response('200 OK', [('Content-Type', 'image/svg+xml; charset=UTF-8'),
|
||||
('Etag', 'W/"%s"' % digest),
|
||||
('Cache-Control', 'no-cache'),
|
||||
])
|
||||
return [body]
|
||||
else:
|
||||
def read_file(file_path):
|
||||
buffer_size = 1024
|
||||
with open(file_path, 'rb') as f:
|
||||
while True:
|
||||
result = f.read(buffer_size)
|
||||
if len(result) == 0:
|
||||
break
|
||||
yield result
|
||||
|
||||
start_response('200 OK',
|
||||
[('Content-Type', guess_type(basename(path))[0] or 'application/octet-stream'),
|
||||
('Etag', 'W/"%s"' % digest),
|
||||
('Cache-Control', 'no-cache'),
|
||||
])
|
||||
return read_file(path)
|
||||
elif isdir(path):
|
||||
body = self.directory_listing(url_path, path).encode()
|
||||
start_response('200 OK', [
|
||||
('Content-Type', 'text/html; charset=UTF-8'),
|
||||
])
|
||||
return [body]
|
||||
start_response('404 NOT_FOUND', [])
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def stream_hash(source: BinaryIO, bufsize=0x1000) -> bytes:
|
||||
if bufsize <= 0:
|
||||
raise ValueError("Buffer size must be greater than 0")
|
||||
md5 = hashlib.md5()
|
||||
while True:
|
||||
buf = source.read(bufsize)
|
||||
if len(buf) == 0:
|
||||
break
|
||||
md5.update(buf)
|
||||
return md5.digest()
|
||||
|
||||
@staticmethod
|
||||
def file_hash(filepath, bufsize=0x1000) -> bytes:
|
||||
if bufsize <= 0:
|
||||
raise ValueError("Buffer size must be greater than 0")
|
||||
md5 = hashlib.md5()
|
||||
with open(filepath, 'rb') as f:
|
||||
while True:
|
||||
buf = f.read(bufsize)
|
||||
if len(buf) == 0:
|
||||
break
|
||||
md5.update(buf)
|
||||
return md5.digest()
|
||||
|
||||
@staticmethod
|
||||
def parse_etag(etag: str) -> Optional[str]:
|
||||
if etag is None:
|
||||
return
|
||||
start = etag.find('"')
|
||||
if start < 0:
|
||||
return
|
||||
end = etag.find('"', start + 1)
|
||||
return etag[start + 1: end]
|
||||
|
||||
def compute_etag_and_digest(
|
||||
self,
|
||||
etag_header: str,
|
||||
path: str,
|
||||
stream_source: Callable[[], BinaryIO],
|
||||
mtime_supplier: Callable[[], float]
|
||||
) -> tuple[str, str]:
|
||||
cache_result = self.cache.get(path)
|
||||
_mtime: Optional[float] = None
|
||||
|
||||
def mtime() -> float:
|
||||
nonlocal _mtime
|
||||
if not _mtime:
|
||||
_mtime = mtime_supplier()
|
||||
return _mtime
|
||||
|
||||
if not cache_result or cache_result[1] < mtime():
|
||||
with stream_source() as stream:
|
||||
digest = Server.stream_hash(stream).hex()
|
||||
self.cache[path] = digest, mtime()
|
||||
else:
|
||||
digest = cache_result[0]
|
||||
|
||||
etag = Server.parse_etag(etag_header)
|
||||
return etag, digest
|
||||
|
||||
@staticmethod
|
||||
def not_modified(start_response, digest: str, cache_control=('Cache-Control', 'no-cache')) -> []:
|
||||
start_response('304 Not Modified', [
|
||||
('Etag', f'W/"{digest}"'),
|
||||
cache_control,
|
||||
])
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def directory_listing(path_info, path) -> str:
|
||||
title = "Directory listing for %s" % path_info
|
||||
result = "<!DOCTYPE html><html><head>"
|
||||
result += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">"
|
||||
result += "<title>" + title + "</title></head>"
|
||||
result += "<body><h1>" + title + "</h1><hr>"
|
||||
result += "<ul>"
|
||||
if path_info != '/':
|
||||
result += "<li><a href=\"../\"/>../</li>"
|
||||
|
||||
def ls(filter):
|
||||
return (entry for entry in sorted(listdir(path)) if filter(join(path, entry)))
|
||||
|
||||
for entry in ls(isdir):
|
||||
result += '<li><a href="' + entry + '/' + '"/>' + entry + '/' + '</li>'
|
||||
for entry in ls(lambda entry: isfile(entry) and is_markdown(entry)):
|
||||
result += '<li><a href="' + entry + '"/>' + entry + '</li>'
|
||||
return result
|
13
src/md2html/static/custom.css
Normal file
13
src/md2html/static/custom.css
Normal file
@@ -0,0 +1,13 @@
|
||||
.markdown-body {
|
||||
box-sizing: border-box;
|
||||
min-width: 200px;
|
||||
max-width: 980px;
|
||||
margin: 0 auto;
|
||||
padding: 45px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.markdown-body {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
695
src/md2html/static/github-markdown.css
Normal file
695
src/md2html/static/github-markdown.css
Normal file
@@ -0,0 +1,695 @@
|
||||
@font-face {
|
||||
font-family: octicons-link;
|
||||
src: url(data:font/woff;charset=utf-8;base64,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) format('woff');
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
line-height: 1.5;
|
||||
color: #24292e;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.markdown-body .pl-c {
|
||||
color: #6a737d;
|
||||
}
|
||||
|
||||
.markdown-body .pl-c1,
|
||||
.markdown-body .pl-s .pl-v {
|
||||
color: #005cc5;
|
||||
}
|
||||
|
||||
.markdown-body .pl-e,
|
||||
.markdown-body .pl-en {
|
||||
color: #6f42c1;
|
||||
}
|
||||
|
||||
.markdown-body .pl-smi,
|
||||
.markdown-body .pl-s .pl-s1 {
|
||||
color: #24292e;
|
||||
}
|
||||
|
||||
.markdown-body .pl-ent {
|
||||
color: #22863a;
|
||||
}
|
||||
|
||||
.markdown-body .pl-k {
|
||||
color: #d73a49;
|
||||
}
|
||||
|
||||
.markdown-body .pl-s,
|
||||
.markdown-body .pl-pds,
|
||||
.markdown-body .pl-s .pl-pse .pl-s1,
|
||||
.markdown-body .pl-sr,
|
||||
.markdown-body .pl-sr .pl-cce,
|
||||
.markdown-body .pl-sr .pl-sre,
|
||||
.markdown-body .pl-sr .pl-sra {
|
||||
color: #032f62;
|
||||
}
|
||||
|
||||
.markdown-body .pl-v,
|
||||
.markdown-body .pl-smw {
|
||||
color: #e36209;
|
||||
}
|
||||
|
||||
.markdown-body .pl-bu {
|
||||
color: #b31d28;
|
||||
}
|
||||
|
||||
.markdown-body .pl-ii {
|
||||
color: #fafbfc;
|
||||
background-color: #b31d28;
|
||||
}
|
||||
|
||||
.markdown-body .pl-c2 {
|
||||
color: #fafbfc;
|
||||
background-color: #d73a49;
|
||||
}
|
||||
|
||||
.markdown-body .pl-c2::before {
|
||||
content: "^M";
|
||||
}
|
||||
|
||||
.markdown-body .pl-sr .pl-cce {
|
||||
font-weight: bold;
|
||||
color: #22863a;
|
||||
}
|
||||
|
||||
.markdown-body .pl-ml {
|
||||
color: #735c0f;
|
||||
}
|
||||
|
||||
.markdown-body .pl-mh,
|
||||
.markdown-body .pl-mh .pl-en,
|
||||
.markdown-body .pl-ms {
|
||||
font-weight: bold;
|
||||
color: #005cc5;
|
||||
}
|
||||
|
||||
.markdown-body .pl-mi {
|
||||
font-style: italic;
|
||||
color: #24292e;
|
||||
}
|
||||
|
||||
.markdown-body .pl-mb {
|
||||
font-weight: bold;
|
||||
color: #24292e;
|
||||
}
|
||||
|
||||
.markdown-body .pl-md {
|
||||
color: #b31d28;
|
||||
background-color: #ffeef0;
|
||||
}
|
||||
|
||||
.markdown-body .pl-mi1 {
|
||||
color: #22863a;
|
||||
background-color: #f0fff4;
|
||||
}
|
||||
|
||||
.markdown-body .pl-mc {
|
||||
color: #e36209;
|
||||
background-color: #ffebda;
|
||||
}
|
||||
|
||||
.markdown-body .pl-mi2 {
|
||||
color: #f6f8fa;
|
||||
background-color: #005cc5;
|
||||
}
|
||||
|
||||
.markdown-body .pl-mdr {
|
||||
font-weight: bold;
|
||||
color: #6f42c1;
|
||||
}
|
||||
|
||||
.markdown-body .pl-ba {
|
||||
color: #586069;
|
||||
}
|
||||
|
||||
.markdown-body .pl-sg {
|
||||
color: #959da5;
|
||||
}
|
||||
|
||||
.markdown-body .pl-corl {
|
||||
text-decoration: underline;
|
||||
color: #032f62;
|
||||
}
|
||||
|
||||
.markdown-body .octicon {
|
||||
display: inline-block;
|
||||
vertical-align: text-top;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.markdown-body a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.markdown-body a:active,
|
||||
.markdown-body a:hover {
|
||||
outline-width: 0;
|
||||
}
|
||||
|
||||
.markdown-body strong {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
.markdown-body strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
.markdown-body img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
.markdown-body code,
|
||||
.markdown-body kbd,
|
||||
.markdown-body pre {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.markdown-body hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.markdown-body input {
|
||||
font: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown-body input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.markdown-body [type="checkbox"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.markdown-body * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.markdown-body input {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.markdown-body a {
|
||||
color: #0366d6;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.markdown-body a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.markdown-body strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body hr {
|
||||
height: 0;
|
||||
margin: 15px 0;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #dfe2e5;
|
||||
}
|
||||
|
||||
.markdown-body hr::before {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.markdown-body hr::after {
|
||||
display: table;
|
||||
clear: both;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.markdown-body table {
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.markdown-body td,
|
||||
.markdown-body th {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.markdown-body h1,
|
||||
.markdown-body h2,
|
||||
.markdown-body h3,
|
||||
.markdown-body h4,
|
||||
.markdown-body h5,
|
||||
.markdown-body h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body h5 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body h6 {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown-body ul,
|
||||
.markdown-body ol {
|
||||
padding-left: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.markdown-body ol ol,
|
||||
.markdown-body ul ol {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
|
||||
.markdown-body ul ul ol,
|
||||
.markdown-body ul ol ol,
|
||||
.markdown-body ol ul ol,
|
||||
.markdown-body ol ol ol {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
|
||||
.markdown-body dd {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.markdown-body code {
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown-body .octicon {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.markdown-body .pl-0 {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
.markdown-body .pl-1 {
|
||||
padding-left: 4px !important;
|
||||
}
|
||||
|
||||
.markdown-body .pl-2 {
|
||||
padding-left: 8px !important;
|
||||
}
|
||||
|
||||
.markdown-body .pl-3 {
|
||||
padding-left: 16px !important;
|
||||
}
|
||||
|
||||
.markdown-body .pl-4 {
|
||||
padding-left: 24px !important;
|
||||
}
|
||||
|
||||
.markdown-body .pl-5 {
|
||||
padding-left: 32px !important;
|
||||
}
|
||||
|
||||
.markdown-body .pl-6 {
|
||||
padding-left: 40px !important;
|
||||
}
|
||||
|
||||
.markdown-body::before {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.markdown-body::after {
|
||||
display: table;
|
||||
clear: both;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.markdown-body>*:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.markdown-body>*:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.markdown-body a:not([href]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.markdown-body .anchor {
|
||||
float: left;
|
||||
padding-right: 4px;
|
||||
margin-left: -20px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.markdown-body .anchor:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.markdown-body p,
|
||||
.markdown-body blockquote,
|
||||
.markdown-body ul,
|
||||
.markdown-body ol,
|
||||
.markdown-body dl,
|
||||
.markdown-body table,
|
||||
.markdown-body pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.markdown-body hr {
|
||||
height: 0.25em;
|
||||
padding: 0;
|
||||
margin: 24px 0;
|
||||
background-color: #e1e4e8;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
padding: 0 1em;
|
||||
color: #6a737d;
|
||||
border-left: 0.25em solid #dfe2e5;
|
||||
}
|
||||
|
||||
.markdown-body blockquote>:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.markdown-body blockquote>:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.markdown-body kbd {
|
||||
display: inline-block;
|
||||
padding: 3px 5px;
|
||||
font-size: 11px;
|
||||
line-height: 10px;
|
||||
color: #444d56;
|
||||
vertical-align: middle;
|
||||
background-color: #fafbfc;
|
||||
border: solid 1px #c6cbd1;
|
||||
border-bottom-color: #959da5;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 -1px 0 #959da5;
|
||||
}
|
||||
|
||||
.markdown-body h1,
|
||||
.markdown-body h2,
|
||||
.markdown-body h3,
|
||||
.markdown-body h4,
|
||||
.markdown-body h5,
|
||||
.markdown-body h6 {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.markdown-body h1 .octicon-link,
|
||||
.markdown-body h2 .octicon-link,
|
||||
.markdown-body h3 .octicon-link,
|
||||
.markdown-body h4 .octicon-link,
|
||||
.markdown-body h5 .octicon-link,
|
||||
.markdown-body h6 .octicon-link {
|
||||
color: #1b1f23;
|
||||
vertical-align: middle;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.markdown-body h1:hover .anchor,
|
||||
.markdown-body h2:hover .anchor,
|
||||
.markdown-body h3:hover .anchor,
|
||||
.markdown-body h4:hover .anchor,
|
||||
.markdown-body h5:hover .anchor,
|
||||
.markdown-body h6:hover .anchor {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.markdown-body h1:hover .anchor .octicon-link,
|
||||
.markdown-body h2:hover .anchor .octicon-link,
|
||||
.markdown-body h3:hover .anchor .octicon-link,
|
||||
.markdown-body h4:hover .anchor .octicon-link,
|
||||
.markdown-body h5:hover .anchor .octicon-link,
|
||||
.markdown-body h6:hover .anchor .octicon-link {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
padding-bottom: 0.3em;
|
||||
font-size: 2em;
|
||||
border-bottom: 1px solid #eaecef;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
padding-bottom: 0.3em;
|
||||
font-size: 1.5em;
|
||||
border-bottom: 1px solid #eaecef;
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.markdown-body h4 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.markdown-body h5 {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
.markdown-body h6 {
|
||||
font-size: 0.85em;
|
||||
color: #6a737d;
|
||||
}
|
||||
|
||||
.markdown-body ul,
|
||||
.markdown-body ol {
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.markdown-body ul ul,
|
||||
.markdown-body ul ol,
|
||||
.markdown-body ol ol,
|
||||
.markdown-body ol ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.markdown-body li {
|
||||
word-wrap: break-all;
|
||||
}
|
||||
|
||||
.markdown-body li>p {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.markdown-body li+li {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.markdown-body dl {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.markdown-body dl dt {
|
||||
padding: 0;
|
||||
margin-top: 16px;
|
||||
font-size: 1em;
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body dl dd {
|
||||
padding: 0 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.markdown-body table {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.markdown-body table th {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-body table th,
|
||||
.markdown-body table td {
|
||||
padding: 6px 13px;
|
||||
border: 1px solid #dfe2e5;
|
||||
}
|
||||
|
||||
.markdown-body table tr {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #c6cbd1;
|
||||
}
|
||||
|
||||
.markdown-body table tr:nth-child(2n) {
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
.markdown-body img {
|
||||
max-width: 100%;
|
||||
box-sizing: content-box;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.markdown-body img[align=right] {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.markdown-body img[align=left] {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.markdown-body code {
|
||||
padding: 0.2em 0.4em;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
background-color: rgba(27,31,35,0.05);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
.markdown-body pre>code {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 100%;
|
||||
word-break: normal;
|
||||
white-space: pre;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.markdown-body .highlight {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.markdown-body .highlight pre {
|
||||
margin-bottom: 0;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.markdown-body .highlight pre,
|
||||
.markdown-body pre {
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
background-color: #f6f8fa;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.markdown-body pre code {
|
||||
display: inline;
|
||||
max-width: auto;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
line-height: inherit;
|
||||
word-wrap: normal;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.markdown-body .full-commit .btn-outline:not(:disabled):hover {
|
||||
color: #005cc5;
|
||||
border-color: #005cc5;
|
||||
}
|
||||
|
||||
.markdown-body kbd {
|
||||
display: inline-block;
|
||||
padding: 3px 5px;
|
||||
font: 11px "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
line-height: 10px;
|
||||
color: #444d56;
|
||||
vertical-align: middle;
|
||||
background-color: #fafbfc;
|
||||
border: solid 1px #d1d5da;
|
||||
border-bottom-color: #c6cbd1;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 -1px 0 #c6cbd1;
|
||||
}
|
||||
|
||||
.markdown-body :checked+.radio-label {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
border-color: #0366d6;
|
||||
}
|
||||
|
||||
.markdown-body .task-list-item {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.markdown-body .task-list-item+.task-list-item {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.markdown-body .task-list-item input {
|
||||
margin: 0 0.2em 0.25em -1.6em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.markdown-body hr {
|
||||
border-bottom-color: #eee;
|
||||
}
|
19
src/md2html/static/hot-reload.js
Normal file
19
src/md2html/static/hot-reload.js
Normal file
@@ -0,0 +1,19 @@
|
||||
function req(first) {
|
||||
var xmlhttp = new XMLHttpRequest();
|
||||
xmlhttp.onload = function() {
|
||||
if (xmlhttp.status == 200) {
|
||||
document.querySelector("article.markdown-body").innerHTML = xmlhttp.responseText;
|
||||
} else if(xmlhttp.status == 304) {
|
||||
} else {
|
||||
console.log(xmlhttp.status, xmlhttp.statusText);
|
||||
}
|
||||
req(false);
|
||||
};
|
||||
xmlhttp.onerror = function() {
|
||||
console.log(xmlhttp.status, xmlhttp.statusText);
|
||||
setTimeout(req, 1000, false);
|
||||
};
|
||||
xmlhttp.open("GET", location.pathname + "?reload", true);
|
||||
xmlhttp.send();
|
||||
}
|
||||
req(true);
|
75
src/md2html/static/pygment.css
Normal file
75
src/md2html/static/pygment.css
Normal file
@@ -0,0 +1,75 @@
|
||||
pre { line-height: 125%; }
|
||||
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
||||
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
||||
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
||||
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
||||
.codehilite .hll { background-color: #ffffcc }
|
||||
.codehilite { background: #f8f8f8; }
|
||||
.codehilite .c { color: #3D7B7B; font-style: italic } /* Comment */
|
||||
.codehilite .err { border: 1px solid #FF0000 } /* Error */
|
||||
.codehilite .k { color: #008000; font-weight: bold } /* Keyword */
|
||||
.codehilite .o { color: #666666 } /* Operator */
|
||||
.codehilite .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
|
||||
.codehilite .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
|
||||
.codehilite .cp { color: #9C6500 } /* Comment.Preproc */
|
||||
.codehilite .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */
|
||||
.codehilite .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */
|
||||
.codehilite .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
|
||||
.codehilite .gd { color: #A00000 } /* Generic.Deleted */
|
||||
.codehilite .ge { font-style: italic } /* Generic.Emph */
|
||||
.codehilite .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
|
||||
.codehilite .gr { color: #E40000 } /* Generic.Error */
|
||||
.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||
.codehilite .gi { color: #008400 } /* Generic.Inserted */
|
||||
.codehilite .go { color: #717171 } /* Generic.Output */
|
||||
.codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
|
||||
.codehilite .gs { font-weight: bold } /* Generic.Strong */
|
||||
.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||
.codehilite .gt { color: #0044DD } /* Generic.Traceback */
|
||||
.codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
|
||||
.codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
|
||||
.codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
|
||||
.codehilite .kp { color: #008000 } /* Keyword.Pseudo */
|
||||
.codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
|
||||
.codehilite .kt { color: #B00040 } /* Keyword.Type */
|
||||
.codehilite .m { color: #666666 } /* Literal.Number */
|
||||
.codehilite .s { color: #BA2121 } /* Literal.String */
|
||||
.codehilite .na { color: #687822 } /* Name.Attribute */
|
||||
.codehilite .nb { color: #008000 } /* Name.Builtin */
|
||||
.codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */
|
||||
.codehilite .no { color: #880000 } /* Name.Constant */
|
||||
.codehilite .nd { color: #AA22FF } /* Name.Decorator */
|
||||
.codehilite .ni { color: #717171; font-weight: bold } /* Name.Entity */
|
||||
.codehilite .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
|
||||
.codehilite .nf { color: #0000FF } /* Name.Function */
|
||||
.codehilite .nl { color: #767600 } /* Name.Label */
|
||||
.codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
|
||||
.codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */
|
||||
.codehilite .nv { color: #19177C } /* Name.Variable */
|
||||
.codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
|
||||
.codehilite .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.codehilite .mb { color: #666666 } /* Literal.Number.Bin */
|
||||
.codehilite .mf { color: #666666 } /* Literal.Number.Float */
|
||||
.codehilite .mh { color: #666666 } /* Literal.Number.Hex */
|
||||
.codehilite .mi { color: #666666 } /* Literal.Number.Integer */
|
||||
.codehilite .mo { color: #666666 } /* Literal.Number.Oct */
|
||||
.codehilite .sa { color: #BA2121 } /* Literal.String.Affix */
|
||||
.codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */
|
||||
.codehilite .sc { color: #BA2121 } /* Literal.String.Char */
|
||||
.codehilite .dl { color: #BA2121 } /* Literal.String.Delimiter */
|
||||
.codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
|
||||
.codehilite .s2 { color: #BA2121 } /* Literal.String.Double */
|
||||
.codehilite .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */
|
||||
.codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */
|
||||
.codehilite .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */
|
||||
.codehilite .sx { color: #008000 } /* Literal.String.Other */
|
||||
.codehilite .sr { color: #A45A77 } /* Literal.String.Regex */
|
||||
.codehilite .s1 { color: #BA2121 } /* Literal.String.Single */
|
||||
.codehilite .ss { color: #19177C } /* Literal.String.Symbol */
|
||||
.codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */
|
||||
.codehilite .fm { color: #0000FF } /* Name.Function.Magic */
|
||||
.codehilite .vc { color: #19177C } /* Name.Variable.Class */
|
||||
.codehilite .vg { color: #19177C } /* Name.Variable.Global */
|
||||
.codehilite .vi { color: #19177C } /* Name.Variable.Instance */
|
||||
.codehilite .vm { color: #19177C } /* Name.Variable.Magic */
|
||||
.codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */
|
13
src/md2html/static/template.html
Normal file
13
src/md2html/static/template.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
{css}
|
||||
{script}
|
||||
</head>
|
||||
<body>
|
||||
<article class="markdown-body">
|
||||
{content}
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
25
src/md2html/uwsgi_handler.py
Normal file
25
src/md2html/uwsgi_handler.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import logging
|
||||
from .server import Server
|
||||
from uwsgi import log
|
||||
class UwsgiHandler(logging.Handler):
|
||||
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
log(self.formatter.format(record))
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s [%(threadName)s] (%(name)s) %(levelname)s %(message)s',
|
||||
handlers=[UwsgiHandler()]
|
||||
)
|
||||
|
||||
server = Server()
|
||||
|
||||
def application(env, start_response):
|
||||
return server.handle_request(
|
||||
env['REQUEST_METHOD'],
|
||||
env['PATH_INFO'],
|
||||
env.get('HTTP_IF_NONE_MATCH', None),
|
||||
env.get('QUERY_STRING', None),
|
||||
start_response
|
||||
)
|
Reference in New Issue
Block a user