import datetime import subprocess from typing import Dict, List, Optional, Set from gitlab.exceptions import GitlabGetError, GitlabListError from api.canvas_classes import CanvasCourse, CanvasAssignment, CanvasUser from api.composite_user import CompositeUser from api.gitlab_classes import GitlabUser, GitlabProject, GitlabCommit from common_functions import select_from_list, strip_html, semester_stamp from course import Course DEFAULT_HOMEWORK_REPOSITORY = 'csce361-homework' def get_responses(assignment: CanvasAssignment, students: List[CanvasUser]) -> Dict[CanvasUser, str]: # TODO: Can this be made more general and be useful to grade_team_contribution.display_peer_reviews() ? student_responses: Dict[CanvasUser, Optional[str]] = {} # noinspection PyUnusedLocal student: CanvasUser for student in students: student_responses[student] = strip_html(assignment.get_submission_text(student)) return student_responses def extract_gitlab_usernames(student_responses: Dict[CanvasUser, str]) -> Dict[CanvasUser, str]: student_gitlab_usernames: Dict[CanvasUser, str] = {} # noinspection PyUnusedLocal student: CanvasUser for student in student_responses: response: str = student_responses[student] if response is None: response = '' candidate_username: List[str] = response.split() if len(candidate_username) == 1: username: str = candidate_username[0] if username[0] == '@': student_gitlab_usernames[student] = username[1:] else: student_gitlab_usernames[student] = username elif len(candidate_username) > 1: print(f'Unexpected response from {student.get_name()}: {response}') potential_usernames = list(filter(lambda s: s[0] == '@', candidate_username)) if len(potential_usernames) == 1: username: str = potential_usernames[0] print(f'Assigning {username} as username.') student_gitlab_usernames[student] = username[1:] else: username: str = input('Enter gitlab username: ') student_gitlab_usernames[student] = username else: print(f'{student.get_name()} has no response; assigning blank gitlab username.') student_gitlab_usernames[student] = '' print(f'\tCanvas username: {student.get_username()}\tGitlab username: @{student_gitlab_usernames[student]}') return student_gitlab_usernames def retrieve_gitlab_user(gitlab_username: str, student_name: str) -> Optional[GitlabUser]: try: if gitlab_username != '': return GitlabUser(gitlab_username) else: return None except IndexError: print(f'\t{student_name} has an invalid gitlab username: {gitlab_username}.') new_username: str = input('\tEnter new gitlab username (enter blank to skip): ') return retrieve_gitlab_user(new_username, student_name) # noinspection PyShadowingNames def create_composite_users(student_usernames: Dict[CanvasUser, str]) -> (Set[CompositeUser], Set[CanvasUser]): composite_users: Set[CompositeUser] = set() skipped_users: Set[CanvasUser] = set() # noinspection PyUnusedLocal canvas_student: CanvasUser for canvas_student in student_usernames: gitlab_username: str = student_usernames[canvas_student] gitlab_student: GitlabUser = retrieve_gitlab_user(gitlab_username, canvas_student.get_name()) if gitlab_student is not None: composite_user: CompositeUser = CompositeUser.initialize_composite_user(canvas_student, gitlab_student) composite_users.add(composite_user) else: print(f'{canvas_student.get_name()} has a blank gitlab username; skipping.') skipped_users.add(canvas_student) return composite_users, skipped_users def retrieve_homework_repos(students: Set[CompositeUser], repo_name: str) \ -> Dict[CompositeUser, Optional[GitlabProject]]: user_repos: Dict[CompositeUser, GitlabProject] = {} for student in students: path = student.get_gitlab_user().get_username() + '/' + repo_name try: print(f'\tRetrieving {path}.git at {datetime.datetime.now()}.') repo = GitlabProject(path) except GitlabGetError: print(f'*** WARNING *** Could not retrieve {path}.git!') repo = None user_repos[student] = repo return user_repos def add_gitlab_email(user_repos: Dict[CompositeUser, Optional[GitlabProject]]) -> None: # noinspection PyUnusedLocal student: CompositeUser for student in user_repos: project = user_repos[student] if project is not None: try: commits: List[GitlabCommit] = project.get_commits() except GitlabListError: print(f'*** NOTE *** Could not access commits for {project}. ' f'This probably indicates Guest permissions.') commits = [] if len(commits) > 0: commit = commits[-1] # should be the initial commit; regardless, all commits should be by the student email = commit.get_author()['email'] student.set_gitlab_email(email) def create_cloning_script(user_repos: Dict[CompositeUser, Optional[GitlabProject]], no_username: Set[CanvasUser], filename: str) -> None: file = open(filename, 'x') file.write('#!/bin/bash\n\n') file.write('# Auto-generated clone script.\n') no_repo: Set[CompositeUser] = set() # noinspection PyUnusedLocal composite_student: CompositeUser for composite_student in user_repos: repo = user_repos[composite_student] if repo is not None: repo_url = repo.get_cloning_url() student_name = composite_student.get_canvas_user().get_name().replace(' ', '_') file.write(f'git clone {repo_url} {student_name}') if composite_student.gitlab_email is None: # breaking encapsulation? file.write(' # may have inadequate permissions') file.write('\n') else: no_repo.add(composite_student) if len(no_repo) > 0: file.write('\n# Students without homework repositories ' '(check for misspelled repository names or repositories created but not shared):\n') for composite_student in no_repo: student_name = composite_student.get_canvas_user().get_name().replace(' ', '_') file.write(f'# git clone {composite_student.get_gitlab_user().get_username()}/... {student_name}\n') if len(no_username) > 0: file.write('\n# Students without valid gitlab usernames (check for repositories shared but username not ' 'submitted):\n') # noinspection PyUnusedLocal canvas_student: CanvasUser for canvas_student in no_username: student_name = canvas_student.get_name().replace(' ', '_') file.write(f'# git clone ... {student_name}\n') file.close() subprocess.call(['chmod', '+x', filename]) print(f'\tCreated {filename}. Distribute to TAs so they can clone the homework repositories.') if __name__ == '__main__': course = CanvasCourse(Course.canvas_course_id) print('First, select the "setup" assignment to extract student information from.\n') assignment_groups = course.get_assignment_groups() assignment_group = select_from_list(assignment_groups, 'assignment group') print() assignments = assignment_group.get_assignments() setup_assignment = select_from_list(assignments, 'assignment') print(f'\nWe will now extract student information from {setup_assignment}.') responses = get_responses(setup_assignment, course.get_students()) gitlab_usernames = extract_gitlab_usernames(responses) print('\nWe will now check the validity of the gitlab usernames.') composite_users, skipped_users = create_composite_users(gitlab_usernames) if len(skipped_users) == 0: print(f'\nSuccessfully retrieved {len(composite_users)} students.') else: print( f'\n*** WARNING *** Skipped {len(skipped_users)} ' f'out of {len(skipped_users)+len(composite_users)} students!') print('We will now retrieve the students\' homework repositories.') choice = input(f'Enter homework repository name [{DEFAULT_HOMEWORK_REPOSITORY}]: ') if choice == '': choice = DEFAULT_HOMEWORK_REPOSITORY homework_repos = retrieve_homework_repos(composite_users, choice) add_gitlab_email(homework_repos) print('\nCreating cloning script for homework repositories.') create_cloning_script(homework_repos, skipped_users, f'{semester_stamp()}-homework.sh') CompositeUser.write_student_csv(set(homework_repos.keys()), f'{semester_stamp()}-students.csv') print('\tCreated student csv file.')