Merge pull request #1 from dirtbags/master

pulling from master
This commit is contained in:
pflarr 2016-10-18 13:14:03 -06:00 committed by GitHub
commit e6c6b129f4
5 changed files with 101 additions and 59 deletions

3
CREDITS.md Normal file
View File

@ -0,0 +1,3 @@
Neale Pickett
Patrick Avery
Shannon Steinfadt

View File

@ -1,5 +1,6 @@
#!/usr/bin/python3 #!/usr/bin/env python3
import cgi
import glob import glob
import http.server import http.server
import mistune import mistune
@ -7,6 +8,8 @@ import os
import pathlib import pathlib
import puzzles import puzzles
import socketserver import socketserver
import sys
import traceback
try: try:
from http.server import HTTPStatus from http.server import HTTPStatus
@ -15,6 +18,9 @@ except ImportError:
NOT_FOUND = 404 NOT_FOUND = 404
OK = 200 OK = 200
# XXX: This will eventually cause a problem. Do something more clever here.
seed = 1
def page(title, body): def page(title, body):
return """<!DOCTYPE html> return """<!DOCTYPE html>
@ -45,7 +51,20 @@ class ThreadingServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
pass pass
class MothHandler(http.server.CGIHTTPRequestHandler): class MothHandler(http.server.SimpleHTTPRequestHandler):
def handle_one_request(self):
try:
super().handle_one_request()
except:
tbtype, value, tb = sys.exc_info()
tblist = traceback.format_tb(tb, None) + traceback.format_exception_only(tbtype, value)
page = ("# Traceback (most recent call last)\n" +
" " +
" ".join(tblist[:-1]) +
tblist[-1])
self.serve_md(page)
def do_GET(self): def do_GET(self):
if self.path == "/": if self.path == "/":
self.serve_front() self.serve_front()
@ -91,25 +110,22 @@ you are a fool.
elif len(parts) == 3: elif len(parts) == 3:
# List all point values in a category # List all point values in a category
body.append("# Puzzles in category `{}`".format(parts[2])) body.append("# Puzzles in category `{}`".format(parts[2]))
puzz = [] fpath = os.path.join("puzzles", parts[2])
for i in glob.glob(os.path.join("puzzles", parts[2], "*.moth")): cat = puzzles.Category(fpath, seed)
base = os.path.basename(i) for points in cat.pointvals:
root, _ = os.path.splitext(base) body.append("* [puzzles/{cat}/{points}](/puzzles/{cat}/{points}/)".format(cat=parts[2], points=points))
points = int(root)
puzz.append(points)
for puzzle in sorted(puzz):
body.append("* [puzzles/{cat}/{points}](/puzzles/{cat}/{points}/)".format(cat=parts[2], points=puzzle))
elif len(parts) == 4: elif len(parts) == 4:
body.append("# {} puzzle {}".format(parts[2], parts[3])) body.append("# {} puzzle {}".format(parts[2], parts[3]))
with open("puzzles/{}/{}.moth".format(parts[2], parts[3])) as f: fpath = os.path.join("puzzles", parts[2])
p = puzzles.Puzzle(f) cat = puzzles.Category(fpath, seed)
body.append("* Author: `{}`".format(p.fields.get("author"))) p = cat.puzzle(int(parts[3]))
body.append("* Summary: `{}`".format(p.fields.get("summary"))) body.append("* Author: `{}`".format(p['author']))
body.append("* Summary: `{}`".format(p['summary']))
body.append('') body.append('')
body.append("## Body") body.append("## Body")
body.append(p.body) body.append(p.body)
body.append("## Answers:") body.append("## Answers")
for a in p.answers: for a in p['answers']:
body.append("* `{}`".format(a)) body.append("* `{}`".format(a))
body.append("") body.append("")
else: else:
@ -133,8 +149,9 @@ you are a fool.
return None return None
content = mdpage(text) content = mdpage(text)
self.send_response(http.server.HTTPStatus.OK) self.send_response(HTTPStatus.OK)
self.send_header("Content-type", "text/html; encoding=utf-8")
self.send_header("Content-type", "text/html; charset=utf-8")
self.send_header("Content-Length", len(content)) self.send_header("Content-Length", len(content))
try: try:
fs = fspath.stat() fs = fspath.stat()
@ -145,9 +162,9 @@ you are a fool.
self.wfile.write(content.encode('utf-8')) self.wfile.write(content.encode('utf-8'))
def run(address=('', 8080)): def run(address=('localhost', 8080)):
httpd = ThreadingServer(address, MothHandler) httpd = ThreadingServer(address, MothHandler)
print("=== Listening on http://{}:{}/".format(address[0] or "localhost", address[1])) print("=== Listening on http://{}:{}/".format(address[0], address[1]))
httpd.serve_forever() httpd.serve_forever()
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -335,7 +335,7 @@ class BlockLexer(object):
rest = len(item) rest = len(item)
if i != length - 1 and rest: if i != length - 1 and rest:
_next = item[rest-1] == '\n' _next = item[rest - 1] == '\n'
if not loose: if not loose:
loose = _next loose = _next

View File

@ -55,6 +55,9 @@ class Puzzle:
super().__init__() super().__init__()
if not os.path.isdir(path):
raise ValueError("No such directory: {}".format(path))
self._dict = defaultdict(lambda: []) self._dict = defaultdict(lambda: [])
if os.path.isdir(path): if os.path.isdir(path):
self._puzzle_dir = path self._puzzle_dir = path
@ -63,17 +66,20 @@ class Puzzle:
self.message = bytes(random.choice(messageChars) for i in range(20)) self.message = bytes(random.choice(messageChars) for i in range(20))
self.body = '' self.body = ''
if not os.path.exists(path): # A list of temporary files we've created that will need to be deleted.
raise ValueError("No puzzle at path: {]".format(path)) self._temp_files = []
elif os.path.isdir(path):
# Expected format is path/<points_int>.moth # Expected format is path/<points_int>.moth
pathname = os.path.split(path)[-1] pathname = os.path.split(path)[-1]
try: try:
self.points = int(pathname) self.points = int(pathname)
except ValueError: except ValueError:
pass raise ValueError("Directory name must be a point value: {}".format(path))
files = os.listdir(path) files = os.listdir(path)
self._seed = category_seed * self.points
self.rand = random.Random(self._seed)
if 'puzzle.moth' in files: if 'puzzle.moth' in files:
self._read_config(open(os.path.join(path, 'puzzle.moth'))) self._read_config(open(os.path.join(path, 'puzzle.moth')))
@ -82,18 +88,10 @@ class Puzzle:
loader = SourceFileLoader('puzzle_mod', os.path.join(path, 'puzzle.py')) loader = SourceFileLoader('puzzle_mod', os.path.join(path, 'puzzle.py'))
puzzle_mod = loader.load_module() puzzle_mod = loader.load_module()
if hasattr(puzzle_mod, 'make'): if hasattr(puzzle_mod, 'make'):
self.body = '# `puzzle.body` was not set by the `make` function'
puzzle_mod.make(self) puzzle_mod.make(self)
else: else:
raise ValueError("Unacceptable file type for puzzle at {}".format(path)) self.body = '# `puzzle.py` does not define a `make` function'
self._seed = hashlib.sha1(category_seed + bytes(self['points'])).digest()
self.rand = random.Random(self._seed)
# Set our 'files' as a dict, since we want register them uniquely by name.
self['files'] = dict()
# A list of temporary files we've created that will need to be deleted.
self._temp_files = []
def cleanup(self): def cleanup(self):
"""Cleanup any outstanding temporary files.""" """Cleanup any outstanding temporary files."""
@ -217,16 +215,13 @@ class Puzzle:
def __getitem__(self, item): def __getitem__(self, item):
return self._dict[item.lower()] return self._dict[item.lower()]
def make_answer(self, word_count, sep=b' '): def make_answer(self, word_count, sep=' '):
"""Generate and return a new answer. It's automatically added to the puzzle answer list. """Generate and return a new answer. It's automatically added to the puzzle answer list.
:param int word_count: The number of words to include in the answer. :param int word_count: The number of words to include in the answer.
:param str|bytes sep: The word separator. :param str|bytes sep: The word separator.
:returns: The answer bytes :returns: The answer string
""" """
if type(sep) == str:
sep = sep.encode('ascii')
answer = sep.join(self.rand.sample(self.ANSWER_WORDS, word_count)) answer = sep.join(self.rand.sample(self.ANSWER_WORDS, word_count))
self['answer'] = answer self['answer'] = answer
@ -270,3 +265,22 @@ if __name__ == '__main__':
puzzle = puzzles[points] puzzle = puzzles[points]
print(puzzle.secrets()) print(puzzle.secrets())
class Category:
def __init__(self, path, seed):
self.path = path
self.seed = seed
self.pointvals = []
for fpath in glob.glob(os.path.join(path, "[0-9]*")):
pn = os.path.basename(fpath)
points = int(pn)
self.pointvals.append(points)
self.pointvals.sort()
def puzzle(self, points):
path = os.path.join(self.path, str(points))
return Puzzle(path, self.seed)
def puzzles(self):
for points in self.pointvals:
yield self.puzzle(points)

8
setup.cfg Normal file
View File

@ -0,0 +1,8 @@
[flake8]
# flake8 is an automated code formatting pedant.
# Use it, please.
#
# python3 -m flake8 .
#
ignore = E501
exclude = .git