codecov[bot]: # [Codecov](https://codecov.io/gh/repobee/repobee/pull/526?src=pr&el=h1) Report
> Merging [#526](https://codecov.io/gh/repobee/repobee/pull/526?src=pr&el=desc) into [master](https://codecov.io/gh/repobee/repobee/commit/9b9bed1bcb1e0da6ba5bb45fb996669d4cd14a32&el=desc) will **decrease** coverage by `3.64%`.
> The diff coverage is `89.65%`.

[![Impacted file tree graph](https://codecov.io/gh/repobee/repobee/pull/526/graphs/tree.svg?width=650&height=150&src=pr&token=5vOKgkphZe)](https://codecov.io/gh/repobee/repobee/pull/526?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #526      +/-   ##
==========================================
- Coverage   92.21%   88.56%   -3.65%     
==========================================
  Files          47       47              
  Lines        2504     2510       +6     
  Branches      407      409       +2     
==========================================
- Hits         2309     2223      -86     
- Misses        152      239      +87     
- Partials       43       48       +5     
```


| [Impacted Files](https://codecov.io/gh/repobee/repobee/pull/526?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/\_repobee/ext/javac.py](https://codecov.io/gh/repobee/repobee/pull/526/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2V4dC9qYXZhYy5weQ==) | `42.85% <0.00%> (-3.81%)` | :arrow_down: |
| [src/\_repobee/ext/defaults/configwizard.py](https://codecov.io/gh/repobee/repobee/pull/526/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2V4dC9kZWZhdWx0cy9jb25maWd3aXphcmQucHk=) | `97.29% <50.00%> (ø)` | |
| [src/repobee\_plug/\_pluginmeta.py](https://codecov.io/gh/repobee/repobee/pull/526/diff?src=pr&el=tree#diff-c3JjL3JlcG9iZWVfcGx1Zy9fcGx1Z2lubWV0YS5weQ==) | `94.59% <95.45%> (+0.08%)` | :arrow_up: |
| [src/\_repobee/cli/dispatch.py](https://codecov.io/gh/repobee/repobee/pull/526/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2NsaS9kaXNwYXRjaC5weQ==) | `88.88% <100.00%> (ø)` | |
| [src/repobee\_plug/cli/\_\_init\_\_.py](https://codecov.io/gh/repobee/repobee/pull/526/diff?src=pr&el=tree#diff-c3JjL3JlcG9iZWVfcGx1Zy9jbGkvX19pbml0X18ucHk=) | `100.00% <100.00%> (ø)` | |
| [src/repobee\_plug/cli/args.py](https://codecov.io/gh/repobee/repobee/pull/526/diff?src=pr&el=tree#diff-c3JjL3JlcG9iZWVfcGx1Zy9jbGkvYXJncy5weQ==) | `100.00% <100.00%> (ø)` | |
| [src/\_repobee/ext/gitlab.py](https://codecov.io/gh/repobee/repobee/pull/526/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2V4dC9naXRsYWIucHk=) | `63.86% <0.00%> (-28.56%)` | :arrow_down: |
| [src/\_repobee/cli/parsing.py](https://codecov.io/gh/repobee/repobee/pull/526/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2NsaS9wYXJzaW5nLnB5) | `87.58% <0.00%> (-5.23%)` | :arrow_down: |
| [src/\_repobee/git.py](https://codecov.io/gh/repobee/repobee/pull/526/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2dpdC5weQ==) | `96.62% <0.00%> (-3.38%)` | :arrow_down: |
| [src/\_repobee/command/repos.py](https://codecov.io/gh/repobee/repobee/pull/526/diff?src=pr&el=tree#diff-c3JjL19yZXBvYmVlL2NvbW1hbmQvcmVwb3MucHk=) | `91.80% <0.00%> (-3.28%)` | :arrow_down: |
| ... and [8 more](https://codecov.io/gh/repobee/repobee/pull/526/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/repobee/repobee/pull/526?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/repobee/repobee/pull/526?src=pr&el=footer). Last update [9b9bed1...30518d2](https://codecov.io/gh/repobee/repobee/pull/526?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

diff --git a/src/_repobee/cli/dispatch.py b/src/_repobee/cli/dispatch.py
index 330594e..59374dc 100644
--- a/src/_repobee/cli/dispatch.py
+++ b/src/_repobee/cli/dispatch.py
@@ -47,7 +47,7 @@ def dispatch_command(
     is_ext_command = "_extension_command" in args
     if is_ext_command:
         ext_cmd = args._extension_command
-        res = ext_cmd.callback(args, api)
+        res = ext_cmd.callback(api)
         hook_results = {ext_cmd.name: [res]} if res else hook_results
     else:
         category = args.category
diff --git a/src/_repobee/ext/defaults/configwizard.py b/src/_repobee/ext/defaults/configwizard.py
index 21a86c8..eaab7d8 100644
--- a/src/_repobee/ext/defaults/configwizard.py
+++ b/src/_repobee/ext/defaults/configwizard.py
@@ -30,8 +30,8 @@ class Wizard(plug.Plugin, plug.cli.Command):
         ),
     )
 
-    def command(self, args: argparse.Namespace, api: plug.API) -> None:
-        return callback(args, api)
+    def command(self, api: plug.API) -> None:
+        return callback(self.args, api)
 
 
 def callback(args: argparse.Namespace, api: plug.API) -> None:
diff --git a/src/_repobee/ext/javac.py b/src/_repobee/ext/javac.py
index 9842030..7d2b687 100644
--- a/src/_repobee/ext/javac.py
+++ b/src/_repobee/ext/javac.py
@@ -20,7 +20,6 @@ and specifically the more advanced use of the ``clone_parser_hook`` and
 """
 import subprocess
 import sys
-import argparse
 import pathlib
 from typing import Union, Iterable, Tuple
 
@@ -46,9 +45,6 @@ class JavacCloneHook(plug.Plugin, plug.cli.CommandExtension):
         argparse_kwargs={"nargs": "+"},
     )
 
-    def handle_processed_args(self, args: argparse.Namespace):
-        self.ignore = args.javac_ignore or []
-
     def post_clone(self, path: pathlib.Path, api: plug.API) -> plug.Result:
         """Run ``javac`` on all .java files in the repo.
 
@@ -58,10 +54,11 @@ class JavacCloneHook(plug.Plugin, plug.cli.CommandExtension):
         Returns:
             a Result specifying the outcome.
         """
+        ignore = self.javac_ignore or []
         java_files = [
             str(file)
             for file in util.find_files_by_extension(path, ".java")
-            if file.name not in self.ignore
+            if file.name not in ignore
         ]
 
         if not java_files:
diff --git a/src/repobee_plug/_pluginmeta.py b/src/repobee_plug/_pluginmeta.py
index 198aba3..8316a60 100644
--- a/src/repobee_plug/_pluginmeta.py
+++ b/src/repobee_plug/_pluginmeta.py
@@ -1,4 +1,5 @@
 import argparse
+import itertools
 import daiquiri
 import functools
 
@@ -55,6 +56,8 @@ class _PluginMeta(type):
         if cli.Command in bases:
             ext_cmd_func = _generate_command_func(attrdict)
             attrdict[ext_cmd_func.__name__] = ext_cmd_func
+            handle_processed_args = _generate_handle_processed_args_func()
+            attrdict[handle_processed_args.__name__] = handle_processed_args
         elif cli.CommandExtension in bases:
             if "__settings__" not in attrdict:
                 raise _exceptions.PlugError(
@@ -62,6 +65,8 @@ class _PluginMeta(type):
                 )
             ext_cmd_func = _generate_command_extension_func()
             attrdict[ext_cmd_func.__name__] = ext_cmd_func
+            handle_processed_args = _generate_handle_processed_args_func()
+            attrdict[handle_processed_args.__name__] = handle_processed_args
 
         methods = cls._extract_public_methods(attrdict)
         cls._check_names(methods)
@@ -175,6 +180,41 @@ def _generate_command_func(attrdict: Mapping[str, Any]) -> Callable:
     return create_extension_command
 
 
+def _generate_handle_processed_args_func():
+    def handle_processed_args(self, args):
+        self.args = args
+        cli_args = [
+            (k, v)
+            for k, v in self.__class__.__dict__.items()
+            if cli.is_cli_arg(v)
+        ]
+        flattened_args = itertools.chain.from_iterable(
+            map(_flatten_arg, cli_args)
+        )
+
+        for name, arg in flattened_args:
+            dest = (
+                name
+                if "dest" not in arg.argparse_kwargs
+                else arg.argparse_kwargs["dest"]
+            )
+            if dest in args:
+                parsed_arg = getattr(args, dest)
+                setattr(self, dest, parsed_arg)
+
+    return handle_processed_args
+
+
+def _flatten_arg(arg_tup):
+    name, arg = arg_tup
+    assert cli.is_cli_arg(arg)
+
+    if isinstance(arg, MutuallyExclusiveGroup):
+        return itertools.chain.from_iterable(map(_flatten_arg, arg.options))
+    else:
+        return [arg_tup]
+
+
 def _add_option(
     name: str,
     opt: Union[Option, MutuallyExclusiveGroup],
diff --git a/src/repobee_plug/cli/__init__.py b/src/repobee_plug/cli/__init__.py
index ff8484b..9ed56eb 100644
--- a/src/repobee_plug/cli/__init__.py
+++ b/src/repobee_plug/cli/__init__.py
@@ -1,6 +1,12 @@
 from .settings import command_settings, command_extension_settings
 from .categorization import category
-from .args import option, positional, mutually_exclusive_group, ArgumentType
+from .args import (
+    option,
+    positional,
+    mutually_exclusive_group,
+    ArgumentType,
+    is_cli_arg,
+)
 from .commandmarkers import Command, CommandExtension
 
 from ._corecommand import _CoreCommand
@@ -18,4 +24,5 @@ __all__ = [
     "Command",
     "CommandExtension",
     "CoreCommand",
+    "is_cli_arg",
 ]
diff --git a/src/repobee_plug/cli/args.py b/src/repobee_plug/cli/args.py
index 40030f6..e44431a 100644
--- a/src/repobee_plug/cli/args.py
+++ b/src/repobee_plug/cli/args.py
@@ -31,6 +31,17 @@ MutuallyExclusiveGroup = collections.namedtuple(
 )
 
 
+def is_cli_arg(obj: Any) -> bool:
+    """Determine if an object is a CLI argument.
+
+    Args:
+        obj: An object.
+    Returns:
+        True if the object is an instance of a CLI argument class.
+    """
+    return isinstance(obj, (Option, MutuallyExclusiveGroup))
+
+
 def option(
     short_name: Optional[str] = None,
     long_name: Optional[str] = None,
@@ -56,10 +67,10 @@ def option(
             name = plug.cli.option(help="Your name.")
             age = plug.cli.option(converter=int, help="Your age.")
 
-            def command(self, args, api):
+            def command(self, api):
                 print(
-                    f"Hello, my name is {args.name} "
-                    f"and I am {args.age} years old"
+                    f"Hello, my name is {self.name} "
+                    f"and I am {self.age} years old"
                 )
 
     This command can then be called like so:
@@ -122,10 +133,10 @@ def positional(
             name = plug.cli.Positional(help="Your name.")
             age = plug.cli.Positional(converter=int, help="Your age.")
 
-            def command(self, args, api):
+            def command(self, api):
                 print(
-                    f"Hello, my name is {args.name} "
-                    f"and I am {args.age} years old"
+                    f"Hello, my name is {self.name} "
+                    f"and I am {self.age} years old"
                 )
 
     This command can then be called like so:

Automatically assign processed CLI arguments to plugin command/command extension instance
As it's inconvenient to to pass the parsed CLI arguments to _all_ hooks, some plugins will simply have to access the arguments through the `handle_processed_args` or `handle_parsed_args` hooks. This could be implemented automatically for commands and command extensions.

This also means that the `args` argument can be dropped from the `command` function.
diff --git a/tests/unit_tests/repobee/test_cli.py b/tests/unit_tests/repobee/test_cli.py
index 2b696f6..0ba9ace 100644
--- a/tests/unit_tests/repobee/test_cli.py
+++ b/tests/unit_tests/repobee/test_cli.py
@@ -852,7 +852,9 @@ class TestExtensionCommands:
             parsed_args, None, EMPTY_PATH, [ext_command]
         )
 
-        mock_callback.assert_called_once_with(parsed_args, None)
+        calls = mock_callback.mock_calls
+        assert len(calls) == 1
+        assert calls[0] == mock.call(None)
 
     def test_dispatch_ext_command_that_requires_api(
         self,
@@ -883,7 +885,9 @@ class TestExtensionCommands:
             parsed_args, api_instance_mock, EMPTY_PATH, [ext_command]
         )
 
-        mock_callback.assert_called_once_with(parsed_args, api_instance_mock)
+        calls = mock_callback.mock_calls
+        assert len(calls) == 1
+        assert calls[0] == mock.call(api_instance_mock)
 
     def test_parse_ext_command_that_requires_base_parsers(self):
         """The query command requires the students and repo names parsers."""
diff --git a/tests/unit_tests/repobee/test_main.py b/tests/unit_tests/repobee/test_main.py
index 367b3b4..14e30de 100644
--- a/tests/unit_tests/repobee/test_main.py
+++ b/tests/unit_tests/repobee/test_main.py
@@ -446,7 +446,7 @@ class TestRun:
     """Tests for the run function."""
 
     class Workdir(plug.Plugin, plug.cli.Command):
-        def command(self, args, api):
+        def command(self, api):
             return plug.Result(
                 name="workdir",
                 msg="workdir",
diff --git a/tests/unit_tests/repobee_plug/test_pluginmeta.py b/tests/unit_tests/repobee_plug/test_pluginmeta.py
index eb5daa2..54c8cbc 100644
--- a/tests/unit_tests/repobee_plug/test_pluginmeta.py
+++ b/tests/unit_tests/repobee_plug/test_pluginmeta.py
@@ -124,8 +124,8 @@ class TestDeclarativeExtensionCommand:
             name = plug.cli.option(help="your name", required=True)
             age = plug.cli.option(converter=int, help="your age", default=30)
 
-            def command(self, args, api):
-                print(f"My name is {args.name} and I am {args.age} years old")
+            def command(self, api):
+                print(f"My name is {self.name} and I am {self.age} years old")
 
         return Greeting
 
@@ -167,7 +167,7 @@ class TestDeclarativeExtensionCommand:
                 requires_api=expected_requires_api,
             )
 
-            def command(self, args, api):
+            def command(self, api):
                 pass
 
         plugin_instance = ExtCommand("greeting")
@@ -199,7 +199,7 @@ class TestDeclarativeExtensionCommand:
                 help="Your name.", required=True, configurable=True
             )
 
-            def command(self, args, api):
+            def command(self, api):
                 pass
 
         plugin_name = "greeting"
@@ -221,7 +221,7 @@ class TestDeclarativeExtensionCommand:
         class Greeting(plug.Plugin, plug.cli.Command):
             name = plug.cli.option(help="Your name.", required=True)
 
-            def command(self, args, api):
+            def command(self, api):
                 pass
 
         plugin_name = "greeting"
@@ -254,7 +254,7 @@ class TestDeclarativeExtensionCommand:
                 required=True,
             )
 
-            def command(self, args, api):
+            def command(self, api):
                 pass
 
         ext_cmd = Greeting("g").create_extension_command()
@@ -273,7 +273,7 @@ class TestDeclarativeExtensionCommand:
             name = plug.cli.positional()
             age = plug.cli.positional(converter=int)
 
-            def command(self, args, api):
+            def command(self, api):
                 pass
 
         ext_cmd = Greeting("g").create_extension_command()
@@ -301,7 +301,7 @@ class TestDeclarativeExtensionCommand:
                 __required__=True,
             )
 
-            def command(self, args, api):
+            def command(self, api):
                 pass
 
         ext_cmd = Greeting("g").create_extension_command()
@@ -343,7 +343,7 @@ class TestDeclarativeExtensionCommand:
                 __required__=True,
             )
 
-            def command(self, args, api):
+            def command(self, api):
                 pass
 
         ext_cmd = Greeting("g").create_extension_command()
@@ -365,12 +365,12 @@ class TestDeclarativeExtensionCommand:
             name = plug.cli.positional()
             age = plug.cli.positional(converter=int)
 
-            def command(self, args, api):
+            def command(self, api):
                 return plug.Result(
                     name=self.plugin_name,
                     msg="Nice!",
                     status=plug.Status.SUCCESS,
-                    data={"name": args.name, "age": args.age},
+                    data={"name": self.name, "age": self.age},
                 )
 
         name = "Bob"
@@ -399,10 +399,10 @@ class TestDeclarativeExtensionCommand:
             __settings__ = plug.cli.command_settings(action=category.hello)
             name = plug.cli.positional()
 
-            def command(self, args, api):
+            def command(self, api):
                 return plug.Result(
                     name=self.plugin_name,
-                    msg=f"Hello {args.name}",
+                    msg=f"Hello {self.name}",
                     status=plug.Status.SUCCESS,
                 )
 
@@ -410,10 +410,10 @@ class TestDeclarativeExtensionCommand:
             __settings__ = plug.cli.command_settings(action=category.bye)
             name = plug.cli.positional()
 
-            def command(self, args, api):
+            def command(self, api):
                 return plug.Result(
                     name=self.plugin_name,
-                    msg=f"Bye {args.name}",
+                    msg=f"Bye {self.name}",
                     status=plug.Status.SUCCESS,
                 )
 
@@ -442,7 +442,7 @@ class TestDeclarativeExtensionCommand:
                     action=category.greetings, category=category
                 )
 
-                def command(self, args, api):
+                def command(self, api):
                     pass
 
         assert (
@@ -450,6 +450,42 @@ class TestDeclarativeExtensionCommand:
             "'action' is an Action object"
         ) in str(exc_info.value)
 
+    def test_parsed_args_are_added_to_self(self):
+        """Parsed cli arguments should automatically be added to the plugin
+        object instance.
+        """
+
+        class Ext(plug.Plugin, plug.cli.Command):
+            name = plug.cli.option()
+            age = plug.cli.positional(converter=int)
+            tolerance = plug.cli.mutually_exclusive_group(
+                high=plug.cli.option(
+                    argparse_kwargs=dict(action="store_true")
+                ),
+                low=plug.cli.option(argparse_kwargs=dict(action="store_true")),
+                __required__=True,
+            )
+
+            def command(self, api):
+                return plug.Result(
+                    name="ext",
+                    msg="",
+                    status=plug.Status.SUCCESS,
+                    data={"instance": self},
+                )
+
+        name = "Eve"
+        age = 22
+        instance = repobee.run(
+            f"ext {age} --name {name} --high".split(), plugins=[Ext]
+        )["ext"][0].data["instance"]
+
+        assert instance.name == name
+        assert instance.age == age
+        assert instance.high
+        assert not instance.low
+        assert isinstance(instance.args, argparse.Namespace)
+
 
 class TestDeclarativeCommandExtension:
     """Test creating command extensions to existing commands."""

pip install -e .[TEST]
parse_log_pytest
requirements.txt
pytest>=4.0.0 pytest-cov>=2.6.1 pytest-mock codecov bandit flake8 black pytest
apt-get update apt-get install -y gcc
3.7
requirements/app.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
appdirs==1.4.4
bandit==1.7.5
black==23.3.0
certifi @ file:///croot/certifi_1671487769961/work/certifi
cffi==1.15.1
charset-normalizer==3.4.1
click==8.1.8
codecov==2.1.13
colored==1.4.4
coverage==7.2.7
cryptography==44.0.2
daiquiri==3.2.1
Deprecated==1.2.18
exceptiongroup==1.2.2
flake8==5.0.4
gitdb==4.0.12
GitPython==3.1.44
idna==3.10
importlib-metadata==4.2.0
iniconfig==2.0.0
markdown-it-py==2.2.0
mccabe==0.7.0
mdurl==0.1.2
mypy-extensions==1.0.0
packaging==24.0
pathspec==0.11.2
pbr==6.1.1
platformdirs==4.0.0
pluggy==0.13.1
pycodestyle==2.9.1
pycparser==2.21
pyflakes==2.5.0
PyGithub==2.3.0
Pygments==2.17.2
PyJWT==2.8.0
PyNaCl==1.5.0
pytest==7.4.4
pytest-cov==4.1.0
pytest-mock==3.11.1
python-gitlab==1.15.0
python-json-logger==3.0.1
PyYAML==6.0.1
-e git+https://github.com/repobee/repobee.git@9b9bed1bcb1e0da6ba5bb45fb996669d4cd14a32#egg=repobee
requests==2.31.0
rich==13.8.1
six==1.17.0
smmap==5.0.2
stevedore==3.5.2
tomli==2.0.1
typed-ast==1.5.5
typing_extensions==4.7.1
urllib3==2.0.7
wrapt==1.16.0
zipp==3.15.0

name: repobee
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - appdirs==1.4.4
      - bandit==1.7.5
      - black==23.3.0
      - cffi==1.15.1
      - charset-normalizer==3.4.1
      - click==8.1.8
      - codecov==2.1.13
      - colored==1.4.4
      - coverage==7.2.7
      - cryptography==44.0.2
      - daiquiri==3.2.1
      - deprecated==1.2.18
      - exceptiongroup==1.2.2
      - flake8==5.0.4
      - gitdb==4.0.12
      - gitpython==3.1.44
      - idna==3.10
      - importlib-metadata==4.2.0
      - iniconfig==2.0.0
      - markdown-it-py==2.2.0
      - mccabe==0.7.0
      - mdurl==0.1.2
      - mypy-extensions==1.0.0
      - packaging==24.0
      - pathspec==0.11.2
      - pbr==6.1.1
      - platformdirs==4.0.0
      - pluggy==0.13.1
      - pycodestyle==2.9.1
      - pycparser==2.21
      - pyflakes==2.5.0
      - pygithub==2.3.0
      - pygments==2.17.2
      - pyjwt==2.8.0
      - pynacl==1.5.0
      - pytest==7.4.4
      - pytest-cov==4.1.0
      - pytest-mock==3.11.1
      - python-gitlab==1.15.0
      - python-json-logger==3.0.1
      - pyyaml==6.0.1
      - repobee==3.0.0a2
      - requests==2.31.0
      - rich==13.8.1
      - six==1.17.0
      - smmap==5.0.2
      - stevedore==3.5.2
      - tomli==2.0.1
      - typed-ast==1.5.5
      - typing-extensions==4.7.1
      - urllib3==2.0.7
      - wrapt==1.16.0
      - zipp==3.15.0
prefix: /opt/conda/envs/repobee

tests/unit_tests/repobee/test_cli.py::TestExtensionCommands::test_dispatch_ext_command_that_does_not_require_api tests/unit_tests/repobee/test_cli.py::TestExtensionCommands::test_dispatch_ext_command_that_requires_api tests/unit_tests/repobee/test_main.py::TestRun::test_operates_in_current_workdir_by_default tests/unit_tests/repobee/test_main.py::TestRun::test_operates_in_specified_workdir tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeExtensionCommand::test_create_new_category tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeExtensionCommand::test_add_two_actions_to_new_category tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeExtensionCommand::test_parsed_args_are_added_to_self
tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_writes_hook_results_correctly[action0-_repobee.command.clone_repos] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_writes_hook_results_correctly[action1-_repobee.command.list_issues] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_does_not_write_hook_results_if_there_are_none tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers0] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers1] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers2] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers3] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers4] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers5] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers6] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers7] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers8] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers9] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers10] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers11] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_no_crash_on_valid_args[parsed_args_all_subparsers12] tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_setup_student_repos_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_update_student_repos_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_create_teams_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_open_issue_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_close_issue_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_migrate_repos_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_clone_repos_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_verify_settings_called_with_correct_args tests/unit_tests/repobee/test_cli.py::TestDispatchCommand::test_verify_settings_called_with_master_org_name tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action0] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action1] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action2] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action3] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action4] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action5] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action6] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action7] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action8] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action9] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action10] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action11] tests/unit_tests/repobee/test_cli.py::test_help_calls_add_arguments[action12] tests/unit_tests/repobee/test_cli.py::test_create_parser_for_docs tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_show_all_opts_true_shows_configured_args tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_show_all_opts_false_hides_configured_args tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_raises_on_invalid_org tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_raises_on_bad_credentials tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_raises_on_invalid_base_url tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_master_org_overrides_target_org_for_master_repos[action0] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_master_org_overrides_target_org_for_master_repos[action1] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_master_org_name_defaults_to_org_name[action0] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_master_org_name_defaults_to_org_name[action1] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_token_env_variable_picked_up[action0] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_token_env_variable_picked_up[action1] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_token_cli_arg_picked_up[action0] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_token_cli_arg_picked_up[action1] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_raises_on_non_tls_api_url[http://some_enterprise_host/api/v3] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_raises_on_non_tls_api_url[ftp://some_enterprise_host/api/v3] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_raises_on_non_tls_api_url[some_enterprise_host/api/v3] tests/unit_tests/repobee/test_cli.py::TestBaseParsing::test_correctly_parses_create_teams tests/unit_tests/repobee/test_cli.py::TestExtensionCommands::test_parse_ext_command_that_does_not_require_api tests/unit_tests/repobee/test_cli.py::TestExtensionCommands::test_parse_ext_command_that_requires_api tests/unit_tests/repobee/test_cli.py::TestExtensionCommands::test_parse_ext_command_that_requires_base_parsers tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_raises_if_students_file_is_not_a_file[repos tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_raises_if_students_file_is_not_a_file[issues tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_parser_listing_students[repos tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_parser_listing_students[issues tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_parser_student_file[repos tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_parser_student_file[issues tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_student_parsers_raise_on_empty_student_file[repos tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_student_parsers_raise_on_empty_student_file[issues tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_parsers_raise_if_both_file_and_listing[repos tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_parsers_raise_if_both_file_and_listing[issues tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_student_groups_parsed_correcly[repos tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_student_groups_parsed_correcly[issues tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_raises_if_generated_team_name_too_long[repos tests/unit_tests/repobee/test_cli.py::TestStudentParsing::test_raises_if_generated_team_name_too_long[issues tests/unit_tests/repobee/test_cli.py::TestConfig::test_full_config[action0-extra_args0] tests/unit_tests/repobee/test_cli.py::TestConfig::test_full_config[action1-extra_args1] tests/unit_tests/repobee/test_cli.py::TestConfig::test_full_config[action2-extra_args2] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_is_required[--bu] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_is_required[-u] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_is_required[--sf] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_is_required[-o] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_is_required[--mo] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_can_be_specified[--bu] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_can_be_specified[-u] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_can_be_specified[--sf] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_can_be_specified[-o] tests/unit_tests/repobee/test_cli.py::TestConfig::test_missing_option_can_be_specified[--mo] tests/unit_tests/repobee/test_cli.py::TestSetupAndUpdateParsers::test_happy_path[action0] tests/unit_tests/repobee/test_cli.py::TestSetupAndUpdateParsers::test_happy_path[action1] tests/unit_tests/repobee/test_cli.py::TestSetupAndUpdateParsers::test_finds_local_repo[action0] tests/unit_tests/repobee/test_cli.py::TestSetupAndUpdateParsers::test_finds_local_repo[action1] tests/unit_tests/repobee/test_cli.py::TestMigrateParser::test_happy_path tests/unit_tests/repobee/test_cli.py::TestVerifyParser::test_happy_path tests/unit_tests/repobee/test_cli.py::TestCloneParser::test_happy_path tests/unit_tests/repobee/test_cli.py::TestShowConfigParser::test_happy_path tests/unit_tests/repobee/test_main.py::test_happy_path tests/unit_tests/repobee/test_main.py::test_exit_status_1_on_exception_in_parsing tests/unit_tests/repobee/test_main.py::test_exit_status_1_on_exception_in_handling_parsed_args tests/unit_tests/repobee/test_main.py::test_plugins_args tests/unit_tests/repobee/test_main.py::test_no_plugins_arg tests/unit_tests/repobee/test_main.py::test_no_plugins_with_configured_plugins tests/unit_tests/repobee/test_main.py::test_configured_plugins_are_loaded tests/unit_tests/repobee/test_main.py::test_dist_plugins_are_loaded_when_dist_install tests/unit_tests/repobee/test_main.py::test_plugin_with_subparser_name tests/unit_tests/repobee/test_main.py::test_plug_arg_incompatible_with_no_plugins tests/unit_tests/repobee/test_main.py::test_invalid_plug_options tests/unit_tests/repobee/test_main.py::test_does_not_raise_on_exception_in_dispatch tests/unit_tests/repobee/test_main.py::test_logs_traceback_on_exception_in_dispatch_if_traceback tests/unit_tests/repobee/test_main.py::test_show_all_opts_correctly_separated tests/unit_tests/repobee/test_main.py::test_non_zero_exit_status_on_exception tests/unit_tests/repobee/test_main.py::test_show_config_custom_config tests/unit_tests/repobee_plug/test_pluginmeta.py::TestPluginInheritance::test_raises_on_non_hook_public_method tests/unit_tests/repobee_plug/test_pluginmeta.py::TestPluginInheritance::test_happy_path tests/unit_tests/repobee_plug/test_pluginmeta.py::TestPluginInheritance::test_with_private_methods tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeExtensionCommand::test_defaults tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeExtensionCommand::test_with_settings tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeExtensionCommand::test_generated_parser tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeExtensionCommand::test_configuration tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeExtensionCommand::test_raises_when_non_configurable_value_is_configured tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeExtensionCommand::test_override_opt_names tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeExtensionCommand::test_positional_arguments tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeExtensionCommand::test_mutex_group_arguments_are_mutually_exclusive tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeExtensionCommand::test_positionals_not_allowed_in_mutex_group tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeExtensionCommand::test_mutex_group_allows_one_argument tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeExtensionCommand::test_raises_when_both_action_and_category_given tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeCommandExtension::test_add_required_option_to_config_show tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeCommandExtension::test_raises_when_command_and_command_extension_are_subclassed_together tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeCommandExtension::test_requires_settings tests/unit_tests/repobee_plug/test_pluginmeta.py::TestDeclarativeCommandExtension::test_requires_non_empty_actions_list
src/repobee_plug/cli/args.py src/_repobee/ext/defaults/configwizard.py src/_repobee/ext/javac.py src/repobee_plug/cli/__init__.py src/repobee_plug/_pluginmeta.py src/_repobee/cli/dispatch.py
src/repobee_plug/cli/args.py src/_repobee/ext/defaults/configwizard.py src/_repobee/ext/javac.py src/repobee_plug/cli/__init__.py src/repobee_plug/_pluginmeta.py src/_repobee/cli/dispatch.py