Skip to content
Snippets Groups Projects
Verified Commit 68872e76 authored by Jan Maximilian Michal's avatar Jan Maximilian Michal
Browse files

Fixed a bug in the qti converter

parent 828ac4f8
No related branches found
No related tags found
No related merge requests found
Pipeline #
......@@ -94,6 +94,11 @@ def setup_argparse():
default=True,
help='asserts that output data will be in a certain format'
)
parser.add_argument(
'-r', '--readable-code',
action='store_true',
default=True,
help='make student code readable by inserting artificial line breaks')
args = parser.parse_args()
......@@ -105,7 +110,7 @@ def setup_argparse():
def compose(*functions: Sequence[Callable]) -> Callable:
""" Standard function composition. Takes a Sequence of functions [f, g, h, ...]
and returns the composite function i(x) = f(g(h(x))). There are no checks
that validate if domain and image of these functions are compatible."""
that validate if domain and image of these functions are compatible. """
return functools.reduce(lambda f,
g: lambda x: f(g(x)),
functions,
......@@ -119,6 +124,20 @@ def abort(message='Bye.'):
# ========================== =- Post processors -= ========================== #
def student_replacer(processor):
''' A simple decorator that is used to remove students and put them back in
when the preprocessor is dome with them'''
@functools.wraps(processor)
def processor_closure(structured_data: Dict[str, Any]) -> Dict[str, Any]:
students = structured_data.pop('students')
students_replacement = processor(students)
structured_data['students'] = students_replacement
return structured_data
return processor_closure
def do_add_meta(structured_data: Dict[str, Any]) -> Dict[str, Any]:
''' Asks the user for metadata about the exam '''
structured_data['author'] = input('[Q] author: ')
......@@ -132,6 +151,7 @@ def do_verify(structured_data: Dict[str, Any]) -> Dict[str, Any]:
on by default. The impact on performance is neglectable. '''
def assert_submission(submission):
assert 'code' in submission, 'A submission needs code'
assert type(submission['code']) in [str, list], 'Code is readable'
assert 'type' in submission, 'A submission has to be of some type'
assert 'tests' in submission, 'A tests dict has to be present.'
......@@ -172,18 +192,12 @@ def do_verify(structured_data: Dict[str, Any]) -> Dict[str, Any]:
return structured_data
def student_replacer(processor):
''' A simple decorator that is used to remove students and put them back in
when the preprocessor is dome with them'''
@functools.wraps(processor)
def processor_closure(structured_data: Dict[str, Any]) -> Dict[str, Any]:
students = structured_data.pop('students')
students_replacement = processor(students)
structured_data['students'] = students_replacement
return structured_data
return processor_closure
@student_replacer
def do_readable_code(students: Dict[str, Union[str, List]]):
for student in students:
for submission in student['submissions']:
submission['code'] = submission['code'].split('\n')
return students
@student_replacer
......@@ -249,6 +263,7 @@ def do_decrypt(students):
abort('Your key is bad (%s).' % err)
# ======================= =- Post processor helper -= ======================= #
def transform(students, function):
return [
{'fullname': function(student['fullname']),
......@@ -294,6 +309,7 @@ def get_active_postprocessors():
postprocessor_order = (
do_add_meta,
do_verify,
do_readable_code,
do_anonymous,
do_encrypt,
do_decrypt
......@@ -309,6 +325,8 @@ def _preprocessing(filepath: str) -> str:
def _processing(filepath: str) -> Dict[str, Any]:
''' Find the first apropriate converter and run pass it the path to the
datafile. '''
try:
return next(converter().convert(filepath)
for converter in Converter.implementations()
......
......@@ -55,7 +55,7 @@ def process_users(results_tree):
def convert_code(text):
return base64.b64decode(text).decode('utf-8').split('\n')
return base64.b64decode(text).decode('utf-8')
def process_solutions(results_tree, task_id):
......@@ -128,8 +128,9 @@ ignore_user_fields = ("user_fi",
def add_users(base, data):
for userdata in data['results'].values():
for userdata in data['results']:
userdata['identifier'] = userdata['user_fi']
userdata['username'] = userdata['user_fi']
for field in ignore_user_fields:
userdata.pop(field)
base['students'] = data['results']
......
......@@ -50,7 +50,7 @@ task_head_re = re.compile(r'^Quellcode Frage (?P<title>.*?) ?(\d{8})?$')
# nor parsing the weird mat no
matno_re = re.compile(r'^(?P<matrikel_no>\d{8})-(\d+)-(\d+)$')
COLUMNS_BEFORE_TASKS = 19
TABWIDTH = 4
def converter(infile, usernames=None, number_of_tasks=0,):
......@@ -94,7 +94,9 @@ def converter(infile, usernames=None, number_of_tasks=0,):
'type': 'SourceCode'
}
root[-1].append(task.group('title'))
root[-1].append(urllib.parse.unquote(code).strip())
root[-1].append(urllib.parse
.unquote(code)
.replace('\t', ' ' * TABWIDTH))
if number_of_tasks:
for (user, *task_list) in sorted(root, key=lambda u: u[0].name):
......
......@@ -4,7 +4,7 @@ from setuptools import setup
setup(
name='hektor',
version='0.3',
version='0.3.1',
description='A QTI-XML/XLS to JSON converter for humans',
author='Jan Maximilian Michal',
author_email='mail@janmax.org',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment