mirror of https://github.com/dirtbags/moth.git
Oops, stuff that should have been in last commit
This commit is contained in:
parent
3b51593f2b
commit
e39f1f8da8
|
@ -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/
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue