1# Copyright 2016 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""User input handlers."""
6
7
8class InputError(Exception):
9    """An error with a user provided input."""
10
11
12class _InputHandler(object):
13    """An input handler base class."""
14
15    def get_choices_supplements(self):
16        """Returns a pair of supplement strings representing input choices.
17
18        @return: A pair consisting of a detailed representation of available
19                 choices, and a corresponding concise descriptor of available
20                 input choices with optional default.
21        """
22        input_choices, default = self._get_input_choices_and_default()
23        if input_choices:
24            input_choices = '[%s]' % input_choices
25            if default is not None:
26                input_choices += ' (default: %s)' % default
27
28        return self._get_choices_details(), input_choices
29
30
31    def _get_input_choices_and_default(self):
32        """Returns an input choices descriptor and a default (if any)."""
33        raise NotImplementedError
34
35
36    def _get_choices_details(self):
37        """Returns a detailed description (string) of input choices."""
38        raise NotImplementedError
39
40
41    def process(self, input_str):
42        """Returns the result of processing the user input.
43
44        @param input_str: The user input.
45
46        @return: The result of processing the input.
47
48        @raise InputError: Provided input is invalid.
49        """
50        raise NotImplementedError
51
52
53class PauseInputHandler(_InputHandler):
54    """A quiet input handler that just returns on any input."""
55
56    # Interface overrides.
57    #
58    def _get_input_choices_and_default(self):
59        return None, None
60
61
62    def _get_choices_details(self):
63        return None
64
65
66    def process(self, input_str):
67        pass
68
69
70class YesNoInputHandler(_InputHandler):
71    "A yes/no input handler with optional default."""
72
73    def __init__(self, default=None):
74        """Initializes the input handler.
75
76        @param default: The Boolean value to return by default.
77        """
78        self._default = default
79        self._input_choices = '%s/%s' % ('Y' if default is True else 'y',
80                                         'N' if default is False else 'n')
81
82
83    # Interface overrides.
84    #
85    def _get_input_choices_and_default(self):
86        # We highlight the default by uppercasing the corresponding choice
87        # directly, so no need to return a default separately.
88        return self._input_choices, None
89
90
91    def _get_choices_details(self):
92        return None
93
94
95    def process(self, input_str):
96        input_str = input_str.lower().strip()
97        if input_str == 'y':
98            return True
99        if input_str == 'n':
100            return False
101        if not input_str and self._default is not None:
102            return self._default
103        raise InputError
104
105
106class MultipleChoiceInputHandler(_InputHandler):
107    """A multiple choice input handler with optional default."""
108
109    def __init__(self, choices, default=None):
110        """Initializes the input handler.
111
112        @param choices: An iterable of input choices.
113        @param default: Index of default choice (integer).
114        """
115        max_idx = len(choices)
116        if not (default is None or default in range(1, max_idx + 1)):
117            raise ValueError('Default choice is not a valid index')
118        self._choices = choices
119        self._idx_range = '1-%d' % max_idx if max_idx > 1 else str(max_idx)
120        self._default = None if default is None else str(default)
121
122
123    # Interface overrides.
124    #
125    def _get_input_choices_and_default(self):
126        return self._idx_range, self._default
127
128
129    def _get_choices_details(self):
130        return '\n'.join(['%d) %s' % (idx, choice)
131                          for idx, choice in enumerate(self._choices, 1)])
132
133
134    def process(self, input_str):
135        """Returns the index (zero-based) and value of the chosen option."""
136        input_str = input_str or self._default
137        if input_str:
138            try:
139                input_idx = int(input_str) - 1
140                if input_idx in range(len(self._choices)):
141                    return input_idx, self._choices[input_idx]
142            except ValueError:
143                pass
144
145        raise InputError
146