-
Notifications
You must be signed in to change notification settings - Fork 105
Expand file tree
/
Copy path__init__.py
More file actions
178 lines (144 loc) · 5.89 KB
/
Copy path__init__.py
File metadata and controls
178 lines (144 loc) · 5.89 KB
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
import sys
from blessed import Terminal
from inquirer import errors
from inquirer import events
from inquirer import themes
from inquirer.render.console._text import Text
from inquirer.render.console._editor import Editor
from inquirer.render.console._password import Password
from inquirer.render.console._confirm import Confirm
from inquirer.render.console._list import List
from inquirer.render.console._keyed_list import KeyedList
from inquirer.render.console._checkbox import Checkbox
from inquirer.render.console._path import Path
class ConsoleRender:
def __init__(self, event_generator=None, theme=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self._event_gen = event_generator or events.KeyEventGenerator()
self.terminal = Terminal()
self._previous_error = None
self._position = 0
self._theme = theme or themes.Default()
def render(self, question, answers=None):
question.answers = answers or {}
if question.ignore:
return question.default
clazz = self.render_factory(question.kind)
render = clazz(question, terminal=self.terminal, theme=self._theme, show_default=question.show_default)
self.clear_eos()
try:
return self._event_loop(render)
finally:
print("")
def _event_loop(self, render):
try:
while True:
self._relocate()
self._print_status_bar(render)
self._print_header(render)
self._print_options(render)
self._process_input(render)
self._force_initial_column()
except errors.EndOfInput as e:
self._go_to_end(render)
return e.selection
def _print_status_bar(self, render):
if self._previous_error is None:
self.clear_bottombar()
return
self.render_error(self._previous_error)
self._previous_error = None
def _print_options(self, render):
for message, symbol, color in render.get_options():
if hasattr(message, "decode"): # python 2
message = message.decode("utf-8")
self.print_line(" {color}{s} {m}{t.normal}", m=message, color=color, s=symbol)
def _print_header(self, render):
base = render.get_header()
header = base[: self.width - 9] + "..." if len(base) > self.width - 6 else base
default_value = " ({color}{default}{normal})".format(
default=render.question.default, color=self._theme.Question.default_color, normal=self.terminal.normal
)
show_default = render.question.default and render.show_default
header += default_value if show_default else ""
msg_template = (
"{t.move_up}{t.clear_eol}{tq.brackets_color}[" "{tq.mark_color}?{tq.brackets_color}]{t.normal} {msg}"
)
# ensure any user input with { or } will not cause a formatting error
escaped_current_value = str(render.get_current_value()).replace("{", "{{").replace("}", "}}")
self.print_str(
f"\n{msg_template}: {escaped_current_value}",
msg=header,
lf=not render.title_inline,
tq=self._theme.Question,
)
def _process_input(self, render):
try:
ev = self._event_gen.next()
if isinstance(ev, events.KeyPressed):
render.process_input(ev.value)
except errors.ValidationError as e:
self._previous_error = e.value
except errors.EndOfInput as e:
try:
render.question.validate(e.selection)
raise
except errors.ValidationError as e:
self._previous_error = render.handle_validation_error(e)
def _relocate(self):
print(self._position * self.terminal.move_up, end="")
self._force_initial_column()
self._position = 0
def _go_to_end(self, render):
positions = len(list(render.get_options())) - self._position
if positions > 0:
print(self._position * self.terminal.move_down, end="")
self._position = 0
def _force_initial_column(self):
self.print_str("\r")
def render_error(self, message):
if message:
symbol = ">> "
size = len(symbol) + 1
length = len(message)
message = message.rstrip()
message = message if length + size < self.width else message[: self.width - (size + 3)] + "..."
self.render_in_bottombar(
"{t.red}{s}{t.normal}{t.bold}{msg}{t.normal} ".format(msg=message, s=symbol, t=self.terminal)
)
def render_in_bottombar(self, message):
with self.terminal.location(0, self.height - 2):
self.clear_eos()
self.print_str(message)
def clear_bottombar(self):
with self.terminal.location(0, self.height - 2):
self.clear_eos()
def render_factory(self, question_type):
matrix = {
"text": Text,
"editor": Editor,
"password": Password,
"confirm": Confirm,
"list": List,
"keyed_list": KeyedList,
"checkbox": Checkbox,
"path": Path,
}
if question_type not in matrix:
raise errors.UnknownQuestionTypeError()
return matrix.get(question_type)
def print_line(self, base, lf=True, **kwargs):
self.print_str(base + self.terminal.clear_eol(), lf=lf, **kwargs)
def print_str(self, base, lf=False, **kwargs):
if lf:
self._position += 1
print(base.format(t=self.terminal, **kwargs), end="\n" if lf else "")
sys.stdout.flush()
def clear_eos(self):
print(self.terminal.clear_eos(), end="")
@property
def width(self):
return self.terminal.width or 80
@property
def height(self):
return self.terminal.width or 24