File: interface.py

package info (click to toggle)
python-userpath 1.9.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 228 kB
  • sloc: python: 771; makefile: 21; sh: 16
file content (167 lines) | stat: -rw-r--r-- 5,881 bytes parent folder | download
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