This commit is contained in:
macbook_max
2026-10-15 20:36:59 +08:00
commit 6bf949949c
7923 changed files with 1369582 additions and 0 deletions
@@ -0,0 +1,238 @@
from __future__ import annotations
import os
import shlex
import typing as t
from collections import defaultdict
from typing import Callable, Generator, Iterator, NoReturn, Sequence
import click
from typing_extensions import TypeAlias
from .exceptions import CommandLineParserError, ExitReplException
T = t.TypeVar("T")
InternalCommandCallback: TypeAlias = Callable[[], None]
__all__ = [
"_execute_internal_and_sys_cmds",
"_exit_internal",
"_get_registered_target",
"_help_internal",
"_resolve_context",
"_register_internal_command",
"dispatch_repl_commands",
"handle_internal_commands",
"split_arg_string",
"exit",
]
def _get_protected_args(ctx: click.Context) -> list[str]:
# In click >= 8.2, protected_args is a read-only property over _protected_args.
if hasattr(ctx, "_protected_args"):
return t.cast(list[str], getattr(ctx, "_protected_args"))
return ctx.protected_args
def _set_protected_args(ctx: click.Context, args: list[str]) -> None:
if hasattr(ctx, "_protected_args"):
setattr(ctx, "_protected_args", args)
else:
ctx.protected_args = args
def _resolve_context(args: list[str], ctx: click.Context) -> click.Context:
"""Produce the context hierarchy starting with the command and
traversing the complete arguments. This only follows the commands,
it doesn't trigger input prompts or callbacks.
:param args: List of complete args before the incomplete value.
:param cli_ctx: `click.Context` object of the CLI group
"""
while args:
command = ctx.command
if isinstance(command, click.MultiCommand):
if not command.chain:
name, cmd, args = command.resolve_command(ctx, args)
if cmd is None:
return ctx
ctx = cmd.make_context(name, args, parent=ctx, resilient_parsing=True)
args = _get_protected_args(ctx) + ctx.args
else:
while args:
name, cmd, args = command.resolve_command(ctx, args)
if cmd is None:
return ctx
sub_ctx = cmd.make_context(
name,
args,
parent=ctx,
allow_extra_args=True,
allow_interspersed_args=False,
resilient_parsing=True,
)
args = sub_ctx.args
ctx = sub_ctx
args = [*_get_protected_args(sub_ctx), *sub_ctx.args]
else:
break
return ctx
_internal_commands: dict[str, tuple[InternalCommandCallback, str | None]] = {}
def split_arg_string(string: str, posix: bool = True) -> list[str]:
"""Split an argument string as with :func:`shlex.split`, but don't
fail if the string is incomplete. Ignores a missing closing quote or
incomplete escape sequence and uses the partial token as-is.
.. code-block:: python
split_arg_string("example 'my file")
["example", "my file"]
split_arg_string("example my\\")
["example", "my"]
:param string: String to split.
"""
lex = shlex.shlex(string, posix=posix)
lex.whitespace_split = True
lex.commenters = ""
out = []
try:
for token in lex:
out.append(token)
except ValueError:
# Raised when end-of-string is reached in an invalid state. Use
# the partial token as-is. The quote or escape character is in
# lex.state, not lex.token.
out.append(lex.token)
return out
def _register_internal_command(
names: str | Sequence[str] | Generator[str, None, None] | Iterator[str],
target: InternalCommandCallback,
description: str | None = None,
) -> None:
if not hasattr(target, "__call__"):
raise ValueError("Internal command must be a callable")
if isinstance(names, str):
names = [names]
elif not isinstance(names, (Sequence, Generator, Iterator)):
raise ValueError(
'"names" must be a string, or a Sequence of strings, but got "{}"'.format(
type(names).__name__
)
)
for name in names:
_internal_commands[name] = (target, description)
def _get_registered_target(
name: str, default: T | None = None
) -> InternalCommandCallback | T | None:
target_info = _internal_commands.get(name, None)
if target_info:
return target_info[0]
return default
def _exit_internal() -> NoReturn:
raise ExitReplException()
def _help_internal() -> None:
formatter = click.HelpFormatter()
formatter.write_heading("REPL help")
formatter.indent()
with formatter.section("External Commands"):
formatter.write_text('prefix external commands with "!"')
with formatter.section("Internal Commands"):
formatter.write_text('prefix internal commands with ":"')
info_table = defaultdict(list)
for mnemonic, target_info in _internal_commands.items():
info_table[target_info[1]].append(mnemonic)
formatter.write_dl( # type: ignore[arg-type]
( # type: ignore[arg-type]
", ".join(map(":{}".format, sorted(mnemonics))),
description,
)
for description, mnemonics in info_table.items()
)
print(formatter.getvalue())
_register_internal_command(["q", "quit", "exit"], _exit_internal, "exits the repl")
_register_internal_command(
["?", "h", "help"], _help_internal, "displays general help information"
)
def _execute_internal_and_sys_cmds(
command: str,
allow_internal_commands: bool = True,
allow_system_commands: bool = True,
) -> list[str] | None:
"""
Executes internal, system, and all the other registered click commands from the input
"""
if allow_system_commands and dispatch_repl_commands(command):
return None
if allow_internal_commands and command.startswith(":"):
handle_internal_commands(command)
return None
try:
return split_arg_string(command)
except ValueError as e:
raise CommandLineParserError("{}".format(e))
def exit() -> NoReturn:
"""Exit the repl"""
_exit_internal()
def dispatch_repl_commands(command: str) -> bool:
"""
Execute system commands entered in the repl.
System commands are all commands starting with "!".
"""
if command.startswith("!"):
os.system(command[1:])
return True
return False
def handle_internal_commands(command: str) -> None:
"""
Run repl-internal commands.
Repl-internal commands are all commands starting with ":".
"""
target = _get_registered_target(command[1:], default=None)
if target:
target()