From 37914e0b37e94fe6c3b05854c3ca27821f1a39bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Prpi=C4=8D?= Date: Tue, 6 Jun 2023 10:59:27 -0400 Subject: [PATCH] Skip required options when using help option This lets users use `cve list -h` without having to also specify auth-related options (user, org, api key) that are required for non-help actions. --- cvelib/cli.py | 20 +++++++++++++++++++- tests/test_cli.py | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/cvelib/cli.py b/cvelib/cli.py index f978825..2b9cdfc 100644 --- a/cvelib/cli.py +++ b/cvelib/cli.py @@ -210,7 +210,25 @@ def init_cve_api(self) -> CveApi: ) -@click.group(context_settings=CONTEXT_SETTINGS) +class SkipRequiredOnHelp(click.Group): + def parse_args(self, ctx: click.Context, args: list) -> list: + """If any help options are used, mark global required options as not required. + + That way, we can skip directly to showing the help without requiring users to specify + values that don't end up getting used anyway. + """ + if any(arg in ctx.help_option_names for arg in args): + # Iterate over all options and flip them to not required and not to prompt for input. + for param in self.params: + if isinstance(param, click.Option): + param.required = False + # Type ignored due to `"Option" has no attribute "prompt_required"` error: + # https://github.com/pallets/click/blob/d0af32d8/src/click/core.py#L2455 + param.prompt_required = False # type: ignore + return super(SkipRequiredOnHelp, self).parse_args(ctx, args) + + +@click.group(context_settings=CONTEXT_SETTINGS, cls=SkipRequiredOnHelp) @click.option( "-u", "--username", envvar="CVE_USER", required=True, help="Your username (env var: CVE_USER)" ) diff --git a/tests/test_cli.py b/tests/test_cli.py index 43e16cb..8ec6c31 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -559,3 +559,22 @@ def test_show_org(): "├─ Created:\tWed Apr 21 02:09:07 2021 +0000\n" "└─ Modified:\tWed Apr 21 02:09:07 2021 +0000\n" ) + + +class TestSubcommandHelp: + def test_required_opts(self): + with mock.patch("cvelib.cli.CveApi.show_org") as show_org: + show_org.return_value = {} + runner = CliRunner() + result = runner.invoke(cli, ["org"]) + assert result.exit_code == 2, result.output + assert "Error: Missing option" in result.output + + def test_exit_on_help(self): + with mock.patch("cvelib.cli.CveApi.show_org") as show_org: + show_org.return_value = {} + runner = CliRunner() + result = runner.invoke(cli, ["org", "--help"]) + assert result.exit_code == 0, result.output + # The command is named after the function that is called. + assert result.output.startswith("Usage: cli org")