2 Interface compatible with :class:`PromptInterface` for doing
3 non-ncurses interaction.
5 By convention, the last line of a text parameter should be
6 a short value with a trailing colon so that we can prompt a user
7 for a value immediately after it.
11 from wizard.prompt import *
29 def fill(text, width=60, **kwargs):
31 Convenience wrapper for :func:`textwrap.fill` that preserves
34 return "\n\n".join(textwrap.fill(p, width=width, **kwargs) for p in text.split("\n\n"))
36 def guess_dimensions(text, width=60):
38 Guesses the dimensions that any given piece of text will
39 need to display on terminal, given some width.
41 # +1 for the fact that there's no trailing newline from fill
43 # +1 as a buffer in case we underestimate
45 return width, fill(text, width-2).count("\n") + 1 + 2 + 1
47 def make(prompt, non_interactive):
49 Makes a :class:`dialog.Dialog` compatible class based on
54 if prompt or os.getenv('TERM') == 'dumb' or not has_dialog:
58 except (dialog.ExecutableNotFound, UnsupportedTerminal):
63 Joins a list of disjunctions into a human readable sentence.
67 >>> join_or(['foo', 'bar', 'baz'])
74 return ', '.join(items[:-1]) + ' or ' + items[-1]
76 class PromptInterface(object):
77 def inputbox(self, text, init='', **kwargs):
79 Request a free-form, single line of text from the user.
80 Prompt the user using ``text``; and ``init`` is the
81 initial value filling the field; not all implementations
82 support editing ``init``. Returns the typed string.
84 raise NotImplementedError
85 def menu(self, text, choices=[], **kwargs):
87 Request a selection from a number of choices from the user.
88 Prompt the user using ``text``; ``choices`` is a list
89 of tuples of form ``(value to return, description)``, where
90 ``value to return`` is the value that this function will
93 raise NotImplementedError
94 def passwordbox(self, text, **kwargs):
96 Securely requests a password from the user. Prompts the user
97 using ``text``; return value is the password.
99 raise NotImplementedError
100 def msgbox(self, text, **kwargs):
102 Gives the user a message that they must dismiss before proceeding.
104 raise NotImplementedError
105 def infobox(self, text, **kwargs):
107 Gives the user a non-blocking message; useful if you are about
108 to do an operation that will take some time.
110 raise NotImplementedError
113 def dialog_wrap(f, self, text, *args, **kwargs):
115 Convenience decorator that automatically:
117 1. Removes already handled keyword arguments,
118 2. Configures the dimensions of the dialog box, and
119 3. Handles the different ext possibilities of dialog.
121 if 'cmdopt' in kwargs: del kwargs['cmdopt']
122 if 'width' not in kwargs and 'height' not in kwargs:
123 kwargs["width"], kwargs["height"] = guess_dimensions(text)
124 elif 'width' in kwargs and 'height' not in kwargs:
125 kwargs["width"], kwargs["height"] = guess_dimensions(text, width=kwargs["width"])
126 result = f(self, text, *args, **kwargs)
127 if not isinstance(result, tuple):
132 if exit == self.dialog.DIALOG_CANCEL or exit == self.dialog.DIALOG_ESC:
134 elif exit != self.dialog.DIALOG_OK:
135 # XXX: We don't support stuff like DIALOG_EXTRA or DIALOG_HELP
136 raise DialogError(exit)
139 class Dialog(PromptInterface):
140 """Ncurses interface using dialog."""
143 self.dialog = dialog.Dialog()
144 exit = self.dialog.infobox("Setting up...")
146 raise UnsupportedTerminal
148 def inputbox(self, *args, **kwargs):
149 kwargs.setdefault('initerror', "You cannot edit initial value; please type characters after it.")
150 initerror = kwargs['initerror']
151 del kwargs['initerror']
152 kwargs['height'] += 5 # for the text box
153 exit, result = self.dialog.inputbox(*args, **kwargs)
154 if exit == self.dialog.DIALOG_OK: # pylint: disable-msg=E1101
155 # do some funny munging
156 kwargs.setdefault('init', '')
157 if result[0:len(kwargs['init'])] != kwargs['init']:
158 self.msgbox(initerror, height=10, width=50)
159 exit = self.dialog.DIALOG_OK # pylint: disable-msg=E1101
160 result = self.inputbox(*args, initerror=initerror, **kwargs)
162 result = result[len(kwargs['init']):]
163 return (exit, result)
165 def menu(self, *args, **kwargs):
166 kwargs['height'] += 6 + len(kwargs['choices']) # for the border and menu entries
167 return self.dialog.menu(*args, **kwargs)
169 def msgbox(self, *args, **kwargs):
170 kwargs['height'] += 3
171 return self.dialog.msgbox(*args, **kwargs)
173 def passwordbox(self, *args, **kwargs):
174 kwargs['height'] += 6
175 return self.dialog.passwordbox(*args, **kwargs)
177 def infobox(self, text, **kwargs):
178 return self.dialog.infobox(text, **kwargs)
181 def prompt_wrap(f, self, *args, **kwargs):
182 """Convenience decorator that handles end-of-document and interrupts."""
184 return f(self, *args, **kwargs)
185 except (EOFError, KeyboardInterrupt):
188 class Prompt(PromptInterface):
189 """Simple stdin/stdout prompt object."""
192 def inputbox(self, text, init='', **kwargs):
194 return raw_input(fill(text.strip()) + " " + init)
196 def menu(self, text, choices=[], **kwargs):
198 print fill(text.strip())
199 values = list(choice[0] for choice in choices)
200 for choice in choices:
201 print "% 4s %s" % (choice[0], choice[1])
203 out = raw_input("Please enter %s: " % join_or(values))
204 if out not in values:
205 print "'%s' is not a valid value" % out
209 def passwordbox(self, text, **kwargs):
211 return getpass.getpass(text + " ")
213 def msgbox(self, text, **kwargs):
215 print fill(text.strip())
216 print "Press <Enter> to continue..."
218 def infobox(self, text, **kwargs):
220 print fill(text.strip())
222 class FailPrompt(PromptInterface):
224 Prompt that doesn't actually ask the user; just fails with
228 def inputbox(self, *args, **kwargs):
229 kwargs.setdefault('cmdopt', '(unknown)')
230 raise MissingRequiredParam(kwargs['cmdopt'])
231 def passwordbox(self, *args, **kwargs):
232 kwargs.setdefault('cmdopt', '(unknown)')
233 raise MissingRequiredParam(kwargs['cmdopt'])
234 def menu(self, *args, **kwargs):
235 kwargs.setdefault('cmdopt', '(unknown)')
236 raise MissingRequiredParam(kwargs['cmdopt'])
237 def msgbox(self, text, **kwargs):
239 print fill(text.strip(), break_long_words=False)
240 def infobox(self, text, **kwargs):
242 print fill(text.strip(), break_long_words=False)
244 class Error(wizard.Error):
245 """Base error class."""
248 class MissingRequiredParam(Error):
249 """Non-interactive, but we needed more info."""
250 def __init__(self, cmdopt):
251 """``cmdopt`` is the command line option that should be specified."""
256 ERROR: Missing required parameter, try specifying %s""" % self.cmdopt
258 class UserCancel(Error):
259 """User canceled the input process."""
261 return "\nAborting installation process; no changes were made"
263 class DialogError(Error):
264 """Dialog returned a mysterious error."""
265 def __init__(self, exit):
266 """``exit`` is the mysterious exit code."""
271 ERROR: Dialog returned a mysterious exit code %d. Please
272 send mail to scripts@mit.edu with the contents of this error
273 message and the preceding backtrace.
276 class UnsupportedTerminal(Error):
277 """It doesn't look like we support this terminal. Internal error."""