import sys import os import re from nevow.loaders import stan from nevow.flat import flatten from nevow import tags as T from nevow import entities def htmlquote(s): s = s.replace("&", "&") return s def categoryquote(s): s = s.replace("/", "") return s longwords = set() def hyphenate(s, maxwidth=8): swaps = { 'AUTOGRAPHIING:': 'AUTO GRAPHING', 'ROBOT-MANGA-WARRIOR-PIRATE-FIGHTING': 'ROBOT- MANGA- WARRIOR- PIRATE- FIGHTING', } words = s.split() for offset, word in enumerate(words): if len(word) > maxwidth: if word in swaps: print "Swapping out %s with %s" % (`word`, `swaps[word]`) words[offset] = swaps[word] else: parts = re.split("([aeiouAEIOU])", word[2:-2], maxsplit=1) print parts if len(parts) == 3: parts.insert(0, word[:2]) parts.insert(3, '- ') parts.append(word[-2:]) print "hyphenated %s as %s" % (`word`, `parts`) word = ''.join(parts) words[offset] = word else: global longwords longwords.add(word) word = word[:maxwidth/2] + ' ' + word[maxwidth/2:] words[offset] = word return ' '.join(words) class ConTime(object): def __init__(self, timestr): hr, mn = map(int, timestr.split(':')) self.minutes = hr * 60 + mn def __str__(self): return "%02d:%02d" % divmod(self.minutes, 60) def __add__(self, other): sum = self.minutes + other return ConTime("%02d:%02d" % divmod(sum, 60)) def __cmp__(self, other): return cmp(self.minutes, other.minutes) class SortedIndex(dict): _keycmpfn = cmp def __iter__(self): return iter(self.keys()) def keys(self): klist = dict.keys(self) klist.sort(cmp=self._keycmpfn) return klist def values(self): klist = self.keys() klist.sort(cmp=self._keycmpfn) return [self[k] for k in klist] def items(self): klist = self.keys() klist.sort(cmp=self._keycmpfn) return [(k, self[k]) for k in klist] def __getitem__(self, key): if key not in self: self[key] = [] return dict.__getitem__(self, key) class CategoryIndex(SortedIndex): def __init__(self, talklist): for talk in talklist: self[talk.category].append(talk) class SpeakerIndex(SortedIndex): def __init__(self, talklist): def groom_speakername(s): s = s.strip() def trim_end(s, e): if s.endswith(e): return s[:-len(e)] return s s = trim_end(s, '(M)') s = trim_end(s, ' II') s = trim_end(s, ' III') s = trim_end(s, 'Jr.') s = trim_end(s, 'Ph.D.') s = trim_end(s, 'PhD') s = trim_end(s, 'Esq.') parts = s.strip().rsplit(' ', 1) # print "splitting speaker %s into %s" % (`s`, `parts`) if len(parts) == 2: s = ', '.join((parts[1], parts[0])) else: s = parts[0] # print "re-joined back into %s" % `s` return s for talk in talklist: speakers = talk.speakers.split(',') for speaker in speakers: speaker = groom_speakername(speaker) if speaker: self[speaker].append(talk) class RoomIndex(SortedIndex): def __init__(self, talklist): for talk in talklist: self[talk.room].append(talk) def iterday(self, day): "Only return rooms that are in use on the specified day" def inuse_on_day(talkslist, day): for talk in talkslist: if talk.startday == day: return True return False for room, talkslist in self.items(): if inuse_on_day(talkslist, day): yield room class TimeslotIndex(SortedIndex): dayset = {'wed': 0, 'thu': 1, 'fri': 2, 'sat': 3, 'sun': 4} def _keycmpfn(x, y): xi = TimeslotIndex.dayset[x[:3]], x[4:] yi = TimeslotIndex.dayset[y[:3]], y[4:] return cmp(xi, yi) _keycmpfn = staticmethod(_keycmpfn) def __init__(self, talklist): earliest_times = {} latest_times = {} for talk in talklist: if talk.starttime < earliest_times.get(talk.startday, "23:59"): earliest_times[talk.startday] = talk.starttime if talk.stoptime > latest_times.get(talk.startday, "00:00"): latest_times[talk.startday] = talk.stoptime for day in TimeslotIndex.dayset.keys(): early = ConTime(earliest_times[day]) late = ConTime(latest_times[day]) slot = early while slot <= late: slotkey = "%s %s" % (day, str(slot)) self[slotkey] = [] for talk in talklist: if talk.startday == day and talk.begminute <= slot < talk.endminute: self[slotkey].append(talk) slot += 30 def iterday(self, day): "Only return timeslots for the specified day" for slot in self.keys(): if slot.startswith(day): yield slot class Talk(object): def __init__(self, line): cols = line.split('\t') self.id = cols[0] self.startday = cols[1] self.duration = int(cols[3]) self.roomno = cols[4] self.hotel = cols[5] self.title = cols[6] self.desc = cols[7] self.speakers = cols[8] self.category = cols[9] self.changeid = cols[10] self.timestamp = cols[11] self.begminute = ConTime(cols[2]) self.endminute = self.begminute + self.duration starttime = property(lambda self: str(self.begminute)) stoptime = property(lambda self: str(self.endminute)) def __repr__(self): return "#%4s %s" % (self.id, self.title) def timeslot(self): return "%s %s" % (self.startday, self.starttime) timeslot = property(timeslot) def room(self): return "%s/%s" % (self.hotel, self.roomno) room = property(room) def is_during(self, timeslot): day, slot = timeslot.split(' ') slot = ConTime(slot) if self.startday == day and self.begminute <= slot < self.endminute: return True return False def legend(categorylist): #TBD: break into N columns or leave scattered across the grid bottom ? return stan( T.table[ T.tr[ [T.td(class_=categoryquote(category))[category] for category in categorylist] ] ] ) def timefmt(s): hr, mn = map(int, s.split(':')) if hr > 12: ampm = 'pm'; hr -= 12 else: ampm = 'am' s = "%02d:%02d" % (hr, mn) return s, ampm def grid_colheadings(day, values): return stan( [ T.th(class_="roomlabel")[ T.font(size="+1")[ day.capitalize() ] ] ] + [ T.th(class_="timelabel")[tm, T.br, ampm] for tm, ampm in values ] ) def chart_byday(day, filename, rooms, timeslots, categories): f = open(filename, 'w') print >>f, """\ """ print >>f, flatten(stan( T.head[ T.title['WorldCon Talks for %s' % day.capitalize()], T.link(rel="stylesheet", href="talkgrid.css") ] )) print >>f, "" room_names = list(rooms.iterday(day)) room_names.sort() #### # Rooms down left side, Timeslots across top print >>f, '' print >>f, flatten(stan( # Colum headings across top of grid T.thead[ T.tr(class_="colheading")[ grid_colheadings(day, [timefmt(timeslot[4:]) for timeslot in timeslots.iterday(day)]) ] ] )) print >>f, '' for room_name in room_names: # sequence down left side # list of talks in this room on the specified day, all timeslots talkslist = [talk for talk in rooms[room_name] if talk.startday == day] print >>f, '' hotel, roomid = room_name.split('/') print >>f, '' prior_talk_in_slot = None for timeslot in timeslots.iterday(day): # sequence across top # list of talks in this room on the specified day _during the specific timeslot_ talks_in_this_room = filter(lambda talk: talk.is_during(timeslot), talkslist) if len(talks_in_this_room) > 1: print "WARNING: more than one talk in room!" sys.exit(1) if len(talks_in_this_room) == 1: talk = talks_in_this_room[0] if prior_talk_in_slot is not talk: print >>f, '' else: # room unused at this timeslot print >>f, '' print >>f, flatten(stan( # Column footings across bottom of grid T.tr(class_="colfooting")[ grid_colheadings(day, [timefmt(timeslot[4:]) for timeslot in timeslots.iterday(day)]) ] )) print >>f, "
', htmlquote(hotel), '
', htmlquote(roomid), '
' % ( categoryquote(talk.category), talk.duration/30) print >>f, '%s •%s' % ( talk.id, 'talkinfo/%s.html' % talk.id, htmlquote(hyphenate(talk.title, talk.duration/30 * 6))) print >>f, '' prior_talk_in_slot = talk print >>f, '
" #### # Output legend block along bottom-left of chart print >>f, flatten(stan( T.table(id="legend", style='width="100%"')[ T.tr[ T.td[ legend(categories.keys()) ], T.td(id="daylabel")[ T.font[ day.capitalize() ] ] ] ] )) print >>f, """\ """ f.close() def format_one_talk(talk): when = '%s %s %s' % ( talk.startday.capitalize(), timefmt(talk.starttime)[0], timefmt(talk.starttime)[1]) place = '%s %s' % (talk.hotel, talk.roomno) def fmt_talkid(id): s = '%s' % id s = s.ljust(5) s = s.replace(' ', '.') return s return ( T.div(class_='talkentry')[ T.div(class_='titleline')[ T.span(class_='ident')[ T.span(class_='talkid')[fmt_talkid(talk.id)], T.span(class_='title')[T.xml(talk.title)], T.span(class_='category')[' (', talk.category, ')'] ], T.span(class_='details')[ T.span(style='font-size: 0.8em')['(%s min)' % talk.duration], entities.nbsp, entities.nbsp, entities.nbsp, entities.nbsp, place, ' | ', when ], T.span(style='break: both;')[entities.nbsp], ], T.div(class_='speakerline')[ T.b['Speaker(s): '], talk.speakers or '(none)' ], T.div(class_='descarea')[ T.xml(talk.desc or '(no description)') ] ] ) def format_speaker_talks(speaker, talkslist): return ( T.h2[speaker], [format_one_talk(talk) for talk in talkslist] ) def generate_speaker_index(speakers): f = open('index-speaker.html', 'w') print >>f, flatten(stan( T.html(xmlns="http://www.w3.org/1999/xhtml", lang="en", xml_lang="en")[ T.head[ T.title['Talk Descriptions, Organized by Speaker (count %d)' % len(speakers)], T.link(rel="stylesheet", href="talkindex.css") ], T.body[ T.h1['Talk Descriptions, Organized by Speaker (count %d)' % len(speakers)], [format_speaker_talks(speaker, talkslist) for speaker, talkslist in speakers.items()] ] ] )) f.close() def list_speaker_talks(speaker, talkslist): def fmt_talkid(id): s = '%s' % id s = s.ljust(5) s = s.replace(' ', '.') return s entries = [ T.tr(class_='talkauthor')[ T.td(colspan=3)[ T.h2[speaker] ] ] ] for talk in talkslist: when = '%s %s %s' % ( talk.startday.capitalize(), timefmt(talk.starttime)[0], timefmt(talk.starttime)[1]) place = '%s %s' % (talk.hotel, talk.roomno) entries.append( T.tr(class_='talkentry')[ T.td(style='width: 20px; background: white'), T.td(class_='ident')[ T.span(class_='talkid')[fmt_talkid(talk.id)], T.span(class_='title')[T.xml(talk.title)], T.span(class_='category')[' (', talk.category, ')'] ], T.td(class_='details')[ T.span['(%s min)' % talk.duration], entities.nbsp, entities.nbsp, entities.nbsp, entities.nbsp, place, ' | ', when ], ] ) return entries def generate_speaker_list(speakers): f = open('list-speaker.html', 'w') print >>f, flatten(stan( T.html(xmlns="http://www.w3.org/1999/xhtml", lang="en", xml_lang="en")[ T.head[ T.title['Talk Titles, Organized by Speaker (count %d)' % len(speakers)], T.link(rel="stylesheet", href="talklist.css") ], T.body[ T.h1['Talk Titles, Organized by Speaker (count %d)' % len(speakers)], T.table(border=0)[ [list_speaker_talks(speaker, talkslist) for speaker, talkslist in speakers.items()] ] ] ] )) f.close() def generate_category_index(category, talkslist): f = open('cat-%s.html' % categoryquote(category), 'w') def sort_by_talkid(x, y): if x.id == '-': xi = 0 else: xi = int(x.id) if y.id == '-': yi = 0 else: yi = int(y.id) return cmp(xi, yi) def sort_by_title(x, y): return cmp(x.title, y.title) if category == 'autog': # sort by title/person, NOT by id sorted_by = 'by author first name' sortfn = sort_by_title else: sorted_by = 'in numeric order' sortfn = sort_by_talkid talkslist.sort(sortfn) print >>f, flatten(stan( T.html(xmlns="http://www.w3.org/1999/xhtml", lang="en", xml_lang="en")[ T.head[ T.title['%s Talk Descriptions (%s) (count %d)' % (category.capitalize(), sorted_by, len(talkslist))], T.link(rel="stylesheet", href="talkindex.css") ], T.body[ T.h1['%s Talk Descriptions (%s) (count %d)' % (category.capitalize(), sorted_by, len(talkslist))], [format_one_talk(talk) for talk in talkslist] ] ] )) f.close() def generate_talksid_index(day, talkslist): f = open('index-%s.html' % day, 'w') talks = {} for talk in talkslist: if talk.startday == day: talks[talk.id] = talk def sortfn(x, y): if x == '-': x = '0' if y == '-': y = '0' xi = int(x) yi = int(y) return cmp(xi, yi) talkids = talks.keys() talkids.sort(sortfn) print >>f, flatten(stan( T.html(xmlns="http://www.w3.org/1999/xhtml", lang="en", xml_lang="en")[ T.head[ T.title['%s Talk Descriptions (in numeric order) (count %d)' % (day.capitalize(), len(talkids))], T.link(rel="stylesheet", href="talkindex.css") ], T.body[ T.h1['%s Talk Descriptions (in numeric order) (count %d)' % (day.capitalize(), len(talkids))], [format_one_talk(talks[talkid]) for talkid in talkids] ] ] )) f.close() def generate_talkinfo(talk): f = open('talkinfo/%s.html' % talk.id, 'w') print >>f, flatten(stan( T.html(xmlns="http://www.w3.org/1999/xhtml", lang="en", xml_lang="en")[ T.head[ T.title['Talk #%s: %s' % (talk.id, talk.title)], T.link(rel="stylesheet", href="../talkinfo.css") ], T.body[ T.h1['Talk #%s' % talk.id], T.table(cellpadding='4px', cellspacing='4px')[ T.tr[ T.th['Title:'], T.td[talk.title] ], T.tr[ T.th['Category:'], T.td[talk.category] ], T.tr[ T.th['Timeslot:'], T.td['%s %s %s (%s minutes)' % ( talk.startday.capitalize(), timefmt(talk.starttime)[0], timefmt(talk.starttime)[1], talk.duration)] ], T.tr[ T.th['Location:'], T.td['%s / %s' % ( talk.hotel, talk.roomno)] ], T.tr[ T.th['Description:'], T.td[talk.desc] ], T.tr[ T.th['Speaker(s):'], T.td[talk.speakers] ] ] ] ] )) f.close() def main(): talks = [] for line in file('sched-tab-delim.txt'): talks.append(Talk(line)) for talk in talks: generate_talkinfo(talk) for day in TimeslotIndex.dayset.keys(): generate_talksid_index(day, talks) print "Loaded %d Talks" % len(talks) categories = CategoryIndex(talks) rooms = RoomIndex(talks) timeslots = TimeslotIndex(talks) speakers = SpeakerIndex(talks) print "There are %d unique speakers" % len(speakers) for speaker in speakers: print " ", `speaker` generate_speaker_index(speakers) generate_speaker_list(speakers) for category, talkslist in categories.items(): generate_category_index(category, talkslist) print "There are %d rooms, with at most %d used at one time." % ( len(rooms), max(len(talkslist) for talkslist in rooms)) for room, talks in rooms.items(): print room for talk in talks: print " ", talk.timeslot, talk.room, talk # Determine the set of timeslots used print "There are %d timeslots, with at most %d talks at one time." % ( len(timeslots), max(len(talkslist) for talkslist in timeslots.values())) for timeslot, talkslist in timeslots.items(): print timeslot, len(talkslist) for category in categories: print category for talk in categories[category]: print " ", talk.timeslot, talk.room, talk for day in TimeslotIndex.dayset.keys(): chart_byday(day, 'talks-%s.html' % day, rooms, timeslots, categories) print "Long Words" for word in longwords: print " ", word if __name__ == "__main__": main()