diff --git a/package-puzzles b/package-puzzles new file mode 100755 index 0000000..344d85a --- /dev/null +++ b/package-puzzles @@ -0,0 +1,118 @@ +#!/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]) + puzzlejson = puzzle.publish() + + # 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(puzzle.publish())) + + # clean up + zf.close() + shutil.move(TMPFILE % zipfilename, zipfilename) +#vim:py