Last active
February 28, 2018 14:07
-
-
Save zlhaa23/0b4522f112b46ff638ead84f305b615d to your computer and use it in GitHub Desktop.
Canvas grading
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
An interactive CLI script for entering grades on Canvas (an LMS). | |
Python 3.6+ | |
Usage: | |
1. Export the CSV file with Canvas | |
2. Modify the file with this script | |
3. Import the modified file to Canvas | |
Tip: use rlwrap. | |
Author: Linghan Zhang | |
Date: Feb 2018 | |
""" | |
import sys | |
assert sys.version_info >= (3, 0) | |
import csv | |
import io | |
import bisect | |
class Grader(object): | |
WELCOME = '##### Canvas Grader #####' | |
TOP_MENU = '\n'.join([ | |
'(TOP)', | |
'exit', | |
'help', | |
'load <fname>: load data from CSV file', | |
'save <fname>: save data to CSV file', | |
'print: print data', | |
'cols: list columns', | |
'list [<name>...]: filter and list students and their grades', | |
'col [<num>]: get or set the current column', | |
'grade: enter GRADE mode to set grades one by one', | |
'selmake: enter SEL mode to make a selection of students', | |
'sel: print the current selection', | |
'selclear: clear the selection', | |
'selset <grade>: set grade for all selected students' | |
]) | |
GRADE_MENU = '\n'.join([ | |
'(GRADE)', | |
'exit', | |
'<name>... <grade>: set grade for the given student' | |
]) | |
SEL_MENU = '\n'.join([ | |
'(SEL)', | |
'exit', | |
'<name>...: select/deselect the given student' | |
]) | |
def __init__(self): | |
self.data = None # list of rows | |
self.col = 0 # current col index | |
self.sel = [] # a selection of students as sorted row indices | |
print(self.WELCOME) | |
print(self.TOP_MENU) | |
self.top() | |
def top(self): | |
"""Top menu""" | |
PROMPT = '>> ' | |
words = input(PROMPT).split() | |
it = iter(words) | |
try: | |
s = next(it) | |
if s == 'exit': | |
print('Bye') | |
return | |
elif s in ['help', 'h']: | |
print(self.TOP_MENU) | |
elif s == 'load': | |
fname = next(it) | |
try: | |
with open(fname, newline='') as f: | |
self.data = list(csv.reader(f)) | |
print('{} rows loaded.'.format(len(self.data))) | |
except Exception as e: | |
print(e) | |
elif self.data: | |
if s == 'save': | |
fname = next(it) | |
try: | |
with open(fname, 'w', newline='') as f: | |
csv.writer(f).writerows(self.data) | |
print('Data saved to file.') | |
except Exception as e: | |
print(e) | |
elif s == 'print': | |
with io.StringIO() as f: | |
csv.writer(f).writerows(self.data) | |
print(f.getvalue()) | |
elif s == 'cols': | |
for j in range(len(self.data[0])): | |
self.print_col(j) | |
elif s in ['list', 'ls']: | |
for i in self.find(list(it)): | |
self.print_row(i) | |
elif s == 'col': | |
try: | |
j = int(next(it)) - 1 | |
# Set col | |
if 0 <= j < len(self.data[0]): | |
self.col = j | |
self.print_col(j) | |
else: | |
raise ValueError() | |
except StopIteration: | |
# Get col | |
self.print_col(self.col) | |
except ValueError: | |
print('Wrong col number!') | |
elif s == 'grade': | |
print(self.GRADE_MENU) | |
self.grade() | |
elif s == 'selmake': | |
print(self.SEL_MENU) | |
self.selmake() | |
elif s == 'sel': | |
print('{} students selected.'.format(len(self.sel))) | |
for i in self.sel: | |
self.print_row(i) | |
elif s == 'selclear': | |
self.sel.clear() | |
print('Selection cleared.') | |
elif s == 'selset': | |
v = next(it) | |
for i in self.sel: | |
self.data[i][self.col] = v | |
print('{} scores set to {}.'.format(len(self.sel), v)) | |
else: | |
print('Unrecognized command "{}"'.format(s)) | |
else: | |
print('No data!') | |
except StopIteration: | |
if words: | |
print('Argument(s) missing!') | |
self.top() | |
def print_col(self, j): | |
print('[{:>2}] "{}"'.format(j + 1, self.data[0][j])) | |
def print_row(self, i): | |
print('[{:>3}] {:<30} {}'.format | |
(i + 1, self.data[i][0], self.data[i][self.col])) | |
def find(self, names): | |
"""Find students with words.""" | |
names = [n.lower() for n in names] | |
def match(i): | |
n0 = self.data[i][0].lower() | |
return all(n in n0 for n in names) | |
return list(filter(match, range(len(self.data)))) | |
def choose_student(self, names): | |
""" | |
Let the user choose one from the filtered students. | |
Return: row index (-1 if fail) | |
""" | |
FAIL = -1 | |
i_list = self.find(names) | |
c = len(i_list) | |
if not c: | |
print('Name not found!') | |
return FAIL | |
if c == 1: | |
return i_list[0] | |
for i in i_list: | |
self.print_row(i) | |
try: | |
i = int(input('Choose one: ')) - 1 | |
if i not in i_list: | |
raise ValueError() | |
return i | |
except ValueError: | |
print('Wrong number!') | |
return FAIL | |
def selmake(self): | |
"""SEL mode""" | |
PROMPT = 'SEL>> ' | |
cmd = input(PROMPT) | |
if cmd == 'exit': | |
return | |
names = cmd.split() | |
if names: | |
i = self.choose_student(names) | |
if i >= 0: | |
if i in self.sel: | |
self.sel.remove(i) | |
print('Deselected:') | |
else: | |
bisect.insort(self.sel, i) | |
print('Selected:') | |
self.print_row(i) | |
self.selmake() | |
def grade(self): | |
"""GRADE mode""" | |
PROMPT = 'GRADE>> ' | |
cmd = input(PROMPT) | |
if cmd == 'exit': | |
return | |
names = cmd.split() | |
try: | |
v = names.pop() | |
if not names: | |
raise ValueError() | |
except IndexError: | |
pass | |
except ValueError: | |
print('Wrong syntax!') | |
else: | |
i = self.choose_student(names) | |
if i >= 0: | |
self.data[i][self.col] = v | |
self.print_row(i) | |
self.grade() | |
def main(): | |
Grader() | |
return 0 | |
if __name__ == '__main__': | |
sys.exit(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment