Commit 9acd2d23 authored by John L. Villalovos's avatar John L. Villalovos Committed by Nejc Habjan
Browse files

fix: add ability to add help to custom_actions

Now when registering a custom_action can add help text if desired.

Also delete the VerticalHelpFormatter as no longer needed. When the
help value is set to `None` or some other value, the actions will get
printed vertically. Before when the help value was not set the actions
would all get put onto one line.
parent 18aa1fc0
Loading
Loading
Loading
Loading
+4 −29
Original line number Diff line number Diff line
@@ -5,7 +5,6 @@ import os
import pathlib
import re
import sys
import textwrap
from types import ModuleType
from typing import (
    Any,
@@ -37,11 +36,12 @@ class CustomAction:
    optional: Tuple[str, ...]
    in_object: bool
    requires_id: bool  # if the `_id_attr` value should be a required argument
    help: Optional[str]  # help text for the custom action


# custom_actions = {
#    cls: {
#        action: (mandatory_args, optional_args, in_obj),
#        action: CustomAction,
#    },
# }
custom_actions: Dict[str, Dict[str, CustomAction]] = {}
@@ -54,33 +54,6 @@ custom_actions: Dict[str, Dict[str, CustomAction]] = {}
__F = TypeVar("__F", bound=Callable[..., Any])


class VerticalHelpFormatter(argparse.HelpFormatter):
    def format_help(self) -> str:
        result = super().format_help()
        output = ""
        indent = self._indent_increment * " "
        for line in result.splitlines(keepends=True):
            # All of our resources are on one line and wrapped inside braces.
            # For example: {application,resource1,resource2}
            # except if there are fewer resources - then the line and help text
            # are collapsed on the same line.
            # For example:  {list}      Action to execute on the GitLab resource.
            # We then put each resource on its own line to make it easier to read.
            if line.strip().startswith("{"):
                choice_string, help_string = line.split("}", 1)
                choice_list = choice_string.strip(" {").split(",")
                help_string = help_string.strip()

                if help_string:
                    help_indent = len(max(choice_list, key=len)) * " "
                    choice_list.append(f"{help_indent} {help_string}")

                choices = "\n".join(choice_list)
                line = f"{textwrap.indent(choices, indent)}\n"
            output += line
        return output


def register_custom_action(
    *,
    cls_names: Union[str, Tuple[str, ...]],
@@ -88,6 +61,7 @@ def register_custom_action(
    optional: Tuple[str, ...] = (),
    custom_action: Optional[str] = None,
    requires_id: bool = True,  # if the `_id_attr` value should be a required argument
    help: Optional[str] = None,  # help text for the action
) -> Callable[[__F], __F]:
    def wrap(f: __F) -> __F:
        @functools.wraps(f)
@@ -115,6 +89,7 @@ def register_custom_action(
                optional=optional,
                in_object=in_obj,
                requires_id=requires_id,
                help=help,
            )

        return cast(__F, wrapped_f)
+4 −3
Original line number Diff line number Diff line
@@ -300,11 +300,14 @@ def _populate_sub_parser_by_class(
    if cls.__name__ in cli.custom_actions:
        name = cls.__name__
        for action_name in cli.custom_actions[name]:
            custom_action = cli.custom_actions[name][action_name]
            # NOTE(jlvillal): If we put a function for the `default` value of
            # the `get` it will always get called, which will break things.
            action_parser = action_parsers.get(action_name)
            if action_parser is None:
                sub_parser_action = sub_parser.add_parser(action_name)
                sub_parser_action = sub_parser.add_parser(
                    action_name, help=custom_action.help
                )
            else:
                sub_parser_action = action_parser
            # Get the attributes for URL/path construction
@@ -315,7 +318,6 @@ def _populate_sub_parser_by_class(
                    )
                sub_parser_action.add_argument("--sudo", required=False)

            custom_action = cli.custom_actions[name][action_name]
            # We need to get the object somehow
            if not issubclass(cls, gitlab.mixins.GetWithoutIdMixin):
                if cls._id_attr is not None and custom_action.requires_id:
@@ -386,7 +388,6 @@ def extend_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
        mgr_cls = getattr(gitlab.v4.objects, mgr_cls_name)
        object_group = subparsers.add_parser(
            arg_name,
            formatter_class=cli.VerticalHelpFormatter,
            help=f"API endpoint: {mgr_cls._path}",
        )

+4 −2
Original line number Diff line number Diff line
@@ -36,13 +36,15 @@ def test_config_error_with_help_prints_help(script_runner):

def test_resource_help_prints_actions_vertically(script_runner):
    ret = script_runner.run(["gitlab", "project", "--help"])
    assert """action:\n  list\n  get""" in ret.stdout
    assert "    list                List the GitLab resources\n" in ret.stdout
    assert "    get                 Get a GitLab resource\n" in ret.stdout
    assert ret.returncode == 0


def test_resource_help_prints_actions_vertically_only_one_action(script_runner):
    ret = script_runner.run(["gitlab", "event", "--help"])
    assert """action:\n  list\n""" in ret.stdout
    assert "  {list}      Action to execute on the GitLab resource.\n"
    assert "    list      List the GitLab resources\n" in ret.stdout
    assert ret.returncode == 0