diff --git a/README.md b/README.md index c167201..ac36fca 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ for details. Getting Started Developing ------------------------------- - $ git clone $your_puzzles_repo puzzles $ python3 tools/devel-server.py Then point a web browser at http://localhost:8080/ diff --git a/example-puzzles/example/1/puzzle.moth b/example-puzzles/example/1/puzzle.moth index 294c99c..8f06395 100644 --- a/example-puzzles/example/1/puzzle.moth +++ b/example-puzzles/example/1/puzzle.moth @@ -13,17 +13,17 @@ Puzzle categories are laid out on the filesystem: ├─3 │ └─puzzle.py ├─10 - │ └─puzzle.py + │ └─puzzle.moth └─100 - └─puzzle.moth + └─puzzle.py In this example, there are puzzles with point values 1, 2, 3, 10, and 100. -Puzzles 1, 2, and 100 are "static" puzzles: +Puzzles 1, 2, and 10 are "static" puzzles: their content was written by hand. -Puzzles 3 and 10 are "dynamic" puzzles: +Puzzles 3 and 100 are "dynamic" puzzles: they are generated from a Python module. To create a static puzzle, all you must have is a diff --git a/example-puzzles/example/2/puzzle.moth b/example-puzzles/example/2/puzzle.moth index 61b63ec..d909c6c 100644 --- a/example-puzzles/example/2/puzzle.moth +++ b/example-puzzles/example/2/puzzle.moth @@ -1,5 +1,6 @@ Author: neale Summary: Static puzzle resource files +File: salad.jpg Answer: salad You can include additional resources in a static puzzle, diff --git a/example-puzzles/example/3/puzzle.py b/example-puzzles/example/3/puzzle.py index 39636f7..a5e93df 100644 --- a/example-puzzles/example/3/puzzle.py +++ b/example-puzzles/example/3/puzzle.py @@ -14,7 +14,14 @@ def make(puzzle): puzzle.body.write("(Participants don't like it when puzzles and answers change.)\n") puzzle.body.write("\n") + puzzle.add_file('salad.jpg') + puzzle.body.write("Here are some more pictures of salad:\n") + puzzle.body.write("Markdown lets you insert raw HTML if you want") + puzzle.body.write("![salad](salad.jpg)") + puzzle.body.write("\n\n") + number = puzzle.rand.randint(20, 500) puzzle.log("One is the loneliest number, but {} is the saddest number.".format(number)) - puzzle.body.write("The answer for this page is {}.\n".format(answer)) + puzzle.body.write("The answer for this page is `{}`.\n".format(answer)) + diff --git a/tools/devel-server.py b/tools/devel-server.py index 4b9ecd2..3b934e1 100755 --- a/tools/devel-server.py +++ b/tools/devel-server.py @@ -1,5 +1,10 @@ #!/usr/bin/env python3 +# To pick up any changes to this file without restarting anything: +# while true; do ./tools/devel-server.py --once; done +# It's kludgy, but it gets the job done. +# Feel free to make it suck less, for example using the `tcpserver` program. + import glob import html import http.server @@ -26,7 +31,6 @@ sys.dont_write_bytecode = True # XXX: This will eventually cause a problem. Do something more clever here. seed = 1 - def page(title, body): return """ @@ -58,6 +62,8 @@ class ThreadingServer(socketserver.ThreadingMixIn, http.server.HTTPServer): class MothHandler(http.server.SimpleHTTPRequestHandler): + puzzles_dir = "puzzles" + def handle_one_request(self): try: super().handle_one_request() @@ -122,7 +128,7 @@ you are a fool. puzzle = None try: - fpath = os.path.join("puzzles", parts[2]) + fpath = os.path.join(self.puzzles_dir, parts[2]) points = int(parts[3]) except: pass @@ -135,15 +141,16 @@ you are a fool. if not cat: title = "Puzzle Categories" body.write("") elif not puzzle: # List all point values in a category title = "Puzzles in category `{}`".format(parts[2]) body.write("") elif len(parts) == 4: # Serve up a puzzle @@ -175,7 +182,7 @@ you are a fool. try: pfile = puzzle.files[parts[4]] except KeyError: - self.send_error(HTTPStatus.NOT_FOUND, "File not found") + self.send_error(HTTPStatus.NOT_FOUND, "File not found. Did you add it to the Files: header or puzzle.add_stream?") return ctype = self.guess_type(pfile.name) self.send_response(HTTPStatus.OK) @@ -226,10 +233,22 @@ you are a fool. self.wfile.write(payload) -def run(address=('localhost', 8080)): +def run(address=('localhost', 8080), once=False): httpd = ThreadingServer(address, MothHandler) print("=== Listening on http://{}:{}/".format(address[0], address[1])) - httpd.serve_forever() + if once: + httpd.handle_request() + else: + httpd.serve_forever() if __name__ == '__main__': - run() + import argparse + + parser = argparse.ArgumentParser(description="MOTH puzzle development server") + parser.add_argument('--puzzles', default='puzzles', + help="Directory containing your puzzles") + parser.add_argument('--once', default=False, action='store_true', + help="Serve one page, then exit. For debugging the server.") + args = parser.parse_args() + MothHandler.puzzles_dir = args.puzzles + run(once=args.once) diff --git a/tools/moth.py b/tools/moth.py index 7ba1a23..017f882 100644 --- a/tools/moth.py +++ b/tools/moth.py @@ -74,7 +74,7 @@ class Puzzle: def log(self, msg): """Add a new log message to this puzzle.""" - self.logs.append(msg) + self.logs.append(str(msg)) def read_stream(self, stream): header = True @@ -116,12 +116,12 @@ class Puzzle: except FileNotFoundError: puzzle_mod = None - if puzzle_mod: - with pushd(path): + with pushd(path): + if puzzle_mod: puzzle_mod.make(self) - else: - with open(os.path.join(path, 'puzzle.moth')) as f: - self.read_stream(f) + else: + with open('puzzle.moth') as f: + self.read_stream(f) def random_hash(self): """Create a file basename (no extension) with our number generator.""" @@ -146,12 +146,17 @@ class Puzzle: name = self.random_hash() self.files[name] = PuzzleFile(stream, name, visible) + def add_file(self, filename, visible=True): + fd = open(filename, 'rb') + name = os.path.basename(filename) + self.add_stream(fd, name=name, visible=visible) + def randword(self): """Return a randomly-chosen word""" return self.rand.choice(ANSWER_WORDS) - def make_answer(self, word_count, sep=' '): + def make_answer(self, word_count=4, 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.