diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 732217e..788c820 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,9 @@ name: CI -on: [push, pull_request] +on: + push: + branches: + - main + pull_request: defaults: run: shell: bash diff --git a/CHANGES.md b/CHANGES.md index e187d9c..c63bf87 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Release Notes +## 0.11.0 + +Fix commands with an argv0 of "python" on Windows and add support for argv0 being a Python script. + ## 0.10.4 Fix `-p` / `--parallel` handling of tasks - no longer flatten them. diff --git a/README.md b/README.md index 13134c7..36b9986 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,11 @@ greet = ["python", "-c", "import os; print(f'Hello from {os.getcwd()!r}.')"] More on execution in various environments [below](#Execution), but you can run the greet command with, for example `uv run dev-cmd greet`. +There are two special argv0's you can use in your commands: +1. "python": This will be mapped to the Python interpreter that is executing `dev-cmd`. +2. A file name ending in ".py": This will be assumed to be a python script, and it will be run using + the Python interpreter that is executing `dev-cmd`. + You can define as many commands as you want. They will all run from the project root directory (the directory containing the `pyproject.toml` the commands are defined in) and accept no arguments besides those defined in the command. You can gain further control over the command by defining it diff --git a/dev_cmd/__init__.py b/dev_cmd/__init__.py index 481345f..d1566dc 100644 --- a/dev_cmd/__init__.py +++ b/dev_cmd/__init__.py @@ -1,4 +1,4 @@ # Copyright 2024 John Sirois. # Licensed under the Apache License, Version 2.0 (see LICENSE). -__version__ = "0.10.4" +__version__ = "0.11.0" diff --git a/dev_cmd/invoke.py b/dev_cmd/invoke.py index 216433c..7e563ed 100644 --- a/dev_cmd/invoke.py +++ b/dev_cmd/invoke.py @@ -174,6 +174,11 @@ async def _invoke_command( if USE_COLOR and not any(color_env in env for color_env in ("PYTHON_COLORS", "NO_COLOR")): env.setdefault("FORCE_COLOR", "1") + if args[0].endswith(".py"): + args.insert(0, sys.executable) + elif "python" == args[0]: + args[0] = sys.executable + process = await asyncio.create_subprocess_exec( args[0], *args[1:], diff --git a/pyproject.toml b/pyproject.toml index b8f5bb0..3fafe3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,6 +60,7 @@ include = ["dev_cmd*"] [dependency-groups] dev = [ + "ansicolors", "mypy", "pytest", "ruff", diff --git a/tests/test_exec.py b/tests/test_exec.py new file mode 100644 index 0000000..e43cf2d --- /dev/null +++ b/tests/test_exec.py @@ -0,0 +1,114 @@ +# Copyright 2025 John Sirois. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +import subprocess +from pathlib import Path, PurePath +from textwrap import dedent + +import colors +import pytest +from pytest import MonkeyPatch + + +@pytest.fixture +def project_dir() -> PurePath: + return PurePath( + subprocess.run( + args=["git", "rev-parse", "--show-toplevel"], + cwd=PurePath(__file__).parent, + stdout=subprocess.PIPE, + text=True, + check=True, + ).stdout.strip() + ) + + +@pytest.fixture +def pyproject_toml(monkeypatch: MonkeyPatch, tmp_path: Path, project_dir: PurePath) -> Path: + monkeypatch.chdir(tmp_path) + pyproject_toml_file = tmp_path / "pyproject.toml" + pyproject_toml_file.write_text( + dedent( + f""" + [project] + name = "script-test" + version = "0.1.0" + + [dependency-groups] + dev = [ + "ansicolors", + "dev-cmd @ {project_dir.as_posix()}", + ] + """ + ) + ) + return pyproject_toml_file + + +@pytest.fixture +def script(tmp_path: Path) -> PurePath: + script = tmp_path / "script.py" + script.write_text( + dedent( + """\ + import sys + + import colors + + if sys.argv[1].endswith(":"): + color = sys.argv[1][:-1] + args = sys.argv[2:] + else: + color = "cyan" + args = sys.argv[1:] + print(colors.color(" ".join(args), fg=color)) + """ + ) + ) + return script + + +def test_exec_python(script: PurePath, pyproject_toml: Path) -> None: + with pyproject_toml.open("a") as fp: + fp.write( + dedent( + f""" + [tool.dev-cmd.commands.test] + args = ["python", "{script.as_posix()}"] + accepts-extra-args = true + """ + ) + ) + + assert ( + colors.cyan("Slartibartfast 42") + == subprocess.run( + args=["uv", "run", "dev-cmd", "test", "--", "Slartibartfast", "42"], + stdout=subprocess.PIPE, + text=True, + check=True, + ).stdout.strip() + ) + + +def test_exec_script(script: PurePath, pyproject_toml: Path) -> None: + with pyproject_toml.open("a") as fp: + fp.write( + dedent( + f""" + [tool.dev-cmd.commands.test] + args = ["{script.as_posix()}"] + accepts-extra-args = true + """ + ) + ) + + assert ( + colors.magenta("Ford Marvin") + == subprocess.run( + args=["uv", "run", "dev-cmd", "test", "--", "magenta:", "Ford", "Marvin"], + stdout=subprocess.PIPE, + text=True, + check=True, + ).stdout.strip() + ) diff --git a/uv.lock b/uv.lock index c74f6e7..07177d5 100644 --- a/uv.lock +++ b/uv.lock @@ -30,7 +30,7 @@ wheels = [ [[package]] name = "dev-cmd" -version = "0.10.4" +version = "0.11.0" source = { editable = "." } dependencies = [ { name = "aioconsole" }, @@ -42,6 +42,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "ansicolors" }, { name = "mypy" }, { name = "pytest" }, { name = "ruff" }, @@ -60,6 +61,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "ansicolors" }, { name = "mypy" }, { name = "pytest" }, { name = "ruff" },