Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ReplContext class #118

Merged
merged 5 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading