forked from python-poetry/poetry
-
Notifications
You must be signed in to change notification settings - Fork 0
/
shell.py
172 lines (135 loc) · 5.09 KB
/
shell.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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
from __future__ import annotations
import os
import shutil
import signal
import subprocess
import sys
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
import pexpect
from shellingham import ShellDetectionFailure
from shellingham import detect_shell
from poetry.utils._compat import WINDOWS
if TYPE_CHECKING:
from poetry.utils.env import VirtualEnv
class Shell:
"""
Represents the current shell.
"""
_shell = None
def __init__(self, name: str, path: str) -> None:
self._name = name
self._path = path
@property
def name(self) -> str:
return self._name
@property
def path(self) -> str:
return self._path
@classmethod
def get(cls) -> Shell:
"""
Retrieve the current shell.
"""
if cls._shell is not None:
return cls._shell
try:
name, path = detect_shell(os.getpid())
except (RuntimeError, ShellDetectionFailure):
shell = None
if os.name == "posix":
shell = os.environ.get("SHELL")
elif os.name == "nt":
shell = os.environ.get("COMSPEC")
if not shell:
raise RuntimeError("Unable to detect the current shell.")
name, path = Path(shell).stem, shell
cls._shell = cls(name, path)
return cls._shell
def activate(self, env: VirtualEnv) -> int | None:
activate_script = self._get_activate_script()
if WINDOWS:
bin_path = env.path / "Scripts"
# Python innstalled via msys2 on Windows might produce a POSIX-like venv
# See https://github.com/python-poetry/poetry/issues/8638
bin_dir = "Scripts" if bin_path.exists() else "bin"
else:
bin_dir = "bin"
activate_path = env.path / bin_dir / activate_script
# mypy requires using sys.platform instead of WINDOWS constant
# in if statements to properly type check on Windows
if sys.platform == "win32":
args = None
if self._name in ("powershell", "pwsh"):
args = ["-NoExit", "-File", str(activate_path)]
elif self._name == "cmd":
# /K will execute the bat file and
# keep the cmd process from terminating
args = ["/K", str(activate_path)]
if args:
completed_proc = subprocess.run([self.path, *args])
return completed_proc.returncode
else:
# If no args are set, execute the shell within the venv
# This activates it, but there could be some features missing:
# deactivate command might not work
# shell prompt will not be modified.
return env.execute(self._path)
import shlex
terminal = shutil.get_terminal_size()
cmd = f"{self._get_source_command()} {shlex.quote(str(activate_path))}"
with env.temp_environ():
if self._name == "nu":
args = ["-e", cmd]
elif self._name == "fish":
args = ["-i", "--init-command", cmd]
else:
args = ["-i"]
c = pexpect.spawn(
self._path, args, dimensions=(terminal.lines, terminal.columns)
)
if self._name in ["zsh"]:
c.setecho(False)
if self._name == "zsh":
# Under ZSH the source command should be invoked in zsh's bash emulator
quoted_activate_path = shlex.quote(str(activate_path))
c.sendline(f"emulate bash -c {shlex.quote(f'. {quoted_activate_path}')}")
elif self._name == "xonsh":
c.sendline(f"vox activate {shlex.quote(str(env.path))}")
elif self._name in ["nu", "fish"]:
# If this is nu or fish, we don't want to send the activation command to the
# command line since we already ran it via the shell's invocation.
pass
else:
c.sendline(cmd)
def resize(sig: Any, data: Any) -> None:
terminal = shutil.get_terminal_size()
c.setwinsize(terminal.lines, terminal.columns)
signal.signal(signal.SIGWINCH, resize)
# Interact with the new shell.
c.interact(escape_character=None)
c.close()
sys.exit(c.exitstatus)
def _get_activate_script(self) -> str:
if self._name == "fish":
suffix = ".fish"
elif self._name in ("csh", "tcsh"):
suffix = ".csh"
elif self._name in ("powershell", "pwsh"):
suffix = ".ps1"
elif self._name == "cmd":
suffix = ".bat"
elif self._name == "nu":
suffix = ".nu"
else:
suffix = ""
return "activate" + suffix
def _get_source_command(self) -> str:
if self._name in ("fish", "csh", "tcsh"):
return "source"
elif self._name == "nu":
return "overlay use"
return "."
def __repr__(self) -> str:
return f'{self.__class__.__name__}("{self._name}", "{self._path}")'