2
# This file is part of Checkbox.
4
# Copyright 2008 Canonical Ltd.
6
# Checkbox is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation, either version 3 of the License, or
9
# (at your option) any later version.
11
# Checkbox is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
24
from gettext import gettext as _
26
from checkbox.user_interface import (UserInterface, ANSWER_TO_STATUS,
27
ALL_ANSWERS, YES_ANSWER, NO_ANSWER, SKIP_ANSWER)
33
SKIP_ANSWER: _("skip")}
35
OPTION_TO_ANSWER = dict((o, a) for a, o in ANSWER_TO_OPTION.items())
39
"""Command line dialog wrapper."""
41
def __init__(self, text):
46
sys.stdout.write(text)
49
def put_line(self, line):
50
self.put("%s\n" % line)
52
def put_newline(self):
55
def get(self, label=None, limit=1, separator=termios.CEOT):
60
fileno = sys.stdin.fileno()
61
saved_attributes = termios.tcgetattr(fileno)
62
attributes = termios.tcgetattr(fileno)
63
attributes[3] &= ~(termios.ICANON | termios.ECHO)
64
attributes[6][termios.VMIN] = 1
65
attributes[6][termios.VTIME] = 0
66
termios.tcsetattr(fileno, termios.TCSANOW, attributes)
71
while len(input) < limit:
72
ch = sys.stdin.read(1)
73
if ord(ch) == separator:
75
elif ord(ch) == 0o33: # ESC
77
elif ord(ch) == termios.CERASE or ord(ch) == 0o10:
81
elif ord(ch) == termios.CKILL:
82
self.put("\010 \010" * len(input))
96
termios.tcsetattr(fileno, termios.TCSANOW, saved_attributes)
103
self.put_line(self.text)
106
class CLIChoiceDialog(CLIDialog):
108
def __init__(self, text):
109
super(CLIChoiceDialog, self).__init__(text)
113
def get(self, *args, **kwargs):
114
response = super(CLIChoiceDialog, self).get(*args, **kwargs)
116
return self.keys.index(response[0])
120
def run(self, label=None, defaults=[]):
126
if len(self.keys) <= 1:
127
self.get(_("Press any key to continue..."))
132
for key, option in zip(self.keys, self.options):
133
default = "*" if option in defaults else " "
134
self.put_line("%s %s: %s" % (default, key, option))
136
response = self.get(_("Please choose (%s): ") %
137
("/".join(self.keys)))
141
if label is not None:
144
except KeyboardInterrupt:
148
def add_option(self, option, key=None):
150
keys = option.lower() + \
151
string.ascii_letters + \
155
keys = keys.replace(' ', '')
156
keys = keys.replace('+', '')
159
if key not in self.keys:
161
self.keys.append(key)
162
self.options.append(option)
165
class CLIReportDialog(CLIDialog):
169
STATUS = {'pass': '[0;32m{0}[0m',
170
'fail': '[0;31m{0}[0m'}
172
def __init__(self, text, results):
173
super(CLIReportDialog, self).__init__(text)
174
self.results = results
178
Show root of the tree
179
and provide the ability to further display subtress
183
self._display(title, root)
185
def _is_suite(self, root):
187
Return True if root contains a suite
188
that is, a job containing other jobs
190
return all(issubclass(type(value), dict)
191
for value in root.values())
193
def _display(self, title, root):
195
Display dialog until user decides to exit
196
(recursively for subtrees)
207
def add_option(option, key=None):
210
and generate automatic key value
214
key = string.ascii_lowercase[len(keys)]
216
options.append(option)
218
for job_name, job_data in sorted(root.items()):
219
if self._is_suite(job_data):
221
self.put_line('{key}: {option}'
222
.format(key=keys[-1],
225
job_status = job_data.get('status')
226
status_string = (self.STATUS.get(job_status, '{0}')
228
self.put_line(' {name} [{status}]'
229
.format(name=job_name,
230
status=status_string))
232
add_option(_("Space when finished"), " ")
233
self.put_line('{key}: {option}'
234
.format(key=keys[-1],
237
response = self.get(_("Please choose (%s): ") % ("/".join(keys)))
241
selected_option = options[keys.index(response)]
246
# Display new menu with the contents of the selected option
247
self._display(selected_option, root[selected_option])
249
# Exit from this menu display
250
# (display again parent menu or exit)
254
class CLITextDialog(CLIDialog):
257
separator = termios.CEOT
259
def run(self, label=None):
265
return self.get(label, self.limit, self.separator)
266
except KeyboardInterrupt:
271
class CLILineDialog(CLITextDialog):
274
separator = ord("\n")
277
class Twirly(object):
280
self.twirlies = "-\\|/"
283
next_twirly = self.twirlies[self.index]
284
self.index = (self.index + 1) % len(self.twirlies)
288
class CLIProgressDialog(CLIDialog):
289
"""Command line progress dialog wrapper."""
291
def __init__(self, text):
292
super(CLIProgressDialog, self).__init__(text)
293
self.progress_count = 0
294
self.twirly = Twirly()
296
def set(self, progress=None):
297
self.progress_count = (self.progress_count + 1) % 5
298
if self.progress_count:
302
self.put("\r%u%%" % (progress * 100))
305
self.put(self.twirly.next())
310
class CLIInterface(UserInterface):
312
def _toggle_results(self, key, options, results):
313
if isinstance(results, dict):
318
if isinstance(options[key], dict):
320
elif isinstance(options[key], (list, tuple)):
325
for k in options[key]:
326
self._toggle_results(k, options[key], results[key])
328
elif isinstance(results, (list, tuple)):
334
def show_progress_start(self, text):
335
self.progress = CLIProgressDialog(text)
338
def show_progress_pulse(self):
341
def show_text(self, text, previous=None, next=None):
342
dialog = CLIChoiceDialog(text)
345
def show_entry(self, text, value, showSubmitToHexr=False, label=None, previous=None, next=None):
346
dialog = CLILineDialog(text)
348
return (dialog.run(), False)
350
def show_check(self, text, options=[], default=[]):
351
dialog = CLIChoiceDialog(text)
352
for option in options:
353
dialog.add_option(option)
355
dialog.add_option(_("Space when finished"), " ")
359
response = dialog.run(defaults=results)
360
if response >= len(options):
363
result = options[response]
364
self._toggle_results(result, options, results)
366
return (results, False)
368
def show_radio(self, text, options=[], default=None):
369
dialog = CLIChoiceDialog(text)
370
for option in options:
371
dialog.add_option(option)
373
# Show options dialog
374
response = dialog.run()
375
return options[response]
377
def show_tree(self, text, options={}, default={}, deselect_warning=""):
378
keys = sorted(options.keys())
380
dialog = CLIChoiceDialog(text)
382
dialog.add_option(option)
384
dialog.add_option(_("Combine with character above to expand node"),
386
dialog.add_option(_("Space when finished"), " ")
391
response = dialog.run(defaults=results)
392
if response > len(options):
395
elif response == len(options):
396
response = dialog.get()
407
result = keys[response]
409
self._toggle_results(result, options, results)
413
dialog.visible = False
415
branch_results = results.get(result, {})
416
self.show_tree(result, options[result], branch_results)
417
if branch_results and result not in results:
418
results[result] = branch_results
422
def show_report(self, text, results):
424
Show test case results in a tree hierarchy
426
dialog = CLIReportDialog(text, results)
429
def show_test(self, test, runner):
430
options = list([ANSWER_TO_OPTION[a] for a in ALL_ANSWERS])
431
if "command" in test:
432
options.append(_("test"))
434
if re.search(r"\$output", test["description"]):
435
output = runner(test)[1]
441
description = string.Template(test["description"]).substitute({
442
"output": output.strip()})
443
dialog = CLIChoiceDialog(description)
445
for option in options:
446
dialog.add_option(option, option[0])
448
# Get option from dialog
449
response = dialog.run()
450
option = options[response]
451
if response < len(ALL_ANSWERS):
454
output = runner(test)[1]
456
options[-1] = _("test again")
458
answer = OPTION_TO_ANSWER[option]
459
if answer == NO_ANSWER:
460
text = _("Further information:")
461
dialog = CLITextDialog(text)
462
test["data"] = dialog.run(_("Please type here and press"
463
" Ctrl-D when finished:\n"))
467
test["status"] = ANSWER_TO_STATUS[answer]
469
def show_info(self, text, options=[], default=None):
470
return self.show_radio(text, options, default)
472
def show_error(self, primary_text,
473
secondary_text=None, detailed_text=None):
474
text = filter(None, [primary_text, secondary_text, detailed_text])
475
text = '\n'.join(text)
476
dialog = CLIChoiceDialog("Error: %s" % text)