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",
|
"Programming Language :: Python :: 3.10",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.poetry.scripts]
|
||||||
|
textual = "textual.cli.cli:run"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.7"
|
python = "^3.7"
|
||||||
rich = "^12.0.0"
|
rich = "^12.0.0"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import inspect
|
|||||||
from rich.console import RenderableType
|
from rich.console import RenderableType
|
||||||
|
|
||||||
__all__ = ["log", "panic"]
|
__all__ = ["log", "panic"]
|
||||||
|
__version__ = "0.1.15"
|
||||||
|
|
||||||
|
|
||||||
def log(*args: object, verbosity: int = 0, **kwargs) -> None:
|
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 pathlib import Path
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
|
from rich.containers import Renderables
|
||||||
from rich.style import Style
|
from rich.style import Style
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
|
import textual
|
||||||
|
|
||||||
if sys.version_info >= (3, 8):
|
if sys.version_info >= (3, 8):
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
else:
|
else:
|
||||||
@@ -21,10 +24,31 @@ from rich.rule import Rule
|
|||||||
from rich.segment import Segment, Segments
|
from rich.segment import Segment, Segments
|
||||||
from rich.table import Table
|
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
|
"""Renderable representing a single log message
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -71,8 +95,8 @@ class DevtoolsLogMessage:
|
|||||||
yield Segments(self.segments)
|
yield Segments(self.segments)
|
||||||
|
|
||||||
|
|
||||||
class DevtoolsInternalMessage:
|
class DevConsoleNotice:
|
||||||
"""Renderable for messages written by the devtools server itself
|
"""Renderable for messages written by the devtools console itself
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
message (str): The message to display
|
message (str): The message to display
|
||||||
@@ -80,7 +104,7 @@ class DevtoolsInternalMessage:
|
|||||||
Determines colors used to render the message and the perceived importance.
|
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.message = message
|
||||||
self.level = level
|
self.level = level
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ async def _on_startup(app: Application) -> None:
|
|||||||
|
|
||||||
def _run_devtools() -> None:
|
def _run_devtools() -> None:
|
||||||
app = _make_devtools_aiohttp_app()
|
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(
|
def _make_devtools_aiohttp_app(
|
||||||
|
|||||||
@@ -14,7 +14,11 @@ from aiohttp.web_ws import WebSocketResponse
|
|||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.markup import escape
|
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"}
|
QUEUEABLE_TYPES = {"client_log", "client_spillover"}
|
||||||
|
|
||||||
@@ -38,6 +42,7 @@ class DevtoolsService:
|
|||||||
async def start(self):
|
async def start(self):
|
||||||
"""Starts devtools tasks"""
|
"""Starts devtools tasks"""
|
||||||
self.size_poll_task = asyncio.create_task(self._console_size_poller())
|
self.size_poll_task = asyncio.create_task(self._console_size_poller())
|
||||||
|
self.console.print(DevConsoleHeader())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def clients_connected(self) -> bool:
|
def clients_connected(self) -> bool:
|
||||||
@@ -167,7 +172,7 @@ class ClientHandler:
|
|||||||
decoded_segments = base64.b64decode(encoded_segments)
|
decoded_segments = base64.b64decode(encoded_segments)
|
||||||
segments = pickle.loads(decoded_segments)
|
segments = pickle.loads(decoded_segments)
|
||||||
self.service.console.print(
|
self.service.console.print(
|
||||||
DevtoolsLogMessage(
|
DevConsoleLog(
|
||||||
segments=segments,
|
segments=segments,
|
||||||
path=path,
|
path=path,
|
||||||
line_number=line_number,
|
line_number=line_number,
|
||||||
@@ -176,7 +181,7 @@ class ClientHandler:
|
|||||||
)
|
)
|
||||||
elif type == "client_spillover":
|
elif type == "client_spillover":
|
||||||
spillover = int(message_json["payload"]["spillover"])
|
spillover = int(message_json["payload"]["spillover"])
|
||||||
info_renderable = DevtoolsInternalMessage(
|
info_renderable = DevConsoleNotice(
|
||||||
f"Discarded {spillover} messages", level="warning"
|
f"Discarded {spillover} messages", level="warning"
|
||||||
)
|
)
|
||||||
self.service.console.print(info_renderable)
|
self.service.console.print(info_renderable)
|
||||||
@@ -198,9 +203,7 @@ class ClientHandler:
|
|||||||
|
|
||||||
if self.request.remote:
|
if self.request.remote:
|
||||||
self.service.console.print(
|
self.service.console.print(
|
||||||
DevtoolsInternalMessage(
|
DevConsoleNotice(f"Client '{escape(self.request.remote)}' connected")
|
||||||
f"Client '{escape(self.request.remote)}' connected"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await self.service.send_server_info(client_handler=self)
|
await self.service.send_server_info(client_handler=self)
|
||||||
@@ -223,20 +226,16 @@ class ClientHandler:
|
|||||||
await self.incoming_queue.put(message_json)
|
await self.incoming_queue.put(message_json)
|
||||||
elif message.type == WSMsgType.ERROR:
|
elif message.type == WSMsgType.ERROR:
|
||||||
self.service.console.print(
|
self.service.console.print(
|
||||||
DevtoolsInternalMessage(
|
DevConsoleNotice("Websocket error occurred", level="error")
|
||||||
"Websocket error occurred", level="error"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
self.service.console.print(
|
self.service.console.print(DevConsoleNotice(str(error), level="error"))
|
||||||
DevtoolsInternalMessage(str(error), level="error")
|
|
||||||
)
|
|
||||||
finally:
|
finally:
|
||||||
if self.request.remote:
|
if self.request.remote:
|
||||||
self.service.console.print(
|
self.service.console.print(
|
||||||
"\n",
|
"\n",
|
||||||
DevtoolsInternalMessage(
|
DevConsoleNotice(
|
||||||
f"Client '{escape(self.request.remote)}' disconnected"
|
f"Client '{escape(self.request.remote)}' disconnected"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from rich.console import Console
|
|||||||
from rich.segment import Segment
|
from rich.segment import Segment
|
||||||
|
|
||||||
from tests.utilities.render import wait_for_predicate
|
from tests.utilities.render import wait_for_predicate
|
||||||
from textual.devtools.renderables import DevtoolsLogMessage, DevtoolsInternalMessage
|
from textual.devtools.renderables import DevConsoleLog, DevConsoleNotice
|
||||||
|
|
||||||
TIMESTAMP = 1649166819
|
TIMESTAMP = 1649166819
|
||||||
WIDTH = 40
|
WIDTH = 40
|
||||||
@@ -31,7 +31,7 @@ def console():
|
|||||||
|
|
||||||
@time_machine.travel(TIMESTAMP)
|
@time_machine.travel(TIMESTAMP)
|
||||||
def test_log_message_render(console):
|
def test_log_message_render(console):
|
||||||
message = DevtoolsLogMessage(
|
message = DevConsoleLog(
|
||||||
[Segment("content")],
|
[Segment("content")],
|
||||||
path="abc/hello.py",
|
path="abc/hello.py",
|
||||||
line_number=123,
|
line_number=123,
|
||||||
@@ -62,7 +62,7 @@ def test_log_message_render(console):
|
|||||||
|
|
||||||
|
|
||||||
def test_internal_message_render(console):
|
def test_internal_message_render(console):
|
||||||
message = DevtoolsInternalMessage("hello")
|
message = DevConsoleNotice("hello")
|
||||||
rule = next(iter(message.__rich_console__(console, console.options)))
|
rule = next(iter(message.__rich_console__(console, console.options)))
|
||||||
assert rule.title == "hello"
|
assert rule.title == "hello"
|
||||||
assert rule.characters == "─"
|
assert rule.characters == "─"
|
||||||
|
|||||||
Reference in New Issue
Block a user