From ee409479efb5af7ea36479f7b35d431f1ba02ee7 Mon Sep 17 00:00:00 2001 From: Shane Date: Mon, 17 Oct 2016 15:37:11 -0600 Subject: [PATCH 01/10] Backward compatible with Python 3.4 --- devel-server.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/devel-server.py b/devel-server.py index 410e109..016a5d6 100755 --- a/devel-server.py +++ b/devel-server.py @@ -8,7 +8,14 @@ import pathlib import puzzles import socketserver -HTTPStatus = http.server.HTTPStatus + +#HTTPStatus = http.server.HTTPStatus +if hasattr(http.server, 'HTTPStatus'): + HTTPStatus = http.HTTPStatus +else: + class HTTPStatus: + NOT_FOUND = 404 + OK = 200 def page(title, body): return """ @@ -125,7 +132,7 @@ you are a fool. return None 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-Length", len(content)) try: From 7469daa6d1f7911a57bc00cbd0836bdc4c8a289a Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Mon, 17 Oct 2016 16:10:41 -0600 Subject: [PATCH 02/10] Fix character set issues on Windows --- devel-server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devel-server.py b/devel-server.py index 410e109..6ec4e74 100755 --- a/devel-server.py +++ b/devel-server.py @@ -93,7 +93,7 @@ you are a fool. body.append("* [puzzles/{cat}/{points}](/puzzles/{cat}/{points}/)".format(cat=parts[2], points=puzzle)) elif len(parts) == 4: body.append("# {} puzzle {}".format(parts[2], parts[3])) - with open("puzzles/{}/{}.moth".format(parts[2], parts[3])) as f: + with open("puzzles/{}/{}.moth".format(parts[2], parts[3]), encoding="utf-8") as f: p = puzzles.Puzzle(f) body.append("* Author: `{}`".format(p.fields.get("author"))) body.append("* Summary: `{}`".format(p.fields.get("summary"))) @@ -126,7 +126,7 @@ you are a fool. content = mdpage(text) self.send_response(http.server.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)) try: fs = fspath.stat() From a72c5f81a8f3fe4624042ef9758f269af7fa9f4a Mon Sep 17 00:00:00 2001 From: Shane Date: Mon, 17 Oct 2016 17:23:03 -0600 Subject: [PATCH 03/10] Created new credits file. --- CREDITS.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 CREDITS.md diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 0000000..6bb89ff --- /dev/null +++ b/CREDITS.md @@ -0,0 +1 @@ +Shannon Steinfadt From dd43df9404a0d740b7c5b7280b557acc8f9c9f54 Mon Sep 17 00:00:00 2001 From: Shane Date: Mon, 17 Oct 2016 17:25:45 -0600 Subject: [PATCH 04/10] credit Neale --- CREDITS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CREDITS.md b/CREDITS.md index 6bb89ff..5d74b0b 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1 +1,2 @@ +Neale Pickett Shannon Steinfadt From 3c4a340652282c0a465ab76d151450a6419651b8 Mon Sep 17 00:00:00 2001 From: Shane Date: Mon, 17 Oct 2016 17:26:53 -0600 Subject: [PATCH 05/10] Add Pat to credits --- CREDITS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CREDITS.md b/CREDITS.md index 5d74b0b..d452e20 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,2 +1,3 @@ Neale Pickett +Patrick Avery Shannon Steinfadt From 0ac8d8e9ac893e2d9f1915d0d7df081f3d87fed7 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 18 Oct 2016 02:28:24 +0000 Subject: [PATCH 06/10] flake8 --- devel-server.py | 3 ++- mistune.py | 2 +- puzzles.py | 7 +++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/devel-server.py b/devel-server.py index 785bb3e..b1d0a3b 100755 --- a/devel-server.py +++ b/devel-server.py @@ -15,6 +15,7 @@ except ImportError: NOT_FOUND = 404 OK = 200 + def page(title, body): return """ @@ -120,7 +121,7 @@ you are a fool. self.serve_md() else: super().do_GET() - + def serve_md(self, text=None): fspathstr = self.translate_path(self.path) fspath = pathlib.Path(fspathstr) diff --git a/mistune.py b/mistune.py index c0f976d..a81c4c1 100644 --- a/mistune.py +++ b/mistune.py @@ -335,7 +335,7 @@ class BlockLexer(object): rest = len(item) if i != length - 1 and rest: - _next = item[rest-1] == '\n' + _next = item[rest - 1] == '\n' if not loose: loose = _next diff --git a/puzzles.py b/puzzles.py index 5b057d6..652b655 100644 --- a/puzzles.py +++ b/puzzles.py @@ -23,6 +23,7 @@ def djb2hash(buf): # to be done with Puzzle's random number generator, and it's cleaner to not pass that around. PuzzleFile = namedtuple('PuzzleFile', ['path', 'handle', 'name', 'visible']) + class Puzzle: KNOWN_KEYS = [ @@ -197,7 +198,6 @@ class Puzzle: return answer - def htmlify(self): return mistune.markdown(self.body) @@ -227,8 +227,8 @@ class Puzzle: 'summary': self['summary'], } return obj - -if __name__ == '__main__': + +if __name__ == '__main__': parser = argparse.ArgumentParser(description='Build a puzzle category') parser.add_argument('puzzledir', nargs='+', help='Directory of puzzle source') args = parser.parse_args() @@ -246,4 +246,3 @@ if __name__ == '__main__': for points in sorted(puzzles): puzzle = puzzles[points] print(puzzle.secrets()) - From c10ad383ad9bfe9dc8daa2e89598521c581ca2fc Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 18 Oct 2016 05:02:05 +0000 Subject: [PATCH 07/10] devel-server use new Puzzles obj. Needs cleanup. --- devel-server.py | 30 +++++++++++++++--------------- puzzles.py | 30 +++++++++++++++++++++++++----- setup.cfg | 8 ++++++++ 3 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 setup.cfg diff --git a/devel-server.py b/devel-server.py index b1d0a3b..9ae36ff 100755 --- a/devel-server.py +++ b/devel-server.py @@ -1,4 +1,4 @@ -#!/usr/local/bin/python3 +#!/usr/bin/env python3 import glob import http.server @@ -15,6 +15,9 @@ except ImportError: NOT_FOUND = 404 OK = 200 +# XXX: This will eventually cause a problem. Do something more clever here. +seed = 1 + def page(title, body): return """ @@ -91,25 +94,22 @@ you are a fool. elif len(parts) == 3: # List all point values in a category body.append("# Puzzles in category `{}`".format(parts[2])) - puzz = [] - for i in glob.glob(os.path.join("puzzles", parts[2], "*.moth")): - base = os.path.basename(i) - root, _ = os.path.splitext(base) - 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)) + fpath = os.path.join("puzzles", parts[2]) + cat = puzzles.Category(fpath, seed) + for points in cat.pointvals: + body.append("* [puzzles/{cat}/{points}](/puzzles/{cat}/{points}/)".format(cat=parts[2], points=points)) elif len(parts) == 4: body.append("# {} puzzle {}".format(parts[2], parts[3])) - with open("puzzles/{}/{}.moth".format(parts[2], parts[3]), encoding="utf-8") as f: - p = puzzles.Puzzle(f) - body.append("* Author: `{}`".format(p.fields.get("author"))) - body.append("* Summary: `{}`".format(p.fields.get("summary"))) + fpath = os.path.join("puzzles", parts[2]) + cat = puzzles.Category(fpath, seed) + p = cat.puzzle(int(parts[3])) + body.append("* Author: `{}`".format(p['author'])) + body.append("* Summary: `{}`".format(p['summary'])) body.append('') body.append("## Body") body.append(p.body) - body.append("## Answers:") - for a in p.answers: + body.append("## Answers") + for a in p['answers']: body.append("* `{}`".format(a)) body.append("") else: diff --git a/puzzles.py b/puzzles.py index 652b655..57b2ddc 100644 --- a/puzzles.py +++ b/puzzles.py @@ -27,7 +27,7 @@ PuzzleFile = namedtuple('PuzzleFile', ['path', 'handle', 'name', 'visible']) class Puzzle: KNOWN_KEYS = [ - 'file', + 'files', 'resource', 'temp_file', 'answer', @@ -60,7 +60,7 @@ class Puzzle: self.body = '' if not os.path.exists(path): - raise ValueError("No puzzle at path: {]".format(path)) + raise ValueError("No puzzle at path: {}".format(path)) elif os.path.isfile(path): try: # Expected format is path/.moth @@ -81,8 +81,8 @@ class Puzzle: files = os.listdir(path) - if 'config.moth' in files: - self._read_config(open(os.path.join(path, 'config.moth'))) + if 'puzzle.moth' in files: + self._read_config(open(os.path.join(path, 'puzzle.moth'))) if 'make.py' in files: # Good Lord this is dangerous as fuck. @@ -93,7 +93,7 @@ class Puzzle: else: raise ValueError("Unacceptable file type for puzzle at {}".format(path)) - self._seed = hashlib.sha1(category_seed + bytes(self['points'])).digest() + self._seed = category_seed * self['points'] self.rand = random.Random(self._seed) # Set our 'files' as a dict, since we want register them uniquely by name. @@ -246,3 +246,23 @@ if __name__ == '__main__': for points in sorted(puzzles): puzzle = puzzles[points] 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) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..3bf77b8 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,8 @@ +[flake8] +# flake8 is an automated code formatting pedant. +# Use it, please. +# +# python3 -m flake8 . +# +ignore = E501 +exclude = .git \ No newline at end of file From 80b80e895c574dcabdfa415c31dac80e6356754d Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 18 Oct 2016 05:20:48 +0000 Subject: [PATCH 08/10] Make it work again --- devel-server.py | 4 ++-- puzzles.py | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/devel-server.py b/devel-server.py index 9ae36ff..9330761 100755 --- a/devel-server.py +++ b/devel-server.py @@ -146,9 +146,9 @@ you are a fool. self.wfile.write(content.encode('utf-8')) -def run(address=('', 8080)): +def run(address=('localhost', 8080)): 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() if __name__ == '__main__': diff --git a/puzzles.py b/puzzles.py index 634049e..b481592 100644 --- a/puzzles.py +++ b/puzzles.py @@ -86,12 +86,9 @@ class Puzzle: else: raise ValueError("Unacceptable file type for puzzle at {}".format(path)) - self._seed = category_seed * self['points'] + self._seed = category_seed * self.points 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 = [] From 0cd6dc97c5828f1113bc81ef468a282635675579 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 18 Oct 2016 09:34:06 -0600 Subject: [PATCH 09/10] Send tracebacks to browser --- devel-server.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/devel-server.py b/devel-server.py index 9330761..5543f83 100755 --- a/devel-server.py +++ b/devel-server.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import cgi import glob import http.server import mistune @@ -7,6 +8,8 @@ import os import pathlib import puzzles import socketserver +import sys +import traceback try: from http.server import HTTPStatus @@ -48,7 +51,20 @@ class ThreadingServer(socketserver.ThreadingMixIn, http.server.HTTPServer): 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): if self.path == "/": self.serve_front() From 881e5a46e1053f815812a3eee2ba0cc263e25a3e Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Tue, 18 Oct 2016 09:51:33 -0600 Subject: [PATCH 10/10] Get generated puzzles working --- puzzles.py | 54 ++++++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/puzzles.py b/puzzles.py index b481592..f9b78cd 100644 --- a/puzzles.py +++ b/puzzles.py @@ -55,6 +55,9 @@ class Puzzle: super().__init__() + if not os.path.isdir(path): + raise ValueError("No such directory: {}".format(path)) + self._dict = defaultdict(lambda: []) if os.path.isdir(path): self._puzzle_dir = path @@ -63,34 +66,32 @@ class Puzzle: self.message = bytes(random.choice(messageChars) for i in range(20)) self.body = '' - if not os.path.exists(path): - raise ValueError("No puzzle at path: {}".format(path)) - elif os.path.isdir(path): - # Expected format is path/.moth - pathname = os.path.split(path)[-1] - try: - self.points = int(pathname) - except ValueError: - pass - files = os.listdir(path) + # A list of temporary files we've created that will need to be deleted. + self._temp_files = [] - if 'puzzle.moth' in files: - self._read_config(open(os.path.join(path, 'puzzle.moth'))) - - if 'puzzle.py' in files: - # Good Lord this is dangerous as fuck. - loader = SourceFileLoader('puzzle_mod', os.path.join(path, 'puzzle.py')) - puzzle_mod = loader.load_module() - if hasattr(puzzle_mod, 'make'): - puzzle_mod.make(self) - else: - raise ValueError("Unacceptable file type for puzzle at {}".format(path)) + # Expected format is path/.moth + pathname = os.path.split(path)[-1] + try: + self.points = int(pathname) + except ValueError: + raise ValueError("Directory name must be a point value: {}".format(path)) + files = os.listdir(path) self._seed = category_seed * self.points self.rand = random.Random(self._seed) - # A list of temporary files we've created that will need to be deleted. - self._temp_files = [] + if 'puzzle.moth' in files: + self._read_config(open(os.path.join(path, 'puzzle.moth'))) + + if 'puzzle.py' in files: + # Good Lord this is dangerous as fuck. + loader = SourceFileLoader('puzzle_mod', os.path.join(path, 'puzzle.py')) + puzzle_mod = loader.load_module() + if hasattr(puzzle_mod, 'make'): + self.body = '# `puzzle.body` was not set by the `make` function' + puzzle_mod.make(self) + else: + self.body = '# `puzzle.py` does not define a `make` function' def cleanup(self): """Cleanup any outstanding temporary files.""" @@ -214,16 +215,13 @@ class Puzzle: def __getitem__(self, item): 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. :param int word_count: The number of words to include in the answer. :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)) self['answer'] = answer