Skip to content

Commit

Permalink
modified: src/click_repl/_repl.py
Browse files Browse the repository at this point in the history
	modified:   src/click_repl/completer.py
	modified:   src/click_repl/parser.py
	modified:   src/click_repl/utils.py
	modified:   src/click_repl/validator.py
	modified:   tests/__init__.py
	modified:   tests/test_completion/test_click_v7/test_arg_completion_v7.py
	modified:   tests/test_completion/test_click_v7/test_option_completion_v7.py
	modified:   tests/test_completion/test_click_version_ge_8/test_arg_completion_v8.py
	modified:   tests/test_completion/test_click_version_ge_8/test_option_completion_v8.py
	modified:   tests/test_completion/test_common_tests/test_arg_completion.py
	modified:   tests/test_completion/test_common_tests/test_hidden_cmd_and_args.py
	modified:   tests/test_completion/test_common_tests/test_option_completion.py
	modified:   tests/test_completion/test_common_tests/test_unprocessed_args.py
	modified:   tests/test_completion/test_path_type.py
	new file:   tests/test_completion/test_range_type_completer.py
	modified:   tests/test_completion/test_with_group_args.py
	new file:   tests/test_parser/test_replparsingstate.py
	modified:   tests/test_validator.py
  • Loading branch information
GhostOps77 committed Mar 10, 2024
1 parent bd15b65 commit 2d4bf50
Show file tree
Hide file tree
Showing 19 changed files with 229 additions and 96 deletions.
51 changes: 17 additions & 34 deletions src/click_repl/_repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@
from .utils import print_error
from .validator import ClickValidator

# if t.TYPE_CHECKING or ISATTY:


__all__ = ["Repl", "repl"]

Expand Down Expand Up @@ -107,19 +105,15 @@ def __init__(
"show_hidden_params", False
)

self.completer_kwargs = self._bootstrap_completer_kwargs(
completer_cls, completer_kwargs
)

self.validator_kwargs = self._bootstrap_validator_kwargs(
validator_cls, validator_kwargs
)

else:
self.bottom_bar = None

self.prompt_kwargs = self._bootstrap_prompt_kwargs(
completer_cls, validator_cls, prompt_kwargs
completer_cls,
completer_kwargs,
validator_cls,
validator_kwargs,
prompt_kwargs,
)

self.repl_ctx = ReplContext(
Expand Down Expand Up @@ -227,31 +221,12 @@ def _bootstrap_validator_kwargs(
def _bootstrap_prompt_kwargs(
self,
completer_cls: type[Completer] | None,
completer_kwargs: dict[str, Any],
validator_cls: type[Validator] | None,
validator_kwargs: dict[str, Any],
prompt_kwargs: dict[str, Any],
) -> dict[str, Any]:
"""
Generates bootstrap keyword arguments for initializing a
`prompt_toolkit.PromptSession` object, either
using default values or user-defined values, if available.
Parameters
----------
prompt_kwargs : Dictionary of `str: Any` pairs
A dictionary that contains values for keyword arguments supplied by the
user, that to be passed to the `prompt_toolkit.PromptSession` class.
Returns
-------
Dictionary of `str: Any` pairs
A dictionary that contains all the keyword arguments to be passed
to the `prompt_toolkit.PromptSession` class.
"""

if not ISATTY:
# If the standard input is not a TTY device, there is no need
# to generate any keyword arguments for rendering. In this case,
# an empty dictionary is returned.
return {}

default_prompt_kwargs = {
Expand All @@ -269,10 +244,18 @@ def _bootstrap_prompt_kwargs(
default_prompt_kwargs.update(bottom_toolbar=self.bottom_bar)

if completer_cls is not None:
default_prompt_kwargs.update(completer=completer_cls(**self.completer_kwargs))
default_prompt_kwargs.update(
completer=completer_cls(
**self._bootstrap_completer_kwargs(completer_cls, completer_kwargs)
)
)

if validator_cls is not None:
default_prompt_kwargs.update(validator=validator_cls(**self.validator_kwargs))
default_prompt_kwargs.update(
validator=validator_cls(
**self._bootstrap_validator_kwargs(validator_cls, validator_kwargs)
)
)

styles = Style.from_dict(DEFAULT_PROMPTSESSION_STYLE_CONFIG)

Expand Down
6 changes: 3 additions & 3 deletions src/click_repl/completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1293,9 +1293,9 @@ def __init__(
if isinstance(incomplete, Incomplete):
incomplete = incomplete.raw_str

diff = len(incomplete) - len(text)
if diff > 0:
text += " " * diff + "\b" * diff
# diff = len(incomplete) - len(text)
# if diff > 0:
# text += " " * diff + "\b" * diff

kwargs.setdefault("start_position", -len(incomplete))

Expand Down
33 changes: 25 additions & 8 deletions src/click_repl/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from ._globals import HAS_CLICK8
from .exceptions import ArgumentPositionError


_KEY: t.TypeAlias = tuple[
dict[str, Any] | None,
dict[str, Any] | None,
Expand All @@ -40,7 +41,12 @@

_flag_needs_value = object()
_quotes_to_empty_str_dict = str.maketrans(dict.fromkeys("'\"", ""))
_EQUALS_SIGN_AFTER_OPT_FLAG = re.compile(r"^([^a-z\d\s]+[^=\s]+)=(.+)$", re.I)

# Just gonna assume that people use only '-' and '--' as prefix for option flags
# _EQUALS_SIGN_AFTER_OPT_FLAG = re.compile(r"^(--?[a-z][\w-]*)=(.*)$", re.I)
_EQUALS_SIGN_AFTER_OPT_FLAG = re.compile(
r"^(([^a-z\d\s])\2?[a-z]+(?:-[a-z\d]+)?)=(.*)$", re.I
)


def split_arg_string(string: str, posix: bool = True) -> list[str]:
Expand All @@ -67,7 +73,6 @@ def split_arg_string(string: str, posix: bool = True) -> list[str]:

lex = shlex(string, posix=posix, punctuation_chars=True)
lex.whitespace_split = True
# lex.commenters = ""
lex.escape = ""
out: list[str] = []

Expand Down Expand Up @@ -95,6 +100,9 @@ def __repr__(self) -> str:
def __bool__(self) -> bool:
return bool(self.raw_str)

def __len__(self) -> int:
return len(self.parsed_str)

def expand_envvars(self) -> str:
self.parsed_str = utils._expand_envvars(self.parsed_str).strip()
return self.parsed_str
Expand All @@ -118,10 +126,10 @@ def _resolve_incomplete(document_text: str) -> tuple[tuple[str, ...], Incomplete
else:
incomplete = ""

match = _EQUALS_SIGN_AFTER_OPT_FLAG.match(incomplete)
equal_sign_match = _EQUALS_SIGN_AFTER_OPT_FLAG.match(incomplete)

if match:
_, opt, incomplete = match.groups()
if equal_sign_match:
opt, _, incomplete = equal_sign_match.groups()
args.append(opt)

_args = tuple(args)
Expand All @@ -132,7 +140,16 @@ def _resolve_incomplete(document_text: str) -> tuple[tuple[str, ...], Incomplete
raw_incomplete_with_quotes = ""
secondary_check = False

for token in reversed(document_text.split(" ")):
space_splitted_args = document_text.split(" ")

if equal_sign_match:
opt_len = len(opt)
space_splitted_args[-1:] = [
space_splitted_args[-1][:opt_len],
space_splitted_args[-1][opt_len + 1 :],
]

for token in reversed(space_splitted_args):
_tmp = f"{token} {raw_incomplete_with_quotes}".rstrip()

if _tmp.translate(_quotes_to_empty_str_dict).strip() == incomplete:
Expand Down Expand Up @@ -346,7 +363,7 @@ def _resolve_repl_parsing_state(
return ReplParsingState(cli_ctx, current_ctx, args)


class Argument(_Argument):
class ArgumentParamParser(_Argument):
def process(
self,
value: str | Sequence[str | None] | None,
Expand All @@ -369,7 +386,7 @@ def __init__(self, ctx: Context) -> None:
opt.add_to_parser(self, ctx)

def add_argument(self, obj: CoreArgument, dest: str | None, nargs: int = 1) -> None:
self._args.append(Argument(obj=obj, dest=dest, nargs=nargs))
self._args.append(ArgumentParamParser(obj=obj, dest=dest, nargs=nargs))

def _process_args_for_options(self, state: ParsingState) -> None:
while state.rargs:
Expand Down
8 changes: 4 additions & 4 deletions src/click_repl/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,8 @@ def _get_visible_subcommands(
def get_info_dict(
obj: Context | Command | Parameter | click.ParamType,
) -> dict[str, Any]:
# Similar to the 'get_info_dict' method implementation # in click objects,
# but it only retrieves the essential attributes # required to
# Similar to the 'get_info_dict' method implementation in click objects,
# but it only retrieves the essential attributes required to
# differentiate between different 'ReplParsingState' objects.

if isinstance(obj, click.Context):
Expand Down Expand Up @@ -274,7 +274,7 @@ def get_info_dict(

@lru_cache(maxsize=3)
def _generate_next_click_ctx(
command: MultiCommand,
multicommand: MultiCommand,
parent_ctx: Context,
args: tuple[str, ...],
proxy: bool = False,
Expand All @@ -287,7 +287,7 @@ def _generate_next_click_ctx(
# list format, we explicitly convert args into a list.
_args = list(_expand_envvars(i) for i in args)

name, cmd, _args = command.resolve_command(parent_ctx, _args)
name, cmd, _args = multicommand.resolve_command(parent_ctx, _args)

if cmd is None:
return parent_ctx, None
Expand Down
13 changes: 4 additions & 9 deletions src/click_repl/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ class ClickValidator(Validator):
ctx : `click.Context`
The current `click.Context` object.
internal_commands_system : `InternalCommandSystem`
The `InternalCommandSystem` object
that holds information about the internal commands and their prefixes.
display_all_errors : bool
If `False`, all generic Python Exceptions that are raised, will not be
displayed in the Validator bar, resulting in the full error traceback
Expand All @@ -73,7 +69,6 @@ def __init__(
self.cli: Final[MultiCommand] = self.cli_ctx.command # type: ignore[assignment]

self.internal_commands_system = internal_commands_system

self.display_all_errors = display_all_errors

def _validate(self, document_text: str) -> None:
Expand Down Expand Up @@ -120,18 +115,18 @@ def validate(self, document: Document) -> None:
try:
self._validate(document.text_before_cursor)

except UsageError as e:
except UsageError as ue:
# UsageError's error messages are simple and are raised when there
# is an improper use of arguments and options. In this case, we can
# simply display the error message without mentioning the specific
# error class.
raise ValidationError(0, e.format_message())
raise ValidationError(0, ue.format_message())

except ClickException as e:
except ClickException as ce:
# Click formats its error messages to provide more detail. Therefore,
# we can use it to display error messages along with the specific error
# type.
raise ValidationError(0, f"{type(e).__name__}: {e.format_message()}")
raise ValidationError(0, f"{type(ce).__name__}: {ce.format_message()}")

except Exception as e:
if self.display_all_errors:
Expand Down
13 changes: 13 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
import contextlib
import sys
from io import StringIO
from typing import Callable

from click_repl._internal_cmds import InternalCommandSystem


class DummyInternalCommandSystem(InternalCommandSystem):
def _group_commands_by_callback_and_description(
self,
) -> dict[tuple[Callable[[], None], str], list[str]]:
return {}

def get_prefix(self, command: str) -> tuple[str, str | None]:
return ("", None)


@contextlib.contextmanager
Expand Down
4 changes: 2 additions & 2 deletions tests/test_completion/test_click_v7/test_arg_completion_v7.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
import pytest
from prompt_toolkit.document import Document

from click_repl._internal_cmds import InternalCommandSystem
from click_repl.completer import ClickCompleter
from tests import DummyInternalCommandSystem


@click.group()
def root_command():
pass


c = ClickCompleter(click.Context(root_command), InternalCommandSystem())
c = ClickCompleter(click.Context(root_command), DummyInternalCommandSystem())


@pytest.mark.skipif(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
import pytest
from prompt_toolkit.document import Document

from click_repl._internal_cmds import InternalCommandSystem
from click_repl.completer import ClickCompleter
from tests import DummyInternalCommandSystem


@click.group()
def root_command():
pass


c = ClickCompleter(click.Context(root_command), InternalCommandSystem())
c = ClickCompleter(click.Context(root_command), DummyInternalCommandSystem())


@pytest.mark.skipif(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
import pytest
from prompt_toolkit.document import Document

from click_repl._internal_cmds import InternalCommandSystem
from click_repl.completer import ClickCompleter
from tests import DummyInternalCommandSystem


@click.group()
def root_command():
pass


c = ClickCompleter(click.Context(root_command), InternalCommandSystem())
c = ClickCompleter(click.Context(root_command), DummyInternalCommandSystem())

with pytest.importorskip(
"click.shell_complete.CompletionItem",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
import pytest
from prompt_toolkit.document import Document

from click_repl._internal_cmds import InternalCommandSystem
from click_repl.completer import ClickCompleter
from tests import DummyInternalCommandSystem


@click.group()
def root_command():
pass


c = ClickCompleter(click.Context(root_command), InternalCommandSystem())
c = ClickCompleter(click.Context(root_command), DummyInternalCommandSystem())

with pytest.importorskip(
"click.shell_complete.CompletionItem",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import pytest
from prompt_toolkit.document import Document

from click_repl._internal_cmds import InternalCommandSystem
from click_repl.completer import ClickCompleter
from tests import DummyInternalCommandSystem


@click.group()
Expand All @@ -25,7 +25,7 @@ def arg_choices(handler):
pass


c = ClickCompleter(click.Context(root_command), InternalCommandSystem())
c = ClickCompleter(click.Context(root_command), DummyInternalCommandSystem())


@pytest.mark.parametrize(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
import pytest
from prompt_toolkit.document import Document

from click_repl._internal_cmds import InternalCommandSystem
from click_repl.completer import ClickCompleter
from tests import DummyInternalCommandSystem


@click.group()
def root_command():
pass


c = ClickCompleter(click.Context(root_command), InternalCommandSystem())
c = ClickCompleter(click.Context(root_command), DummyInternalCommandSystem())


# @pytest.mark.skipif(
Expand Down
Loading

0 comments on commit 2d4bf50

Please sign in to comment.