assignment-codeval 0.0.27__tar.gz → 0.0.29__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/PKG-INFO +4 -1
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/README.md +3 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/pyproject.toml +1 -1
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/check_grading.py +1 -1
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/convertMD2Html.py +49 -15
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/evaluate.py +0 -5
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/recent_comments.py +2 -2
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/submissions.py +40 -1
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval.egg-info/PKG-INFO +4 -1
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/setup.cfg +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/__init__.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/ai_benchmark.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/canvas_utils.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/cli.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/commons.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/create_assignment.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/export_tests.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/file_utils.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/github_connect.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/install_assignment.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/test_template.html +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval.egg-info/SOURCES.txt +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval.egg-info/dependency_links.txt +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval.egg-info/entry_points.txt +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval.egg-info/requires.txt +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval.egg-info/top_level.txt +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/tests/test_check_grading.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/tests/test_codeval.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/tests/test_create_assignment.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/tests/test_evaluate_submissions.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/tests/test_export_tests.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/tests/test_function_detection.py +0 -0
- {assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/tests/test_install_assignment.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: assignment-codeval
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.29
|
|
4
4
|
Summary: CodEval for evaluating programming assignments
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -57,6 +57,9 @@ Use a part of the course name that can uniquely identify the course on Canvas.
|
|
|
57
57
|
- Default: --no-copytmpdir
|
|
58
58
|
- Copy temporary directory content to current directory for debugging
|
|
59
59
|
|
|
60
|
+
### Codeval File Matching
|
|
61
|
+
When looking up the codeval file for an assignment, CodEval first tries to match by filename (case-insensitive). If no filename match is found, it falls back to checking the `CRT_HW START` title inside each `.codeval` file in the codeval directory. This allows the codeval filename to differ from the assignment name on Canvas as long as the `CRT_HW START` title matches.
|
|
62
|
+
|
|
60
63
|
### Specification Tags
|
|
61
64
|
Tags used in a spec file (\<course name>.codeval)
|
|
62
65
|
|
|
@@ -37,6 +37,9 @@ Use a part of the course name that can uniquely identify the course on Canvas.
|
|
|
37
37
|
- Default: --no-copytmpdir
|
|
38
38
|
- Copy temporary directory content to current directory for debugging
|
|
39
39
|
|
|
40
|
+
### Codeval File Matching
|
|
41
|
+
When looking up the codeval file for an assignment, CodEval first tries to match by filename (case-insensitive). If no filename match is found, it falls back to checking the `CRT_HW START` title inside each `.codeval` file in the codeval directory. This allows the codeval filename to differ from the assignment name on Canvas as long as the `CRT_HW START` title matches.
|
|
42
|
+
|
|
40
43
|
### Specification Tags
|
|
41
44
|
Tags used in a spec file (\<course name>.codeval)
|
|
42
45
|
|
{assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/check_grading.py
RENAMED
|
@@ -62,7 +62,7 @@ def _has_codeval_comment_after_submission(submission_node, codeval_prefix):
|
|
|
62
62
|
comments = submission_node.get("commentsConnection", {}).get("nodes", [])
|
|
63
63
|
for comment in comments:
|
|
64
64
|
text = comment.get("comment", "")
|
|
65
|
-
if not text.startswith(codeval_prefix):
|
|
65
|
+
if not text.startswith(codeval_prefix.rstrip()):
|
|
66
66
|
continue
|
|
67
67
|
created_at = comment.get("createdAt")
|
|
68
68
|
if created_at and created_at > submitted_at:
|
{assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/convertMD2Html.py
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
|
+
import zipfile
|
|
3
4
|
|
|
4
5
|
import markdown
|
|
5
6
|
|
|
@@ -41,19 +42,28 @@ def ansi_to_html(text):
|
|
|
41
42
|
return ''.join(result)
|
|
42
43
|
|
|
43
44
|
|
|
44
|
-
def _read_file_content(filename, spec_dir):
|
|
45
|
-
"""Read the content of a file referenced by an OF
|
|
46
|
-
if not spec_dir:
|
|
47
|
-
return None
|
|
48
|
-
filepath = os.path.join(spec_dir, filename)
|
|
49
|
-
try:
|
|
50
|
-
with open(filepath, 'r') as f:
|
|
51
|
-
return f.read()
|
|
52
|
-
except (FileNotFoundError, OSError):
|
|
53
|
-
return None
|
|
45
|
+
def _read_file_content(filename, spec_dir, zip_archives=None):
|
|
46
|
+
"""Read the content of a file referenced by an IF/OF/EF tag.
|
|
54
47
|
|
|
48
|
+
Searches local spec_dir first, then zip archives. Returns content as string.
|
|
49
|
+
"""
|
|
50
|
+
if spec_dir:
|
|
51
|
+
filepath = os.path.join(spec_dir, filename)
|
|
52
|
+
try:
|
|
53
|
+
with open(filepath, 'r') as f:
|
|
54
|
+
return f.read()
|
|
55
|
+
except (FileNotFoundError, OSError):
|
|
56
|
+
pass
|
|
57
|
+
if zip_archives:
|
|
58
|
+
for zf in zip_archives:
|
|
59
|
+
try:
|
|
60
|
+
return zf.read(filename).decode('utf-8')
|
|
61
|
+
except KeyError:
|
|
62
|
+
continue
|
|
63
|
+
return None
|
|
55
64
|
|
|
56
|
-
|
|
65
|
+
|
|
66
|
+
def sampleTestCases(listOfTC, numOfTC, spec_dir=None, zip_archives=None):
|
|
57
67
|
counter = 0
|
|
58
68
|
samples = "<pre><code>"
|
|
59
69
|
for line in listOfTC:
|
|
@@ -64,7 +74,7 @@ def sampleTestCases(listOfTC, numOfTC, spec_dir=None):
|
|
|
64
74
|
samples = samples + "\n" + "Command to RUN: " + line[2:]
|
|
65
75
|
elif line.startswith('IF '):
|
|
66
76
|
filename = line[3:].strip()
|
|
67
|
-
content = _read_file_content(filename, spec_dir)
|
|
77
|
+
content = _read_file_content(filename, spec_dir, zip_archives)
|
|
68
78
|
if content is not None:
|
|
69
79
|
samples = samples + "<span style=\"color:green\">" + ansi_to_html(content) + "</span>"
|
|
70
80
|
else:
|
|
@@ -75,7 +85,7 @@ def sampleTestCases(listOfTC, numOfTC, spec_dir=None):
|
|
|
75
85
|
samples = samples + "<span style=\"color:green\">" + line[2:] + "</span>"
|
|
76
86
|
elif line.startswith('OF '):
|
|
77
87
|
filename = line[3:].strip()
|
|
78
|
-
content = _read_file_content(filename, spec_dir)
|
|
88
|
+
content = _read_file_content(filename, spec_dir, zip_archives)
|
|
79
89
|
if content is not None:
|
|
80
90
|
samples = samples + "<span style=\"color:blue\">" + ansi_to_html(content) + "</span>"
|
|
81
91
|
else:
|
|
@@ -86,6 +96,13 @@ def sampleTestCases(listOfTC, numOfTC, spec_dir=None):
|
|
|
86
96
|
samples = samples + "<span style=\"color:blue\">" + line[2:] + "</span>"
|
|
87
97
|
elif line.startswith('X '):
|
|
88
98
|
samples = samples + "Expected Exit Code: " + line[2:]
|
|
99
|
+
elif line.startswith('EF '):
|
|
100
|
+
filename = line[3:].strip()
|
|
101
|
+
content = _read_file_content(filename, spec_dir, zip_archives)
|
|
102
|
+
if content is not None:
|
|
103
|
+
samples = samples + "<span style=\"color:Tomato\">" + ansi_to_html(content) + "</span>"
|
|
104
|
+
else:
|
|
105
|
+
samples = samples + "<span style=\"color:Tomato\">Expected error from file: " + filename + "\n</span>"
|
|
89
106
|
elif line.startswith('EB '):
|
|
90
107
|
samples = samples + "<span style=\"color:Tomato\">" + line[3:] + "</span>"
|
|
91
108
|
elif line.startswith('E '):
|
|
@@ -104,24 +121,41 @@ def mdToHtml(file_name, files_resolver=None):
|
|
|
104
121
|
compile_command = ""
|
|
105
122
|
past_crt_hw = False
|
|
106
123
|
numOfSampleTC = 1
|
|
124
|
+
zip_paths = []
|
|
107
125
|
for line in f:
|
|
108
126
|
if 'CRT_HW START' in line:
|
|
109
127
|
assignment_name = line[13:].strip()
|
|
110
128
|
elif 'CRT_HW END' in line:
|
|
111
129
|
assignment = text
|
|
112
130
|
past_crt_hw = True
|
|
113
|
-
elif line.startswith(('T ', 'I ', 'IB ', 'IF ', 'O ', 'OB ', 'OF ',
|
|
131
|
+
elif line.startswith(('T ', 'I ', 'IB ', 'IF ', 'O ', 'OB ', 'OF ',
|
|
132
|
+
'EF ', 'X ', 'E ', 'EB ')):
|
|
114
133
|
examples.append(line)
|
|
115
134
|
elif line.startswith('HT '):
|
|
116
135
|
break
|
|
117
136
|
else:
|
|
118
137
|
if past_crt_hw and line.startswith('C '):
|
|
119
138
|
compile_command = line[2:].strip()
|
|
139
|
+
if past_crt_hw and line.startswith('Z '):
|
|
140
|
+
zip_paths.append(line[2:].strip())
|
|
120
141
|
if 'EXMPLS ' in line:
|
|
121
142
|
numOfSampleTC = int(line[7:])
|
|
122
143
|
text = text + line
|
|
123
144
|
spec_dir = os.path.dirname(os.path.abspath(file_name))
|
|
124
|
-
|
|
145
|
+
|
|
146
|
+
# Open zip archives for IF/OF/EF file resolution
|
|
147
|
+
zip_archives = []
|
|
148
|
+
try:
|
|
149
|
+
for zp in zip_paths:
|
|
150
|
+
full_path = os.path.join(spec_dir, zp)
|
|
151
|
+
if os.path.exists(full_path):
|
|
152
|
+
zip_archives.append(zipfile.ZipFile(full_path, 'r'))
|
|
153
|
+
samples = sampleTestCases(examples, numOfSampleTC, spec_dir,
|
|
154
|
+
zip_archives)
|
|
155
|
+
finally:
|
|
156
|
+
for zf in zip_archives:
|
|
157
|
+
zf.close()
|
|
158
|
+
|
|
125
159
|
assignment = re.sub('EXMPLS [0-9]+', samples, assignment)
|
|
126
160
|
if compile_command:
|
|
127
161
|
assignment = assignment.replace('COMPILE', compile_command)
|
|
@@ -1138,8 +1138,6 @@ def check_test():
|
|
|
1138
1138
|
if len(lines) > 22:
|
|
1139
1139
|
print(f" ... ({len(lines) - 22} more lines)")
|
|
1140
1140
|
|
|
1141
|
-
cleanup()
|
|
1142
|
-
|
|
1143
1141
|
# Exit program after failed test case
|
|
1144
1142
|
sys.exit(2)
|
|
1145
1143
|
|
|
@@ -1215,8 +1213,5 @@ def run_evaluation(codeval_file):
|
|
|
1215
1213
|
|
|
1216
1214
|
check_test()
|
|
1217
1215
|
|
|
1218
|
-
# cleanup
|
|
1219
|
-
cleanup()
|
|
1220
|
-
|
|
1221
1216
|
end_time_seconds = time.time()
|
|
1222
1217
|
print(f"took {end_time_seconds - start_time_seconds} seconds")
|
{assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/recent_comments.py
RENAMED
|
@@ -12,6 +12,7 @@ from assignment_codeval.canvas_utils import (
|
|
|
12
12
|
get_canvas_credentials, graphql_request, fetch_all_submissions,
|
|
13
13
|
)
|
|
14
14
|
from assignment_codeval.commons import despace
|
|
15
|
+
from assignment_codeval.submissions import find_codeval_file
|
|
15
16
|
|
|
16
17
|
COURSE_ASSIGNMENTS_QUERY = """
|
|
17
18
|
query CourseAssignmentsQuery($courseId: ID!) {
|
|
@@ -164,8 +165,7 @@ def recent_comments(course_name, active, time_period, codeval_prefix, verbose, s
|
|
|
164
165
|
# Check if this assignment has a codeval file
|
|
165
166
|
has_codeval = True
|
|
166
167
|
if codeval_dir:
|
|
167
|
-
|
|
168
|
-
has_codeval = os.path.exists(codeval_file)
|
|
168
|
+
has_codeval = find_codeval_file(codeval_dir, despace(assignment_name)) is not None
|
|
169
169
|
if not has_codeval:
|
|
170
170
|
if verbose:
|
|
171
171
|
click.echo(f" (no codeval file)", err=True)
|
{assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/submissions.py
RENAMED
|
@@ -17,13 +17,41 @@ from assignment_codeval.canvas_utils import connect_to_canvas, get_course, get_c
|
|
|
17
17
|
from assignment_codeval.commons import debug, error, info, warn, despace
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def _extract_codeval_title(filepath):
|
|
21
|
+
"""Extract the assignment title from the CRT_HW START line of a codeval file.
|
|
22
|
+
Returns None if no CRT_HW START line is found."""
|
|
23
|
+
try:
|
|
24
|
+
with open(filepath, 'r') as f:
|
|
25
|
+
for line in f:
|
|
26
|
+
if 'CRT_HW START' in line:
|
|
27
|
+
idx = line.index('CRT_HW START') + len('CRT_HW START')
|
|
28
|
+
title = line[idx:].strip()
|
|
29
|
+
return title if title else None
|
|
30
|
+
except (OSError, UnicodeDecodeError):
|
|
31
|
+
pass
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
20
35
|
def find_codeval_file(codeval_dir, assignment_name):
|
|
21
36
|
"""Find a codeval file by assignment name, case-insensitive.
|
|
37
|
+
First tries matching by filename, then falls back to checking
|
|
38
|
+
CRT_HW START titles inside each .codeval file.
|
|
22
39
|
Returns the full path if found, or None if not found."""
|
|
23
40
|
target = f"{assignment_name}.codeval".lower()
|
|
41
|
+
codeval_files = []
|
|
24
42
|
for f in os.listdir(codeval_dir):
|
|
25
43
|
if f.lower() == target:
|
|
26
44
|
return os.path.join(codeval_dir, f)
|
|
45
|
+
if f.endswith('.codeval'):
|
|
46
|
+
codeval_files.append(f)
|
|
47
|
+
|
|
48
|
+
# Fallback: check CRT_HW START titles
|
|
49
|
+
target_name = assignment_name.lower()
|
|
50
|
+
for f in codeval_files:
|
|
51
|
+
filepath = os.path.join(codeval_dir, f)
|
|
52
|
+
title = _extract_codeval_title(filepath)
|
|
53
|
+
if title and despace(title).lower() == target_name:
|
|
54
|
+
return filepath
|
|
27
55
|
return None
|
|
28
56
|
|
|
29
57
|
|
|
@@ -596,7 +624,15 @@ def list_codeval_assignments(course_name):
|
|
|
596
624
|
raise click.UsageError(f"CODEVAL directory does not exist: {codeval_dir}")
|
|
597
625
|
|
|
598
626
|
# Get all codeval files in the directory (map lowercase name -> original name)
|
|
599
|
-
codeval_files = {
|
|
627
|
+
codeval_files = {}
|
|
628
|
+
codeval_titles = {} # map despaced-lowercase CRT_HW title -> original filename stem
|
|
629
|
+
for f in os.listdir(codeval_dir):
|
|
630
|
+
if f.endswith('.codeval'):
|
|
631
|
+
stem = f[:-8]
|
|
632
|
+
codeval_files[stem.lower()] = stem
|
|
633
|
+
title = _extract_codeval_title(os.path.join(codeval_dir, f))
|
|
634
|
+
if title:
|
|
635
|
+
codeval_titles[despace(title).lower()] = stem
|
|
600
636
|
matched_codeval_keys = set()
|
|
601
637
|
|
|
602
638
|
(canvas, user) = connect_to_canvas()
|
|
@@ -611,6 +647,9 @@ def list_codeval_assignments(course_name):
|
|
|
611
647
|
if assignment_key in codeval_files:
|
|
612
648
|
matched_codeval_keys.add(assignment_key)
|
|
613
649
|
info(f"{course.name}: {assignment.name}")
|
|
650
|
+
elif assignment_key in codeval_titles:
|
|
651
|
+
matched_codeval_keys.add(codeval_titles[assignment_key].lower())
|
|
652
|
+
info(f"{course.name}: {assignment.name}")
|
|
614
653
|
|
|
615
654
|
# Report codeval files that don't match any assignment
|
|
616
655
|
unmatched_keys = set(codeval_files.keys()) - matched_codeval_keys
|
{assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: assignment-codeval
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.29
|
|
4
4
|
Summary: CodEval for evaluating programming assignments
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -57,6 +57,9 @@ Use a part of the course name that can uniquely identify the course on Canvas.
|
|
|
57
57
|
- Default: --no-copytmpdir
|
|
58
58
|
- Copy temporary directory content to current directory for debugging
|
|
59
59
|
|
|
60
|
+
### Codeval File Matching
|
|
61
|
+
When looking up the codeval file for an assignment, CodEval first tries to match by filename (case-insensitive). If no filename match is found, it falls back to checking the `CRT_HW START` title inside each `.codeval` file in the codeval directory. This allows the codeval filename to differ from the assignment name on Canvas as long as the `CRT_HW START` title matches.
|
|
62
|
+
|
|
60
63
|
### Specification Tags
|
|
61
64
|
Tags used in a spec file (\<course name>.codeval)
|
|
62
65
|
|
|
File without changes
|
|
File without changes
|
{assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/ai_benchmark.py
RENAMED
|
File without changes
|
{assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/canvas_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/create_assignment.py
RENAMED
|
File without changes
|
{assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/export_tests.py
RENAMED
|
File without changes
|
{assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/file_utils.py
RENAMED
|
File without changes
|
{assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/github_connect.py
RENAMED
|
File without changes
|
{assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/install_assignment.py
RENAMED
|
File without changes
|
{assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval/test_template.html
RENAMED
|
File without changes
|
{assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{assignment_codeval-0.0.27 → assignment_codeval-0.0.29}/src/assignment_codeval.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|