X-Git-Url: https://git.cworth.org/git?p=turbot-web;a=blobdiff_plain;f=html_generator.py;h=0fef3d421c3f1b9158a7e97932f07b43342b24a6;hp=9b1b1147bb966012b97aeef4331aefb0899fa27a;hb=HEAD;hpb=2190ac00d55a00d00f141fc14f0e8a65a47a49dd diff --git a/html_generator.py b/html_generator.py index 9b1b114..f187032 100644 --- a/html_generator.py +++ b/html_generator.py @@ -14,20 +14,46 @@ that would be great Requires sorttable.js, which should be included """ -from turbot.channel import channel_url +import boto3 from boto3.dynamodb.conditions import Key +import os +import re +import sys -website = "https://halibut.cworth.org/" -#change this if we're using AWS or some other subdomain instead +WEBROOT = "/srv/halibut.cworth.org/www" -def find_hunt_for_hunt_id(turb, hunt_id): + +def hunt_file(hunt, name): + """Return a path file 'name' within the given hunt. + + This will be withing WEBROOT and in a hunt-specific path.""" + + return "{}/{}/{}".format(WEBROOT, hunt['channel_id'], name) + +def internal_link(hunt, name): + """Returns a path for a link on this site.""" + + # Just generate a relative link, (which is just the name itself) + return "{}".format(name) + +def filename_from_name(name): + """Returns a string derived from name, but with all spaces and slashes + replaced with underscores, (for making a clean filename)""" + return re.sub(r'[ /]', '_', name) + +def channel_url(channel_id): + """Given a channel ID, return the URL for that channel.""" + + return "https://halibutthatbass.slack.com/archives/{}".format(channel_id) + +def find_hunt_for_hunt_id(table, hunt_id): """Given a hunt ID find the database item for that hunt Returns None if hunt ID is not found, otherwise a dictionary with all fields from the hunt's row in the table, (channel_id, active, hunt_id, name, url, sheet_url, etc.). """ - response = turb.table.get_item( + response = table.get_item( Key={ 'hunt_id': hunt_id, 'SK': 'hunt-{}'.format(hunt_id) @@ -38,10 +64,10 @@ def find_hunt_for_hunt_id(turb, hunt_id): else: return None -def hunt_puzzles_for_hunt_id(turb, hunt_id): +def hunt_puzzles_for_hunt_id(table, hunt_id): """Return all puzzles that belong to the given hunt_id""" - response = turb.table.query( + response = table.query( KeyConditionExpression=( Key('hunt_id').eq(hunt_id) & Key('SK').begins_with('puzzle-') @@ -59,15 +85,17 @@ def link(lin, text): #internal links, doesn't open new tab return '{}'.format(lin, text) -def hunt_info(turb, hunt): +def hunt_info(table, hunt_id): """ Retrieves list of rounds, puzzles for the given hunt """ + + hunt = find_hunt_for_hunt_id(table, hunt_id) + name = hunt["name"] - hunt_id = hunt["hunt_id"] channel_id = hunt["channel_id"] - puzzles = hunt_puzzles_for_hunt_id(turb, hunt_id) + puzzles = hunt_puzzles_for_hunt_id(table, hunt_id) rounds = set() for puzzle in puzzles: @@ -78,7 +106,7 @@ def hunt_info(turb, hunt): rounds = list(rounds) rounds.sort() - return puzzles, rounds + return hunt, puzzles, rounds def round_stat(rnd, puzzles): #Counts puzzles, solved, list of puzzles for a given round @@ -110,15 +138,16 @@ def round_stat(rnd, puzzles): -def overview(puzzles, rounds): +def overview(hunt, puzzles, rounds): #big board, main page. saves as index.html start = ['\n', '\n', '\n', ' \n', ' \n', + ' \n', '\n', - ' \n', + ' \n', ' \n', '\n', ' Hunt Overview\n', - ' \n' + ' \n' '\n', - '
\n' - ' Hunt Overview' - ' All Puzzles\n' - ' Unsolved\n' - ' Solved\n' - '
\n' + '
\n', + ' Hunt Overview', + ' All Puzzles\n', + ' Unsolved\n', + ' Solved\n', + ' Turbot Docs\n' + '
\n', '\n',] columns = ['
\n'] expanding = [] @@ -159,7 +189,7 @@ def overview(puzzles, rounds): expanding += [ ' \n') end = ['\n', '\n'] html = start + expanding + columns + end - file = "index.html" - f = open(file, "w") + file = hunt_file(hunt, "index.html") + f = open(file + ".tmp", "w") for line in html: f.write(line) f.close() + os.rename(file + ".tmp", file) return None -def round_overview(rnd, puzzles): +def round_overview(hunt, rnd, puzzles): #inputs: round name, puzzles #round overview page #saves as (round name)_round.html, in case meta/round share names. @@ -206,13 +237,14 @@ def round_overview(rnd, puzzles): status = 'unsolved' start = ['\n', ' \n', - ' \n', + ' \n', ' Mystery Hunt 2022\n', - ' \n', + ' \n', + ' \n', ' \n', ' \n'.format(status), '

{}

\n'.format(rnd), - '

{}

\n'.format(link(website + "index.html", 'Hunt Overview')), + '

{}

\n'.format(link(internal_link(hunt, "index") + ".html", 'Hunt Overview')), '
\n', ' \n', ' \n', @@ -238,22 +270,22 @@ def round_overview(rnd, puzzles): if puzzle['status'] == 'solved': puzzle_list += [ ' \n', ' \n'.format(elink(slack_url, puzzle['name']+meta)), - ' \n'.format(elink(puzzle['url'], 'Puzzle')), + ' \n'.format(elink(puzzle.get('url',''), 'Puzzle')), ' \n'.format(elink(puzzle['sheet_url'], 'Sheet')), - ' \n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')), - ' \n'.format(puzzle['solution']), + ' \n'.format(link(internal_link(hunt, filename_from_name(puzzle['name'])) + ".html", 'Overview')), + ' \n'.format(", ".join(puzzle['solution']).upper()), # ' \n', - ' \n'.format("".join(puzzle['tags'])), + ' \n'.format(", ".join(puzzle.get('tags',[]))), ' \n'] else: puzzle_list += [ ' \n', ' \n'.format(elink(slack_url, puzzle['name']+meta)), - ' \n'.format(elink(puzzle['url'], 'Puzzle')), + ' \n'.format(elink(puzzle.get('url',''), 'Puzzle')), ' \n'.format(elink(puzzle['sheet_url'], 'Sheet')), - ' \n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')), + ' \n'.format(link(internal_link(hunt, filename_from_name(puzzle['name'])) + ".html", 'Overview')), ' \n', # ' \n', - ' \n'.format(" ".join(puzzle['tags'])), + ' \n'.format(", ".join(puzzle.get('tags',[]))), ' \n'] end = [' \n', '
{}{}{}{}{}{}{}{}{}{}
{}{}{}{}{}{}{}{}
\n', @@ -261,14 +293,15 @@ def round_overview(rnd, puzzles): ' \n', '\n'] html = start + puzzle_list + end - file = "{}_round.html".format('_'.join(rnd.split())) - f = open(file, "w") + file = hunt_file(hunt, "{}_round.html".format(filename_from_name(rnd))) + f = open(file + ".tmp", "w") for line in html: f.write(line) f.close() + os.rename(file + ".tmp", file) return None -def puzzle_overview(puzzle): +def puzzle_overview(hunt, puzzle): #overview page for individual puzzles. saves as (name of puzzle).html, #with underscores rather than spaces name = puzzle['name'] @@ -277,9 +310,12 @@ def puzzle_overview(puzzle): else: meta = '' slack_url = channel_url(puzzle['channel_id']) - round_url = [link(website + "_".join(rnd.split()) + "_round.html", rnd) for rnd in puzzle['rounds']] + if 'rounds' in puzzle: + round_url = [link(internal_link(hunt, filename_from_name(rnd)) + "_round.html", rnd) for rnd in puzzle['rounds']] + else: + round_url = '' if puzzle['status'] == 'solved': - solution = puzzle['solution'] + solution = ", ".join(puzzle['solution']).upper() status = 'solved' else: solution = "" @@ -289,9 +325,10 @@ def puzzle_overview(puzzle): '\n', ' \n', ' \n', - ' \n', + ' \n', + ' \n', ' {}\n'.format(name+meta), - '

{}

'.format(link(website + 'index.html', 'Hunt Overview')), + '

{}

'.format(link(internal_link(hunt, 'index') + ".html", 'Hunt Overview')), '\n', '\n', '\n'.format(status), @@ -300,7 +337,7 @@ def puzzle_overview(puzzle): ' \n', ' \n', ' \n'.format(elink(slack_url, 'Channel')), #slack channel - ' \n'.format(elink(puzzle['url'], 'Puzzle')), + ' \n'.format(elink(puzzle.get('url',''), 'Puzzle')), ' \n'.format(elink(puzzle['sheet_url'], 'Sheet')), ' \n', ' \n', @@ -308,7 +345,7 @@ def puzzle_overview(puzzle): '
{}{}{}{}Additional Resources
\n', ' \n', ' \n'.format(" ".join(round_url)), #round page on our site - ' \n'.format(" ".join(puzzle['tags'])), #add tags + ' \n'.format(", ".join(puzzle.get('tags',[]))), #add tags ' \n', ' \n', ' \n'.format(solution), @@ -319,14 +356,15 @@ def puzzle_overview(puzzle): '\n', '\n', '\n'] - underscored = "_".join(name.split()) - file = "{}.html".format(underscored) - f = open(file, "w") + file = hunt_file(hunt, "{}.html".format(filename_from_name(name))) + f = open(file + ".tmp", "w") for line in html: f.write(line) + f.close() + os.rename(file + ".tmp", file) return None -def puzzle_lists(puzzles, filt): +def puzzle_lists(hunt, puzzles, filt): #filt is one of "All", "Solved", "Unsolved" and spits out the appropriate list of puzzles #generates pages for all puzzles, solved puzzles, unsolved puzzles #saves as all/solved/unsolved.html, has a sidebar to link to each other and to the big board @@ -334,19 +372,21 @@ def puzzle_lists(puzzles, filt): unsolved_puzzles = [puzzle for puzzle in puzzles if puzzle['status'] != 'solved'] start = ['\n', ' \n', - ' \n', + ' \n', ' Mystery Hunt 2022\n', - ' \n', + ' \n', + ' \n', ' \n', - '
\n' - ' Hunt Overview' - ' All Puzzles\n' - ' Unsolved\n' - ' Solved\n' - '
\n' + '
\n', + ' Hunt Overview', + ' All Puzzles\n', + ' Unsolved\n', + ' Solved\n', + ' Turbot Docs\n' + '
\n', ' \n', '

{}

\n'.format('{} Puzzles').format(filt), - '

{}

\n'.format(link(website + "index.html", 'Hunt Overview')), + '

{}

\n'.format(link(internal_link(hunt, 'index') + ".html", 'Hunt Overview')), '
\n', '
Round(s): {}Tags: {}Tags: {}
Answer: {}
\n', ' \n', @@ -369,17 +409,20 @@ def puzzle_lists(puzzles, filt): else: meta = '' slack_url = channel_url(puzzle['channel_id']) - round_url = link(website + "_".join(rnd.split()) + "_round.html", puzzle['rounds'][0]) + if 'rounds' in puzzle: + round_url = link(internal_link(hunt, filename_from_name(puzzle['rounds'][0])) + "_round.html", puzzle['rounds'][0]) + else: + round_url = '' #assuming one round per puzzle for now solved_code += [' \n', ' \n'.format(elink(slack_url, puzzle['name']+meta)), - ' \n'.format(elink(puzzle['url'], 'Puzzle')), + ' \n'.format(elink(puzzle.get('url',''), 'Puzzle')), ' \n'.format(elink(puzzle['sheet_url'], 'Sheet')), - ' \n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')), - ' \n'.format(puzzle['solution']), + ' \n'.format(link(internal_link(hunt, filename_from_name(puzzle['name'])) + ".html", 'Overview')), + ' \n'.format(", ".join(puzzle['solution']).upper()), ' \n'.format(round_url), - ' \n'.format("".join(puzzle['tags'])), + ' \n'.format(", ".join(puzzle.get('tags',[]))), ' \n'] for puzzle in unsolved_puzzles: if puzzle['type'] == 'meta': @@ -387,17 +430,20 @@ def puzzle_lists(puzzles, filt): else: meta = '' slack_url = channel_url(puzzle['channel_id']) - round_url = link(website + "_".join(rnd.split()) + "_round.html", puzzle['rounds'][0]) + if 'rounds' in puzzle: + round_url = link(internal_link(hunt, filename_from_name(puzzle['rounds'][0])) + "_round.html", puzzle['rounds'][0]) + else: + round_url = '' #assuming one round per puzzle for now unsolved_code += [' \n', ' \n'.format(elink(slack_url, puzzle['name']+meta)), - ' \n'.format(elink(puzzle['url'], 'Puzzle')), + ' \n'.format(elink(puzzle.get('url',''), 'Puzzle')), ' \n'.format(elink(puzzle['sheet_url'], 'Sheet')), - ' \n'.format(link(website + "_".join(puzzle['name'].split()) + '.html', 'Overview')), + ' \n'.format(link(internal_link(hunt, filename_from_name(puzzle['name'])) + ".html", 'Overview')), ' \n', ' \n'.format(round_url), - ' \n'.format("".join(puzzle['tags'])), + ' \n'.format(", ".join(puzzle.get('tags',[]))), ' \n'] end = [' \n', '
{}{}{}{}{}{}{}{}{}{}{}
{}{}{}{}{}{}{}{}{}
\n', @@ -405,34 +451,62 @@ def puzzle_lists(puzzles, filt): ' \n', '\n'] if filt == "All": - file1 = 'all.html' - f = open(file1, "w") + file1 = hunt_file(hunt, 'all.html') + f = open(file1 + ".tmp", "w") for line in start + unsolved_code + solved_code + end: f.write(line) f.close() + os.rename(file1 + ".tmp", file1) elif filt == "Solved": - file2 = 'solved.html' - f = open(file2, 'w') + file2 = hunt_file(hunt, 'solved.html') + f = open(file2 + ".tmp", 'w') for line in start + solved_code + end: f.write(line) f.close() + os.rename(file2 + ".tmp", file2) elif filt == "Unsolved": - file3 = 'unsolved.html' - f = open(file3, 'w') + file3 = hunt_file(hunt, 'unsolved.html') + f = open(file3 + ".tmp", 'w') for line in start + unsolved_code + end: f.write(line) f.close() + os.rename(file3 + ".tmp", file3) return None +def generate_for_hunt_id(table, hunt_id): + hunt, puzzles, rounds = hunt_info(table, hunt_id) + + # Create a directory for the hunt in the WEBROOT + root = hunt_file(hunt, "") + try: + os.mkdir(root) + except FileExistsError: + # We're happy as a clam if the directory already exists + pass + + overview(hunt, puzzles, rounds) + for rnd in rounds: + round_overview(hunt, rnd, puzzles) + for puzzle in puzzles: + puzzle_overview(hunt, puzzle) + puzzle_lists(hunt, puzzles, "All") + puzzle_lists(hunt, puzzles, "Solved") + puzzle_lists(hunt, puzzles, "Unsolved") + + +# Initialize AWS resources to talk to the database +db = boto3.resource('dynamodb') +table = db.Table("turbot") + +def usage(): + print("Usage: {} hunt_id [...]") + print("") + print("Generates pages (under {}) ".format(WEBROOT)) + print("for the specified hunt_id(s).") +if len(sys.argv) < 2: + usage() + sys.exit(1) -puzzles, rounds = hunt_info(turb, hunt) -#I am not sure where these come from -overview(puzzles, rounds) -for rnd in rounds: - round_overview(rnd, puzzles) -for puzzle in puzzles: - puzzle_overview(puzzle) -puzzle_lists(puzzles, "All") -puzzle_lists(puzzles, "Solved") -puzzle_lists(puzzles, "Unsolved") +for hunt_id in sys.argv[1:]: + generate_for_hunt_id(table, hunt_id)