Source code for FIREQ_CLIENT.prompt_completer
"""Command completion for the client REPL."""
import os
from collections.abc import Iterable
from prompt_toolkit import PromptSession
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.completion import CompleteEvent, Completer, Completion, PathCompleter, WordCompleter
from prompt_toolkit.document import Document
from prompt_toolkit.history import FileHistory
[docs]
class CommandCompleter(Completer):
"""Complete commands and command-specific arguments."""
def __init__(self) -> None:
"""Initialize the completers for commands and their arguments."""
# YAML files for run_yaml, directories for export
self.yaml_completer = PathCompleter(
expanduser=True,
file_filter=lambda path: os.path.isdir(path) or path.endswith((".yaml", ".yml")),
)
self.dir_completer = PathCompleter(expanduser=True, only_directories=True)
# Define what arguments each command expects (command -> per-arg completers).
# Commands not listed here (ping, set_nyquist, quit, ...) get no suggestions
# in argument position. Note: commands are matched case-sensitively, exactly
# like _dispatch_command.
self.argument_specs = {
"run_yaml": [self.yaml_completer],
"export": [self.dir_completer, self.dir_completer],
}
commands = [
'ping', 'run_yaml', 'reset_all', 'mts_sync', 'set_nyquist',
'trigger_manually', 'export', 'quit', 'exit',
]
self.command_completer = WordCompleter(commands)
[docs]
def get_completions(self, document: Document, complete_event: CompleteEvent) -> Iterable[Completion]:
"""Yield completions for the command or for the argument being typed.
:param document: the current prompt document.
:type document: Document
:param complete_event: the completion event.
:type complete_event: CompleteEvent
:return: the completion suggestions.
:rtype: Iterable[Completion]
"""
text_before = document.text_before_cursor
cmd_tokens = text_before.lstrip().split()
# No command typed yet -> command completion
if not cmd_tokens:
yield from self.command_completer.get_completions(document, complete_event)
return
# Which argument are we completing?
if text_before[-1].isspace():
arg_index = len(cmd_tokens) - 1 # cursor at the start of a new argument
else:
arg_index = len(cmd_tokens) - 2 # cursor inside the last token
# Still completing the command word
if arg_index < 0:
yield from self.command_completer.get_completions(document, complete_event)
return
# Command-specific argument completers
command = cmd_tokens[0]
spec = self.argument_specs.get(command)
if not spec:
return
# Clamp out-of-range args to the last completer
completer = spec[arg_index] if arg_index < len(spec) else spec[-1]
# Micro-document for the argument being typed (empty right after a space)
current_text = "" if text_before[-1].isspace() else cmd_tokens[-1]
arg_doc = Document(current_text, len(current_text))
yield from completer.get_completions(arg_doc, complete_event)
[docs]
def make_prompt_session() -> PromptSession[str]:
"""Create a PromptSession with history, completion and auto-suggestion.
:return: the configured prompt session.
:rtype: PromptSession[str]
"""
history_file = os.path.expanduser("~/.fireq_client_history")
return PromptSession(
history=FileHistory(history_file),
completer=CommandCompleter(),
auto_suggest=AutoSuggestFromHistory(),
)