def get_repo_urls( self, assignment_names: Iterable[str], org_name: Optional[str] = None, team_names: Optional[List[str]] = None, insert_auth: bool = False, ) -> List[str]: """See :py:meth:`repobee_plug.PlatformAPI.get_repo_urls`.""" with _try_api_request(): org = ( self._org if not org_name else self._github.get_organization(org_name) ) scheme, netloc, org_path, *_ = urllib.parse.urlsplit(org.html_url) base_html_url = urllib.parse.urlunsplit([scheme, netloc, *([""] * 3)]) repo_names = ( assignment_names if not team_names else plug.generate_repo_names(team_names, assignment_names) ) repo_urls = [ urllib.parse.urljoin(base_html_url, f"{org_path}/{repo_name}") for repo_name in list(repo_names) ] return list( repo_urls if not insert_auth else map(self.insert_auth, repo_urls) )
def test_use_extended_students_syntax(self, platform_url, tmp_path): students_file = tmp_path / "students.yml" students_file.write_text( """ some-team: members: [simon] other-team: members: [eve, alice] """.strip(), encoding=sys.getdefaultencoding(), ) expected_repo_names = plug.generate_repo_names( team_names=["some-team", "other-team"], assignment_names=const.TEMPLATE_REPO_NAMES, ) funcs.run_repobee( f"{plug.cli.CoreCommand.repos.setup} --base-url {platform_url} " f"--students-file {students_file} " f"--assignments {const.TEMPLATE_REPOS_ARG}", plugins=[_repobee.ext.studentsyml], ) actual_repo_names = [ repo.name for repo in funcs.get_repos(platform_url) ] assert sorted(actual_repo_names) == sorted(expected_repo_names)
def test_pylint_plugin_with_python_syntax_error( self, platform_url, tmp_path_factory ): """Test that the pylint plugin correctly reports errors.""" workdir = tmp_path_factory.mktemp("workdir") python_task = self._setup_task_with_faulty_python_code( platform_url, workdir ) repo_names = plug.generate_repo_names( STUDENT_TEAMS, [python_task.name] ) plug_results = funcs.run_repobee( f"repos clone -a {python_task.name} --base-url {platform_url}", plugins=[_repobee.ext.pylint], workdir=workdir, ) assert plug_results assert len(plug_results) == len(repo_names) for repo_name in repo_names: pylint_result, *_ = plug_results[repo_name] assert pylint_result.name == "pylint" assert pylint_result.status == plug.Status.ERROR assert "src/main.py -- ERROR" in pylint_result.msg
def test_post_clone_hook_invoked_on_all_student_repos( self, platform_url, with_student_repos ): """Test that the post_clone hook is called with the expected repositories. """ expected_repo_names = set( plug.generate_repo_names(STUDENT_TEAMS, TEMPLATE_REPO_NAMES) ) class PostClonePlugin(plug.Plugin): def post_clone( self, repo: plug.StudentRepo, api: plug.PlatformAPI ): # remove repo names one by one, in the end none should remain expected_repo_names.remove(repo.name) assert isinstance(api, localapi.LocalAPI) assert repo.path.is_dir() funcs.run_repobee( f"repos clone -a {TEMPLATE_REPOS_ARG} " f"--base-url {platform_url}", plugins=[PostClonePlugin], ) assert not expected_repo_names
def test_open_issue_for_all_repos(self, with_student_repos, platform_url, issue): expected_repo_names = plug.generate_repo_names( const.STUDENT_TEAMS, const.TEMPLATE_REPO_NAMES) funcs.run_repobee( f"issues open --assignments {const.TEMPLATE_REPOS_ARG} " f"--base-url {platform_url} " f"--issue {issue.path} ") repos = funcs.get_repos(platform_url, const.TARGET_ORG_NAME) issues_dict = {repo.name: repo.issues for repo in repos} num_asserts = 0 for name in expected_repo_names: num_asserts += 1 issues = issues_dict[name] first_issue = issues[0] assert len(issues) == 1 assert first_issue.title == issue.title assert first_issue.body == issue.body assert first_issue.state == plug.IssueState.OPEN assert num_asserts == len(expected_repo_names)
def get_repo_urls( self, assignment_names: Iterable[str], org_name: Optional[str] = None, team_names: Optional[List[str]] = None, insert_auth: bool = False, ) -> List[str]: """See :py:meth:`repobee_plug.PlatformAPI.get_repo_urls`.""" with _try_api_request(): org = ( self._org if not org_name else self._github.get_organization(org_name) ) repo_names = ( assignment_names if not team_names else plug.generate_repo_names(team_names, assignment_names) ) return [ self.insert_auth(url) if insert_auth else url for url in ( "{}/{}".format(org.html_url, repo_name) for repo_name in list(repo_names) ) ]
def with_multi_issues_file(tmp_path): """Create the multi issues file.""" repo_names = plug.generate_repo_names(STUDENT_TEAM_NAMES, ASSIGNMENT_NAMES) repos_and_issues = [(repo_name, random.choice(ISSUES)) for repo_name in repo_names] issues_file = tmp_path / "issues.md" _write_multi_issues_file(repos_and_issues, issues_file) return issues_file, repos_and_issues
def _generate_multi_issues_file_content(students: List[str], assignments: List[str]) -> str: issue_headers = [ f"#ISSUE#{repo_name}#<ISSUE-TITLE>\n<ISSUE-BODY>" for repo_name in plug.generate_repo_names(students, assignments) ] return "\n\n".join(issue_headers)
def assert_repos_exist(student_teams, assignment_names, org_name=ORG_NAME): """Assert that the associated student repos exist.""" repo_names = plug.generate_repo_names(student_teams, assignment_names) gl = gitlab.Gitlab(BASE_URL, private_token=TOKEN, ssl_verify=False) target_group = get_group(org_name, gl=gl) student_groups = gl.groups.list(id=target_group.id) projects = [p for g in student_groups for p in g.projects.list(all=True)] project_names = [p.name for p in projects] assert set(project_names) == set(repo_names)
def _filter_hook_results(hook_results_mapping, teams, assignment_names): """Return an OrderedDict of hook result mappings for which the repo name is contained in the cross product of teams and master repo names. """ repo_names = set(plug.generate_repo_names(teams, assignment_names)) selected_hook_results = collections.OrderedDict() for repo_name, hook_results in sorted(hook_results_mapping.items()): if repo_name in repo_names: selected_hook_results[repo_name] = hook_results missing_repo_names = repo_names - selected_hook_results.keys() _log_missing_repo_names(missing_repo_names) return selected_hook_results
def get_repo_urls( self, assignment_names: Iterable[str], org_name: Optional[str] = None, team_names: Optional[List[str]] = None, insert_auth: bool = False, ) -> List[str]: assert not insert_auth, "not yet implemented" base = self._repodir / (org_name or self._org_name) repo_names = (assignment_names if not team_names else plug.generate_repo_names(team_names, assignment_names)) return [(base / name).as_uri() for name in repo_names]
def with_issues(tmp_path): """Create issue files in a temporary directory and return a list of (team, issue) tuples. """ repo_names = plug.generate_repo_names(STUDENT_TEAM_NAMES, ASSIGNMENT_NAMES) existing_issues = [] for repo_name in repo_names: issue_file = tmp_path / "{}.md".format(repo_name) issue = random.choice(ISSUES) _write_issue(issue, issue_file) existing_issues.append((repo_name, issue)) return existing_issues
def get_repo_urls( self, assignment_names: Iterable[str], org_name: Optional[str] = None, team_names: Optional[List[str]] = None, insert_auth: bool = False, ) -> List[str]: repo_names = (assignment_names if not team_names else plug.generate_repo_names(team_names, assignment_names)) return [ f"{constants.HOST_URL}/{org_name or self.org_name}/{repo_name}" for repo_name in repo_names ]
def _assert_on_projects(student_teams, assignment_names, assertion): """Execute the specified assertion operation on a project. Assertion should be a callable taking precisely on project as an argument. """ gl = gitlab.Gitlab(BASE_URL, private_token=TOKEN, ssl_verify=False) repo_names = plug.generate_repo_names(student_teams, assignment_names) target_group = get_group(ORG_NAME, gl=gl) student_groups = gl.groups.list(id=target_group.id) projects = [ gl.projects.get(p.id) for g in student_groups for p in g.projects.list(all=True) if p.name in repo_names ] for proj in projects: assertion(proj)
def get_repo_urls( self, assignment_names: Iterable[str], org_name: Optional[str] = None, team_names: Optional[List[str]] = None, insert_auth: bool = False, ) -> List[str]: """See :py:meth:`repobee_plug.PlatformAPI.get_repo_urls`.""" org_html_url = self._org_base_url(org_name or self._org_name) repo_names = (assignment_names if not team_names else plug.generate_repo_names(team_names, assignment_names)) return [ self.insert_auth(url) if insert_auth else url for url in (f"{org_html_url}/{repo_name}.git" for repo_name in list(repo_names)) ]
def test_javac_plugin_happy_path(self, platform_url, tmp_path): java_task = self._setup_task_with_java_code(platform_url, tmp_path) repo_names = plug.generate_repo_names(STUDENT_TEAMS, [java_task.name]) plug_results = funcs.run_repobee( f"repos clone -a {java_task.name} --base-url {platform_url} ", plugins=[_repobee.ext.javac], workdir=tmp_path, ) assert plug_results assert len(plug_results) == len(repo_names) for repo_name in repo_names: javac_result, *_ = plug_results[repo_name] assert javac_result.name == "javac" assert javac_result.status == plug.Status.SUCCESS
def test_with_students(self, repos, api): """Test that supplying students causes student repo names to be generated as the Cartesian product of the supplied repo names and the students. """ students = list(constants.STUDENTS) assignment_names = [repo.name for repo in repos] expected_repo_names = plug.generate_repo_names(students, assignment_names) # assume works correctly when called with just repo names expected_urls = api.get_repo_urls(expected_repo_names) actual_urls = api.get_repo_urls(assignment_names, team_names=[t.name for t in students]) assert len(actual_urls) == len(students) * len(assignment_names) assert sorted(expected_urls) == sorted(actual_urls)
def test_lists_matching_issues( self, open_issues, extra_args, discover_repos ): # arrange assert len(open_issues) == 2, "expected there to be only 2 open issues" matched = open_issues[0] unmatched = open_issues[1] repo_names = plug.generate_repo_names(STUDENT_TEAMS, assignment_names) issue_pattern_template = r"^.*{}/#\d:\s+{}.*by {}.?$" expected_issue_output_patterns = [ issue_pattern_template.format(repo_name, matched.title, TEACHER) for repo_name in repo_names ] unexpected_issue_output_patterns = [ issue_pattern_template.format(repo_name, unmatched.title, TEACHER) for repo_name in repo_names ] + [ r"\[ERROR\]" ] # any kind of error is bad repo_arg = ["--discover-repos"] if discover_repos else MASTER_REPOS_ARG command = " ".join( [ REPOBEE_GITLAB, *repobee_plug.cli.CoreCommand.issues.list.as_name_tuple(), *BASE_ARGS, *repo_arg, *STUDENTS_ARG, "-r", matched.title, ] ) # act result = run_in_docker_with_coverage(command, extra_args=extra_args) output = result.stdout.decode("utf-8") # assert assert result.returncode == 0 search_flags = re.MULTILINE for expected_pattern in expected_issue_output_patterns: assert re.search(expected_pattern, output, search_flags) for unexpected_pattern in unexpected_issue_output_patterns: assert not re.search(unexpected_pattern, output, search_flags)
def test_pylint_plugin_happy_path(self, platform_url, tmp_path): python_task = self._setup_task_with_python_code(platform_url, tmp_path) repo_names = plug.generate_repo_names(STUDENT_TEAMS, [python_task.name]) plug_results = funcs.run_repobee( f"repos clone -a {python_task.name} --base-url {platform_url}", plugins=[_repobee.ext.pylint], workdir=tmp_path, ) assert plug_results assert len(plug_results) == len(repo_names) for repo_name in repo_names: pylint_result, *_ = plug_results[repo_name] assert pylint_result.name == "pylint" assert pylint_result.status == plug.Status.SUCCESS assert "src/main.py -- OK" in pylint_result.msg
def test_list_double_blind_issues(self, platform_url, with_student_repos, capsys): key = "1234" assignment = const.TEMPLATE_REPO_NAMES[0] review_title = "This is the peer review" _setup_double_blind_reviews_with_review_issues(assignment, key, platform_url, review_title) funcs.run_repobee(f"issues list --assignments {assignment} " f"--double-blind-key {key} " f"--base-url {platform_url}") stdout = capsys.readouterr().out expected_repo_names = plug.generate_repo_names(const.STUDENT_TEAMS, [assignment]) for repo_name in expected_repo_names: assert re.search(fr"{repo_name}.*{review_title}", stdout)
def test_open_issues_from_double_blind_hook_results( self, with_student_repos, platform_url, tmp_path ): """Test opening issues from a hook results file gathered from listing issues from double-blind peer review. """ # arrange results_file = tmp_path / "hf.json" key = "1234" assignment = const.TEMPLATE_REPO_NAMES[0] review_title = "This is the peer review" _setup_double_blind_reviews_with_review_issues( assignment, key, platform_url, review_title ) funcs.run_repobee( f"issues list --assignments {assignment} " f"--double-blind-key {key} " f"--base-url {platform_url} " f"--hook-results-file {results_file}" ) # act funcs.run_repobee( f"issues open --assignments {assignment} " f"--base-url {platform_url} " f"--hook-results-file {results_file}" ) # assert expected_repo_names = set( plug.generate_repo_names( [t.name for t in const.STUDENT_TEAMS], [assignment] ) ) repos = [ repo for repo in funcs.get_repos(platform_url) if repo.name in expected_repo_names ] assert repos for repo in repos: assert len(repo.issues) == 2 assert review_title in [i.title for i in repo.issues]
def assert_repos_contain(student_teams, assignment_names, filename, text, org=ORG_NAME): """Assert that each of the student repos contain the given file.""" repo_names = plug.generate_repo_names(student_teams, assignment_names) gl = gitlab.Gitlab(BASE_URL, private_token=TOKEN, ssl_verify=False) target_group = get_group(org, gl=gl) student_groups = gl.groups.list(id=target_group.id) projects = [ gl.projects.get(p.id) for g in student_groups for p in g.projects.list(all=True) if p.name in repo_names ] assert len(projects) == len(repo_names) for project in projects: assert (project.files.get(filename, "master").decode().decode("utf8") == text)
def _assert_repos_match_templates( student_teams: List[plug.StudentTeam], template_repo_names: List[str], template_repo_hashes: Mapping[str, str], repos_dict: Mapping[str, pathlib.Path], ): num_asserts = 0 for template_name in template_repo_names: student_repos = [ repos_dict[repo_name] for repo_name in plug.generate_repo_names( student_teams, [template_name]) ] assert len(student_repos) == len(student_teams) for repo in student_repos: num_asserts += 1 assert funcs.tree_hash(repo) == template_repo_hashes[template_name] assert num_asserts == len(student_teams) * len( template_repo_names), "Performed fewer asserts than expected"
def test_clone_all_repos_flat(self, platform_url, with_student_repos, tmp_path): """Test that cloning with flat directory layout results in all repositories ending up in the current working directory. """ expected_dirnames = plug.generate_repo_names(STUDENT_TEAMS, TEMPLATE_REPO_NAMES) funcs.run_repobee( f"repos clone -a {TEMPLATE_REPOS_ARG} " f"--base-url {platform_url} " "--directory-layout " f"{fileutil.DirectoryLayout.FLAT}", workdir=tmp_path, ) actual_dirnames = [ path.name for path in tmp_path.iterdir() if path.is_dir() ] assert sorted(actual_dirnames) == sorted(expected_dirnames)
def test_lists_matching_issues(self, open_issues, tmpdir, discover_repos): # arrange assert len(open_issues) == 2, "expected there to be only 2 open issues" matched = open_issues[0] unmatched = open_issues[1] repo_names = plug.generate_repo_names(STUDENT_TEAMS, assignment_names) issue_pattern_template = r"^.*{}/#\d:\s+{}.*by {}.?$" expected_issue_output_patterns = [ issue_pattern_template.format(repo_name, matched.title, TEACHER) for repo_name in repo_names ] unexpected_issue_output_patterns = [ issue_pattern_template.format(repo_name, unmatched.title, TEACHER) for repo_name in repo_names ] + [r"\[ERROR\]"] # any kind of error is bad repo_arg = ["--discover-repos"] if discover_repos else MASTER_REPOS_ARG command = " ".join([ *repobee_plug.cli.CoreCommand.issues.list.as_name_tuple(), *BASE_ARGS, *repo_arg, *STUDENTS_ARG, "-r", matched.title, ]) # act with contextlib.redirect_stdout(io.StringIO()) as sio: run_repobee(command, workdir=tmpdir, plugins=[_repobee.ext.gitlab]) output = sio.getvalue() # assert search_flags = re.MULTILINE for expected_pattern in expected_issue_output_patterns: assert re.search(expected_pattern, output, search_flags) for unexpected_pattern in unexpected_issue_output_patterns: assert not re.search(unexpected_pattern, output, search_flags)
def test_skips_unexpected_issues_in_multi_issues_file( self, with_multi_issues_file, parsed_args_multi_issues_file, api_mock): """Test that an exception is raised if one or more issues are found relating to student repos that ar not in prod(assignments, students). """ student_teams = parsed_args_multi_issues_file.students args_dict = vars(parsed_args_multi_issues_file) args_dict["students"] = student_teams[:-1] args = argparse.Namespace(**args_dict) unexpected_repos = plug.generate_repo_names(student_teams[-1:], ASSIGNMENT_NAMES) _, repos_and_issues = with_multi_issues_file expected_calls = [ mock.call(issue.title, issue.body, mock.ANY) for repo_name, issue in repos_and_issues if repo_name not in unexpected_repos ] feedback.callback(args=args, api=api_mock) api_mock.create_issue.assert_has_calls(expected_calls, any_order=True)
def test_use_generated_reference_tests_directory( self, tmp_path_factory, platform_url, setup_student_repos, workdir, rtd_path, ): """Test using a generated RTD with the clone command.""" # arrange run_generate_rtd(base_url=platform_url, rtd=rtd_path, workdir=workdir) clone_dir = workdir / "clone_dir" clone_dir.mkdir() # act results = repobee_testhelpers.funcs.run_repobee( f"repos clone -a {ASSIGNMENTS_ARG} " f"--base-url {platform_url} " f"--junit4-reference-tests-dir {rtd_path} " f"--junit4-hamcrest-path {HAMCREST_PATH} " f"--junit4-junit-path {JUNIT_PATH} ", plugins=[junit4], workdir=clone_dir, ) # assert iterations = 0 for repo_name in plug.generate_repo_names( repobee_testhelpers.const.STUDENT_TEAMS, ASSIGNMENT_NAMES): iterations += 1 first_result, *rest = results[repo_name] assert not rest, "there should only be one result" assert first_result.name == SECTION assert first_result.status != plug.Status.ERROR assert iterations > 0, "the assertion loop did not execute"
def check_peer_review_progress( assignment_names: Iterable[str], teams: Iterable[plug.Team], title_regex: str, num_reviews: int, double_blind_key: Optional[str], api: plug.PlatformAPI, ) -> None: """Check which teams have opened peer review issues in their allotted review repos Args: assignment_names: Names of assignments. teams: An iterable of student teams. title_regex: A regex to match against issue titles. num_reviews: Amount of reviews each student is expected to have made. api: An implementation of :py:class:`repobee_plug.PlatformAPI` used to interface with the platform (e.g. GitHub or GitLab) instance. """ teams = list(teams) reviews = collections.defaultdict(list) review_team_names = [ _review_team_name(student_team, assignment_name, double_blind_key) for student_team in teams for assignment_name in assignment_names ] rainbow_table = { _hash_if_key(repo_name, key=double_blind_key): repo_name for repo_name in plug.generate_repo_names(teams, assignment_names) } review_teams = progresswrappers.get_teams(review_team_names, api, desc="Processing review teams") for review_team in review_teams: repos = list(api.get_team_repos(review_team)) if len(repos) != 1: plug.log.warning( f"Expected {review_team.name} to have 1 associated " f"repo, found {len(repos)}. " f"Skipping...") continue reviewed_repo = repos[0] expected_reviewers = set(review_team.members) reviewing_teams = _extract_reviewing_teams(teams, expected_reviewers) review_issue_authors = { issue.author for issue in api.get_repo_issues(reviewed_repo) if re.match(title_regex, issue.title) } for team in reviewing_teams: reviews[str(team)].append( plug.Review( repo=rainbow_table[reviewed_repo.name], done=any( map(review_issue_authors.__contains__, team.members)), )) plug.echo( formatters.format_peer_review_progress_output( reviews, [team.name for team in teams], num_reviews))
def assign_peer_reviews( assignment_names: Iterable[str], teams: Iterable[plug.StudentTeam], num_reviews: int, issue: Optional[plug.Issue], double_blind_key: Optional[str], api: plug.PlatformAPI, ) -> None: """Assign peer reviewers among the students to each student repo. Each student is assigned to review num_reviews repos, and consequently, each repo gets reviewed by num_reviews reviewers. In practice, each student repo has a review team generated (called <student-repo-name>-review), to which num_reviews _other_ students are assigned. The team itself is given pull-access to the student repo, so that reviewers can view code and open issues, but cannot modify the contents of the repo. Args: assignment_names: Names of assginments. teams: Team objects specifying student groups. num_reviews: Amount of reviews each student should perform (consequently, the amount of reviews of each repo) issue: An issue with review instructions to be opened in the considered repos. double_blind_key: If provided, use key to make double-blind review allocation. api: An implementation of :py:class:`repobee_plug.PlatformAPI` used to interface with the platform (e.g. GitHub or GitLab) instance. """ issue = issue or DEFAULT_REVIEW_ISSUE expected_repo_names = set(plug.generate_repo_names(teams, assignment_names)) fetched_teams = progresswrappers.get_teams(teams, api, desc="Fetching teams and repos") team_repo_tuples = [(team, list(api.get_team_repos(team))) for team in fetched_teams] fetched_repos = list( itertools.chain.from_iterable(repos for _, repos in team_repo_tuples)) fetched_repo_dict = {r.name: r for r in fetched_repos} missing = expected_repo_names - fetched_repo_dict.keys() if missing: raise plug.NotFoundError(f"Can't find repos: {', '.join(missing)}") if double_blind_key: plug.log.info(f"Creating anonymous repos with key: {double_blind_key}") fetched_repo_dict = _create_anonymized_repos( [(team, _only_expected_repos(repos, expected_repo_names)) for team, repos in team_repo_tuples], double_blind_key, api, ) allocations_for_output = [] for assignment_name in assignment_names: plug.echo("Allocating reviews") allocations = plug.manager.hook.generate_review_allocations( teams=teams, num_reviews=num_reviews) # adjust names of review teams review_team_specs, reviewed_team_names = list( zip(*[( plug.StudentTeam( members=alloc.review_team.members, name=_review_team_name( alloc.reviewed_team, assignment_name, key=double_blind_key, ), ), alloc.reviewed_team, ) for alloc in allocations])) review_teams = _repobee.command.teams.create_teams( review_team_specs, plug.TeamPermission.PULL, api) review_teams_progress = plug.cli.io.progress_bar( review_teams, desc="Creating review teams", total=len(review_team_specs), ) for review_team, reviewed_team_name in zip(review_teams_progress, reviewed_team_names): reviewed_repo = fetched_repo_dict[plug.generate_repo_name( reviewed_team_name, assignment_name)] review_teams_progress.write( # type: ignore f"Assigning {' and '.join(review_team.members)} " f"to review {reviewed_repo.name}") api.assign_repo(review_team, reviewed_repo, plug.TeamPermission.PULL) api.create_issue( issue.title, issue.body, reviewed_repo, # It's not possible to assign users with read-access in Gitea # FIXME redesign so Gitea does not require special handling assignees=review_team.members if not isinstance(api, _repobee.ext.gitea.GiteaAPI) else None, ) allocations_for_output.append({ "reviewed_repo": { "name": reviewed_repo.name, "url": reviewed_repo.url, }, "review_team": { "name": review_team.name, "members": review_team.members, }, }) if featflags.is_feature_enabled( featflags.FeatureFlag.REPOBEE_4_REVIEW_COMMANDS): output = dict(allocations=allocations_for_output, num_reviews=num_reviews) pathlib.Path("review_allocations.json").write_text( json.dumps(output, indent=4), encoding=sys.getdefaultencoding(), )
import pathlib import repobee_plug as plug from repobee_testhelpers._internal.templates import TEMPLATE_REPOS_DIR DIR = pathlib.Path(__file__).resolve().parent TOKEN = (DIR.parent / "token").read_text(encoding="utf-8").strip() ADMIN_TOKEN = "".join(reversed(TOKEN)) OAUTH_USER = "******" BASE_DOMAIN = "localhost:3000" BASE_URL = "https://" + BASE_DOMAIN ORG_NAME = "dd1337-fall2020" TEMPLATE_ORG_NAME = "dd1337-master" TEACHER = "ric" assignment_names = [p.name for p in TEMPLATE_REPOS_DIR.iterdir() if p.is_dir()] TEMPLATE_REPO_PATHS = list(dir_.absolute() for dir_ in TEMPLATE_REPOS_DIR.iterdir() if dir_.is_dir()) STUDENT_TEAMS = [ plug.StudentTeam(members=[s.strip()]) for s in (DIR.parent / "students.txt").read_text().strip().split("\n") ] STUDENT_TEAM_NAMES = [str(t) for t in STUDENT_TEAMS] STUDENT_REPO_NAMES = plug.generate_repo_names(STUDENT_TEAMS, assignment_names) BASE_ARGS_NO_TB = ["--bu", BASE_URL, "-o", ORG_NAME, "-t", TOKEN] BASE_ARGS = [*BASE_ARGS_NO_TB, "--tb"] STUDENTS_ARG = ["-s", " ".join(STUDENT_TEAM_NAMES)] MASTER_REPOS_ARG = ["-a", " ".join(assignment_names)] TEMPLATE_ORG_ARG = ["--template-org-name", TEMPLATE_ORG_NAME]