#!/usr/bin/env python3 import argparse import binascii import glob import hashlib import io import json import os import shutil import sys import zipfile import puzzles TMPFILE = "%s.tmp" 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%s" % (key, val, os.linesep)) else: filehandle.write("%s: %s%s" % (key, kv[key], os.linesep)) filehandle.seek(0) ziphandle.writestr(filename, filehandle.read()) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Build a category package') parser.add_argument('categorydirs', nargs='+', help='Directory of category source') parser.add_argument('outdir', help='Output directory') args = parser.parse_args() for categorydir in args.categorydirs: puzzles_dict = {} secrets = {} categoryname = os.path.basename(categorydir.strip(os.sep)) # create zipfile zipfilename = os.path.join(args.outdir, "%s.zip" % categoryname) if os.path.exists(zipfilename): # open and gather some state zf = zipfile.ZipFile(zipfilename, 'r') try: category_seed = zf.open(os.path.join(categoryname, "category_seed.txt")).read().strip() except: pass zf.close() zf = zipfile.ZipFile(TMPFILE % zipfilename, 'w') if 'category_seed' not in locals(): category_seed = binascii.b2a_hex(os.urandom(20)) # read in category details (will be pflarr in future) for categorypath in glob.glob(os.path.join(categorydir, "*", "puzzle.moth")): points = categorypath.split(os.sep)[-2] # directory before '/puzzle.moth' categorypath = os.path.dirname(categorypath) print(categorypath) try: points = int(points) except: print("Failed to identify points on: %s" % categorypath, file=sys.stderr) continue puzzle = puzzles.Puzzle(category_seed, categorypath) puzzles_dict[points] = puzzle # build mapping, answers, and summary mapping = {} answers = {} summary = {} for points in sorted(puzzles_dict): puzzle = puzzles_dict[points] hashmap = hashlib.sha1(category_seed) hashmap.update(str(points).encode('utf-8')) mapping[points] = hashmap.hexdigest() answers[points] = puzzle['answer'] if len(puzzle['summary']) > 0: summary[points] = puzzle['summary'] # write mapping, answers, summary, category_seed write_kv_pairs(zf, os.path.join(categoryname, 'map.txt'), mapping) write_kv_pairs(zf, os.path.join(categoryname, 'answers.txt'), answers) write_kv_pairs(zf, os.path.join(categoryname, 'summary.txt'), summary) zf.writestr(os.path.join(categoryname, "category_seed.txt"), category_seed) # write out puzzles for points in sorted(puzzles_dict): puzzle = puzzles_dict[points] puzzledir = os.path.join(categoryname, 'content', mapping[points]) puzzledict = { 'author': puzzle.author, 'hashes': puzzle.hashes(), 'files': [f.name for f in puzzle.files if f.visible], 'body': puzzle.html_body(), } secretsdict = { 'summary': puzzle.summary, 'answers': puzzle.answers, } # write associated files assoc_files = [] for fobj in puzzle['files']: if fobj.visible == True: assoc_files.append(fobj.name) zf.writestr(os.path.join(puzzledir, fobj.name), \ fobj.handle.read()) if len(assoc_files) > 0: puzzlejson["associated_files"] = assoc_files # non-optimal writing of file-like objects, but it works zf.writestr(os.path.join(puzzledir, 'puzzle.json'), \ json.dumps(puzzlejson)) # clean up zf.close() shutil.move(TMPFILE % zipfilename, zipfilename) #vim:py