-
Notifications
You must be signed in to change notification settings - Fork 52
/
_shutil.py
109 lines (89 loc) · 3.04 KB
/
_shutil.py
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
from __future__ import annotations
import contextlib
import dataclasses
import os
import stat
import subprocess
import sys
from typing import TYPE_CHECKING, ClassVar
from ._logging import logger
if TYPE_CHECKING:
from collections.abc import Generator, Iterable
__all__ = ["Run"]
def __dir__() -> list[str]:
return __all__
@dataclasses.dataclass
class Run:
env: dict[str, str] | None = None
cwd: os.PathLike[str] | None = None
# Stores last printout, for cleaner debug logging
_prev_env: ClassVar[dict[str, str]] = {}
def live(self, *args: str | os.PathLike[str]) -> None:
"""
Runs code and prints the results live.
"""
self._run(args, capture=False)
def capture(
self, *args: str | os.PathLike[str]
) -> subprocess.CompletedProcess[str]:
"""
Runs a command and captures the result.
"""
return self._run(args, capture=True)
def _run(
self,
args: Iterable[str | os.PathLike[str]],
*,
capture: bool,
) -> subprocess.CompletedProcess[str]:
options = [
os.fspath(arg) if isinstance(arg, os.PathLike) else arg for arg in args
]
if self.env:
if not self._prev_env:
type(self)._prev_env = self.env.copy()
msg = "\n ".join(f"{k}={v!r}" for k, v in sorted(self.env.items()))
logger.debug("RUNENV:\n {}", msg)
else:
msg = "\n ".join(
f"{self._key_diff(k)} {k}={self.env.get(k, '<unset>')!r}"
for k in sorted(self.env.keys() | self._prev_env.keys())
if self._prev_env.get(k, None) != self.env.get(k, None)
)
logger.debug("RUNENV - changes since last run only:\n {}", msg)
type(self)._prev_env = self.env.copy()
logger.info("RUN: {}", " ".join(options))
return subprocess.run(
options,
text=True,
check=True,
capture_output=capture,
env=self.env,
cwd=self.cwd,
)
def _key_diff(self, k: str) -> str:
assert self.env
if k in self.env and k not in self._prev_env:
return "+"
if k in self._prev_env and k not in self.env:
return "-"
return " "
def _fix_all_permissions(directory: str) -> None:
"""
Makes sure the write permission is set. Only run this on Windows.
"""
with os.scandir(directory) as it:
for entry in it:
if entry.is_dir():
_fix_all_permissions(entry.path)
continue
mode = stat.S_IMODE(entry.stat().st_mode)
if not mode & stat.S_IWRITE:
os.chmod(entry.path, mode | stat.S_IWRITE) # noqa: PTH101
@contextlib.contextmanager
def fix_win_37_all_permissions(tmpdir: str) -> Generator[None, None, None]:
try:
yield
finally:
if sys.version_info < (3, 8) and sys.platform.startswith("win"):
_fix_all_permissions(tmpdir)