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 Getting Started Developing
------------------------------- -------------------------------
$ git clone $your_puzzles_repo puzzles
$ python3 tools/devel-server.py $ python3 tools/devel-server.py
Then point a web browser at http://localhost:8080/ Then point a web browser at http://localhost:8080/

View File

@ -13,17 +13,17 @@ Puzzle categories are laid out on the filesystem:
├─3 ├─3
│ └─puzzle.py │ └─puzzle.py
├─10 ├─10
│ └─puzzle.py │ └─puzzle.moth
└─100 └─100
└─puzzle.moth └─puzzle.py
In this example, In this example,
there are puzzles with point values 1, 2, 3, 10, and 100. 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. 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. they are generated from a Python module.
To create a static puzzle, all you must have is a To create a static puzzle, all you must have is a

View File

@ -1,5 +1,6 @@
Author: neale Author: neale
Summary: Static puzzle resource files Summary: Static puzzle resource files
File: salad.jpg
Answer: salad Answer: salad
You can include additional resources in a static puzzle, 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("(Participants don't like it when puzzles and answers change.)\n")
puzzle.body.write("\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) number = puzzle.rand.randint(20, 500)
puzzle.log("One is the loneliest number, but {} is the saddest number.".format(number)) 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 #!/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 glob
import html import html
import http.server import http.server
@ -26,7 +31,6 @@ sys.dont_write_bytecode = True
# XXX: This will eventually cause a problem. Do something more clever here. # XXX: This will eventually cause a problem. Do something more clever here.
seed = 1 seed = 1
def page(title, body): def page(title, body):
return """<!DOCTYPE html> return """<!DOCTYPE html>
<html> <html>
@ -58,6 +62,8 @@ class ThreadingServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
class MothHandler(http.server.SimpleHTTPRequestHandler): class MothHandler(http.server.SimpleHTTPRequestHandler):
puzzles_dir = "puzzles"
def handle_one_request(self): def handle_one_request(self):
try: try:
super().handle_one_request() super().handle_one_request()
@ -122,7 +128,7 @@ you are a fool.
puzzle = None puzzle = None
try: try:
fpath = os.path.join("puzzles", parts[2]) fpath = os.path.join(self.puzzles_dir, parts[2])
points = int(parts[3]) points = int(parts[3])
except: except:
pass pass
@ -135,15 +141,16 @@ you are a fool.
if not cat: if not cat:
title = "Puzzle Categories" title = "Puzzle Categories"
body.write("<ul>") body.write("<ul>")
for i in sorted(glob.glob(os.path.join("puzzles", "*", ""))): for i in sorted(glob.glob(os.path.join(self.puzzles_dir, "*", ""))):
body.write('<li><a href="{}">{}</a></li>'.format(i, i)) bn = os.path.basename(i.strip('/\\'))
body.write('<li><a href="/puzzles/{}">puzzles/{}/</a></li>'.format(bn, bn))
body.write("</ul>") body.write("</ul>")
elif not puzzle: elif not puzzle:
# List all point values in a category # List all point values in a category
title = "Puzzles in category `{}`".format(parts[2]) title = "Puzzles in category `{}`".format(parts[2])
body.write("<ul>") body.write("<ul>")
for points in cat.pointvals: 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>") body.write("</ul>")
elif len(parts) == 4: elif len(parts) == 4:
# Serve up a puzzle # Serve up a puzzle
@ -175,7 +182,7 @@ you are a fool.
try: try:
pfile = puzzle.files[parts[4]] pfile = puzzle.files[parts[4]]
except KeyError: 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 return
ctype = self.guess_type(pfile.name) ctype = self.guess_type(pfile.name)
self.send_response(HTTPStatus.OK) self.send_response(HTTPStatus.OK)
@ -226,10 +233,22 @@ you are a fool.
self.wfile.write(payload) self.wfile.write(payload)
def run(address=('localhost', 8080)): def run(address=('localhost', 8080), once=False):
httpd = ThreadingServer(address, MothHandler) httpd = ThreadingServer(address, MothHandler)
print("=== Listening on http://{}:{}/".format(address[0], address[1])) print("=== Listening on http://{}:{}/".format(address[0], address[1]))
httpd.serve_forever() if once:
httpd.handle_request()
else:
httpd.serve_forever()
if __name__ == '__main__': 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): def log(self, msg):
"""Add a new log message to this puzzle.""" """Add a new log message to this puzzle."""
self.logs.append(msg) self.logs.append(str(msg))
def read_stream(self, stream): def read_stream(self, stream):
header = True header = True
@ -116,12 +116,12 @@ class Puzzle:
except FileNotFoundError: except FileNotFoundError:
puzzle_mod = None puzzle_mod = None
if puzzle_mod: with pushd(path):
with pushd(path): if puzzle_mod:
puzzle_mod.make(self) puzzle_mod.make(self)
else: else:
with open(os.path.join(path, 'puzzle.moth')) as f: with open('puzzle.moth') as f:
self.read_stream(f) self.read_stream(f)
def random_hash(self): def random_hash(self):
"""Create a file basename (no extension) with our number generator.""" """Create a file basename (no extension) with our number generator."""
@ -146,12 +146,17 @@ class Puzzle:
name = self.random_hash() name = self.random_hash()
self.files[name] = PuzzleFile(stream, name, visible) 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): def randword(self):
"""Return a randomly-chosen word""" """Return a randomly-chosen word"""
return self.rand.choice(ANSWER_WORDS) 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. """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.