Skip to content

Commit

Permalink
Merge branches 'kresctl-tab-completion' 'kresctl-tab-completion-ignor…
Browse files Browse the repository at this point in the history
…e-dash'
  • Loading branch information
Frantisek Tobias committed Nov 15, 2024
2 parents d62d310 + cc05178 commit 5b917b1
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 150 deletions.
40 changes: 36 additions & 4 deletions python/knot_resolver/client/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ def get_subparsers_words(subparser_actions: List[argparse.Action]) -> CompWords:


def get_subparser_by_name(name: str, parser_actions: List[argparse.Action]) -> Optional[argparse.ArgumentParser]:
if name in ["-s", "--socket", "-c", "--config"]:
return None

for action in parser_actions:
if isinstance(action, argparse._SubParsersAction): # pylint: disable=protected-access
if action.choices and name in action.choices:
if action.choices and name in action.choices.keys():
return action.choices[name]
return None

Expand Down Expand Up @@ -136,6 +139,35 @@ def run(self, args: CommandArgs) -> None:
raise NotImplementedError()

@staticmethod
@abstractmethod
def completion(args: List[str], parser: argparse.ArgumentParser) -> CompWords:
raise NotImplementedError()
def completion(parser: argparse.ArgumentParser, args: Optional[List[str]]) -> CompWords:
words: CompWords = get_subparsers_words(parser._actions) # Get subparser words
if args is None or args == [""]:
return words

subparsers = parser._subparsers

if subparsers:
words = get_subparsers_words(subparsers._actions)

for i in range(len(args)):
uarg = args[i]
subparser = get_subparser_by_name(uarg, subparsers._actions) # pylint: disable=W0212

if subparser:
try:
cmd = get_subparser_command(subparser)
subparser_args = args[i + 1 :]
words = cmd.completion(subparser, subparser_args)
except ValueError:
return get_subparsers_words(subparser._actions)

break
elif uarg in ["-s", "--socket", "-c", "--config"]:
# Skip next argument if a known flag is detected
i += 1
# next(uargs, None)
continue
elif uarg in ["--bash", "--space"]:
# Continue if the argument is a valid subparser
continue
return words
8 changes: 2 additions & 6 deletions python/knot_resolver/client/commands/cache.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import argparse
import sys
from enum import Enum
from typing import Any, Dict, List, Optional, Tuple, Type
from typing import Any, Dict, Optional, Tuple, Type

from knot_resolver.client.command import Command, CommandArgs, CompWords, get_subparsers_words, register_command
from knot_resolver.client.command import Command, CommandArgs, 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
Expand Down Expand Up @@ -97,10 +97,6 @@ def register_args_subparser(

return cache_parser, CacheCommand

@staticmethod
def completion(args: List[str], parser: argparse.ArgumentParser) -> CompWords:
return get_subparsers_words(parser._actions)

def run(self, args: CommandArgs) -> None:
if not self.operation:
args.subparser.print_help()
Expand Down
38 changes: 2 additions & 36 deletions python/knot_resolver/client/commands/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,7 @@
from enum import Enum
from typing import List, Tuple, Type

from knot_resolver.client.command import (
Command,
CommandArgs,
CompWords,
get_subparser_by_name,
get_subparser_command,
get_subparsers_words,
register_command,
)
from knot_resolver.client.command import Command, CommandArgs, register_command


class Shells(Enum):
Expand Down Expand Up @@ -59,34 +51,8 @@ def register_args_subparser(

return completion, CompletionCommand

@staticmethod
def completion(args: List[str], parser: argparse.ArgumentParser) -> CompWords:
return get_subparsers_words(parser._actions)

def run(self, args: CommandArgs) -> None:
subparsers = args.parser._subparsers
words: CompWords = {}

if subparsers:
words = get_subparsers_words(subparsers._actions)

uargs = iter(self.args)
for uarg in uargs:
subparser = get_subparser_by_name(uarg, subparsers._actions) # pylint: disable=W0212

if subparser:
cmd: Command = get_subparser_command(subparser)
subparser_args = self.args[self.args.index(uarg) + 1 :]
if subparser_args or self.space:
words = cmd.completion(subparser_args, subparser)
break
elif uarg in ["-s", "--socket", "-c", "--config"]:
# if arg is socket config, skip next arg
next(uargs)
continue
elif uarg in words:
# uarg is valid arg, continue
continue
words = Command.completion(args.parser, self.comp_args)

# print completion words
# based on required bash/fish shell format
Expand Down
158 changes: 104 additions & 54 deletions python/knot_resolver/client/commands/config.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import argparse
import sys
from enum import Enum
from typing import List, Literal, Optional, Tuple, Type

from knot_resolver.client.command import Command, CommandArgs, CompWords, register_command
from typing import Any, Dict, List, Literal, Optional, Tuple, Type

from knot_resolver.client.command import (
Command,
CommandArgs,
CompWords,
get_subparser_by_name,
get_subparsers_words,
register_command,
)
from knot_resolver.datamodel import KresConfig
from knot_resolver.utils.modeling.parsing import DataFormat, parse_json, try_to_parse
from knot_resolver.utils.requests import request

Expand All @@ -22,54 +30,54 @@ def operation_to_method(operation: Operations) -> Literal["PUT", "GET", "DELETE"
return "GET"


# def _properties_words(props: Dict[str, Any]) -> CompWords:
# words: CompWords = {}
# for name, prop in props.items():
# words[name] = prop["description"] if "description" in prop else None
# return words


# def _path_comp_words(node: str, nodes: List[str], props: Dict[str, Any]) -> CompWords:
# i = nodes.index(node)
# ln = len(nodes[i:])

# # if node is last in path, return all possible words on thi level
# if ln == 1:
# return _properties_words(props)
# # if node is valid
# elif node in props:
# node_schema = props[node]

# if "anyOf" in node_schema:
# for item in node_schema["anyOf"]:
# print(item)

# elif "type" not in node_schema:
# pass

# elif node_schema["type"] == "array":
# if ln > 2:
# # skip index for item in array
# return _path_comp_words(nodes[i + 2], nodes, node_schema["items"]["properties"])
# if "enum" in node_schema["items"]:
# print(node_schema["items"]["enum"])
# return {"0": "first array item", "-": "last array item"}
# elif node_schema["type"] == "object":
# if "additionalProperties" in node_schema:
# print(node_schema)
# return _path_comp_words(nodes[i + 1], nodes, node_schema["properties"])
# return {}

# # arrays/lists must be handled sparately
# if node_schema["type"] == "array":
# if ln > 2:
# # skip index for item in array
# return _path_comp_words(nodes[i + 2], nodes, node_schema["items"]["properties"])
# return {"0": "first array item", "-": "last array item"}
# return _path_comp_words(nodes[i + 1], nodes, node_schema["properties"])
# else:
# # if node is not last or valid, value error
# raise ValueError(f"unknown config path node: {node}")
def _properties_words(props: Dict[str, Any]) -> CompWords:
words: CompWords = {}
for name, prop in props.items():
words[name] = prop["description"] if "description" in prop else None
return words


def _path_comp_words(node: str, nodes: List[str], props: Dict[str, Any]) -> CompWords:
i = nodes.index(node)
ln = len(nodes[i:])

# if node is last in path, return all possible words on thi level
if ln == 1:
return _properties_words(props)
# if node is valid
elif node in props:
node_schema = props[node]

if "anyOf" in node_schema:
for item in node_schema["anyOf"]:
print(item)

elif "type" not in node_schema:
pass

elif node_schema["type"] == "array":
if ln > 2:
# skip index for item in array
return _path_comp_words(nodes[i + 2], nodes, node_schema["items"]["properties"])
if "enum" in node_schema["items"]:
print(node_schema["items"]["enum"])
return {"0": "first array item", "-": "last array item"}
elif node_schema["type"] == "object":
if "additionalProperties" in node_schema:
print(node_schema)
return _path_comp_words(nodes[i + 1], nodes, node_schema["properties"])
# return {}

# arrays/lists must be handled sparately
if node_schema["type"] == "array":
if ln > 2:
# skip index for item in array
return _path_comp_words(nodes[i + 2], nodes, node_schema["items"]["properties"])
return {"0": "first array item", "-": "last array item"}
return _path_comp_words(nodes[i + 1], nodes, node_schema["properties"])
else:
# if node is not last or valid, value error
raise ValueError(f"unknown config path node: {node}")


@register_command
Expand Down Expand Up @@ -152,6 +160,24 @@ def register_args_subparser(
nargs="?",
)

# GET & SET config options
prop_words = _properties_words(KresConfig.json_schema()["properties"])
for prop_key in prop_words:
get.add_argument(
prop_key,
help=prop_words[prop_key],
type=str,
nargs="?",
)

set.add_argument(
prop_key,
help=prop_words[prop_key],
action="store",
type=str,
nargs="?",
)

# DELETE operation
delete = config_subparsers.add_parser(
"delete", help="Delete given configuration property or list item at the given index."
Expand All @@ -169,8 +195,31 @@ def register_args_subparser(
return config, ConfigCommand

@staticmethod
def completion(args: List[str], parser: argparse.ArgumentParser) -> CompWords:
# words = parser_words(parser._actions) # pylint: disable=W0212
def completion(parser: argparse.ArgumentParser, args: Optional[List[str]] = None) -> CompWords:
if args is None or len(args) <= 1:
return Command.completion(parser, args)

words: CompWords = get_subparsers_words(parser._actions)
subparsers = parser._subparsers

if subparsers:
for i in range(len(args)):
uarg = args[i]
subparser = get_subparser_by_name(uarg, subparsers._actions) # pylint: disable=W0212
if subparser is not None:
subparser_words = get_subparsers_words(subparser._actions)
words = dict()
for action in subparser._actions:
if action.dest not in subparser_words:
subparser_words[action.dest] = action.help or None

words.update(subparser_words)

subparsers = subparser._subparsers
if not subparsers:
break
else:
break

# for arg in args:
# if arg in words:
Expand All @@ -183,7 +232,8 @@ def completion(args: List[str], parser: argparse.ArgumentParser) -> CompWords:
# return _path_comp_words(config_path[0], config_path, schema_props)
# else:
# break
return {}

return words

def run(self, args: CommandArgs) -> None:
if not self.operation:
Expand Down
8 changes: 2 additions & 6 deletions python/knot_resolver/client/commands/convert.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import argparse
import sys
from pathlib import Path
from typing import List, Optional, Tuple, Type
from typing import Optional, Tuple, Type

from knot_resolver.client.command import Command, CommandArgs, CompWords, register_command
from knot_resolver.client.command import Command, CommandArgs, register_command
from knot_resolver.datamodel import KresConfig
from knot_resolver.datamodel.globals import Context, reset_global_validation_context, set_global_validation_context
from knot_resolver.utils.modeling import try_to_parse
Expand Down Expand Up @@ -50,10 +50,6 @@ def register_args_subparser(

return convert, ConvertCommand

@staticmethod
def completion(args: List[str], parser: argparse.ArgumentParser) -> CompWords:
return {}

def run(self, args: CommandArgs) -> None:
with open(self.input_file, "r") as f:
data = f.read()
Expand Down
8 changes: 2 additions & 6 deletions python/knot_resolver/client/commands/help.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import argparse
from typing import List, Tuple, Type
from typing import Tuple, Type

from knot_resolver.client.command import Command, CommandArgs, CompWords, register_command
from knot_resolver.client.command import Command, CommandArgs, register_command


@register_command
Expand All @@ -12,10 +12,6 @@ def __init__(self, namespace: argparse.Namespace) -> None:
def run(self, args: CommandArgs) -> None:
args.parser.print_help()

@staticmethod
def completion(args: List[str], parser: argparse.ArgumentParser) -> CompWords:
return {}

@staticmethod
def register_args_subparser(
subparser: "argparse._SubParsersAction[argparse.ArgumentParser]",
Expand Down
8 changes: 2 additions & 6 deletions python/knot_resolver/client/commands/metrics.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import argparse
import sys
from typing import List, Optional, Tuple, Type
from typing import Optional, Tuple, Type

from knot_resolver.client.command import Command, CommandArgs, CompWords, register_command
from knot_resolver.client.command import Command, CommandArgs, register_command
from knot_resolver.utils.modeling.parsing import DataFormat, parse_json
from knot_resolver.utils.requests import request

Expand Down Expand Up @@ -42,10 +42,6 @@ def register_args_subparser(
)
return metrics, MetricsCommand

@staticmethod
def completion(args: List[str], parser: argparse.ArgumentParser) -> CompWords:
return {}

def run(self, args: CommandArgs) -> None:
response = request(args.socket, "GET", "metrics/prometheus" if self.prometheus else "metrics/json")

Expand Down
Loading

0 comments on commit 5b917b1

Please sign in to comment.