Commit dba1a74f authored by Alan Marchiori's avatar Alan Marchiori
Browse files

bug fixes and report feature

parent 90aec70a
File mode changed from 100644 to 100755
......@@ -24,3 +24,7 @@ dist:
chmod -R go+r stage
clean:
rm -fr lt.c deploy lt stage lt.spec
release:
rm -rf dist
mv stage dist
File mode changed from 100644 to 100755
......@@ -3,4 +3,5 @@ from .check import check
from .userconfig import userconfig
from .test import test
from .grade import grade
__all__ = ['userconfig', 'init', 'check', 'test', 'grade']
from .report import report
__all__ = ['userconfig', 'init', 'check', 'test', 'grade', 'report']
......@@ -129,17 +129,18 @@ def check(lab, part):
if isinstance(part, int) and part != info['index']:
continue
if 'grade' in hist and partstr in hist['grade']:
show_report(hist, partstr, info)
continue
# if 'grade' in hist and partstr in hist['grade']:
# show_report(hist, partstr, info)
# continue
if 'check' in info:
if 'check' in hist and partstr in hist['check']:
# print("partstr: {}".format(partstr))
# print("pr: {}".format(presults))
# print("hi: {}".format(hist['check'][partstr]['result']))
presults += [hist['check'][partstr]['result']]
else:
# if 'check' in hist and partstr in hist['check']:
# # print("partstr: {}".format(partstr))
# # print("pr: {}".format(presults))
# # print("hi: {}".format(hist['check'][partstr]['result']))
# presults += [hist['check'][partstr]['result']]
# else:
if True:
c = Checker(info, labpath)
newline()
......@@ -165,8 +166,8 @@ def check(lab, part):
break
# show total grade at the end, if it's graded.
if 'grade' in hist and 'TOTAL' in hist['grade']:
show_report(hist, 'TOTAL', None)
# if 'grade' in hist and 'TOTAL' in hist['grade']:
# show_report(hist, 'TOTAL', None)
# only check gitlab if all tests pass
if all(presults):
......
......@@ -133,11 +133,18 @@ class Checker:
if showdiff:
error("{}: Output not as expected (check formatting!)".format(msg))
print(t)
print(x.encode())
for line in difflib.context_diff(
x.split("\n"), t.split("\n"),
fromfile='Expected output',
tofile='Your output'):
echo(line.strip())
# echo ("Your output: "+ str(t))
# echo ("Expected output: "+ str(x.encode()))
else:
error("{}: failed.".format(msg))
return False
......
......@@ -109,14 +109,24 @@ def grade(lab, part, clone, dograde, regrade, user, feedback, push):
echo("-"*(width-4))
echo("[{}] {} ({})".format('./'+student, pname, student))
labpath = os.path.join(coursepath, student, rubric['path'])
if clone:
echo("Getting {} to ./{} ({})".format(
url, student, pname
))
g.do_clone(url, './' + student)
if False == g.do_clone(url, './' + student):
error("Labtool cannot clone, please fixup the git repo or rm the path and it will be re-cloned on the next run.")
if error_confirm(
"Unable to grade {}, exit?".format(
student
), default=True):
return
else:
continue
labpath = os.path.join(coursepath, student, rubric['path'])
if not os.path.isdir(labpath):
error(labpath+ " does not exist!")
......@@ -256,16 +266,16 @@ def grade(lab, part, clone, dograde, regrade, user, feedback, push):
'who': who,
'at': utils.timestamp().isoformat()}
if not regrade and 'grade' in hist and 'TOTAL' in hist['grade']:
pass
else:
hist['grade']['TOTAL'] = {
'grade': sum(
[hist['grade'][pt]['grade'] for pt in rubric['parts'].keys() if pt in hist['grade']]),
'total': sum([hist['grade'][pt]['total'] for pt in rubric['parts'].keys() if pt in hist['grade']]),
'who': who,
'at': utils.timestamp().isoformat()
}
# if not regrade and 'grade' in hist and 'TOTAL' in hist['grade']:
# pass
# else:
hist['grade']['TOTAL'] = {
'grade': sum(
[hist['grade'][pt]['grade'] for pt in rubric['parts'].keys() if pt in hist['grade']]),
'total': sum([hist['grade'][pt]['total'] for pt in rubric['parts'].keys() if pt in hist['grade']]),
'who': who,
'at': utils.timestamp().isoformat()
}
echo("----TOTAL----{} of {}".format(
hist['grade']['TOTAL']['grade'],
hist['grade']['TOTAL']['total']))
......
File mode changed from 100644 to 100755
import click
import config
from config.echo import *
import courses
from config import UserConfig
import shutil
import platform
import os
import os.path
import utils
from utils.history import History
from utils.gitlab import GitLab
from utils.git import Git
from pprint import pprint
import numpy as np
from collections import defaultdict
def ta_report(coursename, coursepath, labname, user, clone):
if labname == None:
labnames = courses.load_labnames(coursename)
else:
labnames = [labname]
echo ("Generating TA report for {}.".format(labnames))
# load rubrics
rubrics = [courses.load_rubric(coursename, l) for l in labnames]
# search and clone student repos....
gl = GitLab()
g = Git()
projects = gl.search_all_projects(coursename, owned=False)
width = min(120, shutil.get_terminal_size().columns)
who = "{}@{}".format(os.environ['USER'], platform.node())
# pull all repos first
if clone:
for p in projects:
pname = p['name_with_namespace']
student = p['owner']['username']
url = p['ssh_url_to_repo']
if user and student != user:
continue
if False == g.do_clone(url, './' + student):
error("Labtool cannot clone, please fixup the git repo or rm the path and it will be re-cloned on the next run.")
if error_confirm(
"Unable to grade {}, exit?".format(
student
), default=True):
return
else:
continue
report_parts = ["{}, {}".format(
"student".ljust(8),
", ".join(
["L{}p, L{}t".format(ln[3:5], ln[3:5]) for ln in labnames]))]
print(", ".join(report_parts))
c_tot = defaultdict(list)
# print reports
for p in projects:
pname = p['name_with_namespace']
student = p['owner']['username']
url = p['ssh_url_to_repo']
if user and student != user:
continue
report_parts = [student.ljust(8)]
for rub in rubrics:
labname = rub['name']
labpath = os.path.join(coursepath, student, rub['path'])
with History(location=labpath,save=False) as h:
if 'grade' in h:
r = h['grade']
if 'TOTAL' in r:
# recompute total due to possible bug in grade...
# eventually this can go away.
h['grade']['TOTAL'] = {
'grade': sum(
[h['grade'][pt]['grade'] for pt in rub['parts'].keys() if pt in h['grade']]),
'total': sum([h['grade'][pt]['total'] for pt in rub['parts'].keys() if pt in h['grade']]),
'who': h['grade']['TOTAL']['who'],
'at': utils.timestamp().isoformat()
}
if 'TOTAL' in r:
c_tot[labname].append(r['TOTAL']['grade'])
report_parts += [str(r['TOTAL']['grade']).ljust(4), str(r['TOTAL']['total']).ljust(4)]
else:
report_parts += [' ', ' ']
else:
report_parts += [' ', ' ']
print(", ".join(report_parts))
report_parts = ["STATS".ljust(8)]
for rub in rubrics:
labname = rub['name']
if len(c_tot[labname]) > 0:
report_parts += [
"{:4.1f}, {:4.1f}".format(
np.mean(c_tot[labname]),
np.std(c_tot[labname])),
]
else:
report_parts += [' ', ' ']
print(", ".join(report_parts))
def student_report(coursename, coursepath, labname, clone):
if labname == None:
labnames = courses.load_labnames(coursename)
else:
labnames = [labname]
echo ("Generating grade report for {}.".format(labnames))
# load rubrics
rubrics = [courses.load_rubric(coursename, l) for l in labnames]
g = Git()
# pull repo
if False == g.do_pull(coursepath):
error("Labtool cannot clone/pull, please fixup the git repo manually!")
return
width = min(120, shutil.get_terminal_size().columns)
for rub in rubrics:
labname = rub['name']
labpath = os.path.join(coursepath, rub['path'])
if os.path.exists(labpath):
newline()
echo ("-"*max(10,width-5))
echo (labname)
with History(location=labpath,save=False) as h:
if 'grade' in h:
r = h['grade']
# recompute total due to possible bug in grade...
# eventually this can go away. (ie after 2020 spring)
h['grade']['TOTAL'] = {
'grade': sum(
[h['grade'][pt]['grade'] for pt in rub['parts'].keys() if pt in h['grade']]),
'total': sum([h['grade'][pt]['total'] for pt in rub['parts'].keys() if pt in h['grade']]),
'who': h['grade']['TOTAL']['who'],
'at': utils.timestamp().isoformat()
}
parts = sorted([(k, r[k]['at']) for k in r.keys()],
key=lambda x: x[1])
for p in parts:
echo()
part, at = p
if part == 'TOTAL':
pstr = '{} TOTAL'.format(labname)
else:
pstr = part
echo("{}:{}{:3} / {:3}".format(
pstr,
" ".rjust(width-15-len(pstr)),
r[part]['grade'],
r[part]['total']
))
if 'comment' in r[part]:
echo ("\tComments: {}".format(r[part]['comment']))
else:
echo ("Ungraded.")
#r[part]['who'].split("@")[0])
echo ("-"*max(10,width-5))
@click.command(short_help="Show the grade report.")
@click.option('--lab', type=int, required=False, default=None,
help='The lab (number) to show the report for.')
@click.option("--user", default=None, show_default = True,
help='Set to only report on a single user (by git username).')
@click.option("--clone/--no-clone", default=True,
help='Clone repo before showing report?\t[default: True]')
def report(lab, user, clone):
"""
print grade report, if in ta context all users, for students all lab scores
"""
# convert lab number to filename magic.
if lab:
labstr = "lab{:02}".format(lab)
else:
labstr = None
coursename, coursepath, labname, ista = courses.detect_course(lab=labstr)
if ista:
return ta_report(coursename, coursepath, labname, user, clone)
else:
return student_report(coursename, coursepath, labname, clone)
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
......@@ -72,8 +72,8 @@ def detect_course(lab = None):
if os.path.samefile(cwd, labpath):
# found a lab with matching path! this must be it!
return coursename, coursepath, fnoext, ista
# no luck
return coursename, coursepath, None, False
# no luck finding a corresponding lab, maybe run from the root?
return coursename, coursepath, None, ista
return None, None, lab, False
def load_json(filepath):
......@@ -96,13 +96,10 @@ def load_rubric(coursename, labname):
))
return None
for pathname in all[coursename]['rubric_paths']:
tpath = os.path.expanduser(os.path.join(pathname, labname + ".json"))
debug("Trying path: {}: {}".format(tpath, os.path.exists(tpath)))
if os.path.exists(tpath):
rub = load_json(tpath)
if rub:
# ensure parts are sorted correctly
rub['parts'] = collections.OrderedDict(
......@@ -110,6 +107,33 @@ def load_rubric(coursename, labname):
return rub
return None
def load_labnames(coursename):
"returns all detected labs for the given course"
if coursename not in all:
error("Course ({}) not properly configured in courses file.".format(
coursename
))
return None
# use a dict to exclude duplicates if multiple paths point to rubrics.
results = {}
for pathname in all[coursename]['rubric_paths']:
debug("getting rubrics from: {}".format(pathname))
realpath = os.path.expanduser(pathname)
if os.path.exists(realpath):
# return filename part of all .json files found in rubric dir
results.update(dict.fromkeys(map(
lambda fn: os.path.splitext(fn)[0],
filter(lambda fn: os.path.splitext(fn)[1] == ".json",
os.listdir(realpath)))))
else:
debug("{} does not exist.".format(realpath))
# sort the results.
return sorted(results.keys())
def load_courses(searchpath):
"searches paths for a courses.json file, if found loads each course"
debug("Looking for courses in {}".format(searchpath))
......
File mode changed from 100644 to 100755
......@@ -35,10 +35,10 @@ smtp_handler = logging.handlers.SMTPHandler(
toaddrs=config.crash_reports,
subject=u"labtool crash report!")
__version__ = '1.0.3'
__date__ = '2020-01-28T09:37:05.342229'
__version__ = '1.0.6'
__date__ = '2020-02-04T14:56:58.320409'
__user__ = 'cs206'
__host__ = 'linuxremote3.bucknell.edu'
__host__ = 'acet116-lnx-1.bucknell.edu'
@click.group()
def main():
"""Bucknell University Computer Science lab tool [lt].
......
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment