]>
Commit | Line | Data |
---|---|---|
bd50a52b | 1 | from threading import Lock |
819e0531 | 2 | from .utils import supports_terminal_sequences, TERMINAL_SEQUENCES |
3 | ||
bd50a52b | 4 | |
819e0531 | 5 | class MultilinePrinterBase: |
6 | def __init__(self, stream=None, lines=1): | |
7 | self.stream = stream | |
8 | self.maximum = lines - 1 | |
bd50a52b | 9 | |
bd50a52b THD |
10 | def __enter__(self): |
11 | return self | |
12 | ||
13 | def __exit__(self, *args): | |
14 | self.end() | |
15 | ||
16 | def print_at_line(self, text, pos): | |
17 | pass | |
18 | ||
19 | def end(self): | |
20 | pass | |
21 | ||
819e0531 | 22 | def _add_line_number(self, text, line): |
23 | if self.maximum: | |
24 | return f'{line + 1}: {text}' | |
25 | return text | |
bd50a52b | 26 | |
bd50a52b | 27 | |
819e0531 | 28 | class QuietMultilinePrinter(MultilinePrinterBase): |
29 | pass | |
bd50a52b | 30 | |
bd50a52b | 31 | |
819e0531 | 32 | class MultilineLogger(MultilinePrinterBase): |
33 | def print_at_line(self, text, pos): | |
34 | # stream is the logger object, not an actual stream | |
35 | self.stream.debug(self._add_line_number(text, pos)) | |
bd50a52b | 36 | |
bd50a52b | 37 | |
819e0531 | 38 | class BreaklineStatusPrinter(MultilinePrinterBase): |
39 | def print_at_line(self, text, pos): | |
40 | self.stream.write(self._add_line_number(text, pos) + '\n') | |
bd50a52b | 41 | |
819e0531 | 42 | |
43 | class MultilinePrinter(MultilinePrinterBase): | |
44 | def __init__(self, stream=None, lines=1, preserve_output=True): | |
45 | super().__init__(stream, lines) | |
46 | self.preserve_output = preserve_output | |
47 | self._lastline = self._lastlength = 0 | |
48 | self._movelock = Lock() | |
49 | self._HAVE_FULLCAP = supports_terminal_sequences(self.stream) | |
50 | ||
51 | def lock(func): | |
52 | def wrapper(self, *args, **kwargs): | |
53 | with self._movelock: | |
54 | return func(self, *args, **kwargs) | |
55 | return wrapper | |
bd50a52b THD |
56 | |
57 | def _move_cursor(self, dest): | |
819e0531 | 58 | current = min(self._lastline, self.maximum) |
59 | self.stream.write('\r') | |
60 | distance = dest - current | |
61 | if distance < 0: | |
62 | self.stream.write(TERMINAL_SEQUENCES['UP'] * -distance) | |
63 | elif distance > 0: | |
64 | self.stream.write(TERMINAL_SEQUENCES['DOWN'] * distance) | |
65 | self._lastline = dest | |
66 | ||
67 | @lock | |
68 | def print_at_line(self, text, pos): | |
69 | if self._HAVE_FULLCAP: | |
70 | self._move_cursor(pos) | |
71 | self.stream.write(TERMINAL_SEQUENCES['ERASE_LINE']) | |
72 | self.stream.write(text) | |
bd50a52b | 73 | return |
bd50a52b | 74 | |
819e0531 | 75 | text = self._add_line_number(text, pos) |
76 | textlen = len(text) | |
77 | if self._lastline == pos: | |
78 | # move cursor at the start of progress when writing to same line | |
79 | self.stream.write('\r') | |
80 | if self._lastlength > textlen: | |
81 | text += ' ' * (self._lastlength - textlen) | |
82 | self._lastlength = textlen | |
83 | else: | |
84 | # otherwise, break the line | |
85 | self.stream.write('\n') | |
86 | self._lastlength = textlen | |
87 | self.stream.write(text) | |
88 | self._lastline = pos | |
bd50a52b | 89 | |
819e0531 | 90 | @lock |
bd50a52b | 91 | def end(self): |
819e0531 | 92 | # move cursor to the end of the last line, and write line break |
93 | # so that other to_screen calls can precede | |
94 | if self._HAVE_FULLCAP: | |
bd50a52b | 95 | self._move_cursor(self.maximum) |
819e0531 | 96 | if self.preserve_output: |
bd50a52b | 97 | self.stream.write('\n') |
819e0531 | 98 | return |
bd50a52b | 99 | |
819e0531 | 100 | if self._HAVE_FULLCAP: |
101 | self.stream.write( | |
102 | TERMINAL_SEQUENCES['ERASE_LINE'] | |
103 | + f'{TERMINAL_SEQUENCES["UP"]}{TERMINAL_SEQUENCES["ERASE_LINE"]}' * self.maximum) | |
104 | else: | |
105 | self.stream.write(' ' * self._lastlength) |