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..8a36cee 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""" @@ -22,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 @@ -35,7 +40,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]} " @@ -54,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/run_command.py b/trogon/run_command.py index afb7c0c..0ccff1c 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 ( @@ -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 = [str(arg) for arg in self._to_cli_args()] if not include_root_command: cli_args = cli_args[1:] @@ -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..bd50cf5 100644 --- a/trogon/trogon.py +++ b/trogon/trogon.py @@ -1,13 +1,15 @@ from __future__ import annotations import os -import shlex +import subprocess +import sys 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 @@ -26,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, @@ -247,14 +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(shlex.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 = shlex.split(self.app_name) - program_name = shlex.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(full_commands, shell=True)) + else: + os.execv(program_path, full_commands) @on(CommandForm.Changed) def update_command_to_run(self, event: CommandForm.Changed):