Oops, stuff that should have been in last commit

This commit is contained in:
Neale Pickett 2016-12-01 16:20:04 -07:00
parent a90d673b01
commit 06e0bddb1a
6 changed files with 53 additions and 22 deletions

View File

@ -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/

View File

@ -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

View File

@ -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,

View File

@ -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("<img src='salad.jpg' alt='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))

View File

@ -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 """<!DOCTYPE html>
<html>
@ -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("<ul>")
for i in sorted(glob.glob(os.path.join("puzzles", "*", ""))):
body.write('<li><a href="{}">{}</a></li>'.format(i, i))
for i in sorted(glob.glob(os.path.join(self.puzzles_dir, "*", ""))):
bn = os.path.basename(i.strip('/\\'))
body.write('<li><a href="/puzzles/{}">puzzles/{}/</a></li>'.format(bn, bn))
body.write("</ul>")
elif not puzzle:
# List all point values in a category
title = "Puzzles in category `{}`".format(parts[2])
body.write("<ul>")
for points in cat.pointvals:
body.write('<li><a href="/puzzles/{cat}/{points}">puzzles/{cat}/{points}</a></li>'.format(cat=parts[2], points=points))
body.write('<li><a href="/puzzles/{cat}/{points}/">puzzles/{cat}/{points}/</a></li>'.format(cat=parts[2], points=points))
body.write("</ul>")
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]))
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)

View File

@ -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,11 +116,11 @@ class Puzzle:
except FileNotFoundError:
puzzle_mod = None
if puzzle_mod:
with pushd(path):
if puzzle_mod:
puzzle_mod.make(self)
else:
with open(os.path.join(path, 'puzzle.moth')) as f:
with open('puzzle.moth') as f:
self.read_stream(f)
def random_hash(self):
@ -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.