Compare commits

..

1 Commits

Author SHA1 Message Date
fabcce0893
fix(regex): full match 2025-06-20 22:58:29 +08:00
4 changed files with 49 additions and 202 deletions

View File

@ -50,17 +50,13 @@ def add_all_canvas_students_to_teams(team_names: List[str]) -> None:
"create-personal-repos",
help="create personal repos on gitea for all canvas students",
)
def create_personal_repos_for_all_canvas_students(
suffix: str = Option(""), template: str = Option("", help="generate from template")
) -> None:
tea.pot.create_personal_repos_for_all_canvas_students(suffix, template)
def create_personal_repos_for_all_canvas_students(suffix: str = Option("")) -> None:
tea.pot.create_personal_repos_for_all_canvas_students(suffix)
@app.command("create-teams", help="create teams on gitea by canvas groups")
def create_teams_and_repos_by_canvas_groups(
group_prefix: str, template: str = Option("", help="generate from template")
) -> None:
tea.pot.create_teams_and_repos_by_canvas_groups(group_prefix, template)
def create_teams_and_repos_by_canvas_groups(group_prefix: str) -> None:
tea.pot.create_teams_and_repos_by_canvas_groups(group_prefix)
@app.command("get-public-keys", help="list all public keys on gitea")
@ -299,23 +295,6 @@ def joj3_all_env(
True,
help="whether to include submitter in issue title",
),
issue_label_name: str = Option(
"Kind/Testing",
help="label name for the issue created by this command",
),
issue_label_color: str = Option(
"#795548",
help="label color for the issue created by this command",
),
end_time: Optional[datetime] = Option(None),
penalty_config: str = Option(
"",
help=(
"Configuration for penalties in the format "
"'hours=factor'. "
"Example: --penalty-config 24=0.75,48=0.5"
),
),
) -> None:
app.pretty_exceptions_enable = False
set_settings(Settings(_env_file=env_path))
@ -331,7 +310,6 @@ def joj3_all_env(
logger.error("missing required env var")
raise Exit(code=1)
submitter_repo_name = env.github_repository.split("/")[-1]
penalty_factor = joj3.get_penalty_factor(end_time, penalty_config)
total_score = joj3.get_total_score(env.joj3_output_path)
res = {
"totalScore": total_score,
@ -358,9 +336,6 @@ def joj3_all_env(
gitea_actions_url,
submitter_in_issue_title,
submitter_repo_name,
issue_label_name,
issue_label_color,
penalty_factor,
)
res["issue"] = issue_number
gitea_issue_url = f"{submitter_repo_url}/issues/{issue_number}"
@ -471,16 +446,8 @@ def joj3_check_env(
"Example: --group-config joj=10:24,run=20:48"
),
),
begin_time: Optional[datetime] = Option(None),
end_time: Optional[datetime] = Option(None),
penalty_config: str = Option(
"",
help=(
"Configuration for penalties in the format "
"'hours=factor'. "
"Example: --penalty-config 24=0.75,48=0.5"
),
),
valid_after: Optional[datetime] = Option(None),
valid_before: Optional[datetime] = Option(None),
) -> None:
app.pretty_exceptions_enable = False
set_settings(Settings(_env_file=env_path))
@ -494,9 +461,8 @@ def joj3_check_env(
logger.error("missing required env var")
raise Exit(code=1)
time_msg, time_failed = tea.pot.joj3_check_submission_time(
begin_time,
end_time,
penalty_config,
valid_after,
valid_before,
)
count_msg, count_failed = tea.pot.joj3_check_submission_count(
env, grading_repo_name, group_config, scoreboard_filename

View File

@ -96,16 +96,15 @@ class Teapot:
return self.gitea.add_canvas_students_to_teams(self.canvas.students, team_names)
def create_personal_repos_for_all_canvas_students(
self, suffix: str = "", template: str = ""
self, suffix: str = ""
) -> List[str]:
return self.gitea.create_personal_repos_for_canvas_students(
self.canvas.students,
lambda user: default_repo_name_convertor(user) + suffix,
template,
)
def create_teams_and_repos_by_canvas_groups(
self, group_prefix: str = "", template: str = ""
self, group_prefix: str = ""
) -> List[str]:
def convertor(name: str) -> Optional[str]:
if group_prefix and not name.startswith(group_prefix):
@ -115,7 +114,7 @@ class Teapot:
return f"{team_name}{number:02}"
return self.gitea.create_teams_and_repos_by_canvas_groups(
self.canvas.students, self.canvas.groups, convertor, convertor, template
self.canvas.students, self.canvas.groups, convertor, convertor
)
def get_public_key_of_all_canvas_students(self) -> Dict[str, List[str]]:
@ -238,9 +237,6 @@ class Teapot:
gitea_actions_url: str,
submitter_in_issue_title: bool,
submitter_repo_name: str,
issue_label_name: str,
issue_label_color: str,
penalty_factor: float,
) -> int:
title, comment = joj3.generate_title_and_comment(
env.joj3_output_path,
@ -252,7 +248,6 @@ class Teapot:
submitter_in_issue_title,
env.joj3_run_id,
max_total_score,
penalty_factor,
)
title_prefix = joj3.get_title_prefix(
env.joj3_conf_name, env.github_actor, submitter_in_issue_title
@ -269,24 +264,10 @@ class Teapot:
break
else:
new_issue = True
labels = self.gitea.issue_api.issue_list_labels(
self.gitea.org_name, submitter_repo_name
)
label_id = 0
label = first(labels, lambda label: label.name == issue_label_name)
if label:
label_id = label.id
else:
label = self.gitea.issue_api.issue_create_label(
self.gitea.org_name,
submitter_repo_name,
body={"name": issue_label_name, "color": issue_label_color},
)
label_id = label.id
joj3_issue = self.gitea.issue_api.issue_create_issue(
self.gitea.org_name,
submitter_repo_name,
body={"title": title, "body": comment, "labels": [label_id]},
body={"title": title, "body": comment},
)
logger.info(f"created joj3 issue: #{joj3_issue.number}")
gitea_issue_url = joj3_issue.html_url
@ -302,49 +283,21 @@ class Teapot:
def joj3_check_submission_time(
self,
begin_time: Optional[datetime] = None,
end_time: Optional[datetime] = None,
penalty_config: str = "",
valid_after: Optional[datetime] = None,
valid_before: Optional[datetime] = None,
) -> Tuple[str, bool]:
now = datetime.now()
penalties = joj3.parse_penalty_config(penalty_config)
if penalties and end_time:
penalty_end_time = end_time + timedelta(hours=penalties[-1][0])
if begin_time and now < begin_time:
if (valid_after and now < valid_after) or (valid_before and now > valid_before):
return (
"### Submission Time Check Failed\n"
"### Submission Time Check Failed:\n"
f"Current time {now} is not in the valid range "
f"[{begin_time}, {end_time}].\n",
True,
)
elif now > penalty_end_time:
return (
"### Submission Time Check Failed\n"
f"Current time {now} is not in the valid range "
f"[{begin_time}, {end_time}], and the penalty range "
f"[{end_time + timedelta(seconds=1)}, {penalty_end_time}].\n",
True,
)
else:
return (
"### Submission Time Check Passed\n"
f"Current time {now} is not in the valid range "
f"[{begin_time}, {end_time}], but in the penalty range "
f"[{end_time + timedelta(seconds=1)}, {penalty_end_time}].\n",
False,
)
else:
if (begin_time and now < begin_time) or (end_time and now > end_time):
return (
"### Submission Time Check Failed\n"
f"Current time {now} is not in the valid range "
f"[{begin_time}, {end_time}].\n",
f"[{valid_after}, {valid_before}].\n",
True,
)
return (
"### Submission Time Check Passed\n"
"### Submission Time Check Passed:\n"
f"Current time {now} is in the valid range "
f"[{begin_time}, {end_time}].\n",
f"[{valid_after}, {valid_before}].\n",
False,
)
@ -369,11 +322,7 @@ class Teapot:
time_windows = []
valid_items = []
for item in items:
if "=" not in item:
continue
name, values = item.split("=")
if ":" not in values:
continue
max_count, time_period = map(int, values.split(":"))
if max_count < 0 or time_period < 0:
continue
@ -445,9 +394,9 @@ class Teapot:
comment += "."
comment += "\n"
if failed:
title = "### Submission Count Check Failed"
title = "### Submission Count Check Failed:"
else:
title = "### Submission Count Check Passed"
title = "### Submission Count Check Passed:"
msg = f"{title}\n{comment}\n"
return msg, failed

View File

@ -2,8 +2,8 @@ import bisect
import csv
import json
import os
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Tuple
from datetime import datetime
from typing import Any, Dict, List, Tuple
from pydantic_settings import BaseSettings
@ -215,7 +215,6 @@ def generate_title_and_comment(
submitter_in_title: bool = True,
run_id: str = "unknown",
max_total_score: int = -1,
penalty_factor: float = 1.0,
) -> Tuple[str, str]:
with open(score_file_path) as json_file:
stages: List[Dict[str, Any]] = json.load(json_file)
@ -235,10 +234,6 @@ def generate_title_and_comment(
"Powered by [JOJ3](https://github.com/joint-online-judge/JOJ3) and "
"[Joint-Teapot](https://github.com/BoYanZh/Joint-Teapot) with ❤️.\n"
)
if penalty_factor != 1.0:
comment += (
f"## Total Score Penalty Warning\nThe total score is multiplied by 0.75.\n"
)
for stage in stages:
if all(
result["score"] == 0 and result["comment"].strip() == ""
@ -259,8 +254,6 @@ def generate_title_and_comment(
comment += "</details>\n\n"
total_score += result["score"]
comment += "\n"
if penalty_factor != 1.0:
total_score = round(total_score * penalty_factor)
title = get_title_prefix(exercise_name, submitter, submitter_in_title)
if max_total_score >= 0:
title += f"{total_score} / {max_total_score}"
@ -288,31 +281,3 @@ def get_title_prefix(
if not submitter_in_title:
title = f"JOJ3 Result for {exercise_name} - Score: "
return title
def parse_penalty_config(penalty_config: str) -> List[Tuple[float, float]]:
res = []
for penalty in penalty_config.split(","):
if "=" not in penalty:
continue
hour, factor = map(float, penalty.split("="))
res.append((hour, factor))
res.sort(key=lambda x: x[0])
return res
def get_penalty_factor(
end_time: Optional[datetime],
penalty_config: str,
) -> float:
if not end_time or not penalty_config:
return 1.0
penalties = parse_penalty_config(penalty_config)
now = datetime.now()
res = 0.0
for hour, factor in penalties[::-1]:
if now < end_time + timedelta(hours=hour):
res = factor
else:
break
return res

View File

@ -116,7 +116,6 @@ class Gitea:
repo_name_convertor: Callable[
[User], Optional[str]
] = default_repo_name_convertor,
template: str = "",
) -> List[str]:
repo_names = []
for student in students:
@ -124,9 +123,6 @@ class Gitea:
if repo_name is None:
continue
repo_names.append(repo_name)
try:
try:
if template == "":
body = {
"auto_init": False,
"default_branch": settings.default_branch,
@ -135,21 +131,9 @@ class Gitea:
"template": False,
"trust_model": "default",
}
try:
try:
self.organization_api.create_org_repo(self.org_name, body=body)
else:
body = {
"default_branch": settings.default_branch,
"git_content": True,
"git_hooks": True,
"labels": True,
"name": repo_name,
"owner": self.org_name,
"private": True,
"protected_branch": True,
}
self.repository_api.generate_repo(
self.org_name, template, body=body
)
logger.info(
f"Personal repo {self.org_name}/{repo_name} for {student} created"
)
@ -174,7 +158,6 @@ class Gitea:
groups: PaginatedList,
team_name_convertor: Callable[[str], Optional[str]] = lambda name: name,
repo_name_convertor: Callable[[str], Optional[str]] = lambda name: name,
template: str = "",
permission: PermissionEnum = PermissionEnum.write,
) -> List[str]:
repo_names = []
@ -207,10 +190,9 @@ class Gitea:
],
},
)
logger.info(f"Team {team_name} created")
logger.info(f"{self.org_name}/{team_name} created")
if first(repos, lambda repo: repo.name == repo_name) is None:
repo_names.append(repo_name)
if template == "":
self.organization_api.create_org_repo(
self.org_name,
body={
@ -222,22 +204,7 @@ class Gitea:
"trust_model": "default",
},
)
else:
self.repository_api.generate_repo(
self.org_name,
template,
body={
"default_branch": settings.default_branch,
"git_content": True,
"git_hooks": True,
"labels": True,
"name": repo_name,
"owner": self.org_name,
"private": True,
"protected_branch": True,
},
)
logger.info(f"{self.org_name}/{team_name} created")
logger.info(f"Team {team_name} created")
try:
self.organization_api.org_add_team_repository(
team.id, self.org_name, repo_name