Skip to content

Commit

Permalink
Added ReplContext class (#118)
Browse files Browse the repository at this point in the history
* implemented ReplContext class

* updated dependencies

* Added test cases for ReplContext

* Moved ISATTY value to 'globals_' for global access

* Fixed flake8 errors
  • Loading branch information
GhostOps77 authored Jun 15, 2024
1 parent b2ef9d9 commit 95e252b
Show file tree
Hide file tree
Showing 8 changed files with 400 additions and 52 deletions.
1 change: 1 addition & 0 deletions click_repl/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from ._completer import ClickCompleter as ClickCompleter # noqa: F401
from .core import pass_context as pass_context # noqa: F401
from ._repl import register_repl as register_repl # noqa: F401
from ._repl import repl as repl # noqa: F401
from .exceptions import CommandLineParserError as CommandLineParserError # noqa: F401
Expand Down
14 changes: 7 additions & 7 deletions click_repl/_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,16 +206,16 @@ def _get_completion_for_cmd_args(
current_args = args[param.nargs * -1 :]

# Show only unused opts
already_present = any([
opt in previous_args for opt in opts
])
already_present = any([opt in previous_args for opt in opts])
hide = self.show_only_unused and already_present and not param.multiple

# Show only shortest opt
if (self.shortest_only
and not incomplete # just typed a space
# not selecting a value for a longer version of this option
and args[-1] not in opts):
if (
self.shortest_only
and not incomplete # just typed a space
# not selecting a value for a longer version of this option
and args[-1] not in opts
):
opts = [min(opts, key=len)]

for option in opts:
Expand Down
28 changes: 28 additions & 0 deletions click_repl/_ctx_stack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from .core import ReplContext


# To store the ReplContext objects generated throughout the Runtime.
_context_stack: list[ReplContext] = []


def _push_context(ctx: ReplContext) -> None:
"""
Pushes a new REPL context onto the current stack.
Parameters
----------
ctx
The :class:`~click_repl.core.ReplContext` object that should be
added to the REPL context stack.
"""
_context_stack.append(ctx)


def _pop_context() -> None:
"""Removes the top-level REPL context from the stack."""
_context_stack.pop()
98 changes: 53 additions & 45 deletions click_repl/_repl.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import click
import sys
from prompt_toolkit import PromptSession
from prompt_toolkit.history import InMemoryHistory

from ._completer import ClickCompleter
from .exceptions import ClickExit # type: ignore[attr-defined]
from .exceptions import CommandLineParserError, ExitReplException, InvalidGroupFormat
from .utils import _execute_internal_and_sys_cmds
from .core import ReplContext
from .globals_ import ISATTY, get_current_repl_ctx


__all__ = ["bootstrap_prompt", "register_repl", "repl"]
Expand Down Expand Up @@ -73,8 +74,6 @@ def repl(
f"an optional argument '{param.name}' in REPL mode"
)

isatty = sys.stdin.isatty()

# Delete the REPL command from those available, as we don't want to allow
# nesting REPLs (note: pass `None` to `pop` as we don't want to error if
# REPL command already not present for some reason).
Expand All @@ -90,58 +89,67 @@ def repl(

original_command = available_commands.pop(repl_command_name, None)

if isatty:
prompt_kwargs = bootstrap_prompt(group, prompt_kwargs, group_ctx)
session = PromptSession(**prompt_kwargs)
repl_ctx = ReplContext(
group_ctx,
bootstrap_prompt(group, prompt_kwargs, group_ctx),
get_current_repl_ctx(silent=True),
)

def get_command():
return session.prompt()
if ISATTY:
# If stdin is a TTY, prompt the user for input using PromptSession.
def get_command() -> str:
return repl_ctx.session.prompt() # type: ignore

else:
get_command = sys.stdin.readline

while True:
try:
command = get_command()
except KeyboardInterrupt:
continue
except EOFError:
break

if not command:
if isatty:
# If stdin is not a TTY, read input from stdin directly.
def get_command() -> str:
inp = sys.stdin.readline().strip()
repl_ctx._history.append(inp)
return inp

with repl_ctx:
while True:
try:
command = get_command()
except KeyboardInterrupt:
continue
else:
except EOFError:
break

try:
args = _execute_internal_and_sys_cmds(
command, allow_internal_commands, allow_system_commands
)
if args is None:
continue
if not command:
if ISATTY:
continue
else:
break

except CommandLineParserError:
continue
try:
args = _execute_internal_and_sys_cmds(
command, allow_internal_commands, allow_system_commands
)
if args is None:
continue

except ExitReplException:
break
except CommandLineParserError:
continue

except ExitReplException:
break

try:
# The group command will dispatch based on args.
old_protected_args = group_ctx.protected_args
try:
group_ctx.protected_args = args
group.invoke(group_ctx)
finally:
group_ctx.protected_args = old_protected_args
except click.ClickException as e:
e.show()
except (ClickExit, SystemExit):
pass

except ExitReplException:
break
# The group command will dispatch based on args.
old_protected_args = group_ctx.protected_args
try:
group_ctx.protected_args = args
group.invoke(group_ctx)
finally:
group_ctx.protected_args = old_protected_args
except click.ClickException as e:
e.show()
except (ClickExit, SystemExit):
pass

except ExitReplException:
break

if original_command is not None:
available_commands[repl_command_name] = original_command
Expand Down
Loading

0 comments on commit 95e252b

Please sign in to comment.