2019-11-30 12:19:33 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
|
2019-11-30 12:46:56 +01:00
|
|
|
import datetime
|
2019-11-30 17:48:29 +01:00
|
|
|
import functools
|
|
|
|
import itertools
|
2019-11-30 12:19:33 +01:00
|
|
|
from mailbox import mbox, mboxMessage
|
2019-11-30 17:48:29 +01:00
|
|
|
import math
|
2019-11-30 12:19:33 +01:00
|
|
|
import re
|
2019-11-30 17:48:29 +01:00
|
|
|
from typing import Dict, List, Tuple
|
2020-04-03 14:10:37 +02:00
|
|
|
import json
|
2019-11-30 17:48:29 +01:00
|
|
|
|
|
|
|
|
|
|
|
from submission import Submission, print_submissions
|
2019-11-30 12:19:33 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Parser:
|
2020-04-03 14:10:37 +02:00
|
|
|
INFO_REGEX = re.compile(r"<td>(\d{6}) \| <code>(x\S*)</code>\s*")
|
2019-12-02 10:52:57 +01:00
|
|
|
SUBMISSION_REGEX = re.compile(r"adresář:\s+(\S*\/(\S*))\s*")
|
2019-11-30 12:19:33 +01:00
|
|
|
POINTS_REGEX = re.compile(r"\*\scelkový počet bodů\s+((\d|\.)*)\s*")
|
2019-11-30 12:46:56 +01:00
|
|
|
DATE_FORMAT = "%Y_%m%d_%H%M%S"
|
|
|
|
OFFSET_FOR_CORRECTION = datetime.timedelta(days=8)
|
2019-11-30 12:19:33 +01:00
|
|
|
|
|
|
|
@staticmethod
|
2020-04-03 14:10:37 +02:00
|
|
|
def get_mail(mail: mboxMessage) -> str:
|
|
|
|
body = None
|
|
|
|
for payload in mail.get_payload():
|
2020-06-08 17:39:36 +02:00
|
|
|
if payload["Content-Type"].startswith("text/html"):
|
2020-04-03 14:10:37 +02:00
|
|
|
body = payload.get_payload()
|
|
|
|
|
|
|
|
return body
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_match_from_mail(regex: re.Pattern, mail: str) -> re.Match:
|
|
|
|
match = regex.search(mail)
|
2019-11-30 12:19:33 +01:00
|
|
|
if not match:
|
|
|
|
raise ValueError("invalid mail has been given")
|
|
|
|
|
2019-11-30 12:22:21 +01:00
|
|
|
return match
|
2019-11-30 12:19:33 +01:00
|
|
|
|
|
|
|
@staticmethod
|
2019-11-30 12:22:21 +01:00
|
|
|
def parse_info(mail: mboxMessage) -> Tuple[str, str]:
|
|
|
|
match = Parser.get_match_from_mail(Parser.INFO_REGEX, mail)
|
|
|
|
return match.group(1), match.group(2)
|
2019-11-30 12:19:33 +01:00
|
|
|
|
2019-11-30 12:22:21 +01:00
|
|
|
@staticmethod
|
|
|
|
def parse_submission(mail: mboxMessage) -> str:
|
|
|
|
match = Parser.get_match_from_mail(Parser.SUBMISSION_REGEX, mail)
|
2019-12-02 10:52:57 +01:00
|
|
|
return match.group(1), match.group(2)
|
2019-11-30 12:19:33 +01:00
|
|
|
|
|
|
|
@staticmethod
|
2019-11-30 12:22:21 +01:00
|
|
|
def parse_points(mail: mboxMessage) -> float:
|
|
|
|
match = Parser.get_match_from_mail(Parser.POINTS_REGEX, mail)
|
2019-11-30 12:19:33 +01:00
|
|
|
return float(match.group(1))
|
|
|
|
|
2020-05-25 23:50:42 +02:00
|
|
|
def __init__(self, details: Dict, reviewer: str, homework: str) -> None:
|
|
|
|
self.homework = homework
|
|
|
|
|
|
|
|
self.from_ids = "submissions" in details
|
|
|
|
if self.from_ids:
|
|
|
|
self.submissions = details["submissions"]
|
|
|
|
else:
|
|
|
|
self.box = mbox(details["mbox_path"])
|
2020-04-03 14:10:37 +02:00
|
|
|
self.reviewer = reviewer
|
2019-11-30 17:48:29 +01:00
|
|
|
|
2020-06-08 17:39:36 +02:00
|
|
|
self.deadline = datetime.datetime.strptime(
|
|
|
|
details["deadline"], Parser.DATE_FORMAT
|
|
|
|
)
|
2020-04-03 14:10:37 +02:00
|
|
|
self.correction = details["correction"]
|
|
|
|
if self.correction:
|
2019-11-30 12:46:56 +01:00
|
|
|
# in case of correction pass the date of
|
|
|
|
# submitting review to IS for automatic computation
|
|
|
|
self.deadline = self.deadline.replace(hour=0, minute=0, second=0)
|
|
|
|
self.deadline += Parser.OFFSET_FOR_CORRECTION
|
2019-11-30 12:19:33 +01:00
|
|
|
|
2019-11-30 17:48:29 +01:00
|
|
|
def __get_submissions(self, hw_tag: str) -> Dict[str, List[Submission]]:
|
2020-05-25 23:50:42 +02:00
|
|
|
mails = []
|
|
|
|
if self.from_ids:
|
|
|
|
print(self.submissions)
|
|
|
|
for submission_id in self.submissions:
|
2020-06-08 17:39:36 +02:00
|
|
|
with open(
|
|
|
|
f"/home/kontr/kontr/_tmp_/pb071/{self.homework}/{submission_id}/teacher_email",
|
|
|
|
"r",
|
|
|
|
) as f:
|
2020-05-25 23:50:42 +02:00
|
|
|
mails.append(f.read())
|
|
|
|
else:
|
|
|
|
for mail in self.box.values():
|
2020-06-08 17:39:36 +02:00
|
|
|
if self.reviewer not in mail["subject"]:
|
2020-05-25 23:50:42 +02:00
|
|
|
continue
|
|
|
|
mails.append(Parser.get_mail(mail))
|
2020-04-03 14:10:37 +02:00
|
|
|
|
2020-05-25 23:50:42 +02:00
|
|
|
submissions = []
|
|
|
|
for text in mails:
|
2020-04-03 14:10:37 +02:00
|
|
|
uco, login = Parser.parse_info(text)
|
|
|
|
path, submission_id = Parser.parse_submission(text)
|
|
|
|
points = Parser.parse_points(text)
|
2019-11-30 17:48:29 +01:00
|
|
|
|
|
|
|
submissions.append(
|
2019-11-30 22:47:08 +01:00
|
|
|
Submission(
|
|
|
|
uco,
|
|
|
|
login,
|
|
|
|
path,
|
2019-12-02 10:52:57 +01:00
|
|
|
submission_id,
|
2019-11-30 22:47:08 +01:00
|
|
|
points,
|
|
|
|
hw_tag,
|
|
|
|
self.correction,
|
2020-04-03 14:10:37 +02:00
|
|
|
text,
|
2019-11-30 22:47:08 +01:00
|
|
|
)
|
2019-11-30 17:48:29 +01:00
|
|
|
)
|
|
|
|
submissions[-1].set_late_tag(self.deadline)
|
|
|
|
|
|
|
|
submissions.sort(key=lambda e: (e.login, e.submitted_at))
|
|
|
|
return dict(
|
|
|
|
map(
|
|
|
|
lambda val: (val[0], list(val[1])),
|
|
|
|
itertools.groupby(submissions, key=lambda s: s.login),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
def __filter(self, all_submissions: Dict[str, List[Submission]]) -> None:
|
|
|
|
for _, submissions in all_submissions.items():
|
2019-12-02 11:39:40 +01:00
|
|
|
if not submissions[0].submitted_before_deadline:
|
|
|
|
continue
|
|
|
|
|
2019-11-30 17:48:29 +01:00
|
|
|
length = len(submissions)
|
2019-12-02 11:39:40 +01:00
|
|
|
max_i = 0
|
|
|
|
|
|
|
|
for i in range(1, length):
|
|
|
|
if not submissions[i].submitted_before_deadline:
|
|
|
|
break
|
|
|
|
|
|
|
|
if submissions[i].points >= submissions[max_i].points:
|
|
|
|
max_i = i
|
2019-11-30 17:48:29 +01:00
|
|
|
|
2019-12-02 11:39:40 +01:00
|
|
|
submissions[max_i].flag = "REVIEW"
|
2019-11-30 17:48:29 +01:00
|
|
|
|
|
|
|
def __correct_errors(self, all_submissions: Dict[str, List[Submission]]) -> None:
|
|
|
|
response = input("Do you wish to fix errors? ").strip()
|
|
|
|
if not response or response == "n":
|
|
|
|
return True
|
|
|
|
|
|
|
|
print("Choose indices of submissions that are to be reviewed")
|
|
|
|
for login, submissions in all_submissions.items():
|
|
|
|
response = input(f"==> Choose submission to review for {login}: ").strip()
|
|
|
|
if not response or response == "n":
|
|
|
|
continue
|
|
|
|
|
|
|
|
j = int(response)
|
|
|
|
for i, submission in enumerate(submissions):
|
|
|
|
if submission.flag == "REVIEW":
|
|
|
|
submission.flag = None
|
|
|
|
if i == j:
|
|
|
|
submission.flag = "REVIEW"
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
def parse(self, hw_tag: str) -> List[Submission]:
|
|
|
|
def __reducer(lst, submission):
|
|
|
|
if submission.flag == "REVIEW":
|
|
|
|
lst.append(submission)
|
|
|
|
return lst
|
|
|
|
|
|
|
|
submissions = self.__get_submissions(hw_tag)
|
|
|
|
|
|
|
|
self.__filter(submissions)
|
|
|
|
print_submissions(submissions)
|
|
|
|
|
|
|
|
while not self.__correct_errors(submissions):
|
|
|
|
print_submissions(submissions)
|
|
|
|
|
|
|
|
result = []
|
|
|
|
for something in submissions.values():
|
2019-12-02 10:52:57 +01:00
|
|
|
functools.reduce(__reducer, something, result)
|
2019-11-30 17:48:29 +01:00
|
|
|
return result
|