Commit 91a8acf3 authored by Alan Marchiori's avatar Alan Marchiori
Browse files

progress

parent 71dca012
import click
import config
from config.echo import *
import courses
from config import UserConfig
@click.command()
from pprint import pprint
@click.command(short_help="Check LAB_NUMBER (e.g. \"lt check lab1\")")
@click.argument('lab_number')
def check(lab_number):
"""check LAB_NUMBER without submitting, format as the string
"lab" plus a number (no spaces!), like "lab1" or "lab10".
"""
click.echo("cmd: Check {}".format(lab_number))
coursename, coursepath = courses.detect_course()
if not coursename:
error("The current directory is not an initialized course! You must first cd into an initialized course to check!")
return
#courseobj = courses.all[coursename]
success("Checking {} of {}".format(lab_number, coursename))
rubric = courses.load_rubric(coursename, lab_number)
if not rubric:
error("{} is not defined for the course {}. Check the case and any leading zeros in the lab name (i.e., Lab1 --> lab01)".format(
lab_number, coursename
))
return
pprint(rubric)
import click
import time
import os
import os.path
import config
......@@ -6,9 +7,11 @@ import config.validation as validation
import shutil
from config.echo import *
from utils.gitlab import GitLab
from utils.git import Git
import courses
@click.command()
@click.command(short_help='Initalize a course.')
@click.argument('course', callback=validation.course)
@click.argument('section', callback=validation.section)
@click.argument('semester', callback=validation.semester)
......@@ -17,23 +20,23 @@ def init(course, semester, section):
and on gitlab for a given COURSE, SECTION, and SEMESTER. Example: init CSCI206 S20 61.
"""
coursedef = courses.all[config.course_objf_fmt.format(
course=course,
semester=semester,
section=section)]
coursename = config.course_name_fmt.format(
course=course,
semester=semester,
section=section)
if coursename not in courses.all:
error("That course name/section/semester combination is not valid!")
return
coursedef = courses.all[coursename]
lp = os.path.expanduser(config.local_home.format(
course=course,
semester=semester,
section=section))
if confirm("Initialize {} into {}?".format(coursename, lp)):
labdir = os.path.join(lp, config.labs_subdir)
if os.path.exists(lp):
# prompt to wipe it.
......@@ -42,20 +45,15 @@ def init(course, semester, section):
shutil.rmtree(lp)
success("Deleted {}".format(lp))
if not os.path.exists(lp):
# make the root folder, plus the labs subfolder
# ensure permissions are not globally readable
for d in [lp, labdir]:
os.makedirs(d)
os.chmod(d,mode=0o0770)
success("Created {}".format(lp))
else:
warn("Skipping directory creation.")
if os.path.exists(lp):
error("ABROT: Cannot initalize since the path {} already exists!".format(
lp))
return
with config.UserConfig() as uc:
uc.add_course(
name=coursename,
path=labdir)
path=lp)
gl = GitLab()
while not 'api_token' in uc.cfg or not gl.check_token():
......@@ -77,12 +75,20 @@ def init(course, semester, section):
"Gitlab repo \"{}\" already exists--should I DELETE it?".format(
prj['name_with_namespace'])):
if gl.remove_project(prj['id']):
success("Removed \"{}\"".format(prj['name_with_namespace']))
success("Removing \"{}\"".format(prj['name_with_namespace']))
# fake delay
with click.progressbar(range(10)) as bar:
for item in bar:
time.sleep(0.1*item)
else:
error("Failed to remove \"{}\"".format(prj['name_with_namespace']))
# try again
p = gl.search_for_project(coursename)
repo = None
if len(p) == 0:
repo = gl.create_project(coursename)
if 'id' in repo:
......@@ -94,12 +100,23 @@ def init(course, semester, section):
p = gl.search_for_project(coursename)
if len(p) == 1:
repo = p[0]
curmembers = (gl.get(repo['_links']['members']).json())
curmembers = [x['username'] for x in curmembers]
# add members
for name in coursedef['instructors'] + coursedef['tas']:
uobj = gl.get_users(username=name)
if len(uobj)==0:
warn('No gitlab user "{}" found!.'.format(name))
elif len(uobj)==1:
if name in curmembers:
success("{} ({}) is already a member".format(
uobj[0]['username'],
uobj[0]['name']))
continue
mem = gl.add_member_to_project(
repo['id'],
uobj[0]['id'],
......@@ -122,3 +139,11 @@ def init(course, semester, section):
name, uobj))
else:
warn("Skipped repo creation and member addition.")
# finally clone the repo
git = Git()
if git.do_clone(repo['ssh_url_to_repo'], lp):
success("Initialization complete!")
else:
error("Sorry, I was unable to clone the repo (did something go wrong earlier?)!")
......@@ -5,7 +5,7 @@ import json
@click.group()
def userconfig():
"debug interface for userconfig"
"Debug interface for userconfig"
pass
@userconfig.command()
......
......@@ -6,10 +6,10 @@ local_conf = "~/.labtool/config.json"
course_name_fmt = "{course}-{semester}-{section}"
# course name in course object (ignores section assuming shared sections)
course_objf_fmt = "{course}-{semester}"
#course_objf_fmt = "{course}-{semester}"
local_home = "~/" + course_name_fmt
labs_subdir = "labs"
#labs_subdir = "labs"
# shown to the user when asking for a Gitlab API token
gitlab_api_token_url = "https://gitlab.bucknell.edu/profile/personal_access_tokens"
......
......@@ -4,6 +4,11 @@ Unicode icon list
https://www.fileformat.info/info/unicode/category/So/list.htm
"""
import click
from config.user import UserConfig
def debug(*args, **kwargs):
if UserConfig.DEBUG:
click.echo(*args, **kwargs)
def echo(*args, **kwargs):
return click.echo(
......
......@@ -8,10 +8,12 @@ import config
import json
import os
import os.path
#from config.echo import *
class UserConfig:
__instance = None
__depth = 0
DEBUG = False
@staticmethod
def getConfig():
return UserConfig.__instance.cfg
......@@ -42,7 +44,8 @@ class UserConfig:
with open(p, 'r') as cf:
UserConfig.cfg = json.load(cf)
if 'debug' in UserConfig.cfg:
if 'debug' in [x.lower() for x in UserConfig.cfg.keys()]:
UserConfig.DEBUG = True
print("USERCONFIG: READ")
else:
UserConfig.cfg = {}
......
def load_rubrics(path):
"return a dict of rubric docs"
# TODO: this.
pass
import os
import os.path
import json
from config.echo import *
from config.user import UserConfig
def detect_course():
"detect, chdir into root, and return the course based on CWD (or None)."
#https://stackoverflow.com/questions/25836175/python-how-to-discern-if-a-path-is-within-another-path
def is_subpath(path, of_path):
abs_of_path = os.path.abspath(of_path)
return os.path.abspath(path).startswith(abs_of_path)
for coursename, coursepath in UserConfig.getConfig()['courses'].items():
if is_subpath(os.getcwd(), coursepath):
os.chdir(coursepath)
return coursename, coursepath
return None, None
def load_rubric(coursename, labname):
"return a rubric doc or none"
debug("Looking for rubric for {}.{}".format(coursename, labname))
for pathname in all[coursename]['rubric_paths']:
tpath = os.path.join(pathname, labname + ".json")
if os.path.exists(tpath):
debug("Using rubric at {}".format(tpath))
with open(tpath, 'r') as fp:
return json.load(fp)
return None
# define valid course strings
courses = ['CSCI206']
# dictionary of all course info
csci206_s20 = {
'instructors': ['amm042','xmeng','jvs008'],
'tas': ['zp002'],
# rubric paths are searched in this order to allow mounting at various
# paths in the filesystem.
'rubric_paths':[
'/home/accounts/COURSES/cs206',
'/nfs/unixspace/linux/accounts/COURSES/cs206/2020-spring/rubrics',
'/unixspace/csci206/2020-spring/rubrics']
}
# dictionary of all course infos
all = {
'CSCI206-S20': {
'instructors': ['amm042','xmeng','jvs008'],
'tas': ['zp002'],
'rubrics': load_rubrics('labs/CSCI206/')
} # END COURSE CSCI206-S20
'CSCI206-S20-60': csci206_s20,
'CSCI206-S20-61': csci206_s20,
'CSCI206-S20-62': csci206_s20
}
__all__ = [all, courses]
{
"course": "csci206",
"version": 1,
"name": "Lab01",
"path": "Labs/Lab01",
"parts":
{"setup":
{
"index": 1,
"name": "Setup",
"prompt": "[20 points] will be given for correctly creating ~/csci206/Labs/Lab01 and placing your lab01.txt file in the right location.",
"points": 20,
"execute": "ls -lah"
},
"short_answers":
{
"index": 2,
"name": "Short answers",
"points": 80,
"prompt": "<ol><li>[15 points] One line mkdir ~/csci206/Labs/Lab01 command.</li><li>[5 point each] One sentence description of each of the following commands do: cat, more, less, head, and tail.</li><li>[10 points] The complete command line to start up emacs without a GUI.</li><li>[10 points] What is the command to show line numbers in vim?</li><li>[10 points] What is the command to show line numbers in emacs?</li><li>[10 points] Complete this sentence in your file: After careful consideration, I will use <vim or emacs> in csci206 as my text editor.</li></ol>",
"show": "lab01.txt"
}
}
}
{
"course": "csci206",
"version": 1,
"name": "Lab02",
"path": "Labs/Lab02",
"parts":
{"prelab":
{
"index": 1,
"name": "prelab",
"prompt": "[25 points] Prelab reading / activities (zybook).",
"points": 25
},
"ex1":
{
"index": 2,
"name": "isalary",
"points": 10,
"prompt": "ex1 salary and isalary correct and have all header information.",
"show": "isalary.c",
"execute": [ "gcc -Wall isalary.c -o isalary", "/bin/bash -c 'echo \"result should be: Annual salary is: 198000\"; echo 99.5|./isalary'"]
},
"ex2":
{
"index": 3,
"name": "nogood and fsalary",
"points": 20,
"prompt": "nogood and fsalary correct and compile without warning with -Wall. -5 for each warning, -15 for an error",
"show": [ "nogood.c", "fsalary.c"],
"execute": [ "gcc -Wall nogood.c -o nogood",
"gcc -Wall fsalary.c -o fsalary",
"./nogood",
"/bin/bash -c 'echo \"result should be: Annual salary is: \\$19180.80\"; echo 9.99 48 | ./fsalary'" ]
},
"ex3":
{ "index": 4,
"name": "as and ld",
"points": 10,
"prompt": "nogood.s and nogood.o exist.",
"show": [ "nogood.s" ],
"execute": "ls -lah nogood.o"
},
"ex4":
{ "index": 5,
"name": "cvp",
"points": 25,
"prompt": "primefact.py and primefact.c exist, compile without warning, and speedup is reasonable (noted in primefact.c).",
"show": [ "primefact.py", "primefact.c" ],
"execute": [ "gcc -Wall primefact.c -o primefact", "./primefact" ]
},
"ex5":
{
"index": 6,
"name" : "change case",
"points": 10,
"prompt": "switchcase.c compiles without warning and operates correctly.",
"show": ["switchcase.c"],
"execute": [ "gcc -Wall switchcase.c -o switchcase", "/bin/bash -c 'echo \"hello 123...\"|./switchcase" ]
}
}
}
{
"course": "csci206",
"version": 1,
"name": "Lab03",
"path": "Labs/Lab03",
"parts":
{"prelab":
{
"index": 1,
"name": "prelab",
"prompt": "<ul>\t<li>[1 point each, 4 total] Exercise 1: Debugging questions answered correctly.</li>\t<li>[2 points each, 12 total] Exercise 2: Answers for the register groups $at, $a0-$a3, $t0-$t9, $s0-$s7, $gp, and $sp.</li>\t<li>[1 points each, 4 total] Exercise 3: Each instruction and its effect are described accurately.</li>\t<li>[1 point each, 5 total] Exercise 4: Base conversions show intermediate steps and arrive at the correct result.</li></ul>",
"points": 25,
"show": "prelab.txt"
},
"ex1_2":
{
"index": 2,
"name": "mystery.asm",
"points": 35,
"prompt": "All register numbers in mystery.asm converted to the appropriately named register. -1 for each register not converted. Comments added to mystery.asm clearly indicate an understanding of what the program is doing (not just what each line does in isolation).",
"show": "mystery.asm"
},
"ex3a":
{
"index": 3,
"name": "c_mystery.c",
"points": 20,
"prompt": "c_mystery.c was created and it implements the proper algorithm.",
"show": [ "c_mystery.c"],
"execute": [ "gcc -Wall c_mystery.c -o c_mystery", "./c_mystery" ]
},
"ex3b":
{ "index": 4,
"name": "c_mystery.asm",
"points": 10,
"prompt": "generated c_mystery.asm using mipsel-linux-gcc",
"show": [ "c_mystery.asm" ]
},
"coding":
{ "index": 5,
"name": "conventions",
"points": 10,
"prompt": "Followed good coding conventions.",
"show": [ "c_mystery.c" ]
}
}
}
{
"course": "csci206",
"version": 1,
"name": "Lab04",
"path": "Labs/Lab04",
"parts":
{"prelab":
{
"index": 1,
"name": "prelab",
"prompt": "zyBook activities",
"points": 25
},
"ex1":
{
"index": 2,
"name": "calc.c",
"points": 10,
"prompt": " Exercise 1: calc.c program works as described and follows good coding conventions. -5 if it does not compile or has warnings. -1 for each minor error. -2 for each significant error.",
"show": "calc.c",
"execute": ["gcc -Wall calc.c -o calc", "./calc add 1 100",
"./calc add 1 100 0.1",
"./calc mult 1024.0 1000",
"./calc div 1024 256",
"./calc len \"hello world\""]
},
"ex2_1":
{
"index": 3,
"name": "head.c",
"points": 10,
"prompt": "Exercise 2: head.c program works as described and follows good coding conventions. -5 if it does not compile or has warnings. -1 for each minor error. -2 for each significant error.",
"show": "head.c",
"execute": [
"gcc -Wall head.c -o head",
"wget -q http://eg/~amm042/csci206/alice100.txt -O alice100.txt",
"./head alice100.txt 50"
]
},
"notes":
{
"index": 4,
"name": "notes.txt",
"points": 20,
"prompt": "Exercise 2: notes.txt created and correctly describes creat, open, read, write, close (2 points each). <br>Exercise 4: notes.txt correctly describes fopen(3), fgets(3), fprintf(3), fseek(3), and fclose(3) (2 points each).",
"show": "notes.txt"
},
"ex3_1":
{
"index": 5,
"name": "head2",
"points": 5,
"prompt": "Makefile created to compile head2.c using head2.o and fileio.o. -5 points for: gcc –Wall head2.c fileio.c –o head2.",
"show": "makefile",
"execute": [
"make head2"
]
},
"ex3_2":
{
"index": 6,
"name": "read_file_bytes",
"points": 5,
"prompt": "Exercise 3: read_file_bytes removed from head2.c and added to fileio.c. fileio.h has correct declaration with include guard.",
"show": [ "head2.c", "fileio.h", "fileio.c"] ,
"execute": [
"./head2 alice100.txt 50"
]
},
"ex4":
{
"index": 7,
"name": "head3",
"points": 10,
"prompt": "Exercise 4: head3.c created and uses read_file_lines in fileio.c/.h to read lines using the standard libraries. -5 if it does not compile or has warnings. -1 for each minor error. -2 for each significant error.",
"show": [ "head3.c", "fileio.h", "fileio.c"] ,
"execute": [
"make head3",
"./head3 alice100.txt"
]
},
"ex5":
{
"index": 8,
"name": "string_token",
"points": 15,
"prompt": "Exercise 5: string_token.c created; notes.txt updated with the output of the original string_token program which generates something like 1: Hello --> Hello 2: world, --> world 3: how --> how 4: are --> are 5: you? --> you . If notes.txt doesn't contain this segment, -4. Visually examine the source code string_token.c. If code style is bad, -2; if the program doesn't compile, -4; if the program compiles with warning(s), -2.",
"show": [ "string_token.c", "notes.txt"] ,
"execute": [ "make string_token",
"./string_token string_token.c | head"
]
}
}
}
{
"course": "csci206",
"version": 1,
"name": "Lab05",
"path": "Labs/Lab05",
"parts":
{
"ex1":
{
"index": 1,
"name": "Memory Map",
"points": 20,
"prompt": "[2 points each, 20 total] Exercise 1: In notes.txt, 10 values (7 addresses and 3 sizes) should show. The only ones that are blank (unknown) should be the ending address and the size of Heap.",
"show": "notes.txt"
},
"ex2":
{
"index": 2,
"name": "syscalls",
"points": 10,
"prompt": "[2 point each, 10 total] Exercise 2: In notes.txt, the 5 syscalls are provided with the correct details to run using functionality from SPIM or MARS (GUI).",
"show": "notes.txt"
},
"ex3":
{
"index": 3,
"name": "searcharray",
"points": 20,
"prompt": "[20 points] Exercise 3: searcharray.s was modified to prompt the user for the value to search for and stop if the value is not found and print value not found.",
"show": "searcharray.s",
"execute": "/bin/bash -c \"echo 42 | java -jar $(MARS_FILE) searcharray.s\""
},
"ex4":
{
"index": 4,
"name": "sumarray",
"points": 20,
"prompt": "[20 points] Exercise 4: sumarray.s completed, correctly detects the length of the array and computes the sum (including if it is zero-length) using the appropriate array access method.",
"show": "sumarray.s",
"execute": [ "java -jar $(MARS_FILE) sumarray.s" ]
},
"ex5":
{
"index": 5,
"name": "sumsquares",
"points": 30,
"prompt": "[30 points] Exercise 5: sumsquares.s completed and runs; output matches the C version (minor formatting variations are acceptable).",
"show": [ "sumsquares.s"] ,
"execute": ["java -jar $(MARS_FILE) sumsquares.s" ]
}
}
}
{
"course": "csci206",
"version": 1,
"name": "Lab06",
"path": "Labs/Lab06",
"parts":
{
"pre1":
{
"index": 1,
"name": "Leaf Procedure",
"points": 15,
"prompt": "Leaf procedure implemented in prelab.s. Function returns the proper value. The static variable C is properly defined in the data segment. $ra is noted for the two calls to my_func in a comment.",
"show": "prelab.s"
},
"pre2":
{