From 38cf9c6bfca3c3e3ba222ace3926b1cf2f0c3103 Mon Sep 17 00:00:00 2001 From: bigbirdcode Date: Tue, 28 Jan 2025 22:25:00 +0100 Subject: [PATCH 1/5] Fix when option value is not a string --- trogon/run_command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trogon/run_command.py b/trogon/run_command.py index afb7c0c..58c0204 100644 --- a/trogon/run_command.py +++ b/trogon/run_command.py @@ -84,7 +84,7 @@ def to_cli_args(self, include_root_command: bool = False) -> list[str]: Returns: A list of strings that can be passed to subprocess.run to execute the command. """ - cli_args = self._to_cli_args() + cli_args = list(str(arg) for arg in self._to_cli_args()) if not include_root_command: cli_args = cli_args[1:] From 21440111dbe98587da0b1a457bfede1cfe9db1ca Mon Sep 17 00:00:00 2001 From: bigbirdcode Date: Tue, 28 Jan 2025 22:41:53 +0100 Subject: [PATCH 2/5] Replace shlex to oslex and add it to dependencies --- pyproject.toml | 1 + trogon/detect_run_string.py | 5 +++-- trogon/run_command.py | 4 ++-- trogon/trogon.py | 8 ++++---- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4b34cbe..26bf57e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ classifiers = [ python = "^3.9.1" textual = ">=0.61.0" click = ">=8.0.0" +oslex = ">=0.1.3" typer = {version = ">=0.9.0", optional = true} [tool.poetry.extras] diff --git a/trogon/detect_run_string.py b/trogon/detect_run_string.py index 6e757a7..937eb1d 100644 --- a/trogon/detect_run_string.py +++ b/trogon/detect_run_string.py @@ -1,10 +1,11 @@ from __future__ import annotations import os -import shlex import sys from types import ModuleType +import oslex + def get_orig_argv() -> list[str]: """Polyfil for orig_argv""" @@ -35,7 +36,7 @@ def detect_run_string(_main: ModuleType = sys.modules["__main__"]) -> str: and os.path.exists(f"{path}.exe") ): # Executed a file, like "python app.py". - file_path = shlex.quote(os.path.basename(path)) + file_path = oslex.quote(os.path.basename(path)) argv = get_orig_argv() if argv[0] == "python": prefix = f"{argv[0]} " diff --git a/trogon/run_command.py b/trogon/run_command.py index 58c0204..5379707 100644 --- a/trogon/run_command.py +++ b/trogon/run_command.py @@ -1,11 +1,11 @@ from __future__ import annotations import itertools -import shlex from collections import defaultdict from dataclasses import dataclass, field from typing import Any, List, Optional +import oslex from rich.text import Text from trogon.introspect import ( @@ -231,7 +231,7 @@ def to_cli_string(self, include_root_command: bool = False) -> Text: text_renderables: list[Text] = [] for arg in args: text_renderables.append( - Text(shlex.quote(str(arg))) + Text(oslex.quote(str(arg))) if arg != ValueNotSupplied() else Text("???", style="bold black on red") ) diff --git a/trogon/trogon.py b/trogon/trogon.py index e36eb51..c1e8bf5 100644 --- a/trogon/trogon.py +++ b/trogon/trogon.py @@ -1,13 +1,13 @@ from __future__ import annotations import os -import shlex from importlib import metadata # type: ignore from pathlib import Path from typing import Any from webbrowser import open as open_url import click +import oslex from rich.console import Console from rich.highlighter import ReprHighlighter from rich.text import Text @@ -248,11 +248,11 @@ def run( console = Console() if self.post_run_command and self.execute_on_exit: console.print( - f"Running [b cyan]{self.app_name} {' '.join(shlex.quote(s) for s in self.post_run_command)}[/]" + f"Running [b cyan]{self.app_name} {' '.join(oslex.quote(s) for s in self.post_run_command)}[/]" ) - split_app_name = shlex.split(self.app_name) - program_name = shlex.split(self.app_name)[0] + split_app_name = oslex.split(self.app_name) + program_name = oslex.split(self.app_name)[0] arguments = [*split_app_name, *self.post_run_command] os.execvp(program_name, arguments) From 4d289532044e63bcf3e95e9d59adc5db6e8b1581 Mon Sep 17 00:00:00 2001 From: bigbirdcode Date: Sat, 1 Feb 2025 18:43:51 +0100 Subject: [PATCH 3/5] On Windows call subrocess instead of execvp --- trogon/trogon.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/trogon/trogon.py b/trogon/trogon.py index c1e8bf5..714f50c 100644 --- a/trogon/trogon.py +++ b/trogon/trogon.py @@ -1,6 +1,8 @@ from __future__ import annotations import os +import subprocess +import sys from importlib import metadata # type: ignore from pathlib import Path from typing import Any @@ -254,7 +256,10 @@ def run( split_app_name = oslex.split(self.app_name) program_name = oslex.split(self.app_name)[0] arguments = [*split_app_name, *self.post_run_command] - os.execvp(program_name, arguments) + if sys.platform == "win32": + sys.exit(subprocess.call(arguments, shell=True)) + else: + os.execvp(program_name, arguments) @on(CommandForm.Changed) def update_command_to_run(self, event: CommandForm.Changed): From 981db79785e785d9f163fa3d4e22ca85b0e15cbf Mon Sep 17 00:00:00 2001 From: bigbirdcode Date: Sat, 1 Feb 2025 20:38:28 +0100 Subject: [PATCH 4/5] Change functionality to call command with exact original way --- trogon/detect_run_string.py | 14 +++++++++++++- trogon/trogon.py | 15 +++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/trogon/detect_run_string.py b/trogon/detect_run_string.py index 937eb1d..8a36cee 100644 --- a/trogon/detect_run_string.py +++ b/trogon/detect_run_string.py @@ -23,7 +23,11 @@ def get_orig_argv() -> list[str]: def detect_run_string(_main: ModuleType = sys.modules["__main__"]) -> str: - """This is a slightly modified version of a function from Click.""" + """Determine the command used to run the program, for use in preview text only. + + This doesn't try to be too precise. + This is a slightly modified version of a function from Click. + """ path = sys.argv[0] # The value of __package__ indicates how Python was called. It may @@ -55,3 +59,11 @@ def detect_run_string(_main: ModuleType = sys.modules["__main__"]) -> str: py_module = f"{py_module}.{name}" return f"python -m {py_module.lstrip('.')}" + + +def exact_run_commands() -> list[str]: + """Get the calling command to re-execute it as it was called originally.""" + num_of_script_args = len(sys.argv) - 1 + # sys.orig_argv is nearly perfect, just contain a wrong interpreter in case of a venv + calling_command_args = get_orig_argv()[1:-num_of_script_args] + return [sys.executable, *calling_command_args] diff --git a/trogon/trogon.py b/trogon/trogon.py index 714f50c..bd50cf5 100644 --- a/trogon/trogon.py +++ b/trogon/trogon.py @@ -28,7 +28,7 @@ ) from textual.widgets.tree import TreeNode -from trogon.detect_run_string import detect_run_string +from trogon.detect_run_string import detect_run_string, exact_run_commands from trogon.introspect import ( introspect_click_app, CommandSchema, @@ -249,17 +249,16 @@ def run( if self.post_run_command: console = Console() if self.post_run_command and self.execute_on_exit: + run_commands = exact_run_commands() + program_path = run_commands[0] + full_commands = [*run_commands, *self.post_run_command] console.print( - f"Running [b cyan]{self.app_name} {' '.join(oslex.quote(s) for s in self.post_run_command)}[/]" + f"Running [b cyan]{' '.join(oslex.quote(s) for s in full_commands)}[/]" ) - - split_app_name = oslex.split(self.app_name) - program_name = oslex.split(self.app_name)[0] - arguments = [*split_app_name, *self.post_run_command] if sys.platform == "win32": - sys.exit(subprocess.call(arguments, shell=True)) + sys.exit(subprocess.call(full_commands, shell=True)) else: - os.execvp(program_name, arguments) + os.execv(program_path, full_commands) @on(CommandForm.Changed) def update_command_to_run(self, event: CommandForm.Changed): From 20e4e786a5f666b7e74d27ab561dea41840379ef Mon Sep 17 00:00:00 2001 From: Peter V <48657553+bigbirdcode@users.noreply.github.com> Date: Sat, 28 Jun 2025 10:10:44 +0200 Subject: [PATCH 5/5] Review finding fix in run_command.py Co-authored-by: Dane Hillard --- trogon/run_command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trogon/run_command.py b/trogon/run_command.py index 5379707..0ccff1c 100644 --- a/trogon/run_command.py +++ b/trogon/run_command.py @@ -84,7 +84,7 @@ def to_cli_args(self, include_root_command: bool = False) -> list[str]: Returns: A list of strings that can be passed to subprocess.run to execute the command. """ - cli_args = list(str(arg) for arg in self._to_cli_args()) + cli_args = [str(arg) for arg in self._to_cli_args()] if not include_root_command: cli_args = cli_args[1:]