#!/usr/bin/env python ## Version 2.1 ## Written by Peter Sinclair, based on code by Ting Chan ## Previous Versions: ## 1.0 Original script ## 1.1 Documentation added ## 1.2 Updated to accommodate both Scheme and Python ## 1.3 Updated to accommodate any course number ## 2.0 Added "simple" mode (and named previous mode "standard") ## 2.1 Added automatic course detection and permission changing for answers folder ## SAMPLE INPUT for STANDARD MODE (all of the following formats will work): ## ## Single line value: ## ## value = 1 ## value = {1} ## value = "1" ## ## Multi-line values: ## ## output = {One Line} ## output = ## {More than ## one line} ## output = {Also more ## than one ## line} ## ## Multi-line value with filename: ## ## file = filename ## {Content ## of ## file} ## file = {filename} ## {Content of File} import os, pwd, shutil, stat, sys from subprocess import call ## Assigns user input to variables, or prints error message if user input has problems if len(sys.argv)!=4: print "Usage:", sys.argv[0], "assignment, question #, file" sys.exit(1) else: asst_num = sys.argv[1] question = sys.argv[2] filename = sys.argv[3] ## Checks for course number in script path; if not present, prompts the user. try: course = pwd.getpwuid(os.getuid())[0] except: course = raw_input("Please enter the course code (ex: cs116): ") ## Tries to open filename; if filename does not exist in course/marking/asst_num/test.1 ## then the script prints an error message and terminates. try: infile=file('/u/%s/marking/%s/test.1/%s' % (course, asst_num, filename),'r') lines = infile.readlines() infile.close() except: print '/u/%s/marking/%s/test.1/%s file doesn\'t exist; no files created.' % (course, asst_num, filename) sys.exit(1) ## Variables to navigate lines: ## n is the current position in lines ## line is reset to lines[n] each time n changes, but is often reduced ## to only the important part of the line while processing. n=0 while lines[n] == "\n": n += 1 line = lines[n] ## Function to handle multi-line values def multi_line_value(): global line global n temp = "" if '{' in line and '}' in line: temp += line[line.index('{')+1:line.index('}')] else: while '{' not in line: n += 1 line = lines[n] temp += line[line.index('{')+1:] n += 1 line = lines[n] while '}' not in line: temp += line n += 1 line = lines[n] temp += line[:line.index('}')] # if temp[0] == "\n": temp=temp[1:] # Uncomment these lines if you want to remove a # if temp[-1] == "\n": temp=temp[:-1] # single newline from the start/end of temp return temp ## Initialize variables for /in/question/options.rkt options, value, desc, language, loadcode, mode = '', '', '', '', '', '' ## Loop to collect information /in/question/options.rkt as follows: ## If line[0] does not equal 'q', the loop terminates ## Otherwise, options, value, loadcode, or desc will update to equal ## the remainder of the line. while line[0] == 'q': if line.startswith('q_options'): line = line[7:] options += multi_line_value() elif line.startswith('q_value'): line = line[7:].strip('= \n\r{}\'\"') value = '(value %s)\n' % line elif line.startswith('q_loadcode'): line = line[10:].strip('= \n\r{}\'\"') loadcode = '(loadcode "%s")\n' % line elif line.startswith('q_desc'): line = line[6:].strip('= \n\r{}\'\"') desc = '(desc "%s")\n' % line elif line.startswith('q_language'): line = line[10:].strip('= \n\r{}\'\"') language = '(language %s)' % line elif line.startswith('q_mode'): mode = line[6:].strip('= \n\r{}\'\"') n += 1 line = lines[n] ## Checks that a language has been specified either in filename or test.1/in/options.rkt; ## If it hasn't, prints an error message and aborts ## If it has, sets extension to either .rkt or .py depending on the language specified extension = "" if language == "": F = file('/u/%s/marking/%s/test.1/in/options.rkt' % (course, asst_num)) big_options = F.read() F.close() if "scheme" in big_options: extension = "rkt" elif "python" in big_options: extension = "py" else: if "scheme" in language: extension = "rkt" elif "python" in language: extension = "py" if extension == "": print 'No language specified in test.1/%s or in test.1/in/options.rkt; no files created' % filename sys.exit(1) ## Prompts the user for an appropriate mode until a recognised mode is entered. while mode not in ["simple", "standard", ""]: mode = raw_input("Mode not recognised. Please enter either simple or standard: ") ## Deletes test.1/in/question (if it exists) and creates a new, more empty test.1/in/question try: shutil.rmtree('/u/%s/marking/%s/test.1/in/%s' % (course, asst_num, question)) except: None os.mkdir('/u/%s/marking/%s/test.1/in/%s' % (course, asst_num, question)) os.chdir('/u/%s/marking/%s/test.1/in/%s' % (course, asst_num, question)) ## Writes desc, loadcode, value, and options to /in/question/options.rkt ## loadcode is set to aXqY.rkt or aXqY.py if it was not set in filename if loadcode == "": loadcode = '(loadcode "%sq%s.%s")\n' % (asst_num, question, extension) optionsfile = file('options.rkt', 'w') optionsfile.writelines([desc, loadcode, value, options]) optionsfile.close() ## Skip any blank lines between question information and tests while lines[n] == "\n": n += 1 test_num = 1 ## The current test ## name_test_dir produces an appropriate name for the current testing directory ## based on test_num: ## * If test_num is a one-digit number, then produces t0X, where X is test_num ## * If test_num is longer than one digit, then produces tX, where X is test_num def name_test_dir(): if test_num < 10: return 't0%d' % test_num else: return 't%d' % test_num test_dir = name_test_dir() ## Variables used in write_test() and following loop value, desc, options, test_text, input, output = '', '', '', '', '', '' files, duplicate_files = {}, [] ## Function for writing necessary test files (if a field is empty, it is not written): ## IMPORTANT: This function begins by deleting /in/asst_num and all subdirectories ## and creating a new, empty /in/asst_num ## test_text will be written to /in/question/test_dir/test.py (or test.rkt) ## desc, value, and options will be written to /in/question/test_dir/options.rkt ## input will be written to /in/question/test_dir/input ## output will be written to /answers/question_test_dir.out ## The value of each filename in files will be written to /provided/filename ## The function also displays what files are written for the test def write_test(): os.mkdir('/u/%s/marking/%s/test.1/in/%s/%s' % (course, asst_num, question, test_dir)) os.chdir('/u/%s/marking/%s/test.1/in/%s/%s' % (course, asst_num, question, test_dir)) test_file = file('test.%s' % extension, 'w') test_file.write(test_text) test_file.close() files_created = ['test.%s' % extension] if options or value or desc: F = file('options.rkt', 'w') F.writelines([desc, value, options]) F.close files_created.append("options.rkt") if input: F = file('input', 'w') F.write(input) F.close files_created.append('input') if output: os.chdir('/u/%s/marking/%s/test.1/' % (course, asst_num)) call('chmod -R 700 answers', shell=True) os.chdir('answers') F = file('%s_%s.out' % (question, test_dir), 'w') F.write(output) F.close files_created.append('%s_%s.out' % (question, test_dir)) if files: os.chdir('/u/%s/marking/%s/test.1/provided' % (course, asst_num)) for filename in files: F = file(filename, 'w') F.write(files[filename]) F.close files_created.append(filename) ## English is a silly language, so it takes this much code to display ## which files were created in a correctly punctuated fashion print_message = test_dir + ": Created " if len(files_created) == 1: print_message += files_created[0] files_created = [] elif len(files_created) == 2: print_message += files_created[0] + ' and ' + files_created[1] files_created = [] while len(files_created) > 2: print_message += files_created[0] + ', ' files_created = files_created[1:] if len(files_created) == 2: print_message += files_created[0] + ', and ' + files_created[1] print print_message for name in sorted(duplicate_files): print "Attempted to create multiple files named %s; only the first was created" % name ## Loop to take data in from lines and call write_test() as follows: ## If line == '\n' then the information for that test is complete, and the ## loop will call write_test() before resetting all variables ## If line begins with value, desc, options, input, output, or file, ## then the appropriate field will be updated. ## Otherwise, the information will be stored in test_text ## (which will be written verbatim to test.py or test.rkt) ## The loop will finish with the final test unwritten; the if statement ## following the loop writes the final test. while n < len(lines): line = lines[n] if line == '\n': try: write_test() except: raise #print "Error writing", test_dir test_num += 1 test_dir = name_test_dir() value, desc, options, test_text, input, output = '', '', '', '', '', '' files, duplicate_files = {}, [] elif line.startswith('value'): line = line[5:].strip('= \n\r{}\'\"') value = '(value %s)\n' % line elif line.startswith('desc'): line = line[4:].strip('= \n\r{}\'\"') desc = '(desc "%s")\n' % line elif line.startswith('options'): line = line[7:] options += multi_line_value() elif line.startswith('input'): line = line[5:] input += multi_line_value() elif line.startswith('output'): line = line[6:] output += multi_line_value() elif line.startswith('file'): line = line[4:].strip('= \n\r{}\'\"') name = line n += 1 line = lines[n] body = "" body += multi_line_value() if files.has_key(name): duplicate_files.append(name) else: files[name] = body elif mode == "simple" and extension == "py": test_text += "result = " + line n += 1 line = lines[n] test_text += "expected = " + line elif mode == "simple" and extension == "rkt": line = line.strip() test_text += "(result " + line + ")\n" n += 1 line = lines[n].strip() test_text += "(expected " + line + ")\n" else: test_text += line n += 1 ## This if statement writes the final test if test_text != '': try: write_test() except: print "Error writing", test_dir