From 2f80d9068253e3dc6f4d7626d5c2da0b29d97cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20van=20Paassen?= Date: Tue, 18 Apr 2023 12:21:49 +0200 Subject: [PATCH 01/10] check for author / session overlap --- src/authorparse.py | 54 ++++++++++++++++++++++++++------ src/makebook.py | 19 +++++++++-- src/program.py | 78 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 118 insertions(+), 33 deletions(-) diff --git a/src/authorparse.py b/src/authorparse.py index a23eddb..a34b2c8 100644 --- a/src/authorparse.py +++ b/src/authorparse.py @@ -15,35 +15,51 @@ # ensure we parse most accented names unicodePrintables = u''.join(chr(c) for c in range(512) if chr(c).isalpha() or chr(c) == "'" or - chr(c) =='-' or chr(c) == '_') + chr(c) == '-' or chr(c) == '_') + def dprint(*argv, **argkw): #print(*argv, **argkw) pass # functions to assemble different parts + + def setFirst(toks): toks['firstname'] = ' '.join(toks) dprint(f"firstname {toks}") + + def setLast(toks): toks['lastname'] = ' '.join(toks) dprint(f"lastname {toks}") + + def setLastCap(toks): toks['lastname'] = toks[0].capitalize() dprint(f"lastname {toks[0].capitalize()}") + + def setTitle(toks): toks['titlepre'] = toks[0] dprint(f"titlepre {toks}") + + def setTitle2(toks): toks['titlepost'] = toks[0] dprint(f"titlepost {toks}") + + def setAffiliation(toks): toks['affiliation'] = ' '.join(toks) dprint(f"rest {toks}") + + def completeAuthor(toks): toks['author'] = Author(toks) dprint(f"complete {toks} {list(toks.keys())}") + # parsing tokens and language titlepre = oneOf(('Dr', 'Dr.', 'Prof', 'Prof.', 'dr.', 'dr.ir.', 'Lt', 'Lt Col', 'Lt. Col.', 'Prof. Dr.-Ing.', 'Professor', @@ -60,13 +76,15 @@ def completeAuthor(toks): initials = ( (OneOrMore(initial) + Opt(nickname) + - ZeroOrMore(initial)).set_parse_action(setFirst) | \ + ZeroOrMore(initial)).set_parse_action(setFirst) | (ZeroOrMore(initial) + Opt(nickname) + OneOrMore(initial)).set_parse_action(setFirst)) -lastname = OneOrMore(Word(unicodePrintables, asKeyword=True)).set_parse_action(setLast) -lastcaps = Word(alphas.upper(), min=2, asKeyword=True).set_parse_action(setLastCap) +lastname = OneOrMore(Word(unicodePrintables, asKeyword=True) + ).set_parse_action(setLast) +lastcaps = Word(alphas.upper(), min=2, + asKeyword=True).set_parse_action(setLastCap) author = (lastcaps + firstname + Opt(titlepost)) | \ (Opt(titlepre) + (initials | firstname) + lastname + Opt(titlepost)) @@ -80,19 +98,22 @@ def completeAuthor(toks): author_lines = author_line + OneOrMore(LineEnd() + author_line) author_list = author + ZeroOrMore(separator + author) + def printattr(o, attrib, pre='', post=' ', default=''): if hasattr(o, attrib): return pre + str(getattr(o, attrib)) + post return default + def daysort(ses): - _dayvalue = dict(wed=300,thu=400,fri=500,sat=600) + _dayvalue = dict(wed=300, thu=400, fri=500, sat=600) try: return _dayvalue[ses[:3]] + 10*int(ses[4]) + \ ((len(ses) == 6) and (ord(ses[5])-ord('a')) or 0) except: return 0 + class Author: _members = ('orcid', 'affiliation', 'email', @@ -170,10 +191,21 @@ def _from_iterable(cls, row, data, program): pass return obj - def __str__(self): return f'{self.lastname}, {self.firstname}' + def __eq__(self, o): + return self.lastname == o.lastname and self.firstname == o.firstname + + def __lt__(self, o): + if self.lastname < o.lastname: + return True + if self.firstname < o.firstname: + return True + + def __hash__(self): + return hash((self.lastname, self.firstname)) + @classmethod def find(cls, **kw): try: @@ -201,6 +233,7 @@ def getEventCodes(self): res.extend(it.getEvents()) return sorted(res, key=daysort) + class AuthorList(list): def __init__(self, text, program): @@ -218,6 +251,7 @@ def complete(self, toks): dprint(f"list addition {dict(toks.items())}") self.append(Author(toks, self.program)) + class SingleAuthor(list): def __init__(self, text, program): @@ -231,6 +265,7 @@ def __init__(self, text, program): def complete(self, toks): self.append(Author(toks, self.program)) + if __name__ == '__main__': print(initial.parseString(' A. ')) @@ -245,10 +280,9 @@ def complete(self, toks): 'Lt. Col. Pedro Piedade', 'René van Paassen', 'OKINAWA John' - ): + ): res = author.parseString(test) - print (res) - + print(res) res = author_lines.parseString('''Lt Nicholas Armendariz, MSC, USN, Naval Aerospace Medical Institute J. J. Walcutt, Ph.D., Clay Strategic Designs @@ -270,4 +304,4 @@ def complete(self, toks): res = author_lines.parseString(test) else: res = author_list.parseString(test) - print (res) + print(res) diff --git a/src/makebook.py b/src/makebook.py index 1bbe6be..d1ea152 100644 --- a/src/makebook.py +++ b/src/makebook.py @@ -14,7 +14,11 @@ import argparse import os import sys -import getpass, smtplib, ssl, mailbox, imaplib +import getpass +import smtplib +import ssl +import mailbox +import imaplib import time # find the current file's folder, for finding templates and the like @@ -69,6 +73,7 @@ subparsers = parser.add_subparsers(help='commands', title='Commands') + class ProgramPdf: command = 'pdf' @@ -129,6 +134,8 @@ def __call__(self, ns): writer.authorList(outfile, template=author_template, css=css) except AttributeError: pass + + ProgramPdf.args(subparsers) @@ -184,8 +191,11 @@ def __call__(self, ns): ns.authorout.close() except AttributeError: pass + + ProgramHtml.args(subparsers) + class ProgramDocx: command = 'docx' @@ -221,8 +231,11 @@ def __call__(self, ns): writer.authorList(outfile) except AttributeError: pass + + ProgramDocx.args(subparsers) + class ProgramEmail: command = 'email' @@ -314,6 +327,7 @@ def __call__(self, ns): imap and imap.logout() ns.outfile and mbox.close() + ProgramEmail.args(subparsers) # default arguments @@ -324,8 +338,7 @@ def __call__(self, ns): '--outfile=Nonsense.mbox', '--email=M.M.vanPaassen@TUDelft.nl', '--testmail=rene_vanpaassen@yahoo.com' - ) - +) if __name__ == '__main__': diff --git a/src/program.py b/src/program.py index 56cf488..06f1aff 100644 --- a/src/program.py +++ b/src/program.py @@ -6,16 +6,20 @@ @author: repa """ -import openpyxl from authorparse import Author, AuthorList -from datetime import date, time, timedelta, datetime +from datetime import time, datetime from spreadbook import BookOfSheets +import itertools +import sys import re + class Item: - _members = ('item', 'title', 'abstract', 'email', 'corresponding', 'session') - _required = ('author_list', 'item', 'title', 'email', 'corresponding', 'session') + _members = ('item', 'title', 'abstract', + 'email', 'corresponding', 'session') + _required = ('author_list', 'item', 'title', + 'email', 'corresponding', 'session') def __init__(self, row, data, program): @@ -28,7 +32,8 @@ def __init__(self, row, data, program): # these are directly coupled, make empty string cells void for m in Item._members: v = str(data[m]) - if v is not None and v.strip() == '': v = None + if v is not None and v.strip() == '': + v = None setattr(self, m, v) # an item may be presented in multiple sessions, for example the @@ -38,9 +43,10 @@ def __init__(self, row, data, program): # link to the session if available self._session = [] try: - self._session = [ program.sessions[s] for s in self.session] + self._session = [program.sessions[s] for s in self.session] except: - raise ValueError(f"Item: check items row {row}, cannot find session {self.session}") + raise ValueError( + f"Item: check items row {row}, cannot find session {self.session}") for s in self._session: s._items.append(self) @@ -65,7 +71,7 @@ def key(self): def getEvents(self): """ Return the associated events ID's, if present """ - return [ s._event.event for s in self._session ] + return [s._event.event for s in self._session] def printAuthors(self): res = [] @@ -78,7 +84,8 @@ def getFieldDetails(self): recipient=self.email, recipientname=self.corresponding, title=self.title, - time=' and on '.join([ f"{s._event.printDay()} at {s._event.printStart()}" for s in self._session]), + time=' and on '.join( + [f"{s._event.printDay()} at {s._event.printStart()}" for s in self._session]), session=' and in session'.join([ f"{s._event.title}: {s._event._session.title}" for s in self._session]), @@ -88,11 +95,12 @@ def getFieldDetails(self): email=s.chair_email, session=s._event._session.title) for s in self._session - if s.chair and s.chair_email and s._event._session.title ], - ) + if s.chair and s.chair_email and s._event._session.title], + ) + def daysort(e): - _dayvalue = dict(wed=300,thu=400,fri=500,sat=600) + _dayvalue = dict(wed=300, thu=400, fri=500, sat=600) try: ses = e.event return _dayvalue[ses[:3]] + 10*int(ses[4]) + \ @@ -100,6 +108,7 @@ def daysort(e): except: return 0 + class TimeSlot: def __new__(cls, event, program): @@ -110,7 +119,7 @@ def __new__(cls, event, program): slot.events[event.event] = event except KeyError: slot = super().__new__(cls) - slot.events = { event.event : event } + slot.events = {event.event: event} slot.start = event.start slot.end = event.end program.slots[event.start] = slot @@ -120,11 +129,12 @@ def key(self): return self.start def getEvents(self): - return sorted([ e for k, e in self.events.items() ], key=daysort) + return sorted([e for k, e in self.events.items()], key=daysort) _timeparse = re.compile('([0-9]{1,2}):([0-9]{2})\s?(AM|PM)?') + def makeTime(day, t): if isinstance(t, time): return datetime.combine(day, t) @@ -230,6 +240,7 @@ def printTitle(self): except AttributeError: return self.title + class Session: _members = ('session', 'title', 'shorttitle', 'items', 'event', @@ -259,6 +270,13 @@ def __str__(self): def key(self): return self.session + def allAuthors(self): + res = set() + for i in self._items: + res |= set(i.authors) + return res + + class Day: def __init__(self, day, starter): @@ -269,7 +287,6 @@ def printDate(self, fmt='%A %B %d'): return self.date.strftime(fmt) - def processSheet(sheet, Object, program=None): # result @@ -285,6 +302,7 @@ def processSheet(sheet, Object, program=None): return collect + class Program: """ Representation of a conference program. @@ -314,7 +332,8 @@ class Program: - A list of authors, with id's of the events in which they appear and a check on overlap for appearing in parallel sessions """ - def __init__(self, file, title=''): + + def __init__(self, file, title='', check_overlap=True): self.title = title #book = openpyxl.load_workbook(file) @@ -354,7 +373,10 @@ def __init__(self, file, title=''): self.author_list = [ au[1] for au in sorted(self.authors.items(), - key=lambda s: s[0][0].casefold()) ] + key=lambda s: s[0][0].casefold())] + + if check_overlap: + self.checkAuthorEventOverlap() def getEvents(self): res = [] @@ -366,16 +388,32 @@ def getDays(self): return [d for k, d in sorted(self.days.items())] def getAssignedItems(self): - return [ it for k, it in self.items.items() if len(it._session) ] + return [it for k, it in self.items.items() if len(it._session)] + + def checkAuthorEventOverlap(self): + for k, slot in sorted(self.slots.items()): + if len(slot.getEvents()) > 1: + # create sets of authors + authorsets = [ + (e.event, e._session.allAuthors()) + for e in slot.getEvents()] + + for s1, s2 in itertools.combinations(authorsets, 2): + common_authors = s1[1].intersection(s2[1]) + if common_authors: + ca = ' and '.join([str(a) for a in common_authors]) + print(f"Authors for parallel events {s1[0]} and {s2[0]} overlap\n", + f"common authors: {ca}", file=sys.stderr) if __name__ == '__main__': - pr = Program('../../../TUDelft/community/ISAP2023/collated_abstracts.xlsx') + pr = Program( + '../../../TUDelft/community/ISAP2023/ISAP 2023 shedule data230417b.xlsx') kl = sorted(pr.authors.keys(), key=lambda s: s[0].casefold()) for ak in kl: print(ak, pr.authors[ak]._items) for ak in pr.author_list: - print (ak.nameLastFirst()) + print(ak.nameLastFirst()) print(pr.getEvents()) From f792d417d7fb2cf56da074d726e822365834a60b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20van=20Paassen?= Date: Sun, 23 Apr 2023 21:28:07 +0200 Subject: [PATCH 02/10] update for new mail sending --- src/program.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/program.py b/src/program.py index 06f1aff..7594e58 100644 --- a/src/program.py +++ b/src/program.py @@ -84,11 +84,15 @@ def getFieldDetails(self): recipient=self.email, recipientname=self.corresponding, title=self.title, - time=' and on '.join( + daysandtimes=' and on '.join( [f"{s._event.printDay()} at {s._event.printStart()}" for s in self._session]), + day=', '.join([s._event.printDayFull() for s in self._session]), + time=', '.join([s._event.printStart() for s in self._session]), session=' and in session'.join([ f"{s._event.title}: {s._event._session.title}" for s in self._session]), + sessiontitle=', '.join([s._event._session.title + for s in self._session]), authors=self.printAuthors(), poster=('POSTER' in [s.session for s in self._session]), chair=[dict(name=s.chair, @@ -186,6 +190,9 @@ def getEventClass(self): def printDay(self): return self.day.strftime("%a") + + def printDayFull(self): + return self.day.strftime("%A, %B %e") def printStart(self): return self.start.strftime("%H:%M") From 80879f7f8884211d079feb24a37dc0fe6d7fd3c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20van=20Paassen?= Date: Wed, 24 May 2023 14:29:20 +0200 Subject: [PATCH 03/10] minor edits --- src/program.py | 4 ++-- src/spreadbook.py | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/program.py b/src/program.py index 663b536..d8a32cb 100644 --- a/src/program.py +++ b/src/program.py @@ -221,7 +221,7 @@ def getEventClass(self): def printDay(self): return self.day.strftime("%a") - + def printDayFull(self): return self.day.strftime("%A, %B %e") @@ -470,7 +470,7 @@ def checkAuthorEventOverlap(self): if common_authors: ca = ' and '.join([str(a) for a in common_authors]) print(f"Authors for events {s1[0]} and {s2[0]}", - f" overlap\n common authors: {ca}", + f" overlap\n common authors: {ca}", file=sys.stderr) diff --git a/src/spreadbook.py b/src/spreadbook.py index 8f45d6f..14cf433 100644 --- a/src/spreadbook.py +++ b/src/spreadbook.py @@ -11,6 +11,7 @@ import numpy as np import os + class BookOfSheets: def __init__(self, url, accountfile=''): @@ -37,8 +38,8 @@ def __init__(self, url, accountfile=''): setattr(self, s, pd.read_csv( f'{url}/gviz/tq?tqx=out:csv&sheet={s}'). replace(np.nan, None)) - self.events['day'] = pd.to_datetime(self.events['day'], - dayfirst=True) + self.events['day'] = pd.to_datetime(self.events['day'], + dayfirst=True) else: @@ -54,4 +55,5 @@ def __init__(self, url, accountfile=''): base = os.path.dirname(__file__) b1 = BookOfSheets(f'{base}/../example/exampledata.xlsx') sheet_id = '1bfvtA3tBdtqd_M8TpKA1pY_vuov1SiMdtywJ8G20lw0' - b2 = BookOfSheets(f'https://docs.google.com/spreadsheets/d/{sheet_id}/edit?usp=sharing') \ No newline at end of file + b2 = BookOfSheets( + f'https://docs.google.com/spreadsheets/d/{sheet_id}/edit?usp=sharing') From f0ad29f457c682233ff082ecb55d4e50bceabf9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20van=20Paassen?= Date: Wed, 24 May 2023 14:30:45 +0200 Subject: [PATCH 04/10] vscode settings --- src/.vscode/launch.json | 35 +++++++++++++++++++++++++++++++++++ src/.vscode/settings.json | 5 +++++ 2 files changed, 40 insertions(+) create mode 100644 src/.vscode/launch.json create mode 100644 src/.vscode/settings.json diff --git a/src/.vscode/launch.json b/src/.vscode/launch.json new file mode 100644 index 0000000..e1944a3 --- /dev/null +++ b/src/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "Python: sessionmail", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/makebook.py", + "args": [ + "--program=1kg8GQnJvyT8vmpLibgRernYgnokbrILXI0UWlIIX5tE", + "--title=Test Title", + "email", + "--target=chairgroup", + "--format=PAR", + "--outfile=/tmp/ISAP-programtest.mbox", + "--email=M.M.vanPaassen@TUDelft.nl", + "--html-template=/home/repa/TUDelft/community/ISAP2023/templates/mailtemplate-session01.html", + "--txt-template=/home/repa/TUDelft/community/ISAP2023/templates/mailtemplate-session01.txt" + ], + "console": "integratedTerminal", + "justMyCode": true + }, + ] +} \ No newline at end of file diff --git a/src/.vscode/settings.json b/src/.vscode/settings.json new file mode 100644 index 0000000..bc92af5 --- /dev/null +++ b/src/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.linting.pylintEnabled": false, + "python.linting.enabled": true, + "python.linting.flake8Enabled": true +} \ No newline at end of file From 1183a0ab8d35747f14d3520aab8157e3548b9897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20van=20Paassen?= Date: Wed, 24 May 2023 20:56:31 +0200 Subject: [PATCH 05/10] merge main edits --- .vscode/launch.json | 21 ++++++++++++++++++--- .vscode/settings.json | 5 +++++ src/makebook.py | 32 ++++++++++++++++++++++++++++++-- src/program.py | 13 ++++++------- src/spreadbook.py | 33 ++++++++++++++++++++++++++++++--- 5 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 99dd8ee..ac4bf38 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,8 +10,23 @@ "request": "launch", "program": "${file}", "console": "integratedTerminal", - "justMyCode": true, - "cwd": "${workspaceFolder}/src" + "justMyCode": true + }, + { + "name": "Python: makebook.py", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/makebook.py", + "args": [ + "--program=1kg8GQnJvyT8vmpLibgRernYgnokbrILXI0UWlIIX5tE", + "--title=Test title", + "email", + "--target=chairgroup", + "--outfile=/tmp/ISAP-program.mbox", + "--email=M.M.vanPaassen@tudelft.nl", + "--html-template=${userHome}/TUDelft/community/ISAP2023/templates/mailtemplate-session01.html", + "--txt-template=${userHome}/TUDelft/community/ISAP2023/templates/mailtemplate-session01.txt" + ] } ] -} \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..bc92af5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.linting.pylintEnabled": false, + "python.linting.enabled": true, + "python.linting.flake8Enabled": true +} \ No newline at end of file diff --git a/src/makebook.py b/src/makebook.py index 5302f0a..c8e1cda 100644 --- a/src/makebook.py +++ b/src/makebook.py @@ -11,6 +11,7 @@ from programpdf import WritePDF from programdocx import WriteDocx from programmail import WriteEmail +from programsheet import WriteSheet import argparse import os import sys @@ -109,7 +110,7 @@ def args(cls, subparsers): def __call__(self, ns): # process the program spec - program = Program(ns.program, ns.title) + program = Program(ns.program, title=ns.title) # figure out template arguments if ns.author_template is None: @@ -171,7 +172,7 @@ def args(cls, subparsers): def __call__(self, ns): # process the program spec - program = Program(ns.program, ns.title) + program = Program(ns.program, title=ns.title) # figure out template arguments if ns.author_template is None: @@ -348,6 +349,33 @@ def __call__(self, ns): ProgramEmail.args(subparsers) +class ProgramSheet: + + command = 'sheet' + + @classmethod + def args(cls, subparsers): + parser = subparsers.add_parser( + cls.command, + help="Add a datasheet to a google spreadsheet book") + parser.add_argument( + '--accountfile', type=str, default='', + help='Access through Google API') + parser.set_defaults(handler=cls) + + def __call__(self, ns): + + # process the program spec + program = Program(ns.program, accountfile=ns.accountfile) + + # create a writer + writer = WriteSheet(program) + + # write the events + writer.eventList(ns.title) + +ProgramSheet.args(subparsers) + # default arguments argvdef = ( '--title="The Nonsense Conference"', diff --git a/src/program.py b/src/program.py index d8a32cb..0cff1d5 100644 --- a/src/program.py +++ b/src/program.py @@ -400,11 +400,10 @@ class Program: and a check on overlap for appearing in parallel sessions """ - def __init__(self, file, title='', check_overlap=True): + def __init__(self, file, title='', accountfile='', check_overlap=True): self.title = title - # read the file or online sheet - book = BookOfSheets(file) + self.book = BookOfSheets(file, accountfile) # prepare for filling self.authors = dict() @@ -412,21 +411,21 @@ def __init__(self, file, title='', check_overlap=True): # events dict is returned by the call, sorted by event id # this also fills the slots, keyed by slot start time - self.events = processSheet(book.events, Event, self) + self.events = processSheet(self.book.events, Event, self) # the sessions dict is returned by the processSheet call # a session is linked to an event, and thereby to a time slot - self.sessions = processSheet(book.sessions, Session, self) + self.sessions = processSheet(self.book.sessions, Session, self) # the items are linked to a session; they will be added to # the list of items there - self.items = processSheet(book.items, Item, self) + self.items = processSheet(self.book.items, Item, self) # read the full definitions from the authors tab for authors with # further details # # this may also further fill the authors dict - processSheet(book.authors, Author, self) + processSheet(self.book.authors, Author, self) # make an organization per day self.days = dict() diff --git a/src/spreadbook.py b/src/spreadbook.py index 14cf433..a69274e 100644 --- a/src/spreadbook.py +++ b/src/spreadbook.py @@ -6,10 +6,16 @@ @author: repa """ +""" +For opening a spreadsheet with gspread google API: +https://codesolid.com/google-sheets-in-python-and-pandas/ +""" + import gspread import pandas as pd import numpy as np import os +import sys class BookOfSheets: @@ -20,11 +26,17 @@ def __init__(self, url, accountfile=''): # assume google sheets; not tested yet gc = gspread.service_account(filename=accountfile) - book = gc.open_by_url(url) + try: + self.book = gc.open(url) + except gspread.SpreadsheetNotFound: + print(f"Could not open as sheet: {url}") + self.book = gc.open_by_url(url) - for s in ('items', 'sessions', 'event', 'authors'): - ws = book.worksheet(s) + for s in ('items', 'sessions', 'events', 'authors'): + ws = self.book.worksheet(s) setattr(self, s, pd.DataFrame(ws.get_all_records())) + self.events['day'] = pd.to_datetime(self.events['day'], + dayfirst=True) elif not os.path.exists(url): @@ -49,6 +61,21 @@ def __init__(self, url, accountfile=''): url, sheet_name=s). replace(np.nan, None)) + def addSheet(self, name: str, data): + + if getattr(self, 'book', None) is None: + print("Works for now only with gspread account", file=sys.stderr) + return + rows = len(data) + cols = len(data[0]) + if name in [ sh.title for sh in self.book.worksheets()]: + print(f"A sheet named {name} already exists, not overwriting", + file=sys.stderr) + ws = self.book.add_worksheet(title=name, rows=rows, cols=cols) + c2 = chr(ord('A') + cols - 1) + r2 = rows + ws.update(f"A1:{c2}{r2}", data) + if __name__ == '__main__': From e1099b9a26c5658e0f99caf8f471b2dcadc36fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20van=20Paassen?= Date: Wed, 24 May 2023 22:04:03 +0200 Subject: [PATCH 06/10] with creation of additional sheets --- src/.vscode/launch.json | 22 ++++++++++++++-------- src/makebook.py | 3 +++ src/programsheet.py | 33 +++++++++++++++++++++++++++++++++ src/spreadbook.py | 15 ++++++++------- 4 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 src/programsheet.py diff --git a/src/.vscode/launch.json b/src/.vscode/launch.json index e1944a3..3cd3633 100644 --- a/src/.vscode/launch.json +++ b/src/.vscode/launch.json @@ -4,14 +4,6 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - { - "name": "Python: Current File", - "type": "python", - "request": "launch", - "program": "${file}", - "console": "integratedTerminal", - "justMyCode": true - }, { "name": "Python: sessionmail", "type": "python", @@ -31,5 +23,19 @@ "console": "integratedTerminal", "justMyCode": true }, + { + "name": "Python: sheet", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/makebook.py", + "args": [ + "--program=ISAP 2023 schedule data", + "--title=Test Title", + "sheet", + "--accountfile=${userHome}/TUDelft/community/ISAP2023/service_account.json" + ], + "console": "integratedTerminal", + "justMyCode": true + }, ] } \ No newline at end of file diff --git a/src/makebook.py b/src/makebook.py index c8e1cda..8f21ab6 100644 --- a/src/makebook.py +++ b/src/makebook.py @@ -349,6 +349,7 @@ def __call__(self, ns): ProgramEmail.args(subparsers) + class ProgramSheet: command = 'sheet' @@ -374,8 +375,10 @@ def __call__(self, ns): # write the events writer.eventList(ns.title) + ProgramSheet.args(subparsers) + # default arguments argvdef = ( '--title="The Nonsense Conference"', diff --git a/src/programsheet.py b/src/programsheet.py new file mode 100644 index 0000000..de9cb4c --- /dev/null +++ b/src/programsheet.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Sat Feb 11 08:49:33 2023 + +@author: repa +""" + + +class WriteSheet: + + def __init__(self, project): + + self.pr = project + + def eventList(self, fname): + + book = self.pr.book + res = [ + ["day", "start", "end", "code", "room", "title", + "authors/chair"]] + for day in self.pr.getDays(): + for event in day.events: + res.append([ + event.printDay(), event.printStart(), event.printEnd(), + event.printEventCode(), event.venue, + event.printTitle(), event.printChair()]) + if event.hasSession(): + for item in event._session._items: + res.append([ + None, None, None, None, None, + item.title, item.printAuthors()]) + book.addSheet(fname, res) \ No newline at end of file diff --git a/src/spreadbook.py b/src/spreadbook.py index a69274e..10d3691 100644 --- a/src/spreadbook.py +++ b/src/spreadbook.py @@ -1,5 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- + +import gspread +import pandas as pd +import numpy as np +import os +import sys + """ Created on Thu Feb 23 09:10:46 2023 @@ -11,12 +18,6 @@ https://codesolid.com/google-sheets-in-python-and-pandas/ """ -import gspread -import pandas as pd -import numpy as np -import os -import sys - class BookOfSheets: @@ -68,7 +69,7 @@ def addSheet(self, name: str, data): return rows = len(data) cols = len(data[0]) - if name in [ sh.title for sh in self.book.worksheets()]: + if name in [sh.title for sh in self.book.worksheets()]: print(f"A sheet named {name} already exists, not overwriting", file=sys.stderr) ws = self.book.add_worksheet(title=name, rows=rows, cols=cols) From fd4cf26860b68c918efc8393a5f7656223c952e6 Mon Sep 17 00:00:00 2001 From: Rene van Paassen Date: Sun, 28 May 2023 14:24:50 +0200 Subject: [PATCH 07/10] add notification on remote to program --- src/program.py | 14 +++++++++++++- src/templates/eventlist.html | 5 ++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/program.py b/src/program.py index 0cff1d5..3239284 100644 --- a/src/program.py +++ b/src/program.py @@ -45,7 +45,11 @@ def __init__(self, row, data, program): else: v = None setattr(self, m, v) - + + # formats? + if self.requested_format: + self.requested_format = list(map( + str.strip, self.requested_format.split(','))) # an item may be presented in multiple sessions, for example the # best student paper candidates self.session = self.session.split(',') @@ -91,6 +95,13 @@ def printAuthors(self): for a in self.authors: res.append(f"{a.firstname} {a.lastname}") return ', '.join(res) + + def isRemote(self): + if self.requested_format is not None and \ + 'Zoom' in self.requested_format: + print(f"Remote session for {self.title}") + return True + return False def getFieldDetails(self): return dict( @@ -113,6 +124,7 @@ def getFieldDetails(self): for s in self._session]), authors=self.printAuthors(), poster=('POSTER' in [s.session for s in self._session]), + remote=('Zoom' in self.requested_format), chair=[dict(name=s.chair, email=s.chair_email, session=s._event._session.title) diff --git a/src/templates/eventlist.html b/src/templates/eventlist.html index 7cd4daa..a8ce565 100644 --- a/src/templates/eventlist.html +++ b/src/templates/eventlist.html @@ -61,7 +61,10 @@

{{ day.printDate() }}

{% for item in event._session._items %}
- {{ item.title }} + {{ item.title }} + {% if item.isRemote() %} + (Zoom) + {% endif %}
{{ item.printAuthors() }} From f7267250aaab6ea609e7b62f09f723b7d18b8feb Mon Sep 17 00:00:00 2001 From: Rene van Paassen Date: Thu, 30 Jan 2025 18:06:28 +0100 Subject: [PATCH 08/10] some tweaks 2025 --- src/authorparse.py | 2 +- src/makebook.py | 11 ++++++----- src/programdocx.py | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/authorparse.py b/src/authorparse.py index 54f7665..46d0963 100644 --- a/src/authorparse.py +++ b/src/authorparse.py @@ -91,7 +91,7 @@ def completeAuthor(toks): (Opt(titlepre) + (initials | firstname) + lastname + Opt(titlepost)) -separator = Literal(',') | Literal('&') | Literal('\n') +separator = Literal(',') | Literal('&') | Literal('\n') | Literal(';') author_line = (author + Opt(Literal(',') + Regex(r'[^\n]*').set_parse_action(setAffiliation)) diff --git a/src/makebook.py b/src/makebook.py index 8f21ab6..0c618c7 100644 --- a/src/makebook.py +++ b/src/makebook.py @@ -382,14 +382,15 @@ def __call__(self, ns): # default arguments argvdef = ( '--title="The Nonsense Conference"', - f'--program={base}/../example/exampledata.xlsx', - 'email', - '--outfile=Nonsense.mbox', - '--email=M.M.vanPaassen@TUDelft.nl', - '--testmail=rene_vanpaassen@yahoo.com' + f'--program=/home/repa/Downloads/ISAP 2025(2).ods', + 'pdf', + '--outfile=/tmp/Nonsense.pdf', + '--authorout=/tmp/Authors.pdf' ) + + if __name__ == '__main__': if len(sys.argv) > 1: diff --git a/src/programdocx.py b/src/programdocx.py index 87cbb44..ca0a7f8 100644 --- a/src/programdocx.py +++ b/src/programdocx.py @@ -35,13 +35,13 @@ def eventList(self, fname): for event in day.events: t = doc.add_table(2, 3, style="Table Grid") - t.cell(0, 1).merge(t.cell(0,2)) t.cell(0, 0).text = \ f'{event.printDay()} {event.printStart()} - {event.printEnd()}' t.cell(1, 0).text = event.venue t.cell(1, 1).text = event.printEventCode() t.cell(0, 1).text = event.printTitle() t.cell(1, 2).text = event.printChair() + t.cell(0, 1).merge(t.cell(0,2)) if event.hasSession(): for item in event._session._items: From de8212440710551a9ac9bc593a6d9108db1ebb0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20van=20Paassen?= Date: Wed, 5 Feb 2025 23:30:22 +0100 Subject: [PATCH 09/10] robustness changes --- src/authorparse.py | 5 +++-- src/program.py | 29 ++++++++++++++++++----------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/authorparse.py b/src/authorparse.py index 46d0963..e96768a 100644 --- a/src/authorparse.py +++ b/src/authorparse.py @@ -88,10 +88,11 @@ def completeAuthor(toks): asKeyword=True).set_parse_action(setLastCap) author = (lastcaps + firstname + Opt(titlepost)) | \ - (Opt(titlepre) + (initials | firstname) + lastname + Opt(titlepost)) + (Opt(titlepre) + (initials | firstname) + lastname + Opt(titlepost)) | \ + (lastname + Literal(',') + firstname) -separator = Literal(',') | Literal('&') | Literal('\n') | Literal(';') +separator = Literal('&') | Literal('\n') | Literal(';') author_line = (author + Opt(Literal(',') + Regex(r'[^\n]*').set_parse_action(setAffiliation)) diff --git a/src/program.py b/src/program.py index 3239284..a2b72ba 100644 --- a/src/program.py +++ b/src/program.py @@ -25,7 +25,7 @@ class Item: _members = ('item', 'title', 'abstract', 'email', 'corresponding', 'session', 'presenter', 'requested_format') - _required = ('author_list', 'item', 'title', 'email', 'corresponding', + _required = ('item', 'title', 'corresponding', 'session') def __init__(self, row, data, program): @@ -38,14 +38,21 @@ def __init__(self, row, data, program): # these are directly coupled, make empty string cells void for m in Item._members: - if isinstance(data[m], str) and data[m].strip() == '': - v = None - elif data[m] is not None: - v = str(data[m]) - else: + try: + if isinstance(data[m], str) and data[m].strip() == '': + v = None + elif data[m] is not None: + v = str(data[m]) + else: + v = None + except KeyError: v = None setattr(self, m, v) - + + # author_list may be empty if corresponding is filled + if data['author_list'] is None or not str(data['author_list']).strip(): + data['author_list'] = data['corresponding'] + # formats? if self.requested_format: self.requested_format = list(map( @@ -71,7 +78,7 @@ def __init__(self, row, data, program): try: self.authors = list(AuthorList(data['author_list'], program)) except Exception: - print(f"Cannot get authors from author_list in row {row}") + print(f"Cannot get authors from author_list '{data['author_list']}'in row {row}") self.authors = [Author(dict(firstname='', lastname='Anonymous'), program)] @@ -95,7 +102,7 @@ def printAuthors(self): for a in self.authors: res.append(f"{a.firstname} {a.lastname}") return ', '.join(res) - + def isRemote(self): if self.requested_format is not None and \ 'Zoom' in self.requested_format: @@ -286,9 +293,9 @@ def printChair(self): def printTitle(self): try: - return self._session.title + return self._session.title or '' except AttributeError: - return self.title + return self.title or '' class Session: From 553e56baea4a3bea7cc7e5f3f338b060e3241c94 Mon Sep 17 00:00:00 2001 From: Rene van Paassen Date: Wed, 14 May 2025 12:47:49 +0200 Subject: [PATCH 10/10] [no ci] Add chairs to conflict check and author list --- src/authorparse.py | 6 ++++++ src/program.py | 13 +++++++++++-- src/spreadbook.py | 3 --- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/authorparse.py b/src/authorparse.py index e96768a..b74f395 100644 --- a/src/authorparse.py +++ b/src/authorparse.py @@ -162,9 +162,13 @@ def _from_parts(cls, firstname, lastname, orcid, program): obj.firstname = firstname obj.orcid = orcid obj._items = [] + obj._chairing = [] program.authors[obj.key()] = obj return obj + def addChairRole(self, event): + self._chairing.append(event) + @classmethod def _from_dict(cls, data, program): obj = cls._from_parts(data.get('firstname'), @@ -233,6 +237,8 @@ def getEventCodes(self): res = [] for it in self._items: res.extend(it.getEvents()) + for ch in self._chairing: + res.extend((f"{ch.event} (chair)",)) return sorted(res, key=daysort) diff --git a/src/program.py b/src/program.py index a2b72ba..44b2f8c 100644 --- a/src/program.py +++ b/src/program.py @@ -321,6 +321,12 @@ def __init__(self, row, data, program): f"Cannot find event {self.event} for session {self.session}" f", check event in row {row}") self._items = [] + self.program = program + + if self.chair: + self.chairs = list(AuthorList(data['chair'], program)) + for c in self.chairs: + c.addChairRole(self._event) def __str__(self): return str(self.__dict__) @@ -328,8 +334,11 @@ def __str__(self): def key(self): return self.session - def allAuthors(self): - res = set() + def allAuthors(self, withChair=True): + if self.chair: + res = set(AuthorList(self.chair, self.program)) + else: + res = set() for i in self._items: res |= set(i.authors) return res diff --git a/src/spreadbook.py b/src/spreadbook.py index 10d3691..f5d8ce0 100644 --- a/src/spreadbook.py +++ b/src/spreadbook.py @@ -82,6 +82,3 @@ def addSheet(self, name: str, data): base = os.path.dirname(__file__) b1 = BookOfSheets(f'{base}/../example/exampledata.xlsx') - sheet_id = '1bfvtA3tBdtqd_M8TpKA1pY_vuov1SiMdtywJ8G20lw0' - b2 = BookOfSheets( - f'https://docs.google.com/spreadsheets/d/{sheet_id}/edit?usp=sharing')