mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge pull request #448 from Textualize/features-env
Add TEXTUAL features env var
This commit is contained in:
@@ -7,5 +7,5 @@
|
||||
|
||||
.list-item {
|
||||
height: 8;
|
||||
background: dark_blue;
|
||||
background: darkblue;
|
||||
}
|
||||
|
||||
@@ -19,15 +19,15 @@ class XTermParser(Parser[events.Event]):
|
||||
|
||||
_re_sgr_mouse = re.compile(r"\x1b\[<(\d+);(\d+);(\d+)([Mm])")
|
||||
|
||||
def __init__(self, sender: MessageTarget, more_data: Callable[[], bool]) -> None:
|
||||
def __init__(
|
||||
self, sender: MessageTarget, more_data: Callable[[], bool], debug: bool = False
|
||||
) -> None:
|
||||
self.sender = sender
|
||||
self.more_data = more_data
|
||||
self.last_x = 0
|
||||
self.last_y = 0
|
||||
|
||||
self._debug_log_file = (
|
||||
open("keys.log", "wt") if "TEXTUAL_DEBUG" in os.environ else None
|
||||
)
|
||||
self._debug_log_file = open("keys.log", "wt") if debug else None
|
||||
|
||||
super().__init__()
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ from .devtools.client import DevtoolsClient, DevtoolsConnectionError, DevtoolsLo
|
||||
from .devtools.redirect_output import StdoutRedirector
|
||||
from .dom import DOMNode
|
||||
from .driver import Driver
|
||||
from .features import parse_features, FeatureFlag
|
||||
from .file_monitor import FileMonitor
|
||||
from .geometry import Offset, Region, Size
|
||||
from .layouts.dock import Dock
|
||||
@@ -89,7 +90,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
screen: bool = True,
|
||||
driver_class: Type[Driver] | None = None,
|
||||
log: str = "",
|
||||
log_verbosity: int = 1,
|
||||
@@ -98,19 +98,21 @@ class App(Generic[ReturnType], DOMNode):
|
||||
css: str | None = None,
|
||||
watch_css: bool = True,
|
||||
):
|
||||
"""The Textual Application base class
|
||||
"""Textual application base class
|
||||
|
||||
Args:
|
||||
console (Console, optional): A Rich Console. Defaults to None.
|
||||
screen (bool, optional): Enable full-screen application mode. Defaults to True.
|
||||
driver_class (Type[Driver], optional): Driver class, or None to use default. Defaults to None.
|
||||
title (str, optional): Title of the application. Defaults to "Textual Application".
|
||||
driver_class (Type[Driver] | None, optional): Driver class or ``None`` to auto-detect. Defaults to None.
|
||||
log (str, optional): Path to log file, or "" to disable. Defaults to "".
|
||||
log_verbosity (int, optional): Log verbosity from 0-3. Defaults to 1.
|
||||
title (str, optional): Default title of the application. Defaults to "Textual Application".
|
||||
css_file (str | None, optional): Path to CSS or ``None`` for no CSS file. Defaults to None.
|
||||
css (str | None, optional): CSS code to parse, or ``None`` for no literal CSS. Defaults to None.
|
||||
watch_css (bool, optional): Watch CSS for changes. Defaults to True.
|
||||
"""
|
||||
self.console = Console(
|
||||
file=sys.__stdout__, markup=False, highlight=False, emoji=False
|
||||
)
|
||||
self.error_console = Console(markup=False, stderr=True)
|
||||
self._screen = screen
|
||||
self.driver_class = driver_class or self.get_driver_class()
|
||||
self._title = title
|
||||
self._screen_stack: list[Screen] = []
|
||||
@@ -160,6 +162,8 @@ class App(Generic[ReturnType], DOMNode):
|
||||
if css is not None:
|
||||
self.css = css
|
||||
|
||||
self.features: frozenset[FeatureFlag] = parse_features(os.getenv("TEXTUAL", ""))
|
||||
|
||||
self.registry: set[MessagePump] = set()
|
||||
|
||||
self.devtools = DevtoolsClient()
|
||||
@@ -173,6 +177,16 @@ class App(Generic[ReturnType], DOMNode):
|
||||
background: Reactive[str] = Reactive("black")
|
||||
dark = Reactive(False)
|
||||
|
||||
@property
|
||||
def devtools_enabled(self) -> bool:
|
||||
"""Check if devtools are enabled."""
|
||||
return "devtools" in self.features
|
||||
|
||||
@property
|
||||
def debug(self) -> bool:
|
||||
"""Check if debug mode is enabled."""
|
||||
return "debug" in self.features
|
||||
|
||||
def exit(self, result: ReturnType | None = None) -> None:
|
||||
"""Exit the app, and return the supplied result.
|
||||
|
||||
@@ -495,7 +509,7 @@ class App(Generic[ReturnType], DOMNode):
|
||||
log("---")
|
||||
log(f"driver={self.driver_class}")
|
||||
|
||||
if os.getenv("TEXTUAL_DEVTOOLS") == "1":
|
||||
if self.devtools_enabled:
|
||||
try:
|
||||
await self.devtools.connect()
|
||||
self.log(f"Connected to devtools ({self.devtools.url})")
|
||||
@@ -504,7 +518,6 @@ class App(Generic[ReturnType], DOMNode):
|
||||
try:
|
||||
if self.css_file is not None:
|
||||
self.stylesheet.read(self.css_file)
|
||||
self.stylesheet.parse()
|
||||
if self.css is not None:
|
||||
self.stylesheet.add_source(
|
||||
self.css, path=f"<{self.__class__.__name__}>"
|
||||
|
||||
@@ -15,9 +15,12 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class Driver(ABC):
|
||||
def __init__(self, console: "Console", target: "MessageTarget") -> None:
|
||||
def __init__(
|
||||
self, console: "Console", target: "MessageTarget", debug: bool = False
|
||||
) -> None:
|
||||
self.console = console
|
||||
self._target = target
|
||||
self._debug = debug
|
||||
self._loop = asyncio.get_event_loop()
|
||||
self._mouse_down_time = time()
|
||||
|
||||
|
||||
@@ -27,8 +27,10 @@ from .._profile import timer
|
||||
class LinuxDriver(Driver):
|
||||
"""Powers display and input for Linux / MacOS"""
|
||||
|
||||
def __init__(self, console: "Console", target: "MessageTarget") -> None:
|
||||
super().__init__(console, target)
|
||||
def __init__(
|
||||
self, console: "Console", target: "MessageTarget", debug: bool = False
|
||||
) -> None:
|
||||
super().__init__(console, target, debug)
|
||||
self.fileno = sys.stdin.fileno()
|
||||
self.attrs_before: list[Any] | None = None
|
||||
self.exit_event = Event()
|
||||
@@ -189,7 +191,7 @@ class LinuxDriver(Driver):
|
||||
return True
|
||||
return False
|
||||
|
||||
parser = XTermParser(self._target, more_data)
|
||||
parser = XTermParser(self._target, more_data, self._debug)
|
||||
feed = parser.feed
|
||||
|
||||
utf8_decoder = getincrementaldecoder("utf-8")().decode
|
||||
|
||||
@@ -17,8 +17,10 @@ if TYPE_CHECKING:
|
||||
class WindowsDriver(Driver):
|
||||
"""Powers display and input for Windows."""
|
||||
|
||||
def __init__(self, console: "Console", target: "MessageTarget") -> None:
|
||||
super().__init__(console, target)
|
||||
def __init__(
|
||||
self, console: "Console", target: "MessageTarget", debug: bool = False
|
||||
) -> None:
|
||||
super().__init__(console, target, debug)
|
||||
self.in_fileno = sys.stdin.fileno()
|
||||
self.out_fileno = sys.stdout.fileno()
|
||||
|
||||
|
||||
34
src/textual/features.py
Normal file
34
src/textual/features.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
from typing import cast
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from typing import Final, Literal
|
||||
else:
|
||||
from typing_extensions import Final, Literal
|
||||
|
||||
FEATURES: Final = {"devtools", "debug"}
|
||||
|
||||
FeatureFlag = Literal["devtools", "debug"]
|
||||
|
||||
|
||||
def parse_features(features: str) -> frozenset[FeatureFlag]:
|
||||
"""Parse features env var
|
||||
|
||||
Args:
|
||||
features (str): Comma seprated feature flags
|
||||
|
||||
Returns:
|
||||
frozenset[FeatureFlag]: A frozen set of known features.
|
||||
"""
|
||||
|
||||
features_set = frozenset(
|
||||
set(
|
||||
feature.strip().lower()
|
||||
for feature in features.split(",")
|
||||
if feature.strip()
|
||||
).intersection(FEATURES)
|
||||
)
|
||||
return cast("frozenset[FeatureFlag]", features_set)
|
||||
29
tests/test_features.py
Normal file
29
tests/test_features.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from textual.app import App
|
||||
|
||||
|
||||
def test_textual_env_var(monkeypatch):
|
||||
monkeypatch.setenv("TEXTUAL", "")
|
||||
app = App()
|
||||
assert app.features == set()
|
||||
assert app.devtools_enabled is False
|
||||
assert app.debug is False
|
||||
|
||||
monkeypatch.setenv("TEXTUAL", "devtools")
|
||||
app = App()
|
||||
assert app.features == {"devtools"}
|
||||
assert app.devtools_enabled is True
|
||||
assert app.debug is False
|
||||
|
||||
monkeypatch.setenv("TEXTUAL", "devtools,debug")
|
||||
app = App()
|
||||
assert app.features == {"devtools", "debug"}
|
||||
assert app.devtools_enabled is True
|
||||
assert app.debug is True
|
||||
|
||||
monkeypatch.setenv("TEXTUAL", "devtools, debug")
|
||||
app = App()
|
||||
assert app.features == {"devtools", "debug"}
|
||||
assert app.devtools_enabled is True
|
||||
assert app.debug is True
|
||||
Reference in New Issue
Block a user