From 75eb7bd4eacafe4354c9eb9353aeec244a77ec79 Mon Sep 17 00:00:00 2001 From: Michael Deakin Date: Mon, 27 Sep 2021 13:22:53 -0700 Subject: [PATCH 1/2] Make all variable name files lowercase --- canvas2mbgrader.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canvas2mbgrader.m b/canvas2mbgrader.m index 5c7e6c4..ded0a13 100644 --- a/canvas2mbgrader.m +++ b/canvas2mbgrader.m @@ -94,7 +94,7 @@ for i=1:length(vars) varname = vars{i}; value = getfield(S,varname); - + varname = lower(varname); % Ignore the variable called filename and any other user specified % variable names to ignore, and skip duplicate variable names if strcmp(varname,'filename') || any(strcmp(ignore_vars,varname)) From 3623daeee2363480a042ad7dd9ecfd7b555f1cc0 Mon Sep 17 00:00:00 2001 From: Michael Deakin Date: Mon, 27 Sep 2021 13:27:38 -0700 Subject: [PATCH 2/2] Mostly complete reimplementation of canvas2mbgrader in Python The main reason for this is to provide a path for reducing the number of steps to getting submissions from students into the grading scripts It's not complete, it handles string submissions incorrectly, and I'm uncertain how to handle submissions with symbolic types --- canvas2mbgrader.py | 116 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100755 canvas2mbgrader.py diff --git a/canvas2mbgrader.py b/canvas2mbgrader.py new file mode 100755 index 0000000..f1bb2b7 --- /dev/null +++ b/canvas2mbgrader.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python + +import pandas as pd +import numpy as np +from scipy.io.matlab import loadmat +import sys +import os +import argparse +from zipfile import is_zipfile, ZipFile +from enum import Enum + +class FileTypes(Enum): + ARRAY = '.csv' + BOOL = '.log' + STRING = '.txt' + SYMBOL = '.sym' + +def saveFile(path, var, ftype): + with open(path, 'w') as f: + if ftype is FileTypes.ARRAY or ftype is FileTypes.BOOL: + np.savetxt(f, var, delimiter=',', comments='') + else: + print(path, ftype, var) + f.write(var) + +def makeFilename(var, dtype): + var = var.lower() + print('FileTypes', vars(FileTypes.ARRAY), FileTypes.ARRAY.value) + if dtype in [np.uint8, np.int16, np.int32, np.float32, np.float64]: + return FileTypes.ARRAY, var + FileTypes.ARRAY.value + elif dtype in [np.dtype('bool')]: + return FileTypes.BOOL, var + FileTypes.BOOL.value + else: + return FileTypes.STRING, var + FileTypes.STRING.value + +def makeSubmission(sid, submission, dest, issues, ignore_vars): + try: + ml = loadmat(submission) + + except ValueError as ex: + bad_fname = os.path.join(issues, sid.Submission) + with open(bad_fname, 'wb') as f: + extract = submission.read() + f.write(extract) + issues_fname = os.path.join(issues, 'issues.txt') + with open(issues_fname, 'a') as f: + f.write(ex.__repr__() + '\n') + return + + directory = os.path.join(dest, str(sid.SID)) + try: + os.makedirs(directory) + except FileExistsError: + pass + for var in ml: + if var not in ignore_vars: + ftype, fname = makeFilename(var, ml[var].dtype) + path = os.path.join(dest, str(sid.SID), fname) + saveFile(path, ml[var], ftype) + + +def mapStudentIDs(id_file, sub_file): + """Aggregates the canvas ids, student ids, and the names of the submitted files""" + df = pd.read_csv(id_file, header = None, names = ['Canvas', 'SID']) + df['Submission'] = '' + for sub_name in sub_file.namelist(): + try: + # Assumes no one has an underscore in their last name + cid = int(sub_name.split('_')[1]) + except ValueError: + # Handle late submissions + try: + cid = int(sub_name.split('_')[2]) + except ValueError as err: + # Give up + print(err) + continue + df.loc[df['Canvas'] == cid, 'Submission'] = sub_name + print(df) + return df + +def main(): + parser = argparse.ArgumentParser(description = 'Parses student submissions to create the directory structure mbgrader expects') + parser.add_argument('--submissions', action = 'store', default = 'submissions.zip', help = 'The file containing student submissions') + parser.add_argument('--dest', action = 'store', default = 'canvas/hw', help = 'The directory to store student results') + parser.add_argument('--id-file', dest = 'id_file', action = 'store', default = 'canvasIDstudentID.csv', help = 'The file containing canvas IDs and student IDs') + parser.add_argument('--issues', dest = 'issues', action = 'store', default = 'issues/hw', help = 'The directory to store problematic student submissions') + parser.add_argument('--ignore-vars', dest = 'ignore_vars', nargs = '+', action = 'store', default = [], help = 'Variables to ignore') + args = parser.parse_args() + if not os.path.isfile(args.submissions): + print('Error: Could not find {}'.format(args.submissions)) + return + if not is_zipfile(args.submissions): + print('Error: {} is not a zip file of submissions'.format(args.submissions)) + return + if not os.path.isfile(args.id_file): + print('Error: Could not find {}'.format(args.id_file)) + return + ignore_vars = args.ignore_vars + ['__header__', '__version__', '__globals__'] + f = ZipFile(args.submissions) + sids = mapStudentIDs(args.id_file, f) + try: + os.makedirs(args.dest) + except FileExistsError: + pass + try: + os.makedirs(args.issues) + except FileExistsError: + pass + for row in sids.itertuples(): + if row.Submission != '': + with f.open(row.Submission) as submission: + makeSubmission(row, submission, args.dest, args.issues, ignore_vars) + +if __name__ == '__main__': + main()