|
@@ -1,20 +1,21 @@
|
|
|
#!/usr/bin/env python3
|
|
|
from datetime import date,timedelta
|
|
|
-import sys
|
|
|
-import argparse
|
|
|
+import sys, argparse
|
|
|
|
|
|
def str_to_date(string_date):
|
|
|
return date(*[int(s) for s in string_date.split('-')])
|
|
|
|
|
|
class CryspCalendar:
|
|
|
"""Representation of a CrySP weekly meeting schedule for a term"""
|
|
|
- def __init__(self, start, end=None, weeks=None, names=[],\
|
|
|
- constraints=[], practice_talks=[], half_practices=[], notes=[], no_repeat=False):
|
|
|
+ def __init__(self, start, end=None, weeks=None, names=[],
|
|
|
+ constraints=[], practice_talks=[], half_practices=[], notes=[],
|
|
|
+ no_repeat=False):
|
|
|
self.dates = self.meeting_dates(start, end, weeks)
|
|
|
self.names = names
|
|
|
self.constraints = self.parse_constraints(constraints)
|
|
|
self.practice_talks = self.parse_practice_talks(practice_talks)
|
|
|
- self.practice_talks.update(self.parse_practice_talks(half_practices, half=True))
|
|
|
+ self.practice_talks.update(self.parse_practice_talks(half_practices,
|
|
|
+ half=True))
|
|
|
self.notes = self.parse_notes(notes)
|
|
|
self.notes.update(self.parse_notes(practice_talks))
|
|
|
self.notes.update(self.parse_notes(half_practices))
|
|
@@ -27,7 +28,8 @@ class CryspCalendar:
|
|
|
if end:
|
|
|
last_meeting = str_to_date(end)
|
|
|
if weeks:
|
|
|
- last_meeting = max(first_meeting + timedelta(weeks=weeks), last_meeting)
|
|
|
+ last_meeting = max(first_meeting + timedelta(weeks=weeks),
|
|
|
+ last_meeting)
|
|
|
ret = []
|
|
|
next_meeting = first_meeting
|
|
|
one_week = timedelta(weeks=1)
|
|
@@ -54,13 +56,13 @@ class CryspCalendar:
|
|
|
day = str_to_date(talk[1])
|
|
|
if day in practice_dict:
|
|
|
assert half,\
|
|
|
- "conflicting talks: %s and %s on %s" %\
|
|
|
+ 'conflicting talks: %s and %s on %s' %\
|
|
|
(practice_dict[day][0], name, day)
|
|
|
practice_dict[day].append(name)
|
|
|
elif half:
|
|
|
practice_dict[day] = [name,]
|
|
|
else:
|
|
|
- practice_dict[day] = [name, ""]
|
|
|
+ practice_dict[day] = [name, '']
|
|
|
return practice_dict
|
|
|
|
|
|
def parse_notes(self, notes):
|
|
@@ -78,11 +80,11 @@ class CryspCalendar:
|
|
|
for name in self.constraints:
|
|
|
for day in self.constraints[name]:
|
|
|
assert day in self.dates,\
|
|
|
- "constraint added for %s on %s, which is not a meeting date" %\
|
|
|
+ 'constraint added for %s on %s, which is not a meeting date' %\
|
|
|
(name, day)
|
|
|
for day in self.practice_talks:
|
|
|
assert day in self.dates,\
|
|
|
- "talk by %s designated on %s, which is not a meeting date" %\
|
|
|
+ 'talk by %s designated on %s, which is not a meeting date' %\
|
|
|
(self.practice_talks[day][0], day)
|
|
|
for day in self.notes:
|
|
|
assert day in self.dates,\
|
|
@@ -90,6 +92,9 @@ class CryspCalendar:
|
|
|
(self.notes[day], day)
|
|
|
|
|
|
def pop_name(self, day, names, i):
|
|
|
+ """Pops the name of the next available on day from the names stack,
|
|
|
+ where 'i' is which speaker they would be for that day (0 or 1).
|
|
|
+ Returns None if no speakers are left in the given names stack."""
|
|
|
name = None
|
|
|
if day in self.practice_talks:
|
|
|
if i < len(self.practice_talks[day]):
|
|
@@ -104,6 +109,7 @@ class CryspCalendar:
|
|
|
return name
|
|
|
|
|
|
def next_speaker(self, day, names, i):
|
|
|
+ """Wrapper for pop_name() that repopulates names stack if needed."""
|
|
|
name = self.pop_name(day, names, i)
|
|
|
if name or self.no_repeat:
|
|
|
return name
|
|
@@ -113,26 +119,26 @@ class CryspCalendar:
|
|
|
def date_to_speakers(self, day, names):
|
|
|
speaker1 = self.next_speaker(day, names, 0)
|
|
|
speaker2 = self.next_speaker(day, names, 1)
|
|
|
- note = self.notes.get(day, "")
|
|
|
+ note = self.notes.get(day, '')
|
|
|
return speaker1, speaker2, note
|
|
|
|
|
|
- # returns a string containing a twiki table
|
|
|
def table(self):
|
|
|
- table = "| *Date* | *Speakers* | *Notes* |\n"
|
|
|
+ """Returns a string containing a twiki table."""
|
|
|
+ table = '| *Date* | *Speakers* | *Notes* |\n'
|
|
|
names = self.names.copy()
|
|
|
for day in self.dates:
|
|
|
- table += day.strftime("| %b %e |")
|
|
|
+ table += day.strftime('| %b %e |')
|
|
|
speaker1, speaker2, note = self.date_to_speakers(day, names)
|
|
|
if speaker1:
|
|
|
table += ' ' + speaker1
|
|
|
if speaker2:
|
|
|
- table += ", " + speaker2
|
|
|
- table += " | " + note + " |\n"
|
|
|
+ table += ', ' + speaker2
|
|
|
+ table += ' | ' + note + ' |\n'
|
|
|
return table
|
|
|
|
|
|
- # returns a string containing the currently used email layout, filled in
|
|
|
def email(self, weeks=2, today=date.today()):
|
|
|
- emailstr = ""
|
|
|
+ """Returns a string for emailing to the lab who is speaking next"""
|
|
|
+ emailstr = ''
|
|
|
names = self.names.copy()
|
|
|
i = 0
|
|
|
for day in self.dates:
|
|
@@ -143,73 +149,77 @@ class CryspCalendar:
|
|
|
if i > weeks:
|
|
|
break
|
|
|
speaker1, speaker2, note = self.date_to_speakers(day, names)
|
|
|
- emailstr += day.strftime("%b %e: ")
|
|
|
+ emailstr += day.strftime('%b %e: ')
|
|
|
if speaker1:
|
|
|
emailstr += speaker1
|
|
|
if speaker2:
|
|
|
- emailstr += ", " + speaker2
|
|
|
- if note != "":
|
|
|
- emailstr += " (" + note + ")"
|
|
|
+ emailstr += ', ' + speaker2
|
|
|
+ if note != '':
|
|
|
+ emailstr += ' (' + note + ')'
|
|
|
else:
|
|
|
emailstr += note
|
|
|
- emailstr += "\n"
|
|
|
- emailstr += "\nhttps://cs.uwaterloo.ca/twiki/view/CrySP/SpeakerSchedule\n"
|
|
|
+ emailstr += '\n'
|
|
|
+ emailstr += '\nhttps://cs.uwaterloo.ca/twiki/view/CrySP/SpeakerSchedule\n'
|
|
|
return emailstr
|
|
|
|
|
|
def parse_constraintsfile(filename):
|
|
|
- f = open(filename, 'r')
|
|
|
- tweaks = {'c': [], 'p': [], 'h': [], 'n': []}
|
|
|
- for line in f:
|
|
|
- l = [x.strip() for x in line.rstrip().split(',')]
|
|
|
- tweaks[l[0]].append(l[1:])
|
|
|
- return tweaks['c'], tweaks['p'], tweaks['h'], tweaks['n']
|
|
|
+ with open(filename, 'r') as f:
|
|
|
+ tweaks = {'c': [], 'p': [], 'h': [], 'n': []}
|
|
|
+ for line in f:
|
|
|
+ l = [x.strip() for x in line.rstrip().split(',')]
|
|
|
+ tweaks[l[0]].append(l[1:])
|
|
|
+ return tweaks['c'], tweaks['p'], tweaks['h'], tweaks['n']
|
|
|
|
|
|
def make_parser():
|
|
|
parser = argparse.ArgumentParser(description = \
|
|
|
- "Print a speaker schedule for the CrySP weekly meetings.")
|
|
|
- parser.add_argument("-s", "--start",
|
|
|
- metavar = "DATE", required = True,
|
|
|
- help = "date of the first meeting in ISO format (yyyy-mm-dd)")
|
|
|
- parser.add_argument("-e", "--end", metavar = "DATE",
|
|
|
- help = "last date to schedule a meeting on or before " +\
|
|
|
- "(if -w also specified, uses whatever is shortest)")
|
|
|
- parser.add_argument("-w", "--weeks", type=int,
|
|
|
- help = "number of weeks to schedule for " +\
|
|
|
- "(if -e also specified, uses whatever is shortest)")
|
|
|
+ 'Print a speaker schedule for the CrySP weekly meetings.')
|
|
|
+ parser.add_argument('-s', '--start',
|
|
|
+ metavar = 'DATE', required = True,
|
|
|
+ help = 'date of the first meeting in ISO format ' +\
|
|
|
+ '(yyyy-mm-dd)')
|
|
|
+ parser.add_argument('-e', '--end', metavar = 'DATE',
|
|
|
+ help = 'last date to schedule a meeting on or before ' +\
|
|
|
+ '(if -w also specified, uses whatever is shortest)')
|
|
|
+ parser.add_argument('-w', '--weeks', type=int,
|
|
|
+ help = 'number of weeks to schedule for ' +\
|
|
|
+ '(if -e also specified, uses whatever is shortest)')
|
|
|
|
|
|
group = parser.add_mutually_exclusive_group()
|
|
|
- group.add_argument("-n", "--names",
|
|
|
- nargs = '+', default = [], metavar = "NAME",
|
|
|
- help = "names to schedule in their speaking order")
|
|
|
- group.add_argument("-f", "--namesfile",
|
|
|
- metavar = "FILE",
|
|
|
- help = "path of a file that contains a whitespace " +\
|
|
|
- "and/or comma seperated list of names to schedule in " +\
|
|
|
- "their speaking order")
|
|
|
-
|
|
|
- parser.add_argument("-c", "--constraints",
|
|
|
- metavar = ("NAME", "DATES"),
|
|
|
- nargs = '+', action = "append", default = [],
|
|
|
- help = 'specify someone *can\'t* speak on certain dates ' +\
|
|
|
- '(e.g., "-c Justin 2018-01-01 2018-01-08 -c Ian 2018-01-01")')
|
|
|
- parser.add_argument("-p", "--practice",
|
|
|
- metavar = ("NAME", "DATE", "NOTE"),
|
|
|
- nargs = 3, action = "append", default = [],
|
|
|
+ group.add_argument('-n', '--names',
|
|
|
+ nargs = '+', default = [], metavar = 'NAME',
|
|
|
+ help = 'names to schedule in their speaking order')
|
|
|
+ group.add_argument('-f', '--namesfile',
|
|
|
+ metavar = 'FILE',
|
|
|
+ help = 'path of a file that contains a whitespace ' +\
|
|
|
+ 'and/or comma seperated list of names to schedule in ' +\
|
|
|
+ 'their speaking order')
|
|
|
+
|
|
|
+ parser.add_argument('-c', '--constraints',
|
|
|
+ metavar = ('NAME', 'DATES'),
|
|
|
+ nargs = '+', action = 'append', default = [],
|
|
|
+ help = "specify someone *can't* speak on certain " +\
|
|
|
+ 'dates (e.g., "-c Justin 2018-01-01 2018-01-08 -c ' +\
|
|
|
+ 'Ian 2018-01-01")')
|
|
|
+ parser.add_argument('-p', '--practice',
|
|
|
+ metavar = ('NAME', 'DATE', 'NOTE'),
|
|
|
+ nargs = 3, action = 'append', default = [],
|
|
|
help = 'designate given DATE as a talk given by NAME, '+\
|
|
|
- 'and remove the next instance of NAME in the list of names, '+\
|
|
|
- 'if present (NOTE should usually be "practice talk")')
|
|
|
- parser.add_argument("-C", "--constraintsfile",
|
|
|
- metavar = "FILE",
|
|
|
- help = 'provide constraints, practice talks, and notes ' +\
|
|
|
- 'via supplied csv file')
|
|
|
-
|
|
|
- parser.add_argument("-r", "--no-repeat",
|
|
|
+ 'and remove the next instance of NAME in the list of '+\
|
|
|
+ 'names, if present (NOTE should usually be "practice '+\
|
|
|
+ 'talk")')
|
|
|
+ parser.add_argument('-C', '--constraintsfile',
|
|
|
+ metavar = 'FILE',
|
|
|
+ help = 'provide constraints, practice talks, and '+\
|
|
|
+ 'notes via supplied csv file')
|
|
|
+
|
|
|
+ parser.add_argument('-r', '--no-repeat',
|
|
|
action = 'store_true',
|
|
|
- help = "disables repeating the list of names")
|
|
|
- parser.add_argument("-E", "--email",
|
|
|
- nargs='?', const=2, default=False, metavar="WEEKS",
|
|
|
- help = "print an email for notifying who is speaking" +\
|
|
|
- " the next two (or specified) weeks")
|
|
|
+ help = 'disables repeating the list of names')
|
|
|
+ parser.add_argument('-E', '--email',
|
|
|
+ nargs = '?', const = 2, default = False,
|
|
|
+ metavar = 'WEEKS',
|
|
|
+ help = 'print an email for notifying who is speaking' +\
|
|
|
+ ' the next two (or specified) weeks')
|
|
|
return parser
|
|
|
|
|
|
def main(argv=None):
|
|
@@ -219,17 +229,17 @@ def main(argv=None):
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
if not args.end and not args.weeks:
|
|
|
- parser.error("requires -w/--weeks or -e/--end")
|
|
|
+ parser.error('requires -w/--weeks or -e/--end')
|
|
|
|
|
|
if args.namesfile:
|
|
|
- f = open(args.namesfile, 'r')
|
|
|
- names = [x.strip() for x in f.read().split(',')]
|
|
|
- f.close()
|
|
|
+ with open(args.namesfile, 'r') as f:
|
|
|
+ names = [x.strip() for x in f.read().split(',')]
|
|
|
else:
|
|
|
names = args.names
|
|
|
|
|
|
if args.constraintsfile:
|
|
|
- constraints, practice, halves, notes = parse_constraintsfile(args.constraintsfile)
|
|
|
+ constraints, practice, halves, notes = \
|
|
|
+ parse_constraintsfile(args.constraintsfile)
|
|
|
else:
|
|
|
constraints = args.constraints
|
|
|
practice = args.practice
|
|
@@ -240,5 +250,5 @@ def main(argv=None):
|
|
|
print(cal.table() if not args.email else cal.email(int(args.email)))
|
|
|
return 0;
|
|
|
|
|
|
-if __name__ == "__main__":
|
|
|
+if __name__ == '__main__':
|
|
|
sys.exit(main())
|