~jocave/checkbox/hybrid-amd-gpu-mods

« back to all changes in this revision

Viewing changes to checkbox_cli/cli_interface.py

  • Committer: Zygmunt Krynicki
  • Date: 2013-05-17 13:54:25 UTC
  • mto: This revision was merged to the branch mainline in revision 2130.
  • Revision ID: zygmunt.krynicki@canonical.com-20130517135425-cxcenxx5t0qrtbxd
checkbox-ng: add CheckBoxNG sub-project

CheckBoxNG (or lowercase as checkbox-ng, pypi:checkbox-ng) is a clean
implementation of CheckBox on top of PlainBox. It provides a new
executable, 'checkbox' that has some of the same commands that were
previously implemented in the plainbox package.

In particular CheckBoxNG comes with the 'checkbox sru' command
(the same one as in plainbox). Later on this sub-command will be removed
from plainbox.

CheckBoxNG depends on plainbox >= 0.3

Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# This file is part of Checkbox.
 
3
#
 
4
# Copyright 2008 Canonical Ltd.
 
5
#
 
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.
 
10
#
 
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.
 
15
#
 
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/>.
 
18
#
 
19
import re
 
20
import sys
 
21
import string
 
22
import termios
 
23
 
 
24
from gettext import gettext as _
 
25
 
 
26
from checkbox.user_interface import (UserInterface, ANSWER_TO_STATUS,
 
27
    ALL_ANSWERS, YES_ANSWER, NO_ANSWER, SKIP_ANSWER)
 
28
 
 
29
 
 
30
ANSWER_TO_OPTION = {
 
31
    YES_ANSWER: _("yes"),
 
32
    NO_ANSWER: _("no"),
 
33
    SKIP_ANSWER: _("skip")}
 
34
 
 
35
OPTION_TO_ANSWER = dict((o, a) for a, o in ANSWER_TO_OPTION.items())
 
36
 
 
37
 
 
38
class CLIDialog:
 
39
    """Command line dialog wrapper."""
 
40
 
 
41
    def __init__(self, text):
 
42
        self.text = text
 
43
        self.visible = False
 
44
 
 
45
    def put(self, text):
 
46
        sys.stdout.write(text)
 
47
        sys.stdout.flush()
 
48
 
 
49
    def put_line(self, line):
 
50
        self.put("%s\n" % line)
 
51
 
 
52
    def put_newline(self):
 
53
        self.put("\n")
 
54
 
 
55
    def get(self, label=None, limit=1, separator=termios.CEOT):
 
56
        if label is not None:
 
57
            self.put_newline()
 
58
            self.put(label)
 
59
 
 
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)
 
67
 
 
68
        input = []
 
69
        escape = 0
 
70
        try:
 
71
            while len(input) < limit:
 
72
                ch = sys.stdin.read(1)
 
73
                if ord(ch) == separator:
 
74
                    break
 
75
                elif ord(ch) == 0o33:  # ESC
 
76
                    escape = 1
 
77
                elif ord(ch) == termios.CERASE or ord(ch) == 0o10:
 
78
                    if len(input):
 
79
                        self.put("\010 \010")
 
80
                        del input[-1]
 
81
                elif ord(ch) == termios.CKILL:
 
82
                    self.put("\010 \010" * len(input))
 
83
                    input = []
 
84
                else:
 
85
                    if not escape:
 
86
                        input.append(ch)
 
87
                        self.put(ch)
 
88
                    elif escape == 1:
 
89
                        if ch == "[":
 
90
                            escape = 2
 
91
                        else:
 
92
                            escape = 0
 
93
                    elif escape == 2:
 
94
                        escape = 0
 
95
        finally:
 
96
            termios.tcsetattr(fileno, termios.TCSANOW, saved_attributes)
 
97
 
 
98
        return "".join(input)
 
99
 
 
100
    def show(self):
 
101
        self.visible = True
 
102
        self.put_newline()
 
103
        self.put_line(self.text)
 
104
 
 
105
 
 
106
class CLIChoiceDialog(CLIDialog):
 
107
 
 
108
    def __init__(self, text):
 
109
        super(CLIChoiceDialog, self).__init__(text)
 
110
        self.keys = []
 
111
        self.options = []
 
112
 
 
113
    def get(self, *args, **kwargs):
 
114
        response = super(CLIChoiceDialog, self).get(*args, **kwargs)
 
115
        try:
 
116
            return self.keys.index(response[0])
 
117
        except ValueError:
 
118
            return -1
 
119
 
 
120
    def run(self, label=None, defaults=[]):
 
121
        if not self.visible:
 
122
            self.show()
 
123
 
 
124
        try:
 
125
            # Only one option
 
126
            if len(self.keys) <= 1:
 
127
                self.get(_("Press any key to continue..."))
 
128
                return 0
 
129
            # Multiple choices
 
130
            while True:
 
131
                self.put_newline()
 
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))
 
135
 
 
136
                response = self.get(_("Please choose (%s): ") %
 
137
                                     ("/".join(self.keys)))
 
138
                if response >= 0:
 
139
                    return response
 
140
 
 
141
                if label is not None:
 
142
                    self.put_line(label)
 
143
 
 
144
        except KeyboardInterrupt:
 
145
            self.put_newline()
 
146
            raise
 
147
 
 
148
    def add_option(self, option, key=None):
 
149
        if key is None:
 
150
            keys = option.lower() + \
 
151
                   string.ascii_letters + \
 
152
                   string.digits + \
 
153
                   string.punctuation
 
154
 
 
155
            keys = keys.replace(' ', '')
 
156
            keys = keys.replace('+', '')
 
157
 
 
158
            for key in keys:
 
159
                if key not in self.keys:
 
160
                    break
 
161
        self.keys.append(key)
 
162
        self.options.append(option)
 
163
 
 
164
 
 
165
class CLIReportDialog(CLIDialog):
 
166
    """
 
167
    Display test results
 
168
    """
 
169
    STATUS = {'pass': '{0}',
 
170
              'fail': '{0}'}
 
171
 
 
172
    def __init__(self, text, results):
 
173
        super(CLIReportDialog, self).__init__(text)
 
174
        self.results = results
 
175
 
 
176
    def run(self):
 
177
        """
 
178
        Show root of the tree
 
179
        and provide the ability to further display subtress
 
180
        """
 
181
        root = self.results
 
182
        title = self.text
 
183
        self._display(title, root)
 
184
 
 
185
    def _is_suite(self, root):
 
186
        """
 
187
        Return True if root contains a suite
 
188
        that is, a job containing other jobs
 
189
        """
 
190
        return all(issubclass(type(value), dict)
 
191
                   for value in root.values())
 
192
 
 
193
    def _display(self, title, root):
 
194
        """
 
195
        Display dialog until user decides to exit
 
196
        (recursively for subtrees)
 
197
        """
 
198
        while True:
 
199
            self.put_newline()
 
200
            self.put_newline()
 
201
            self.put_line(title)
 
202
            self.put_newline()
 
203
 
 
204
            keys = []
 
205
            options = []
 
206
 
 
207
            def add_option(option, key=None):
 
208
                """
 
209
                Add option to list
 
210
                and generate automatic key value
 
211
                if not provided
 
212
                """
 
213
                if key is None:
 
214
                    key = string.ascii_lowercase[len(keys)]
 
215
                keys.append(key)
 
216
                options.append(option)
 
217
 
 
218
            for job_name, job_data in sorted(root.items()):
 
219
                if self._is_suite(job_data):
 
220
                    add_option(job_name)
 
221
                    self.put_line('{key}: {option}'
 
222
                                  .format(key=keys[-1],
 
223
                                          option=options[-1]))
 
224
                else:
 
225
                    job_status = job_data.get('status')
 
226
                    status_string = (self.STATUS.get(job_status, '{0}')
 
227
                                     .format(job_status))
 
228
                    self.put_line('   {name} [{status}]'
 
229
                                  .format(name=job_name,
 
230
                                          status=status_string))
 
231
 
 
232
            add_option(_("Space when finished"), " ")
 
233
            self.put_line('{key}: {option}'
 
234
                          .format(key=keys[-1],
 
235
                                  option=options[-1]))
 
236
 
 
237
            response = self.get(_("Please choose (%s): ") % ("/".join(keys)))
 
238
 
 
239
            if response != ' ':
 
240
                try:
 
241
                    selected_option = options[keys.index(response)]
 
242
                except ValueError:
 
243
                    # Display again menu
 
244
                    continue
 
245
 
 
246
                # Display new menu with the contents of the selected option
 
247
                self._display(selected_option, root[selected_option])
 
248
            else:
 
249
                # Exit from this menu display
 
250
                # (display again parent menu or exit)
 
251
                break
 
252
 
 
253
 
 
254
class CLITextDialog(CLIDialog):
 
255
 
 
256
    limit = 255
 
257
    separator = termios.CEOT
 
258
 
 
259
    def run(self, label=None):
 
260
        if not self.visible:
 
261
            self.show()
 
262
 
 
263
        self.put_newline()
 
264
        try:
 
265
            return self.get(label, self.limit, self.separator)
 
266
        except KeyboardInterrupt:
 
267
            self.put_newline()
 
268
            raise
 
269
 
 
270
 
 
271
class CLILineDialog(CLITextDialog):
 
272
 
 
273
    limit = 80
 
274
    separator = ord("\n")
 
275
 
 
276
 
 
277
class Twirly(object):
 
278
    def __init__(self):
 
279
        self.index = 0
 
280
        self.twirlies = "-\\|/"
 
281
 
 
282
    def next(self):
 
283
        next_twirly = self.twirlies[self.index]
 
284
        self.index = (self.index + 1) % len(self.twirlies)
 
285
        return next_twirly
 
286
 
 
287
 
 
288
class CLIProgressDialog(CLIDialog):
 
289
    """Command line progress dialog wrapper."""
 
290
 
 
291
    def __init__(self, text):
 
292
        super(CLIProgressDialog, self).__init__(text)
 
293
        self.progress_count = 0
 
294
        self.twirly = Twirly()
 
295
 
 
296
    def set(self, progress=None):
 
297
        self.progress_count = (self.progress_count + 1) % 5
 
298
        if self.progress_count:
 
299
            return
 
300
 
 
301
        if progress != None:
 
302
            self.put("\r%u%%" % (progress * 100))
 
303
        else:
 
304
            self.put("\b\b")
 
305
            self.put(self.twirly.next())
 
306
            self.put(" ")
 
307
        sys.stdout.flush()
 
308
 
 
309
 
 
310
class CLIInterface(UserInterface):
 
311
 
 
312
    def _toggle_results(self, key, options, results):
 
313
        if isinstance(results, dict):
 
314
            if key in results:
 
315
                del results[key]
 
316
 
 
317
            elif key in options:
 
318
                if isinstance(options[key], dict):
 
319
                    results[key] = {}
 
320
                elif isinstance(options[key], (list, tuple)):
 
321
                    results[key] = []
 
322
                else:
 
323
                    results[key] = None
 
324
 
 
325
                for k in options[key]:
 
326
                    self._toggle_results(k, options[key], results[key])
 
327
 
 
328
        elif isinstance(results, (list, tuple)):
 
329
            if key in results:
 
330
                results.remove(key)
 
331
            elif key in options:
 
332
                results.append(key)
 
333
 
 
334
    def show_progress_start(self, text):
 
335
        self.progress = CLIProgressDialog(text)
 
336
        self.progress.show()
 
337
 
 
338
    def show_progress_pulse(self):
 
339
        self.progress.set()
 
340
 
 
341
    def show_text(self, text, previous=None, next=None):
 
342
        dialog = CLIChoiceDialog(text)
 
343
        dialog.run()
 
344
 
 
345
    def show_entry(self, text, value, showSubmitToHexr=False, label=None, previous=None, next=None):
 
346
        dialog = CLILineDialog(text)
 
347
 
 
348
        return (dialog.run(), False)
 
349
 
 
350
    def show_check(self, text, options=[], default=[]):
 
351
        dialog = CLIChoiceDialog(text)
 
352
        for option in options:
 
353
            dialog.add_option(option)
 
354
 
 
355
        dialog.add_option(_("Space when finished"), " ")
 
356
 
 
357
        results = default
 
358
        while True:
 
359
            response = dialog.run(defaults=results)
 
360
            if response >= len(options):
 
361
                break
 
362
 
 
363
            result = options[response]
 
364
            self._toggle_results(result, options, results)
 
365
 
 
366
        return (results, False)
 
367
 
 
368
    def show_radio(self, text, options=[], default=None):
 
369
        dialog = CLIChoiceDialog(text)
 
370
        for option in options:
 
371
            dialog.add_option(option)
 
372
 
 
373
        # Show options dialog
 
374
        response = dialog.run()
 
375
        return options[response]
 
376
 
 
377
    def show_tree(self, text, options={}, default={}, deselect_warning=""):
 
378
        keys = sorted(options.keys())
 
379
 
 
380
        dialog = CLIChoiceDialog(text)
 
381
        for option in keys:
 
382
            dialog.add_option(option)
 
383
 
 
384
        dialog.add_option(_("Combine with character above to expand node"),
 
385
                          "+")
 
386
        dialog.add_option(_("Space when finished"), " ")
 
387
 
 
388
        do_expand = False
 
389
        results = default
 
390
        while True:
 
391
            response = dialog.run(defaults=results)
 
392
            if response > len(options):
 
393
                break
 
394
 
 
395
            elif response == len(options):
 
396
                response = dialog.get()
 
397
                do_expand = True
 
398
 
 
399
            else:
 
400
                do_expand = False
 
401
 
 
402
            # Invalid response
 
403
            if response < 0:
 
404
                continue
 
405
 
 
406
            # Toggle results
 
407
            result = keys[response]
 
408
            if not do_expand:
 
409
                self._toggle_results(result, options, results)
 
410
                continue
 
411
 
 
412
            # Expand tree
 
413
            dialog.visible = False
 
414
            if options[result]:
 
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
 
419
 
 
420
        return results
 
421
 
 
422
    def show_report(self, text, results):
 
423
        """
 
424
        Show test case results in a tree hierarchy
 
425
        """
 
426
        dialog = CLIReportDialog(text, results)
 
427
        dialog.run()
 
428
 
 
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"))
 
433
 
 
434
        if re.search(r"\$output", test["description"]):
 
435
            output = runner(test)[1]
 
436
        else:
 
437
            output = ""
 
438
 
 
439
        while True:
 
440
            # Show option dialog
 
441
            description = string.Template(test["description"]).substitute({
 
442
                "output": output.strip()})
 
443
            dialog = CLIChoiceDialog(description)
 
444
 
 
445
            for option in options:
 
446
                dialog.add_option(option, option[0])
 
447
 
 
448
            # Get option from dialog
 
449
            response = dialog.run()
 
450
            option = options[response]
 
451
            if response < len(ALL_ANSWERS):
 
452
                break
 
453
 
 
454
            output = runner(test)[1]
 
455
 
 
456
            options[-1] = _("test again")
 
457
 
 
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"))
 
464
        else:
 
465
            test["data"] = ""
 
466
 
 
467
        test["status"] = ANSWER_TO_STATUS[answer]
 
468
 
 
469
    def show_info(self, text, options=[], default=None):
 
470
        return self.show_radio(text, options, default)
 
471
 
 
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)
 
477
        dialog.run()