From 2acd145d0a54c315cb03332e5ef766c3cffc55f7 Mon Sep 17 00:00:00 2001 From: Paul Ferrell Date: Tue, 18 Oct 2016 11:24:46 -0600 Subject: [PATCH 1/3] Minor bug fix, documentation added. --- puzzles.py | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/puzzles.py b/puzzles.py index b31a8f7..c9ba825 100644 --- a/puzzles.py +++ b/puzzles.py @@ -47,10 +47,21 @@ class Puzzle: ANSWER_WORDS = [w.strip() for w in open(os.path.join(os.path.dirname(__file__), 'answer_words.txt'))] - def __init__(self, path, category_seed): - """Puzzle objects need a path to a puzzle description ( - :param path: - :param category_seed: + def __init__(self, category_seed, path=None): + """A MOTH Puzzle + :param category_seed: A byte string to use as a seed for random numbers for this puzzle. + It is combined with the puzzle points. + :param path: An optional path to a puzzle directory. The point value for the puzzle is taken + from the puzzle directories name (it must be an integer greater than zero). + Within this directory, we expect: + (optional) A puzzle.moth file in RFC2822 format. The puzzle will get its attributes + from the headers, and the body will be the puzzle description in + Markdown format. + (optional) A puzzle.py file. This is expected to have a callable called make + that takes a single positional argument (this puzzle object). + This callable can then do whatever it needs to with this object. + If neither of the above are given, the point value for the puzzle will have to + be set manually. """ super().__init__() @@ -63,6 +74,16 @@ class Puzzle: self.message = bytes(random.choice(messageChars) for i in range(20)) self.body = '' + self._seed = hashlib.sha1(category_seed + bytes(self['points'])).digest() + self.rand = random.Random(self._seed) + + # Set our 'files' as a dict, since we want register them uniquely by name. + self['files'] = dict() + + # A list of temporary files we've created that will need to be deleted. + self._temp_files = [] + + # All internal variables must be initialized before the following runs if not os.path.exists(path): raise ValueError("No puzzle at path: {]".format(path)) elif os.path.isdir(path): @@ -86,15 +107,6 @@ class Puzzle: else: raise ValueError("Unacceptable file type for puzzle at {}".format(path)) - self._seed = hashlib.sha1(category_seed + bytes(self['points'])).digest() - self.rand = random.Random(self._seed) - - # Set our 'files' as a dict, since we want register them uniquely by name. - self['files'] = dict() - - # A list of temporary files we've created that will need to be deleted. - self._temp_files = [] - def cleanup(self): """Cleanup any outstanding temporary files.""" for path in self._temp_files: From cc9aa66be130956c95cd7a7e236c1b39995c78b1 Mon Sep 17 00:00:00 2001 From: Paul Ferrell Date: Tue, 18 Oct 2016 13:11:53 -0600 Subject: [PATCH 2/3] Minor bug fix, documentation added. --- puzzles.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/puzzles.py b/puzzles.py index c9ba825..5b3bd98 100644 --- a/puzzles.py +++ b/puzzles.py @@ -21,6 +21,15 @@ def djb2hash(buf): # We use a named tuple rather than a full class, because any random name generation has # to be done with Puzzle's random number generator, and it's cleaner to not pass that around. PuzzleFile = namedtuple('PuzzleFile', ['path', 'handle', 'name', 'visible']) +PuzzleFile.__doc__ = """A file associated with a puzzle. + path: The path to the original input file. May be None (when this is created from a file handle + and there is no original input. + handle: A File-like object set to read the file from. You should be able to read straight + from it without having to seek to the beginning of the file. + name: The name of the output file. + visible: A boolean indicating whether this file should visible to the user. If False, + the file is still expected to be accessible, but it's path must be known + (or figured out) to retrieve it.""" class Puzzle: From 66bf05f213a42e195bca4bdb1719589bb73e5d8b Mon Sep 17 00:00:00 2001 From: Paul Ferrell Date: Tue, 18 Oct 2016 13:29:41 -0600 Subject: [PATCH 3/3] Added a bit more documentation. Changed 'answers' to 'answer', 'hashes' to 'hash' for consistency. --- puzzles.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/puzzles.py b/puzzles.py index 94741ce..3923f89 100644 --- a/puzzles.py +++ b/puzzles.py @@ -46,7 +46,6 @@ class Puzzle: REQUIRED_KEYS = [ 'author', 'answer', - 'points' ] SINGULAR_KEYS = [ 'name' @@ -57,7 +56,7 @@ class Puzzle: 'answer_words.txt'))] def __init__(self, category_seed, path=None, points=None): - """A MOTH Puzzle + """A MOTH Puzzle. :param category_seed: A byte string to use as a seed for random numbers for this puzzle. It is combined with the puzzle points. :param path: An optional path to a puzzle directory. The point value for the puzzle is taken @@ -72,6 +71,14 @@ class Puzzle: :param points: The point value of the puzzle. Mutually exclusive with path. If neither of the above are given, the point value for the puzzle will have to be set at instantiation. + + For puzzle attributes, this class acts like a dictionary that in most cases assigns + always returns a list. Certain keys, however behave differently: + - Keys in Puzzle.SINGULAR_KEYS can only have one value, and writing to these overwrites + that value. + - The keys 'hidden', 'file', and 'resource' all create a new PuzzleFile object that + gets added under the 'files' key. + - The 'answer' also adds a new hash under the the 'hash' key. """ super().__init__() @@ -216,8 +223,8 @@ class Puzzle: if key == 'answer': # Handle adding answers to the puzzle - self._dict['hashes'].append(djb2hash(value.encode('utf8'))) - self._dict['answers'].append(value) + self._dict['hash'].append(djb2hash(value.encode('utf8'))) + self._dict['answer'].append(value) elif key == 'file': # Handle adding files to the puzzle path = os.path.join(self._puzzle_dir, 'files', value)