summaryrefslogtreecommitdiff
path: root/src/tox/execute/request.py
blob: 1a2d9158ecdd16b7c515967dc914a878c6b685ec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
"""Module declaring a command execution request."""
from __future__ import annotations

import sys
from enum import Enum
from pathlib import Path
from typing import Sequence


class StdinSource(Enum):
    OFF = 0  #: input disabled
    USER = 1  #: input via the standard input
    API = 2  #: input via programmatic access

    @staticmethod
    def user_only() -> StdinSource:
        """:return: ``USER`` if the standard input is tty type else ``OFF``"""
        return StdinSource.USER if sys.stdin.isatty() else StdinSource.OFF


class ExecuteRequest:
    """Defines a commands execution request"""

    def __init__(
        self,
        cmd: Sequence[str | Path],
        cwd: Path,
        env: dict[str, str],
        stdin: StdinSource,
        run_id: str,
        allow: list[str] | None = None,
    ) -> None:
        """
        Create a new execution request.

        :param cmd: the command to run
        :param cwd: the current working directory
        :param env: the environment variables
        :param stdin: the type of standard input allowed
        :param run_id: an id to identify this run
        """
        if len(cmd) == 0:
            raise ValueError("cannot execute an empty command")
        self.cmd: list[str] = [str(i) for i in cmd]  #: the command to run
        self.cwd = cwd  #: the working directory to use
        self.env = env  #: the environment variables to use
        self.stdin = stdin  #: the type of standard input interaction allowed
        self.run_id = run_id  #: an id to identify this run
        if allow is not None and "*" in allow:
            allow = None  # if we allow everything we can just disable the check
        self.allow = allow

    @property
    def shell_cmd(self) -> str:
        """:return: the command to run as a shell command"""
        try:
            exe = str(Path(self.cmd[0]).relative_to(self.cwd))
        except ValueError:
            exe = self.cmd[0]
        _cmd = [exe]
        _cmd.extend(self.cmd[1:])
        return shell_cmd(_cmd)

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(cmd={self.cmd!r}, cwd={self.cwd!r}, env=..., stdin={self.stdin!r})"


def shell_cmd(cmd: Sequence[str]) -> str:
    if sys.platform == "win32":  # pragma: win32 cover
        from subprocess import list2cmdline

        return list2cmdline(tuple(str(x) for x in cmd))
    else:  # pragma: win32 no cover
        from shlex import quote as shlex_quote

        return " ".join(shlex_quote(str(x)) for x in cmd)


__all__ = (
    "StdinSource",
    "ExecuteRequest",
    "shell_cmd",
)