assignment-codeval 0.0.30__py3-none-any.whl → 0.0.31__py3-none-any.whl

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.
@@ -75,8 +75,8 @@ def extract_assignment_from_codeval(codeval_path: str) -> tuple[str, str, str]:
75
75
  with open(codeval_path, "r", encoding="utf-8") as f:
76
76
  content = f.read()
77
77
 
78
- # Extract content between CRT_HW START and CRT_HW END
79
- match = re.search(r"CRT_HW START \S+\n(.*?)CRT_HW END", content, re.DOTALL)
78
+ # Extract content between ASSIGNMENT START / CRT_HW START and ASSIGNMENT END / CRT_HW END
79
+ match = re.search(r"(?:ASSIGNMENT START|CRT_HW START) \S+\n(.*?)(?:ASSIGNMENT END|CRT_HW END)", content, re.DOTALL)
80
80
  if match:
81
81
  description = match.group(1).strip()
82
82
  else:
@@ -1,180 +1,182 @@
1
- import os
2
- import re
3
- import zipfile
4
-
5
- import markdown
6
-
7
- from assignment_codeval.commons import info, get_config
8
-
9
-
10
- _ANSI_STYLES = {
11
- '1': 'font-weight:bold',
12
- '4': 'text-decoration:underline',
13
- '30': 'color:black', '31': 'color:red', '32': 'color:green',
14
- '33': 'color:#a50', '34': 'color:blue', '35': 'color:magenta',
15
- '36': 'color:cyan', '37': 'color:white',
16
- '40': 'background-color:black', '41': 'background-color:red',
17
- '42': 'background-color:green', '43': 'background-color:yellow',
18
- '44': 'background-color:blue', '45': 'background-color:magenta',
19
- '46': 'background-color:cyan', '47': 'background-color:white',
20
- }
21
-
22
-
23
- def ansi_to_html(text):
24
- """Convert ANSI escape codes in text to HTML span tags."""
25
- parts = re.split(r'\x1b\[([\d;]*)m', text)
26
- result = []
27
- open_spans = 0
28
- for i, part in enumerate(parts):
29
- if i % 2 == 0:
30
- result.append(part)
31
- else:
32
- codes = part.split(';') if part else ['0']
33
- if '0' in codes:
34
- result.append('</span>' * open_spans)
35
- open_spans = 0
36
- else:
37
- styles = [_ANSI_STYLES[c] for c in codes if c in _ANSI_STYLES]
38
- if styles:
39
- result.append(f'<span style="{";".join(styles)}">')
40
- open_spans += 1
41
- result.append('</span>' * open_spans)
42
- return ''.join(result)
43
-
44
-
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.
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
64
-
65
-
66
- def sampleTestCases(listOfTC, numOfTC, spec_dir=None, zip_archives=None):
67
- counter = 0
68
- samples = "<pre><code>"
69
- for line in listOfTC:
70
- if line.startswith('T ', 0, 2):
71
- counter = counter + 1
72
- if counter > numOfTC:
73
- break
74
- samples = samples + "\n" + "Command to RUN: " + line[2:]
75
- elif line.startswith('IF '):
76
- filename = line[3:].strip()
77
- content = _read_file_content(filename, spec_dir, zip_archives)
78
- if content is not None:
79
- samples = samples + "<span style=\"color:green\">" + ansi_to_html(content) + "</span>"
80
- else:
81
- samples = samples + "<span style=\"color:green\">Input from file: " + filename + "\n</span>"
82
- elif line.startswith('IB '):
83
- samples = samples + "<span style=\"color:green\">" + line[3:] + "</span>"
84
- elif line.startswith('I '):
85
- samples = samples + "<span style=\"color:green\">" + line[2:] + "</span>"
86
- elif line.startswith('OF '):
87
- filename = line[3:].strip()
88
- content = _read_file_content(filename, spec_dir, zip_archives)
89
- if content is not None:
90
- samples = samples + "<span style=\"color:blue\">" + ansi_to_html(content) + "</span>"
91
- else:
92
- samples = samples + "<span style=\"color:blue\">Expected output from file: " + filename + "\n</span>"
93
- elif line.startswith('OB '):
94
- samples = samples + "<span style=\"color:blue\">" + line[3:] + "</span>"
95
- elif line.startswith('O '):
96
- samples = samples + "<span style=\"color:blue\">" + line[2:] + "</span>"
97
- elif line.startswith('X '):
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>"
106
- elif line.startswith('EB '):
107
- samples = samples + "<span style=\"color:Tomato\">" + line[3:] + "</span>"
108
- elif line.startswith('E '):
109
- samples = samples + "<span style=\"color:Tomato\">" + line[2:] + "</span>"
110
- else:
111
- continue
112
- samples = samples + "</code></pre>"
113
- return samples
114
-
115
-
116
- def mdToHtml(file_name, files_resolver=None):
117
- with open(file_name, 'r') as f:
118
- text = ""
119
- examples = []
120
- assignment = ""
121
- compile_command = ""
122
- past_crt_hw = False
123
- numOfSampleTC = 1
124
- zip_paths = []
125
- for line in f:
126
- if 'CRT_HW START' in line:
127
- assignment_name = line[13:].strip()
128
- elif 'CRT_HW END' in line:
129
- assignment = text
130
- past_crt_hw = True
131
- elif line.startswith(('T ', 'I ', 'IB ', 'IF ', 'O ', 'OB ', 'OF ',
132
- 'EF ', 'X ', 'E ', 'EB ')):
133
- examples.append(line)
134
- elif line.startswith('HT '):
135
- break
136
- else:
137
- if past_crt_hw and line.startswith('C '):
138
- compile_command = line[2:].strip()
139
- if past_crt_hw and line.startswith('Z '):
140
- zip_paths.append(line[2:].strip())
141
- if 'EXMPLS ' in line:
142
- numOfSampleTC = int(line[7:])
143
- text = text + line
144
- spec_dir = os.path.dirname(os.path.abspath(file_name))
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
-
159
- assignment = re.sub('EXMPLS [0-9]+', samples, assignment)
160
- if compile_command:
161
- assignment = assignment.replace('COMPILE', compile_command)
162
- # Handle FILE macros in two passes:
163
- # 1. FILE inside markdown link syntax: [text](FILE[name]) -> [text](url)
164
- assignment = re.sub(r'\]\(FILE\[([^]]+)]\)',
165
- lambda m: f']({files_resolver(m.group(1))})' if files_resolver else f'](FILE[{m.group(1)}])',
166
- assignment)
167
- # 2. Standalone FILE macros: FILE[name] -> [name](url)
168
- assignment = re.sub(r'FILE\[([^]]+)]',
169
- lambda m: f'[{m.group(1)}]({files_resolver(m.group(1))})' if files_resolver else f'FILE[{m.group(1)}]',
170
- assignment)
171
- html = markdown.markdown(assignment,
172
- extensions=['mdx_better_lists', 'extra'],
173
- extension_configs={'mdx_better_lists': {'split_paragraph_lists': True}})
174
-
175
- if get_config().dry_run:
176
- html_file_name = file_name + '.html'
177
- with open(html_file_name, 'w') as f:
178
- f.write(html)
179
- info(f'HTML preview created in the path : {html_file_name}')
180
- return assignment_name, html
1
+ import os
2
+ import re
3
+ import zipfile
4
+
5
+ import markdown
6
+
7
+ from assignment_codeval.commons import info, get_config
8
+
9
+
10
+ _ANSI_STYLES = {
11
+ '1': 'font-weight:bold',
12
+ '4': 'text-decoration:underline',
13
+ '30': 'color:black', '31': 'color:red', '32': 'color:green',
14
+ '33': 'color:#a50', '34': 'color:blue', '35': 'color:magenta',
15
+ '36': 'color:cyan', '37': 'color:white',
16
+ '40': 'background-color:black', '41': 'background-color:red',
17
+ '42': 'background-color:green', '43': 'background-color:yellow',
18
+ '44': 'background-color:blue', '45': 'background-color:magenta',
19
+ '46': 'background-color:cyan', '47': 'background-color:white',
20
+ }
21
+
22
+
23
+ def ansi_to_html(text):
24
+ """Convert ANSI escape codes in text to HTML span tags."""
25
+ parts = re.split(r'\x1b\[([\d;]*)m', text)
26
+ result = []
27
+ open_spans = 0
28
+ for i, part in enumerate(parts):
29
+ if i % 2 == 0:
30
+ result.append(part)
31
+ else:
32
+ codes = part.split(';') if part else ['0']
33
+ if '0' in codes:
34
+ result.append('</span>' * open_spans)
35
+ open_spans = 0
36
+ else:
37
+ styles = [_ANSI_STYLES[c] for c in codes if c in _ANSI_STYLES]
38
+ if styles:
39
+ result.append(f'<span style="{";".join(styles)}">')
40
+ open_spans += 1
41
+ result.append('</span>' * open_spans)
42
+ return ''.join(result)
43
+
44
+
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.
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
64
+
65
+
66
+ def sampleTestCases(listOfTC, numOfTC, spec_dir=None, zip_archives=None):
67
+ counter = 0
68
+ samples = "<pre><code>"
69
+ for line in listOfTC:
70
+ if line.startswith('T ', 0, 2):
71
+ counter = counter + 1
72
+ if counter > numOfTC:
73
+ break
74
+ samples = samples + "\n" + "Command to RUN: " + line[2:]
75
+ elif line.startswith('IF '):
76
+ filename = line[3:].strip()
77
+ content = _read_file_content(filename, spec_dir, zip_archives)
78
+ if content is not None:
79
+ samples = samples + "<span style=\"color:green\">" + ansi_to_html(content) + "</span>"
80
+ else:
81
+ samples = samples + "<span style=\"color:green\">Input from file: " + filename + "\n</span>"
82
+ elif line.startswith('IB '):
83
+ samples = samples + "<span style=\"color:green\">" + line[3:] + "</span>"
84
+ elif line.startswith('I '):
85
+ samples = samples + "<span style=\"color:green\">" + line[2:] + "</span>"
86
+ elif line.startswith('OF '):
87
+ filename = line[3:].strip()
88
+ content = _read_file_content(filename, spec_dir, zip_archives)
89
+ if content is not None:
90
+ samples = samples + "<span style=\"color:blue\">" + ansi_to_html(content) + "</span>"
91
+ else:
92
+ samples = samples + "<span style=\"color:blue\">Expected output from file: " + filename + "\n</span>"
93
+ elif line.startswith('OB '):
94
+ samples = samples + "<span style=\"color:blue\">" + line[3:] + "</span>"
95
+ elif line.startswith('O '):
96
+ samples = samples + "<span style=\"color:blue\">" + line[2:] + "</span>"
97
+ elif line.startswith('X '):
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>"
106
+ elif line.startswith('EB '):
107
+ samples = samples + "<span style=\"color:Tomato\">" + line[3:] + "</span>"
108
+ elif line.startswith('E '):
109
+ samples = samples + "<span style=\"color:Tomato\">" + line[2:] + "</span>"
110
+ else:
111
+ continue
112
+ samples = samples + "</code></pre>"
113
+ return samples
114
+
115
+
116
+ def mdToHtml(file_name, files_resolver=None):
117
+ with open(file_name, 'r') as f:
118
+ text = ""
119
+ examples = []
120
+ assignment = ""
121
+ compile_command = ""
122
+ past_crt_hw = False
123
+ numOfSampleTC = 1
124
+ zip_paths = []
125
+ for line in f:
126
+ if 'ASSIGNMENT START' in line:
127
+ assignment_name = line[len('ASSIGNMENT START'):].strip()
128
+ elif 'CRT_HW START' in line:
129
+ assignment_name = line[len('CRT_HW START'):].strip()
130
+ elif 'ASSIGNMENT END' in line or 'CRT_HW END' in line:
131
+ assignment = text
132
+ past_crt_hw = True
133
+ elif line.startswith(('T ', 'I ', 'IB ', 'IF ', 'O ', 'OB ', 'OF ',
134
+ 'EF ', 'X ', 'E ', 'EB ')):
135
+ examples.append(line)
136
+ elif line.startswith('HT '):
137
+ break
138
+ else:
139
+ if past_crt_hw and line.startswith('C '):
140
+ compile_command = line[2:].strip()
141
+ if past_crt_hw and line.startswith('Z '):
142
+ zip_paths.append(line[2:].strip())
143
+ if 'EXMPLS ' in line:
144
+ numOfSampleTC = int(line[7:])
145
+ text = text + line
146
+ spec_dir = os.path.dirname(os.path.abspath(file_name))
147
+
148
+ # Open zip archives for IF/OF/EF file resolution
149
+ zip_archives = []
150
+ try:
151
+ for zp in zip_paths:
152
+ full_path = os.path.join(spec_dir, zp)
153
+ if os.path.exists(full_path):
154
+ zip_archives.append(zipfile.ZipFile(full_path, 'r'))
155
+ samples = sampleTestCases(examples, numOfSampleTC, spec_dir,
156
+ zip_archives)
157
+ finally:
158
+ for zf in zip_archives:
159
+ zf.close()
160
+
161
+ assignment = re.sub('EXMPLS [0-9]+', samples, assignment)
162
+ if compile_command:
163
+ assignment = assignment.replace('COMPILE', compile_command)
164
+ # Handle FILE macros in two passes:
165
+ # 1. FILE inside markdown link syntax: [text](FILE[name]) -> [text](url)
166
+ assignment = re.sub(r'\]\(FILE\[([^]]+)]\)',
167
+ lambda m: f']({files_resolver(m.group(1))})' if files_resolver else f'](FILE[{m.group(1)}])',
168
+ assignment)
169
+ # 2. Standalone FILE macros: FILE[name] -> [name](url)
170
+ assignment = re.sub(r'FILE\[([^]]+)]',
171
+ lambda m: f'[{m.group(1)}]({files_resolver(m.group(1))})' if files_resolver else f'FILE[{m.group(1)}]',
172
+ assignment)
173
+ html = markdown.markdown(assignment,
174
+ extensions=['mdx_better_lists', 'extra'],
175
+ extension_configs={'mdx_better_lists': {'split_paragraph_lists': True}})
176
+
177
+ if get_config().dry_run:
178
+ html_file_name = file_name + '.html'
179
+ with open(html_file_name, 'w') as f:
180
+ f.write(html)
181
+ info(f'HTML preview created in the path : {html_file_name}')
182
+ return assignment_name, html
@@ -38,12 +38,14 @@ def get_assignment_subfolder(course, codeval_folder, assignment_name):
38
38
 
39
39
 
40
40
  def extract_assignment_name(specname):
41
- """Extract the assignment name from the CRT_HW START line of a codeval spec file."""
41
+ """Extract the assignment name from the ASSIGNMENT START (or CRT_HW START) line of a codeval spec file."""
42
42
  with open(specname, 'r') as f:
43
43
  for line in f:
44
+ if 'ASSIGNMENT START' in line:
45
+ return line[len('ASSIGNMENT START'):].strip()
44
46
  if 'CRT_HW START' in line:
45
- return line[13:].strip()
46
- error(f"could not find CRT_HW START in {specname}")
47
+ return line[len('CRT_HW START'):].strip()
48
+ error(f"could not find ASSIGNMENT START in {specname}")
47
49
  exit(2)
48
50
 
49
51
 
@@ -53,9 +55,9 @@ def extract_file_macros(specname):
53
55
  in_assignment = False
54
56
  with open(specname, 'r') as f:
55
57
  for line in f:
56
- if 'CRT_HW START' in line:
58
+ if 'ASSIGNMENT START' in line or 'CRT_HW START' in line:
57
59
  in_assignment = True
58
- elif 'CRT_HW END' in line:
60
+ elif 'ASSIGNMENT END' in line or 'CRT_HW END' in line:
59
61
  in_assignment = False
60
62
  elif in_assignment:
61
63
  # Find all FILE[filename] patterns in this line
@@ -939,12 +939,12 @@ def parse_tags(tags: list[str]):
939
939
  if not stripped or stripped.startswith("#"):
940
940
  continue
941
941
 
942
- # Check for CRT_HW block delimiter
943
- if stripped.startswith("CRT_HW"):
942
+ # Check for ASSIGNMENT START/END (or legacy CRT_HW) block delimiter
943
+ if stripped.startswith("CRT_HW") or stripped.startswith("ASSIGNMENT START") or stripped.startswith("ASSIGNMENT END"):
944
944
  in_crt_hw_block = not in_crt_hw_block
945
945
  continue
946
946
 
947
- # Skip lines inside CRT_HW block
947
+ # Skip lines inside assignment description block
948
948
  if in_crt_hw_block:
949
949
  continue
950
950
 
@@ -1349,7 +1349,7 @@ def run_evaluation(codeval_file):
1349
1349
  in_crt_hw_block = False
1350
1350
  for testcase in testcases:
1351
1351
  stripped = testcase.strip()
1352
- if stripped.startswith("CRT_HW"):
1352
+ if stripped.startswith("CRT_HW") or stripped.startswith("ASSIGNMENT START") or stripped.startswith("ASSIGNMENT END"):
1353
1353
  in_crt_hw_block = not in_crt_hw_block
1354
1354
  continue
1355
1355
  if in_crt_hw_block:
@@ -43,7 +43,7 @@ def parse_codeval_tests(codeval_file):
43
43
  in_crt_hw = False
44
44
  for line in lines:
45
45
  stripped = line.strip()
46
- if stripped.startswith("CRT_HW"):
46
+ if stripped.startswith("CRT_HW") or stripped.startswith("ASSIGNMENT START") or stripped.startswith("ASSIGNMENT END"):
47
47
  in_crt_hw = not in_crt_hw
48
48
  continue
49
49
  if in_crt_hw:
@@ -84,7 +84,7 @@ def _parse_codeval_lines(lines, zip_archives):
84
84
  if not stripped or stripped.startswith("#"):
85
85
  continue
86
86
 
87
- if stripped.startswith("CRT_HW"):
87
+ if stripped.startswith("CRT_HW") or stripped.startswith("ASSIGNMENT START") or stripped.startswith("ASSIGNMENT END"):
88
88
  in_crt_hw_block = not in_crt_hw_block
89
89
  continue
90
90