diff --git a/pyproject.toml b/pyproject.toml index 3d430d7..58d7b02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "toolong" -version = "1.2.2" +version = "1.3.0" description = "A terminal log file viewer / tailer / analyzer" authors = ["Will McGugan "] license = "MIT" @@ -13,7 +13,7 @@ documentation = "https://github.com/textualize/toolong" [tool.poetry.dependencies] python = "^3.8" click = "^8.1.7" -textual = "^0.50.0" +textual = "^0.52.0" typing-extensions = "^4.9.0" [tool.poetry.group.dev.dependencies] diff --git a/src/toolong/cli.py b/src/toolong/cli.py index cc2018e..bd35e89 100644 --- a/src/toolong/cli.py +++ b/src/toolong/cli.py @@ -1,6 +1,9 @@ from __future__ import annotations from importlib.metadata import version +import os +import sys + import click from toolong.ui import UI @@ -19,9 +22,55 @@ ) def run(files: list[str], merge: bool, output_merge: str) -> None: """View / tail / search log files.""" - if not files: + stdin_tty = sys.__stdin__.isatty() + if not files and stdin_tty: ctx = click.get_current_context() click.echo(ctx.get_help()) ctx.exit() - ui = UI(files, merge=merge, save_merge=output_merge) - ui.run() + if stdin_tty: + try: + ui = UI(files, merge=merge, save_merge=output_merge) + ui.run() + except Exception: + pass + else: + import signal + import selectors + import subprocess + import tempfile + + def request_exit(*args) -> None: + """Don't write anything when a signal forces an error.""" + sys.stderr.write("^C") + + signal.signal(signal.SIGINT, request_exit) + signal.signal(signal.SIGTERM, request_exit) + + # Write piped data to a temporary file + with tempfile.NamedTemporaryFile( + mode="w+b", buffering=0, prefix="tl_" + ) as temp_file: + + # Get input directly from /dev/tty to free up stdin + with open("/dev/tty", "rb", buffering=0) as tty_stdin: + # Launch a new process to render the UI + with subprocess.Popen( + [sys.argv[0], temp_file.name], + stdin=tty_stdin, + close_fds=True, + env={**os.environ, "TEXTUAL_ALLOW_SIGNALS": "1"}, + ) as process: + + # Current process copies from stdin to the temp file + selector = selectors.SelectSelector() + selector.register(sys.stdin.fileno(), selectors.EVENT_READ) + + while process.poll() is None: + for _, event in selector.select(0.1): + if process.poll() is not None: + break + if event & selectors.EVENT_READ: + if line := os.read(sys.stdin.fileno(), 1024 * 64): + temp_file.write(line) + else: + break