diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..c5879ef
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,15 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+### Added
+- A changelog
+- Support for embedding Python libraries at the category or puzzle level
+- Embedded graph in scoreboard
+- Optional tracking of participant IDs
+- New `notices.html` file for sending broadcast messages to players
+### Changed
+- Use native JS URL objects instead of wrangling everything by hand
diff --git a/VERSION b/VERSION
index 5436ea0..b2696f2 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.3.2
\ No newline at end of file
+3.4_rc1
diff --git a/devel/devel-server.py b/devel/devel-server.py
index 635c22a..0d775dc 100755
--- a/devel/devel-server.py
+++ b/devel/devel-server.py
@@ -266,12 +266,24 @@ if __name__ == '__main__':
'--base', default="",
help="Base URL to this server, for reverse proxy setup"
)
+ parser.add_argument(
+ "-v", "--verbose",
+ action="count",
+ default=1, # Leave at 1, for now, to maintain current default behavior
+ help="Include more verbose logging. Use multiple flags to increase level",
+ )
args = parser.parse_args()
parts = args.bind.split(":")
addr = parts[0] or "0.0.0.0"
port = int(parts[1])
+ if args.verbose >= 2:
+ log_level = logging.DEBUG
+ elif args.verbose == 1:
+ log_level = logging.INFO
+ else:
+ log_level = logging.WARNING
- logging.basicConfig(level=logging.INFO)
+ logging.basicConfig(level=log_level)
server = MothServer((addr, port), MothRequestHandler)
server.args["base_url"] = args.base
diff --git a/devel/moth.py b/devel/moth.py
index 7161029..623a44e 100644
--- a/devel/moth.py
+++ b/devel/moth.py
@@ -2,21 +2,26 @@
import argparse
import contextlib
+import copy
import glob
import hashlib
import html
import io
import importlib.machinery
+import logging
import mistune
import os
import random
import string
+import sys
import tempfile
import shlex
import yaml
messageChars = b'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
+LOGGER = logging.getLogger(__name__)
+
def djb2hash(str):
h = 5381
for c in str.encode("utf-8"):
@@ -26,10 +31,28 @@ def djb2hash(str):
@contextlib.contextmanager
def pushd(newdir):
curdir = os.getcwd()
+ LOGGER.debug("Attempting to chdir from %s to %s" % (curdir, newdir))
os.chdir(newdir)
+
+ # Force a copy of the old path, instead of just a reference
+ old_path = list(sys.path)
+ old_modules = copy.copy(sys.modules)
+ sys.path.append(newdir)
+
try:
yield
finally:
+ # Restore the old path
+ to_remove = []
+ for module in sys.modules:
+ if module not in old_modules:
+ to_remove.append(module)
+
+ for module in to_remove:
+ del(sys.modules[module])
+
+ sys.path = old_path
+ LOGGER.debug("Changing directory back from %s to %s" % (newdir, curdir))
os.chdir(curdir)
@@ -136,8 +159,10 @@ class Puzzle:
stream.seek(0)
if header == "yaml":
+ LOGGER.info("Puzzle is YAML-formatted")
self.read_yaml_header(stream)
elif header == "moth":
+ LOGGER.info("Puzzle is MOTH-formatted")
self.read_moth_header(stream)
for line in stream:
@@ -172,6 +197,7 @@ class Puzzle:
self.handle_header_key(key, val)
def handle_header_key(self, key, val):
+ LOGGER.debug("Handling key: %s, value: %s", key, val)
if key == 'author':
self.authors.append(val)
elif key == 'authors':
@@ -199,6 +225,7 @@ class Puzzle:
parts = shlex.split(val)
name = parts[0]
hidden = False
+ LOGGER.debug("Attempting to open %s", os.path.abspath(name))
stream = open(name, 'rb')
try:
name = parts[1]
@@ -367,7 +394,7 @@ class Puzzle:
files = [fn for fn,f in self.files.items() if f.visible]
return {
- 'authors': self.authors,
+ 'authors': self.get_authors(),
'hashes': self.hashes(),
'files': files,
'scripts': self.scripts,
@@ -415,7 +442,8 @@ class Category:
with pushd(self.path):
self.catmod.make(points, puzzle)
else:
- puzzle.read_directory(path)
+ with pushd(self.path):
+ puzzle.read_directory(path)
return puzzle
def __iter__(self):
diff --git a/devel/package-puzzles.py b/devel/package-puzzles.py
deleted file mode 100755
index cbd7429..0000000
--- a/devel/package-puzzles.py
+++ /dev/null
@@ -1,169 +0,0 @@
-#!/usr/bin/env python3
-
-import argparse
-import binascii
-import glob
-import hashlib
-import io
-import json
-import logging
-import moth
-import os
-import shutil
-import string
-import sys
-import tempfile
-import zipfile
-
-def write_kv_pairs(ziphandle, filename, kv):
- """ Write out a sorted map to file
- :param ziphandle: a zipfile object
- :param filename: The filename to write within the zipfile object
- :param kv: the map to write out
- :return:
- """
- filehandle = io.StringIO()
- for key in sorted(kv.keys()):
- if type(kv[key]) == type([]):
- for val in kv[key]:
- filehandle.write("%s %s\n" % (key, val))
- else:
- filehandle.write("%s %s\n" % (key, kv[key]))
- filehandle.seek(0)
- ziphandle.writestr(filename, filehandle.read())
-
-def escape(s):
- return s.replace('&', '&').replace('<', '<').replace('>', '>')
-
-def generate_html(ziphandle, puzzle, puzzledir, category, points, authors, files):
- html_content = io.StringIO()
- file_content = io.StringIO()
- if files:
- file_content.write(
-''' Associated files:
-
-''')
- for fn in files:
- file_content.write('
-