aboutsummaryrefslogtreecommitdiff
path: root/pydora/utils.py
blob: 5a96ff1e0e1cac17b27f5c55471aa9912be5b86d (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
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
import os
import sys
import getpass
import subprocess


class TerminalPlatformUnsupported(Exception):
    """Platform-specific functionality is not supported

    Raised by code that can not be used to interact with the terminal on this
    platform.
    """

    pass


class Colors:
    def __wrap_with(raw_code):
        @staticmethod
        def inner(text, bold=False):
            code = raw_code
            if bold:
                code = "1;{}".format(code)
            return "\033[{}m{}\033[0m".format(code, text)

        return inner

    red = __wrap_with("31")
    green = __wrap_with("32")
    yellow = __wrap_with("33")
    blue = __wrap_with("34")
    magenta = __wrap_with("35")
    cyan = __wrap_with("36")
    white = __wrap_with("37")


class PosixEchoControl:
    """Posix Console Echo Control Driver

    Uses termios on POSIX compliant platforms to control console echo. Is not
    supported on Windows as termios is not available and will throw a
    TerminalPlatformUnsupported exception if contructed on Windows.
    """

    def __init__(self):
        try:
            import termios

            self.termios = termios
        except ImportError:
            raise TerminalPlatformUnsupported("POSIX not supported")

    def set_echo(self, enabled):
        handle = sys.stdin.fileno()
        if not os.isatty(handle):
            return

        attrs = self.termios.tcgetattr(handle)

        if enabled:
            attrs[3] |= self.termios.ECHO
        else:
            attrs[3] &= ~self.termios.ECHO

        self.termios.tcsetattr(handle, self.termios.TCSANOW, attrs)


class Win32EchoControl:
    """Windows Console Echo Control Driver

    This uses the console API from WinCon.h and ctypes to control console echo
    on Windows clients. It is not possible to construct this class on
    non-Windows systems, on those systems it will throw a
    TerminalPlatformUnsupported exception.
    """

    STD_INPUT_HANDLE = -10
    ENABLE_ECHO_INPUT = 0x4
    DISABLE_ECHO_INPUT = ~ENABLE_ECHO_INPUT

    def __init__(self):
        import ctypes

        if not hasattr(ctypes, "windll"):
            raise TerminalPlatformUnsupported("Windows not supported")

        from ctypes import wintypes

        self.ctypes = ctypes
        self.wintypes = wintypes
        self.kernel32 = ctypes.windll.kernel32

    def _GetStdHandle(self, handle):
        return self.kernel32.GetStdHandle(handle)

    def _GetConsoleMode(self, handle):
        mode = self.wintypes.DWORD()
        self.kernel32.GetConsoleMode(handle, self.ctypes.byref(mode))
        return mode.value

    def _SetConsoleMode(self, handle, value):
        self.kernel32.SetConsoleMode(handle, value)

    def set_echo(self, enabled):
        stdin = self._GetStdHandle(self.STD_INPUT_HANDLE)
        mode = self._GetConsoleMode(stdin)

        if enabled:
            self._SetConsoleMode(stdin, mode | self.ENABLE_ECHO_INPUT)
        else:
            self._SetConsoleMode(stdin, mode & self.DISABLE_ECHO_INPUT)


class Screen:
    def __init__(self):
        try:
            self._echo_driver = PosixEchoControl()
        except TerminalPlatformUnsupported:
            pass

        try:
            self._echo_driver = Win32EchoControl()
        except TerminalPlatformUnsupported:
            pass

        if not self._echo_driver:
            raise TerminalPlatformUnsupported("No supported terminal driver")

    def set_echo(self, enabled):
        self._echo_driver.set_echo(enabled)

    @staticmethod
    def clear():
        sys.stdout.write("\x1b[2J\x1b[H")
        sys.stdout.flush()

    @staticmethod
    def print_error(msg):
        print(Colors.red(msg))

    @staticmethod
    def print_success(msg):
        print(Colors.green(msg))

    @staticmethod
    def get_string(prompt):
        while True:
            value = input(prompt).strip()

            if not value:
                print(Colors.red("Value Required!"))
            else:
                return value

    @staticmethod
    def get_password(prompt="Password: "):
        while True:
            value = getpass.getpass(prompt)

            if not value:
                print(Colors.red("Value Required!"))
            else:
                return value

    @staticmethod
    def get_integer(prompt):
        """Gather user input and convert it to an integer

        Will keep trying till the user enters an interger or until they ^C the
        program.
        """
        while True:
            try:
                return int(input(prompt).strip())
            except ValueError:
                print(Colors.red("Invalid Input!"))


def iterate_forever(func, *args, **kwargs):
    """Iterate over a finite iterator forever

    When the iterator is exhausted will call the function again to generate a
    new iterator and keep iterating.
    """
    output = func(*args, **kwargs)

    while True:
        try:
            playlist_item = next(output)
            playlist_item.prepare_playback()
            yield playlist_item
        except StopIteration:
            output = func(*args, **kwargs)


class SilentPopen(subprocess.Popen):
    """A Popen varient that dumps it's output and error
    """

    def __init__(self, *args, **kwargs):
        self._dev_null = open(os.devnull, "w")
        kwargs["stdin"] = subprocess.PIPE
        kwargs["stdout"] = subprocess.PIPE
        kwargs["stderr"] = self._dev_null
        super().__init__(*args, **kwargs)

    def __del__(self):
        self._dev_null.close()
        super().__del__()