]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
fixup! kresctl: tab-completion: implement suggestions/completion for first argument docs-develop-kres-ph09xl/deployments/5290
authorAleš Mrázek <ales.mrazek@nic.cz>
Tue, 8 Oct 2024 10:49:53 +0000 (12:49 +0200)
committerAleš Mrázek <ales.mrazek@nic.cz>
Tue, 8 Oct 2024 10:49:53 +0000 (12:49 +0200)
python/knot_resolver/client/command.py
python/knot_resolver/client/commands/cache.py
python/knot_resolver/client/commands/completion.py

index 960ac1f544fccafb4ff601559ac3031179c848aa..f2d59223ef1ad1660c9017f90b9c475ad170bb4c 100644 (file)
@@ -1,7 +1,7 @@
 import argparse
 from abc import ABC, abstractmethod  # pylint: disable=[no-name-in-module]
 from pathlib import Path
-from typing import Dict, List, Optional, Tuple, Type, TypeVar
+from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar
 from urllib.parse import quote
 
 from knot_resolver.constants import API_SOCK_FILE, CONFIG_FILE
@@ -17,6 +17,33 @@ CompWords = Dict[str, Optional[str]]
 _registered_commands: List[Type["Command"]] = []
 
 
+def get_subparsers_words(subparser_actions: List[argparse.Action]) -> CompWords:
+    words: CompWords = {}
+    for action in subparser_actions:
+        if isinstance(action, argparse._SubParsersAction) and action.choices:
+            for choice, parser in action.choices.items():
+                words[choice] = parser.description
+        else:
+            for opt in action.option_strings:
+                words[opt] = action.help
+    return words
+
+
+def get_subparser_by_name(name: str, parser_actions: List[argparse.Action]) -> Optional[argparse.ArgumentParser]:
+    for action in parser_actions:
+        if isinstance(action, argparse._SubParsersAction):
+            if action.choices and name in action.choices:
+                return action.choices[name]
+    return None
+
+
+def get_subparser_command(subparser: argparse.ArgumentParser) -> "Command":
+    defaults: Dict[str, Any] = subparser._defaults
+    if "command" in defaults:
+        return defaults["command"]
+    raise ValueError(f"missing 'command' default for '{subparser.prog}' parser")
+
+
 def register_command(cls: T) -> T:
     _registered_commands.append(cls)
     return cls
index ef64302b30cfeb74fac71171aee6d1c2a358ac66..b9c2e46627d960767316318541f6aed9bc70974d 100644 (file)
@@ -3,12 +3,11 @@ import sys
 from enum import Enum
 from typing import Any, Dict, List, Optional, Tuple, Type
 
-from knot_resolver.client.command import Command, CommandArgs, CompWords, register_command
+from knot_resolver.client.command import Command, CommandArgs, CompWords, get_subparsers_words, register_command
 from knot_resolver.datamodel.cache_schema import CacheClearRPCSchema
 from knot_resolver.utils.modeling.exceptions import AggregateDataValidationError, DataValidationError
 from knot_resolver.utils.modeling.parsing import DataFormat, parse_json
 from knot_resolver.utils.requests import request
-from argparse import _SubParsersAction
 
 
 class CacheOperations(Enum):
@@ -100,16 +99,7 @@ class CacheCommand(Command):
 
     @staticmethod
     def completion(args: List[str], parser: argparse.ArgumentParser) -> CompWords:
-        words = dict()
-        for action in parser._actions:
-            if isinstance(action, _SubParsersAction):
-                if action.choices is not None:
-                    for choice in action.choices:
-                        words[choice] = action.choices.get(choice)
-            else:
-                for opt in action.option_strings:
-                    words[opt] = action.help
-        return words
+        return get_subparsers_words(parser._actions)
 
     def run(self, args: CommandArgs) -> None:
         if not self.operation:
index 23871afd7be4839a950f7f05e81834ba639f38c6..94499521d27ee9b7f5a6b16f62e2dd2c64fe056d 100644 (file)
@@ -2,8 +2,15 @@ import argparse
 from enum import Enum
 from typing import List, Tuple, Type
 
-from knot_resolver.client.command import Command, CommandArgs, CompWords, register_command
-from argparse import _SubParsersAction
+from knot_resolver.client.command import (
+    Command,
+    CommandArgs,
+    CompWords,
+    get_subparser_by_name,
+    get_subparser_command,
+    get_subparsers_words,
+    register_command,
+)
 
 
 class Shells(Enum):
@@ -11,38 +18,6 @@ class Shells(Enum):
     FISH = 1
 
 
-def parser_words(actions):
-    words = dict()
-    for action in actions:
-        if isinstance(action, _SubParsersAction):
-            if action.choices is not None:
-                for choice in action.choices:
-                    words[choice] = action.choices.get(choice)
-        else:
-            for opt in action.option_strings:
-                words[opt] = action.help
-
-    return words
-
-
-def subparser_by_name(uarg: str, actions) -> argparse.ArgumentParser | None:
-    for action in actions:
-        if isinstance(action, _SubParsersAction):
-            if action.choices is not None:
-                for choice in action.choices:
-                    # if uarg == choice:
-                    if uarg in choice:
-                        return  action.choices.get(choice)
-    return None
-
-
-def subparser_command(subparser: argparse.ArgumentParser) -> Command:
-    com_class: Command | None = subparser._defaults.get("command")
-    # NOTE: This is just a temporary bandage to silence pyright
-    assert(com_class is not None)
-    return com_class
-
-
 @register_command
 class CompletionCommand(Command):
     def __init__(self, namespace: argparse.Namespace) -> None:
@@ -58,7 +33,10 @@ class CompletionCommand(Command):
     def register_args_subparser(
         subparser: "argparse._SubParsersAction[argparse.ArgumentParser]",
     ) -> Tuple[argparse.ArgumentParser, "Type[Command]"]:
-        completion = subparser.add_parser("completion", help="commands auto-completion")
+        completion = subparser.add_parser(
+            "completion",
+            help="commands auto-completion",
+        )
         completion.add_argument(
             "--space",
             help="space after last word, returns all possible folowing options",
@@ -82,38 +60,31 @@ class CompletionCommand(Command):
 
     @staticmethod
     def completion(args: List[str], parser: argparse.ArgumentParser) -> CompWords:
-        words: CompWords = {}
-        # for action in parser._actions:
-        #     for opt in action.option_strings:
-        #         words[opt] = action.help
-        # return words
-        return words
+        return get_subparsers_words(parser._actions)
 
     def run(self, args: CommandArgs) -> None:
         subparsers = args.parser._subparsers
         words: CompWords = {}
 
         if subparsers:
-            words = parser_words(subparsers._actions)
+            words = get_subparsers_words(subparsers._actions)
 
             uargs = iter(self.comp_args)
-            # skip kresctl
-            next(uargs)
             for uarg in uargs:
-                subparser = subparser_by_name(uarg, subparsers._actions)  # pylint: disable=W0212
+                subparser = get_subparser_by_name(uarg, subparsers._actions)  # pylint: disable=W0212
 
                 if subparser:
-                    cmd: Command = subparser_command(subparser)
+                    cmd: Command = get_subparser_command(subparser)
                     subparser_args = self.comp_args[self.comp_args.index(uarg) + 1 :]
-                    if subparser_args:
+                    if subparser_args or self.space:
                         words = cmd.completion(subparser_args, subparser)
                     break
-                elif uarg in ["-s", "--socket"]:
+                elif uarg in ["-s", "--socket", "-c", "--config"]:
                     # if arg is socket config, skip next arg
                     next(uargs)
                     continue
                 elif uarg in words:
-                    # uarg is walid arg, continue
+                    # uarg is valid arg, continue
                     continue
                 else:
                     raise ValueError(f"unknown argument: {uarg}")