mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Textual console CLI command
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
13
src/textual/cli/cli.py
Normal 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()
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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 == "─"
|
||||
|
||||
Reference in New Issue
Block a user