Skip to content
This repository has been archived by the owner on Oct 5, 2023. It is now read-only.

Commit

Permalink
fix: refactoring repl
Browse files Browse the repository at this point in the history
  • Loading branch information
eshepelyuk committed Jul 24, 2023
1 parent 98bfed5 commit 45078c1
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 81 deletions.
4 changes: 4 additions & 0 deletions pykli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@

HISTORY_FILE = CONFIG_DIR / "history"

LOG_FILE = CONFIG_DIR / "pykli.log"

MONOKAI_STYLE = get_style_by_name('monokai')

CONFIG_DIR.mkdir(exist_ok=True, parents=True)
39 changes: 31 additions & 8 deletions pykli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,43 @@
import sys
import click
from pprint import pformat
from .printer import pok, perr
from .ksqldb import KsqlDBClient
from .cli import cli_loop
from . import CONFIG_DIR

from .ksqldb import KsqlDBClient, is_stmt
from .printer import print_stmt, perr, pok
from .repl_read import pykli_prompt


def repl(ksqldb: KsqlDBClient):
session = pykli_prompt()

while True:
try:
text = session.prompt("pykli> ")
if is_stmt(text):
resp = ksqldb.stmt(text)
print_stmt(resp)
elif text == "quit" or text == "exit":
break
except httpx.HTTPStatusError as e:
perr(e.response.json()["message"])
except httpx.TransportError as e:
perr(f"Transport error: {pformat(e)}")
except KeyboardInterrupt:
continue
except EOFError:
break

return 0


@click.command()
@click.argument("server", default = "http://localhost:8088")
def main(server):
"""
pykli - ksqlDB command line client written in Python.
pyKLI - ksqlDB command line client.
\b SERVER The address of the ksqlDB server."""

CONFIG_DIR.mkdir(exist_ok=True, parents=True)

ksqldb = KsqlDBClient(server)

try:
Expand All @@ -26,7 +48,8 @@ def main(server):
perr(f"Transport error: {pformat(e)}")
sys.exit(1)

sys.exit(cli_loop(ksqldb))
sys.exit(repl(ksqldb))


if __name__ == "__main__":
main()
43 changes: 0 additions & 43 deletions pykli/cli.py

This file was deleted.

71 changes: 44 additions & 27 deletions pykli/completer.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,57 @@
from prompt_toolkit.completion import NestedCompleter
from pprint import pprint
from prompt_toolkit.completion import NestedCompleter, Completer, Completion, CompleteEvent
from prompt_toolkit.completion.word_completer import WordCompleter
from prompt_toolkit.document import Document
from prompt_toolkit.application import get_app, run_in_terminal
from typing import Any, Iterable, Mapping, Set, Union

pykli_completer = NestedCompleter.from_nested_dict({
COMPLETIONS = {
"describe": {
"connector": None,
"connector": WordCompleter(["conn1", "conn2"]),
"function": None,
"streams": None,
"tables": None,
},
"drop": {
"connector": {"if exists": None},
"connector": WordCompleter(["conn1", "conn2"]),
"connector if exists": WordCompleter(["conn1", "conn2"]),
"stream": {"if exists": None},
"table": {"if exists": None},
"type": {"if exists": None},
},
"list": {
"connectors": None,
"functions": None,
"properties": None,
"queries": None,
"topics": None,
"streams": None,
"tables": None,
"types": None,
"variables": None,
},
"show": {
"connectors": None,
"functions": None,
"properties": None,
"queries": None,
"topics": None,
"streams": None,
"tables": None,
"types": None,
"variables": None,
},
"list": {"connectors", "functions", "properties", "queries", "topics", "streams", "tables", "types", "variables"},
"show": {"connectors", "functions", "properties", "queries", "topics", "streams", "tables", "types", "variables"},
"exit": None,
"quit": None,
})
}

class PykliCompleter(Completer):
def __init__(self):
self._nested = NestedCompleter.from_nested_dict(COMPLETIONS)

def get_completions(self, document, complete_event):
if document.on_first_line:
yield from self._nested.get_completions(document, complete_event)
else:
text = document.current_line_before_cursor.lstrip()
stripped_len = len(document.current_line_before_cursor) - len(text)

first_term = [ln for ln in document.lines if ln][0]
completer = self._nested.options.get(first_term)

if completer is not None:
remaining_text = text
move_cursor = len(text) - len(remaining_text) + stripped_len

# run_in_terminal(lambda : print("111", f"##{text}##", f"@@{first_term}@@",
# f"!!{remaining_text}!!", f"=>{document.cursor_position} {move_cursor}"))
new_document = Document(
remaining_text,
cursor_position=len(remaining_text),
)
yield from completer.get_completions(new_document, complete_event)

yield from ()


def pykli_completer(): return PykliCompleter()
28 changes: 25 additions & 3 deletions pykli/keybindgings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
from prompt_toolkit.enums import DEFAULT_BUFFER
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.filters import completion_is_selected, is_searching, has_completions
from prompt_toolkit.filters import completion_is_selected, is_searching, has_completions, Condition
from prompt_toolkit.application import get_app, run_in_terminal

def _is_complete(sql):
# A complete command is an sql statement that ends with a semicolon, unless
# there's an open quote surrounding it, as is common when writing a
# CREATE FUNCTION command
return sql.endswith(";") # and not is_open_quote(sql)

@Condition
def buffer_should_be_handled():
doc = get_app().layout.get_buffer_by_name(DEFAULT_BUFFER).document
text = doc.text.strip()

return (
text.startswith("\\") # Special Command
or text.endswith(r"\e") # Special Command
or text.endswith(r"\G") # Ended with \e which should launch the editor
or _is_complete(text) # A complete SQL command
or (text == "exit") # Exit doesn't need semi-colon
or (text == "quit") # Quit doesn't need semi-colon
)


def pykli_keys():

Expand All @@ -10,8 +33,7 @@ def _(event):
event.current_buffer.complete_state = None
event.app.current_buffer.complete_state = None

# @kb.add("enter", filter=~(completion_is_selected | is_searching) & buffer_should_be_handled(pgcli))
@kb.add("enter", filter=~(completion_is_selected | is_searching))
@kb.add("enter", filter=~(completion_is_selected | is_searching) & buffer_should_be_handled)
def _(event):
event.current_buffer.validate_and_handle()

Expand Down
38 changes: 38 additions & 0 deletions pykli/repl_read.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from pygments.lexers.sql import SqlLexer
from prompt_toolkit.lexers import PygmentsLexer
from prompt_toolkit.history import FileHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.styles import style_from_pygments_cls
from prompt_toolkit import PromptSession
from prompt_toolkit.validation import Validator

from . import MONOKAI_STYLE, HISTORY_FILE
from .completer import pykli_completer
from .keybindgings import pykli_keys


def is_sql_valid(text):
return True


input_validator = Validator.from_callable(
is_sql_valid,
error_message='This input is not valid ksqlDB syntax',
move_cursor_to_end=True
)


def prompt_continuation(width, line_number, is_soft_wrap): return " "


def pykli_prompt():
return PromptSession(
lexer=PygmentsLexer(SqlLexer),
style=style_from_pygments_cls(MONOKAI_STYLE), include_default_pygments_style=False,
history=FileHistory(HISTORY_FILE), auto_suggest=AutoSuggestFromHistory(),
completer=pykli_completer(),
key_bindings=pykli_keys(),
multiline=True, prompt_continuation=prompt_continuation, validator=input_validator,
enable_open_in_editor=True,
)

0 comments on commit 45078c1

Please sign in to comment.