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
|
import os
import platform
from datetime import datetime
from io import open
from .shells import DEFAULT_SHELLS, SHELLS
from .utils import ensure_parent_dir_exists, get_flat_output, get_parent_process_name, location_in_path, normpath
try:
import winreg
except ImportError:
try:
import _winreg as winreg
except ImportError:
winreg = None
class WindowsInterface:
def __init__(self, **kwargs):
pass
@staticmethod
def _get_new_path():
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Environment', 0, winreg.KEY_READ) as key:
return winreg.QueryValueEx(key, 'PATH')[0]
def location_in_new_path(self, location, check=False):
locations = normpath(location).split(os.pathsep)
new_path = self._get_new_path()
for location in locations:
if not location_in_path(location, new_path):
if check:
raise Exception('Unable to find `{}` in:\n{}'.format(location, new_path))
else:
return False
else:
return True
def put(self, location, front=True, check=False, **kwargs):
import ctypes
import ctypes.wintypes
location = normpath(location)
head, tail = (location, self._get_new_path()) if front else (self._get_new_path(), location)
new_path = '{}{}{}'.format(head, os.pathsep, tail)
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Environment', 0, winreg.KEY_WRITE) as key:
winreg.SetValueEx(key, 'PATH', 0, winreg.REG_EXPAND_SZ, new_path)
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessagetimeoutw
# https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-settingchange
ctypes.windll.user32.SendMessageTimeoutW(
0xFFFF, # HWND_BROADCAST
0x1A, # WM_SETTINGCHANGE
0, # must be NULL
'Environment',
0x0002, # SMTO_ABORTIFHUNG
5000, # milliseconds
ctypes.wintypes.DWORD(),
)
return self.location_in_new_path(location, check=check)
class UnixInterface:
def __init__(self, shells=None, all_shells=False, home=None):
if shells:
all_shells = False
else:
if all_shells:
shells = sorted(SHELLS)
else:
shells = [self.detect_shell()]
shells = [os.path.basename(shell).lower() for shell in shells if shell]
shells = [shell for shell in shells if shell in SHELLS]
if not shells:
shells = DEFAULT_SHELLS
# De-dup and retain order
deduplicated_shells = set()
selected_shells = []
for shell in shells:
if shell not in deduplicated_shells:
deduplicated_shells.add(shell)
selected_shells.append(shell)
self.shells = [SHELLS[shell](home) for shell in selected_shells]
self.shells_to_verify = [SHELLS[shell](home) for shell in DEFAULT_SHELLS] if all_shells else self.shells
@classmethod
def detect_shell(cls):
# First, try to see what spawned this process
shell = get_parent_process_name().lower()
if shell in SHELLS:
return shell
# Then, search for environment variables that are known to be set by certain shells
# NOTE: This likely does not work when not directly in the shell
if 'BASH_VERSION' in os.environ:
return 'bash'
# Finally, try global environment
shell = os.path.basename(os.environ.get('SHELL', '')).lower()
if shell in SHELLS:
return shell
def location_in_new_path(self, location, check=False):
locations = normpath(location).split(os.pathsep)
for shell in self.shells_to_verify:
for show_path_command in shell.show_path_commands():
new_path = get_flat_output(show_path_command)
for location in locations:
if not location_in_path(location, new_path):
if check:
raise Exception(
'Unable to find `{}` in the output of `{}`:\n{}'.format(
location, show_path_command, new_path
)
)
else:
return False
else:
return True
def put(self, location, front=True, app_name=None, check=False):
location = normpath(location)
app_name = app_name or 'userpath'
for shell in self.shells:
for file, contents in shell.config(location, front=front).items():
try:
ensure_parent_dir_exists(file)
if os.path.exists(file):
with open(file, 'r', encoding='utf-8') as f:
lines = f.readlines()
else:
lines = []
if any(contents in line for line in lines):
continue
lines.append(
u'\n{} Created by `{}` on {}\n'.format(
shell.comment_starter, app_name, datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
)
)
lines.append(u'{}\n'.format(contents))
with open(file, 'w', encoding='utf-8') as f:
f.writelines(lines)
except Exception:
continue
return self.location_in_new_path(location, check=check)
__default_interface = WindowsInterface if os.name == 'nt' or platform.system() == 'Windows' else UnixInterface
class Interface(__default_interface):
pass
|