Skip to content

Commit 95e252b

Browse files
authored
Added ReplContext class (#118)
* implemented ReplContext class * updated dependencies * Added test cases for ReplContext * Moved ISATTY value to 'globals_' for global access * Fixed flake8 errors
1 parent b2ef9d9 commit 95e252b

File tree

8 files changed

+400
-52
lines changed

8 files changed

+400
-52
lines changed

click_repl/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from ._completer import ClickCompleter as ClickCompleter # noqa: F401
2+
from .core import pass_context as pass_context # noqa: F401
23
from ._repl import register_repl as register_repl # noqa: F401
34
from ._repl import repl as repl # noqa: F401
45
from .exceptions import CommandLineParserError as CommandLineParserError # noqa: F401

click_repl/_completer.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -206,16 +206,16 @@ def _get_completion_for_cmd_args(
206206
current_args = args[param.nargs * -1 :]
207207

208208
# Show only unused opts
209-
already_present = any([
210-
opt in previous_args for opt in opts
211-
])
209+
already_present = any([opt in previous_args for opt in opts])
212210
hide = self.show_only_unused and already_present and not param.multiple
213211

214212
# Show only shortest opt
215-
if (self.shortest_only
216-
and not incomplete # just typed a space
217-
# not selecting a value for a longer version of this option
218-
and args[-1] not in opts):
213+
if (
214+
self.shortest_only
215+
and not incomplete # just typed a space
216+
# not selecting a value for a longer version of this option
217+
and args[-1] not in opts
218+
):
219219
opts = [min(opts, key=len)]
220220

221221
for option in opts:

click_repl/_ctx_stack.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
if TYPE_CHECKING:
6+
from .core import ReplContext
7+
8+
9+
# To store the ReplContext objects generated throughout the Runtime.
10+
_context_stack: list[ReplContext] = []
11+
12+
13+
def _push_context(ctx: ReplContext) -> None:
14+
"""
15+
Pushes a new REPL context onto the current stack.
16+
17+
Parameters
18+
----------
19+
ctx
20+
The :class:`~click_repl.core.ReplContext` object that should be
21+
added to the REPL context stack.
22+
"""
23+
_context_stack.append(ctx)
24+
25+
26+
def _pop_context() -> None:
27+
"""Removes the top-level REPL context from the stack."""
28+
_context_stack.pop()

click_repl/_repl.py

+53-45
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
import click
44
import sys
5-
from prompt_toolkit import PromptSession
65
from prompt_toolkit.history import InMemoryHistory
76

87
from ._completer import ClickCompleter
98
from .exceptions import ClickExit # type: ignore[attr-defined]
109
from .exceptions import CommandLineParserError, ExitReplException, InvalidGroupFormat
1110
from .utils import _execute_internal_and_sys_cmds
11+
from .core import ReplContext
12+
from .globals_ import ISATTY, get_current_repl_ctx
1213

1314

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

76-
isatty = sys.stdin.isatty()
77-
7877
# Delete the REPL command from those available, as we don't want to allow
7978
# nesting REPLs (note: pass `None` to `pop` as we don't want to error if
8079
# REPL command already not present for some reason).
@@ -90,58 +89,67 @@ def repl(
9089

9190
original_command = available_commands.pop(repl_command_name, None)
9291

93-
if isatty:
94-
prompt_kwargs = bootstrap_prompt(group, prompt_kwargs, group_ctx)
95-
session = PromptSession(**prompt_kwargs)
92+
repl_ctx = ReplContext(
93+
group_ctx,
94+
bootstrap_prompt(group, prompt_kwargs, group_ctx),
95+
get_current_repl_ctx(silent=True),
96+
)
9697

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

100103
else:
101-
get_command = sys.stdin.readline
102-
103-
while True:
104-
try:
105-
command = get_command()
106-
except KeyboardInterrupt:
107-
continue
108-
except EOFError:
109-
break
110-
111-
if not command:
112-
if isatty:
104+
# If stdin is not a TTY, read input from stdin directly.
105+
def get_command() -> str:
106+
inp = sys.stdin.readline().strip()
107+
repl_ctx._history.append(inp)
108+
return inp
109+
110+
with repl_ctx:
111+
while True:
112+
try:
113+
command = get_command()
114+
except KeyboardInterrupt:
113115
continue
114-
else:
116+
except EOFError:
115117
break
116118

117-
try:
118-
args = _execute_internal_and_sys_cmds(
119-
command, allow_internal_commands, allow_system_commands
120-
)
121-
if args is None:
122-
continue
119+
if not command:
120+
if ISATTY:
121+
continue
122+
else:
123+
break
123124

124-
except CommandLineParserError:
125-
continue
125+
try:
126+
args = _execute_internal_and_sys_cmds(
127+
command, allow_internal_commands, allow_system_commands
128+
)
129+
if args is None:
130+
continue
126131

127-
except ExitReplException:
128-
break
132+
except CommandLineParserError:
133+
continue
134+
135+
except ExitReplException:
136+
break
129137

130-
try:
131-
# The group command will dispatch based on args.
132-
old_protected_args = group_ctx.protected_args
133138
try:
134-
group_ctx.protected_args = args
135-
group.invoke(group_ctx)
136-
finally:
137-
group_ctx.protected_args = old_protected_args
138-
except click.ClickException as e:
139-
e.show()
140-
except (ClickExit, SystemExit):
141-
pass
142-
143-
except ExitReplException:
144-
break
139+
# The group command will dispatch based on args.
140+
old_protected_args = group_ctx.protected_args
141+
try:
142+
group_ctx.protected_args = args
143+
group.invoke(group_ctx)
144+
finally:
145+
group_ctx.protected_args = old_protected_args
146+
except click.ClickException as e:
147+
e.show()
148+
except (ClickExit, SystemExit):
149+
pass
150+
151+
except ExitReplException:
152+
break
145153

146154
if original_command is not None:
147155
available_commands[repl_command_name] = original_command

0 commit comments

Comments
 (0)