Remove external library dependencies

This commit is contained in:
Neale Pickett 2019-02-26 16:27:20 -07:00
parent 0ff3679b2c
commit 041b582935
3 changed files with 1502 additions and 135 deletions

View File

@ -3,7 +3,8 @@
import asyncio import asyncio
import cgitb import cgitb
import html import html
from aiohttp import web import cgi
import http.server
import io import io
import json import json
import mimetypes import mimetypes
@ -17,130 +18,134 @@ import socketserver
import sys import sys
import traceback import traceback
import mothballer import mothballer
import parse
from http import HTTPStatus
sys.dont_write_bytecode = True # Don't write .pyc files sys.dont_write_bytecode = True # Don't write .pyc files
def get_seed(request): class MothServer(http.server.ThreadingHTTPServer):
seedstr = request.match_info.get("seed") def __init__(self, server_address, RequestHandlerClass):
if seedstr == "random": super().__init__(server_address, RequestHandlerClass)
return random.getrandbits(32) self.args = {}
else:
return int(seedstr)
def get_puzzle(request, data=None): class MothRequestHandler(http.server.SimpleHTTPRequestHandler):
seed = get_seed(request) endpoints = []
if not data:
data = request.match_info
category = data.get("cat")
points = int(data.get("points"))
filename = data.get("filename")
cat = moth.Category(request.app["puzzles_dir"].joinpath(category), seed)
puzzle = cat.puzzle(points)
return puzzle
async def handle_answer(request): def __init__(self, request, client_address, server):
data = await request.post() super().__init__(request, client_address, server, directory=server.args["theme_dir"])
puzzle = get_puzzle(request, data)
ret = {
"status": "success",
"data": {
"short": "",
"description": "Provided answer was not in list of answers"
},
}
if data.get("answer") in puzzle.answers:
ret["data"]["description"] = "Answer is correct"
return web.Response(
content_type="application/json",
body=json.dumps(ret),
)
async def handle_puzzlelist(request): def get_seed(self):
seed = get_seed(request) return seed
puzzles = {
"__devel__": [[0, ""]],
}
for p in request.app["puzzles_dir"].glob("*"):
if not p.is_dir() or p.match(".*"):
continue
catName = p.parts[-1]
cat = moth.Category(p, seed)
puzzles[catName] = [[i, str(i)] for i in cat.pointvals()]
puzzles[catName].append([0, ""])
if len(puzzles) <= 1:
logging.warning("No directories found matching {}/*".format(request.app["puzzles_dir"]))
return web.Response(
content_type="application/json",
body=json.dumps(puzzles),
)
async def handle_puzzle(request): def get_puzzle(self):
seed = get_seed(request) category = self.req.get("cat")
category = request.match_info.get("cat") points = int(self.req.get("points"))
points = int(request.match_info.get("points")) cat = moth.Category(self.server.args["puzzles_dir"].joinpath(category), self.seed)
cat = moth.Category(request.app["puzzles_dir"].joinpath(category), seed) puzzle = cat.puzzle(points)
puzzle = cat.puzzle(points) return puzzle
obj = puzzle.package()
obj["answers"] = puzzle.answers
obj["hint"] = puzzle.hint
obj["summary"] = puzzle.summary
obj["logs"] = puzzle.logs
return web.Response(
content_type="application/json",
body=json.dumps(obj),
)
async def handle_puzzlefile(request): def handle_answer(self):
seed = get_seed(request) for f in ("cat", "points", "answer"):
category = request.match_info.get("cat") self.req[f] = self.fields.getfirst(f)
points = int(request.match_info.get("points")) puzzle = self.get_puzzle()
filename = request.match_info.get("filename") ret = {
cat = moth.Category(request.app["puzzles_dir"].joinpath(category), seed) "status": "success",
puzzle = cat.puzzle(points) "data": {
"short": "",
"description": "Provided answer was not in list of answers"
},
}
try: if self.req.get("answer") in puzzle.answers:
file = puzzle.files[filename] ret["data"]["description"] = "Answer is correct"
except KeyError: self.send_response(200)
return web.Response(status=404) self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps(ret).encode("utf-8"))
endpoints.append(('/{seed}/answer', handle_answer))
content_type, _ = mimetypes.guess_type(file.name)
return web.Response(
body=file.stream.read(), # Is there no way to pipe this, must we slurp the whole thing into memory?
content_type=content_type,
)
async def handle_mothballer(request): def handle_puzzlelist(self):
seed = get_seed(request) puzzles = {
category = request.match_info.get("cat") "__devel__": [[0, ""]],
}
for p in self.server.args["puzzles_dir"].glob("*"):
if not p.is_dir() or p.match(".*"):
continue
catName = p.parts[-1]
cat = moth.Category(p, self.seed)
puzzles[catName] = [[i, str(i)] for i in cat.pointvals()]
puzzles[catName].append([0, ""])
if len(puzzles) <= 1:
logging.warning("No directories found matching {}/*".format(self.server.args["puzzles_dir"]))
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps(puzzles).encode("utf-8"))
endpoints.append(('/{seed}/puzzles.json', handle_puzzlelist))
try:
catdir = request.app["puzzles_dir"].joinpath(category) def handle_puzzle(self):
mb = mothballer.package(category, catdir, seed) puzzle = self.get_puzzle()
except:
body = cgitb.html(sys.exc_info()) obj = puzzle.package()
resp = web.Response(text=body, content_type="text/html") obj["answers"] = puzzle.answers
obj["hint"] = puzzle.hint
obj["summary"] = puzzle.summary
obj["logs"] = puzzle.logs
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps(obj).encode("utf-8"))
endpoints.append(('/{seed}/content/{cat}/{points}/puzzle.json', handle_puzzle))
def handle_puzzlefile(self):
puzzle = self.get_puzzle()
try:
file = puzzle.files[self.req["filename"]]
except KeyError:
return web.Response(status=404)
self.send_response(200)
self.send_header("Content-Type", mimetypes.guess_type(file.name))
self.end_headers()
shutil.copyfileobj(file.stream, self.wfile)
endpoints.append(("/{seed}/content/{cat}/{points}/{filename}", handle_puzzlefile))
def handle_mothballer(self):
category = self.req.get("cat")
try:
catdir = self.server.args["puzzles_dir"].joinpath(category)
mb = mothballer.package(category, catdir, self.seed)
except:
body = cgitb.html(sys.exc_info())
resp = web.Response(text=body, content_type="text/html")
return resp
mb_buf = mb.read()
resp = web.Response(
body=mb_buf,
headers={"Content-Disposition": "attachment; filename={}.mb".format(category)},
content_type="application/octet_stream",
)
return resp return resp
endpoints.append(("/{seed}/mothballer/{cat}", handle_mothballer))
mb_buf = mb.read()
resp = web.Response(
body=mb_buf,
headers={"Content-Disposition": "attachment; filename={}.mb".format(category)},
content_type="application/octet_stream",
)
return resp
async def handle_index(request): def handle_index(self):
seed = random.getrandbits(32) seed = self.get_seed()
body = """<!DOCTYPE html> body = """<!DOCTYPE html>
<html> <html>
<head> <head>
<title>Dev Server</title> <title>Dev Server</title>
@ -177,19 +182,51 @@ sessionStorage.setItem("id", "devel-server")
</body> </body>
</html> </html>
""".format(seed=seed) """.format(seed=seed)
return web.Response(
content_type="text/html", self.send_response(200)
body=body, self.send_header("Content-Type", "text/html; charset=utf-8")
) self.end_headers()
self.wfile.write(body.encode('utf-8'))
endpoints.append((r"/", handle_index))
async def handle_static(request): def handle_theme_file(self):
themes = request.app["theme_dir"] self.path = "/" + self.req.get("path", "")
fn = request.match_info.get("filename") super().do_GET()
if not fn: endpoints.append(("/{seed}/", handle_theme_file))
fn = "index.html" endpoints.append(("/{seed}/{path}", handle_theme_file))
path = themes.joinpath(fn)
return web.FileResponse(path)
def do_GET(self):
self.fields = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={
"REQUEST_METHOD": self.command,
"CONTENT_TYPE": self.headers["Content-Type"],
},
)
for pattern, function in self.endpoints:
result = parse.parse(pattern, self.path)
if result:
self.req = result.named
seed = self.req.get("seed", "random")
if seed == "random":
self.seed = random.getrandbits(32)
else:
self.seed = int(seed)
return function(self)
super().do_GET()
def do_POST(self):
self.do_GET()
def do_HEAD(self):
self.send_error(
HTTPStatus.NOT_IMPLEMENTED,
"Unsupported method (%r)" % self.command,
)
if __name__ == '__main__': if __name__ == '__main__':
@ -220,15 +257,10 @@ if __name__ == '__main__':
mydir = os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))) mydir = os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0])))
app = web.Application() server = MothServer((addr, port), MothRequestHandler)
app["base_url"] = args.base server.args["base_url"] = args.base
app["puzzles_dir"] = pathlib.Path(args.puzzles) server.args["puzzles_dir"] = pathlib.Path(args.puzzles)
app["theme_dir"] = pathlib.Path(args.theme) server.args["theme_dir"] = args.theme
app.router.add_route("GET", "/", handle_index)
app.router.add_route("*", "/{seed}/answer", handle_answer) logging.info("Listening on %s:%d", addr, port)
app.router.add_route("*", "/{seed}/puzzles.json", handle_puzzlelist) server.serve_forever()
app.router.add_route("GET", "/{seed}/content/{cat}/{points}/puzzle.json", handle_puzzle)
app.router.add_route("GET", "/{seed}/content/{cat}/{points}/{filename}", handle_puzzlefile)
app.router.add_route("GET", "/{seed}/mothballer/{cat}", handle_mothballer)
app.router.add_route("GET", "/{seed}/{filename:.*}", handle_static)
web.run_app(app, host=addr, port=port)

1335
devel/parse.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
package main package main
import ( import (
"github.com/namsral/flag" "flag"
"log" "log"
"math/rand" "math/rand"
"mime" "mime"