feat: JOJ3 scoreboard (#28)

Co-authored-by: BoYanZh <boyanzh233@gmail.com>
This commit is contained in:
张佳澈520370910044 2024-06-09 05:12:29 +08:00 committed by GitHub
parent 1d6147db60
commit 1605335a1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 166 additions and 2 deletions

View File

@ -1,10 +1,13 @@
import os
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import List from typing import List
from git import Repo
from typer import Argument, Option, Typer, echo from typer import Argument, Option, Typer, echo
from joint_teapot.teapot import Teapot from joint_teapot.teapot import Teapot
from joint_teapot.utils import joj3
from joint_teapot.utils.logger import logger from joint_teapot.utils.logger import logger
app = Typer(add_completion=False) app = Typer(add_completion=False)
@ -192,6 +195,47 @@ def unsubscribe_from_repos(pattern: str = Argument("")) -> None:
tea.pot.gitea.unsubscribe_from_repos(pattern) tea.pot.gitea.unsubscribe_from_repos(pattern)
@app.command(
"JOJ3-scoreboard",
help="parse JOJ3 scoreboard json file and upload to gitea",
)
def JOJ3_scoreboard(
scorefile_path: str = Argument(
"", help="path to score json file generated by JOJ3"
),
student_name: str = Argument("", help="name of student"),
student_id: str = Argument("", help="id of student"),
repo_name: str = Argument(
"",
help="name of local gitea repo folder, or link to remote gitea repo, to push scoreboard file",
),
scoreboard_file_name: str = Argument(
"", help="name of scoreboard file in the gitea repo"
),
) -> None:
repo_path = tea.pot.git.repo_clean_and_checkout(repo_name, "grading")
repo: Repo = tea.pot.git.get_repo(repo_name)
if "grading" not in repo.remote().refs:
logger.error(
'"grading" branch not found in remote, create and push it to origin first.'
)
return
if "grading" not in repo.branches:
logger.error('"grading" branch not found in local, create it first.')
return
repo.git.reset("--hard", "origin/grading")
joj3.generate_scoreboard(
scorefile_path,
student_name,
student_id,
os.path.join(repo_path, scoreboard_file_name),
)
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
tea.pot.git.add_commit_and_push(
repo_name, [scoreboard_file_name], f"test: JOJ3-dev testing at {now}"
)
if __name__ == "__main__": if __name__ == "__main__":
try: try:
app() app()

104
joint_teapot/utils/joj3.py Normal file
View File

@ -0,0 +1,104 @@
import csv
import json
import os
from datetime import datetime
from typing import Any, Dict
from joint_teapot.utils.logger import logger
def generate_scoreboard(
score_file_path: str, student_name: str, student_id: str, scoreboard_file_path: str
) -> None:
if not scoreboard_file_path.endswith(".csv"):
logger.error(
f"Scoreboard file should be a .csv file, but now it is {scoreboard_file_path}"
)
return
# Load the csv file if it already exists
if os.path.exists(scoreboard_file_path):
with open(scoreboard_file_path, newline="") as file:
reader = csv.reader(file)
rows = list(reader)
columns = rows[0]
data = rows[1:]
else:
columns = [
"",
"last_edit", # FIXME:
# This is just to make changes in the file so that it can be pushed.
# Only used in development stage. Will be removed in the future.
"total",
]
data = []
column_updated = [False] * len(columns) # Record wether a score has been updated
# Update data
with open(score_file_path) as json_file:
scorefile: Dict[str, Any] = json.load(json_file)
student = f"{student_name} {student_id}"
student_found = False
for row in data:
if row[0] == student:
student_row = row # This is a reference of the original data
student_found = True
break
if not student_found:
student_row = [student, "", "0"] + [""] * (
len(columns) - 3
) # FIXME: In formal version should be -2
data.append(student_row)
for stagerecord in scorefile["stagerecords"]:
stagename = stagerecord["stagename"]
for stageresult in stagerecord["stageresults"]:
name = stageresult["name"]
for i, result in enumerate(stageresult["results"]):
score = result["score"]
colname = f"{stagename}/{name}"
if len(stageresult["results"]) != 1:
colname = f"{colname}/{i}"
if colname not in columns:
columns.append(colname)
column_updated.append(True)
for row in data:
row.append("")
student_row[columns.index(colname)] = score
column_updated[columns.index(colname)] = True
# Score of any unupdated columns should be cleared
for i, column in enumerate(columns):
if column in ["", "last_edit", "total"]:
continue
if column_updated[i] == False:
student_row[i] = ""
total = 0
for col in columns:
if col in ["", "total", "last_edit"]:
continue
idx = columns.index(col)
if (student_row[idx] is not None) and (student_row[idx] != ""):
total += int(student_row[idx])
student_row[columns.index("total")] = str(total)
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
student_row[
columns.index("last_edit")
] = now # FIXME: Delete this in formal version
# Sort data by total
data.sort(key=lambda x: int(x[columns.index("total")]), reverse=True)
# Write back to the csv file:
with open(scoreboard_file_path, mode="w", newline="") as file:
writer = csv.writer(file)
writer.writerow(columns)
writer.writerows(data)
def generate_comment(score_file_path: str) -> str:
# TODO
return ""

View File

@ -1,7 +1,7 @@
import os import os
import sys import sys
from time import sleep from time import sleep
from typing import Optional from typing import List, Optional
from joint_teapot.utils.logger import logger from joint_teapot.utils.logger import logger
@ -97,3 +97,19 @@ class Git:
else: else:
raise raise
return repo_dir return repo_dir
def add_commit_and_push(
self, repo_name: str, files_to_add: List[str], commit_message: str
) -> None:
repo: Repo = self.get_repo(repo_name)
for file in files_to_add:
try:
repo.index.add(file)
except OSError:
logger.warning(
f'File path "{file}" does not exist. Skipping this file.'
)
continue
repo.index.commit(commit_message)
origin = repo.remote(name="origin")
origin.push()

View File

@ -8,4 +8,4 @@ mattermostdriver>=7.3.2
patool>=1.12 patool>=1.12
pydantic>=2.0.2 pydantic>=2.0.2
pydantic-settings>=2.0.1 pydantic-settings>=2.0.1
typer[all]>=0.3.2 typer>=0.12.3