diff --git a/tested/dodona.py b/tested/dodona.py
index 90e5f768..098bd9c5 100644
--- a/tested/dodona.py
+++ b/tested/dodona.py
@@ -34,12 +34,20 @@ class ExtendedMessage:
permission: Permission | None = None
+@define
+class LinkedFile:
+ type: Literal["path", "inline"]
+ # Either a path (type "file") or the content (type "inline")
+ content: str
+
+
@define
class Metadata:
- """Currently only used for the Python tutor"""
+ """Used for the debugger and file linking"""
statements: str | None
stdin: str | None
+ files: dict[str, LinkedFile] | None
Message = ExtendedMessage | str
diff --git a/tested/judge/core.py b/tested/judge/core.py
index 6c54c9fb..795617bd 100644
--- a/tested/judge/core.py
+++ b/tested/judge/core.py
@@ -10,6 +10,7 @@
CloseContext,
CloseJudgement,
CloseTab,
+ LinkedFile,
Metadata,
StartContext,
StartJudgement,
@@ -46,7 +47,7 @@
generate_statement,
)
from tested.serialisation import Statement
-from tested.testsuite import LanguageLiterals, MainInput, TextData
+from tested.testsuite import ContentPath, LanguageLiterals, MainInput, TextData
_logger = logging.getLogger(__name__)
@@ -367,11 +368,14 @@ def _process_results(
# Don't add empty statements
meta_statements = None
+ meta_files = _get_meta_files(bundle, planned)
+
collector.add(
CloseContext(
data=Metadata(
statements=meta_statements,
stdin=meta_stdin,
+ files=meta_files,
)
),
planned.context_index,
@@ -382,3 +386,17 @@ def _process_results(
return continue_, currently_open_tab
return None, currently_open_tab
+
+
+def _get_meta_files(bundle: Bundle, planned: PlannedContext) -> dict[str, LinkedFile]:
+ meta_files = {}
+ for f in planned.context.get_input_files():
+ display_path = f.get_display_path()
+
+ if display_path is not None:
+ meta_files[f.path] = LinkedFile(type="path", content=display_path)
+ else:
+ contents = f.get_data_as_string(bundle.config.resources)
+ meta_files[f.path] = LinkedFile(type="inline", content=contents)
+
+ return meta_files
diff --git a/tested/judge/evaluation.py b/tested/judge/evaluation.py
index 4542272e..4bd96200 100644
--- a/tested/judge/evaluation.py
+++ b/tested/judge/evaluation.py
@@ -384,14 +384,8 @@ def link_files_message(
) -> AppendMessage | None:
link_list = []
for link_file in link_files:
- # TODO: handle inline files somehow.
- link_url = link_file.get_display_path()
- if link_file.path is not None and link_url is not None:
- the_url = urllib.parse.quote(link_url)
- link_list.append(
- f''
- f'{html.escape(link_file.path)}'
- )
+ if link_file.path is not None:
+ link_list.append(link_file.path)
if len(link_list) == 0:
return None # Do not append any message if there are no files.
@@ -400,8 +394,7 @@ def link_files_message(
file_list_str = get_i18n_string(
"judge.evaluation.files", count=len(link_list), files=file_list
)
- description = f"
"
- message = ExtendedMessage(description=description, format="html")
+ message = ExtendedMessage(description=file_list_str, format="text")
return AppendMessage(message=message)
diff --git a/tested/languages/generation.py b/tested/languages/generation.py
index 36de5a36..640c110c 100644
--- a/tested/languages/generation.py
+++ b/tested/languages/generation.py
@@ -2,15 +2,10 @@
Translates items from the test suite into the actual programming language.
"""
-import html
-import json
import logging
import re
import shlex
-import urllib.parse
-from collections.abc import Iterable
from pathlib import Path
-from re import Match
from typing import TYPE_CHECKING, TypeAlias
from pygments import highlight
@@ -29,16 +24,8 @@
prepare_execution_unit,
prepare_expression,
)
-from tested.parsing import get_converter
from tested.serialisation import Expression, Statement, VariableType
-from tested.testsuite import (
- ContentPath,
- Context,
- LanguageLiterals,
- MainInput,
- Testcase,
- TextData,
-)
+from tested.testsuite import Context, LanguageLiterals, MainInput, Testcase, TextData
from tested.utils import is_statement_strict
if TYPE_CHECKING:
@@ -77,22 +64,6 @@ def generate_execution_unit(
return bundle.language.generate_execution_unit(prepared_execution)
-def _handle_link_files(
- link_files: Iterable[TextData], language: str
-) -> tuple[str, str]:
- dict_links = dict(
- (link_file.path, get_converter().unstructure(link_file))
- for link_file in link_files
- )
- files = json.dumps(dict_links)
- return (
- f"",
- )
-
-
def _get_heredoc_token(stdin: str) -> str:
delimiter = "STDIN"
while delimiter in stdin:
@@ -163,10 +134,6 @@ def get_readable_input(
if case.line_comment:
text = f"{text} {bundle.language.comment(case.line_comment)}"
- # If there are no files, return now. This means we don't need to do ugly stuff.
- if not case.input_files:
- return ExtendedMessage(description=text, format=format_), set()
-
# We have potential files.
# Check if the file names are present in the string.
# If not, we can also stop before doing ugly things.
@@ -174,50 +141,11 @@ def get_readable_input(
file_paths = [re.escape(x.path) for x in case.input_files if x.path is not None]
if not file_paths:
- # No files to match, so bail now.
return ExtendedMessage(description=text, format=format_), set()
- simple_regex = re.compile("|".join(file_paths))
-
- if not simple_regex.search(text):
- # There is no match, so bail now.
- return ExtendedMessage(description=text, format=format_), set()
-
- # Now we need to do ugly stuff.
- # Begin by compiling the HTML that will be displayed.
- if format_ == "text":
- generated_html = html.escape(text)
- elif format_ == "console":
- generated_html = highlight_code(text)
- else:
- generated_html = highlight_code(text, bundle.config.programming_language)
-
- # Map of file URLs.
- url_map = {html.escape(x.path): x for x in case.input_files if x.path is not None}
-
- seen = set()
- escaped_regex = re.compile("|".join(url_map.keys()))
-
- # Replaces the match with the corresponding link.
- def replace_link(match: Match) -> str:
- filename = match.group()
- the_file = url_map[filename]
- the_url = the_file.get_display_path()
- if the_url is None:
- # TODO: how to handle inline files?
- return filename
-
- quoted_url = urllib.parse.quote(the_url)
- the_replacement = (
- f'{filename}'
- )
- seen.add(the_file)
- return the_replacement
-
- generated_html = escaped_regex.sub(replace_link, generated_html)
- prefix, suffix = _handle_link_files(seen, format_)
- generated_html = f"{prefix}{generated_html}{suffix}"
- return ExtendedMessage(description=generated_html, format="html"), seen
+ path_map = {x.path: x for x in case.input_files if x.path is not None}
+ seen = {path_map[m.group()] for m in re.finditer("|".join(file_paths), text)}
+ return ExtendedMessage(description=text, format=format_), seen
def attempt_readable_input(bundle: Bundle, context: Context) -> ExtendedMessage: