Textual console CLI command

This commit is contained in:
Darren Burns
2022-04-19 11:55:50 +01:00
parent 1d01029cd7
commit 2ff1c9d64a
7 changed files with 66 additions and 22 deletions

View File

@@ -17,6 +17,9 @@ classifiers = [
"Programming Language :: Python :: 3.10",
]
[tool.poetry.scripts]
textual = "textual.cli.cli:run"
[tool.poetry.dependencies]
python = "^3.7"
rich = "^12.0.0"

View File

@@ -3,6 +3,7 @@ import inspect
from rich.console import RenderableType
__all__ = ["log", "panic"]
__version__ = "0.1.15"
def log(*args: object, verbosity: int = 0, **kwargs) -> None:

13
src/textual/cli/cli.py Normal file
View File

@@ -0,0 +1,13 @@
import click
from textual.devtools.server import _run_devtools
@click.group()
def run():
pass
@run.command(help="Run the Textual Devtools console")
def console():
_run_devtools()

View File

@@ -5,9 +5,12 @@ from datetime import datetime, timezone
from pathlib import Path
from typing import Iterable
from rich.containers import Renderables
from rich.style import Style
from rich.text import Text
import textual
if sys.version_info >= (3, 8):
from typing import Literal
else:
@@ -21,10 +24,31 @@ from rich.rule import Rule
from rich.segment import Segment, Segments
from rich.table import Table
DevtoolsMessageLevel = Literal["info", "warning", "error"]
DevConsoleMessageLevel = Literal["info", "warning", "error"]
class DevtoolsLogMessage:
class DevConsoleHeader:
def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
lines = Renderables(
[
f"[bold]Textual Development Console [#b169dd]v{textual.__version__}",
"[#967fa3]Run a Textual app with the environment variable [b]TEXTUAL_DEVTOOLS=1[/] to connect.",
"[#967fa3]Press [b]Ctrl+C[/] to quit.",
]
)
render_options = options.update(width=options.max_width - 4)
lines = console.render_lines(lines, render_options)
new_line = Segment("\n")
padding = Segment("", Style.parse("#b169dd"))
for line in lines:
yield padding
yield from line
yield new_line
class DevConsoleLog:
"""Renderable representing a single log message
Args:
@@ -71,8 +95,8 @@ class DevtoolsLogMessage:
yield Segments(self.segments)
class DevtoolsInternalMessage:
"""Renderable for messages written by the devtools server itself
class DevConsoleNotice:
"""Renderable for messages written by the devtools console itself
Args:
message (str): The message to display
@@ -80,7 +104,7 @@ class DevtoolsInternalMessage:
Determines colors used to render the message and the perceived importance.
"""
def __init__(self, message: str, *, level: DevtoolsMessageLevel = "info") -> None:
def __init__(self, message: str, *, level: DevConsoleMessageLevel = "info") -> None:
self.message = message
self.level = level

View File

@@ -38,7 +38,11 @@ async def _on_startup(app: Application) -> None:
def _run_devtools() -> None:
app = _make_devtools_aiohttp_app()
run_app(app, port=DEVTOOLS_PORT)
def noop_print(_: str):
return None
run_app(app, port=DEVTOOLS_PORT, print=noop_print)
def _make_devtools_aiohttp_app(

View File

@@ -14,7 +14,11 @@ from aiohttp.web_ws import WebSocketResponse
from rich.console import Console
from rich.markup import escape
from textual.devtools.renderables import DevtoolsLogMessage, DevtoolsInternalMessage
from textual.devtools.renderables import (
DevConsoleLog,
DevConsoleNotice,
DevConsoleHeader,
)
QUEUEABLE_TYPES = {"client_log", "client_spillover"}
@@ -38,6 +42,7 @@ class DevtoolsService:
async def start(self):
"""Starts devtools tasks"""
self.size_poll_task = asyncio.create_task(self._console_size_poller())
self.console.print(DevConsoleHeader())
@property
def clients_connected(self) -> bool:
@@ -167,7 +172,7 @@ class ClientHandler:
decoded_segments = base64.b64decode(encoded_segments)
segments = pickle.loads(decoded_segments)
self.service.console.print(
DevtoolsLogMessage(
DevConsoleLog(
segments=segments,
path=path,
line_number=line_number,
@@ -176,7 +181,7 @@ class ClientHandler:
)
elif type == "client_spillover":
spillover = int(message_json["payload"]["spillover"])
info_renderable = DevtoolsInternalMessage(
info_renderable = DevConsoleNotice(
f"Discarded {spillover} messages", level="warning"
)
self.service.console.print(info_renderable)
@@ -198,9 +203,7 @@ class ClientHandler:
if self.request.remote:
self.service.console.print(
DevtoolsInternalMessage(
f"Client '{escape(self.request.remote)}' connected"
)
DevConsoleNotice(f"Client '{escape(self.request.remote)}' connected")
)
try:
await self.service.send_server_info(client_handler=self)
@@ -223,20 +226,16 @@ class ClientHandler:
await self.incoming_queue.put(message_json)
elif message.type == WSMsgType.ERROR:
self.service.console.print(
DevtoolsInternalMessage(
"Websocket error occurred", level="error"
)
DevConsoleNotice("Websocket error occurred", level="error")
)
break
except Exception as error:
self.service.console.print(
DevtoolsInternalMessage(str(error), level="error")
)
self.service.console.print(DevConsoleNotice(str(error), level="error"))
finally:
if self.request.remote:
self.service.console.print(
"\n",
DevtoolsInternalMessage(
DevConsoleNotice(
f"Client '{escape(self.request.remote)}' disconnected"
),
)

View File

@@ -7,7 +7,7 @@ from rich.console import Console
from rich.segment import Segment
from tests.utilities.render import wait_for_predicate
from textual.devtools.renderables import DevtoolsLogMessage, DevtoolsInternalMessage
from textual.devtools.renderables import DevConsoleLog, DevConsoleNotice
TIMESTAMP = 1649166819
WIDTH = 40
@@ -31,7 +31,7 @@ def console():
@time_machine.travel(TIMESTAMP)
def test_log_message_render(console):
message = DevtoolsLogMessage(
message = DevConsoleLog(
[Segment("content")],
path="abc/hello.py",
line_number=123,
@@ -62,7 +62,7 @@ def test_log_message_render(console):
def test_internal_message_render(console):
message = DevtoolsInternalMessage("hello")
message = DevConsoleNotice("hello")
rule = next(iter(message.__rich_console__(console, console.options)))
assert rule.title == "hello"
assert rule.characters == ""