""" Functions to get student projects for CSSE 120 (or other courses) from SVN and to prepare them to be graded. Authors: David Mutchler, Chandan Rupakheti, Amanda Stouder, David Lam, and their colleagues, December 2013, modified May 2015. """ #----------------------------------------------------------------------- # NOTE: This code assumes that you have installed Tortoise SVN # and put command-line tools (e.g. svn ) in your system path. #----------------------------------------------------------------------- import urllib.request import os import re import shutil import fnmatch # NOTE: Change the GRADING_FOLDER as needed for your computer. GRADING_FOLDER = 'C:/EclipseWorkspaces/csse120-grading/' # Don't forget to change the TERM each term, and the SECTIONS. COURSE = 'csse120' TERM = '201510' SECTIONS = ('01', '02', '03', '04') COURSE_REPO_NAME = 'csse120-python/branches/robonew/' ECLIPSE_PROJECTS = 'EclipseProjects' SOLUTION_SUFFIX = '_SOLUTION' SOLUTION_USERNAME = '0_SOLUTION' PROJECTS = ['Session25_Test3', 'Session01_IntroductionToPython', 'Session02_InputComputeOutput', 'Session03_LoopsAndUsingObjects', 'Session04_FunctionsAndAccumulators', 'Session05', 'Session06', 'Session07', 'Session08_Test1', 'Session09_', 'Session10', 'Session11_AccumulatingSequencesAndFancyIterating', 'Session12', 'Session13', 'Session14', 'Session15_Test2', 'Session16_Test2_201430', 'Session30_Test3_Part2', ] URL_FOR_SVN_REPOS = 'http://svn.csse.rose-hulman.edu/repos/' URL_FOR_CSSE_HOME = 'http://www.rose-hulman.edu/class/csse/' FOLDER_FOR_ROSTERS = 'Rosters' URL_FOR_ROSTERS = URL_FOR_CSSE_HOME + COURSE + '/' + TERM + '/' \ + FOLDER_FOR_ROSTERS + '/' + TERM + '/' class Student(object): def __init__(self, username, name, campus_mail, major, class_, year, advisor, email): self.username = username class RepoHelper(): def __init__(self): pass def # class Checker_Outer(object): # default_course = 'csse120' # # def __init__(self, course=Checker_Outer.default_course, term=None): # self.course = course # # TODO: Determine the term from today's date. # self.term = 201510 # # TODO: Determine the sections from Schedule Lookup Page. # self.sections = ['01', '02', '03', '04'] # # self.url_for_course_lookup = URL_FOR_SCHEDULE_LOOKUP # # def enrolled_students(self, section=None): # """ # Returns a list of all the Student (u that are members of the given sequence # of groups. The usernames must be stored in text files that # are in the appropriate place. # """ # students = [] # url_prefix = roster_folder + COURSE + '-' + TERM + '-' # for group in groups: # url = url_prefix + group + '.txt' # usernames_as_byte_array = urllib.request.urlopen(url).read() # usernames_in_group = usernames_as_byte_array.decode().split() # usernames.extend(usernames_in_group) # # return usernames def main(): start(0) def start(project_number): project = PROJECTS[project_number] students = get_usernames_from_rosters() failures1 = checkout_all(project, students) failures2 = fix_all_for_eclipse(project, students) print('Failed checkout:', failures1) print('Failed fix-for-Eclipse:', failures2) remove_extra(project, students) # checkout_solution(project) # folder = get_grading_folder(SOLUTION_USERNAME, project) # fix_for_eclipse(folder, project + SOLUTION_SUFFIX, # SOLUTION_USERNAME) def get_usernames_from_rosters(groups=SECTIONS, roster_folder=URL_FOR_ROSTERS): """ Returns a list of usernames that are members of the given sequence of groups. The usernames must be stored in text files that are in the appropriate place. """ file = open('usernames.txt', 'r') usernames = [] for username in file.readlines(): usernames.append(username.strip()) return usernames # url_prefix = roster_folder + COURSE + '-' + TERM + '-' # for group in groups: # url = url_prefix + group + '.txt' # print(url) # usernames_as_byte_array = urllib.request.urlopen(url).read() # usernames_in_group = usernames_as_byte_array.decode().split() # usernames.extend(usernames_in_group) # # return usernames def checkout_all(project_to_grade, students): """ Checkouts the given project for all the given students. Returns a list of all students SKIPPED or for which checkout appears to have failed. """ failures = [] for student in students: url = get_url(student, project_to_grade) folder = get_grading_folder(student, project_to_grade) if os.access(folder, os.F_OK): print('WARNING: SKIPPING', folder, 'because it already exists') failures.append(student) continue success = checkout(url, folder) if not success: failures.append(student) return failures def checkout_solution(project_to_grade, solution_name=None): """ Checkouts the solution for the given project. Puts it in the same place as the student solutions are placed. Returns True if succeeded, else False. """ url = get_solution_url(project_to_grade) folder = get_grading_folder(SOLUTION_USERNAME, project_to_grade) if os.access(folder, os.F_OK): print('WARNING: SKIPPING', folder, 'because it already exists') return False success_or_failure = checkout(url, folder) return success_or_failure def get_url(student, project_to_grade): """ Returns the URL for the given student's project. """ repo = COURSE + '-' + TERM + '-' + student + '/' + project_to_grade return URL_FOR_SVN_REPOS + repo def get_solution_url(project_to_grade): """ Returns the URL for the solution for the given project. """ repo = COURSE_REPO_NAME + '/' + ECLIPSE_PROJECTS + '/' + TERM solution = project_to_grade + SOLUTION_SUFFIX return URL_FOR_SVN_REPOS + repo + '/' + solution def get_grading_folder(student, project_to_grade): """ Returns the folder into which to checkout the given student's project. """ # FIXME: This presumes a folder structure that others might not want. # But at least the structure is encapsulated in this function. parent = GRADING_FOLDER + TERM + '/' return parent + project_to_grade + '/' + student def checkout(url, folder): """ Checks out the given URL into the given folder. Returns True if the checkout succeeded, else False. """ # FIXME: You need to make Tortoise learn your password somehow. # Otherwise, use something like: # os.system('svn checkout ' + url + ' ' + folder + ' --username mutchler --password XXX') # But note that the latter is a security hazard (see me for details). try: # FIXME: Figure out how to find if the checkout failed. os.system('svn checkout ' + url + ' ' + folder) except: # FIXME: Figure out whether this detects failures correctly. return False return True def fix_all_for_eclipse(project_to_grade, students): """ For each of the given students, renames the project to be the student's username. This allows all the projects to exist in a single Eclipse workspace. """ failures = [] for student in students: try: folder = get_grading_folder(student, project_to_grade) success = fix_for_eclipse(folder, project_to_grade, student) if not success: failures.append(student) except: failures.append(student) return failures def fix_for_eclipse(folder, project_to_grade, student): """ Modifies the .project in the given folder to rename the project from its name to the given student (username). This allows Eclipse to import multiple students' projects for the same assignment (all of which share the same project name before running this function). Returns True if the fix-for-Eclipse succeeded, else False. """ # FIXME: The following is somewhat brittle and WILL BREAK if # Eclipse changes its project filenames. filename = folder + '/.project' try: with open(filename, 'r') as file: file_contents = file.read() lines = file_contents.split('\n') with open(filename, 'w') as file: for line in lines: print(re.sub(project_to_grade, student, line), file=file) except: # FIXME: Figure out whether this detects failures correctly. print('Could not open the file:') print(' ', filename) print('Fix and try again.\n') return False return True # Functions below are NOT correct. def remove_extra(project_to_grade, students): """ Removes all unnecessary information from the projects: -- All SVN information -- All .docx and .pdf files """ for student in students: folder = get_grading_folder(student, project_to_grade) remove_extra_in_folder(folder) def remove_extra_in_folder(folder): """ At the TOP level of the given folder, as well as in the src subfolder (if it exists), removes any of the following that exist: *.docx, *.pdf, *.gif """ # FIXME: THIS IS A DANGEROUS FUNCTION. # If the folder is somehow not what is intended, # LOTS of files can be IRREVOCABLY DELETED. # Temporary attempt at safety: try: assert(folder.startswith(GRADING_FOLDER)) except: print('ERROR ERROR ERROR in remove_extra_in_folder') raise Exception('Error in remove_extra_in_folder') # Here is the dangerous part: for file in os.listdir(folder): if (file == '.svn'): pass # shutil.rmtree(folder + '/.svn') if fnmatch.fnmatch(file, '*.pdf'): os.remove(folder + '/' + file) if fnmatch.fnmatch(file, '*.docx'): os.remove(folder + '/' + file) for file in os.listdir(folder + '/src'): if fnmatch.fnmatch(file, '*.gif'): os.remove(folder + '/src/' + file) if __name__ == '__main__': main()