--- /dev/null
+"""
+Interface compatible with :class:`dialog.Dialog` for doing
+non-ncurses interaction.
+
+By convention, the last line of a text parameter should be
+a short value with a trailing colon so that we can prompt a user
+for a value immediately after it.
+"""
+
+import sys
+import readline
+import decorator
+import textwrap
+import getpass
+
+import wizard
+
+try:
+ import dialog
+ has_dialog = True
+except ImportError:
+ has_dialog = False
+
+def fill(text, width=60):
+ return "\n\n".join(textwrap.fill(p, width=width) for p in text.split("\n\n"))
+
+def guess_dimensions(text, width=60):
+ # +1 for the fact that there's no trailing newline from fill
+ # +2 for the borders
+ # +1 as a buffer in case we underestimate
+ return width, fill(text).count("\n", width-2) + 1 + 2 + 1
+
+def make(prompt, non_interactive):
+ if non_interactive:
+ return FailPrompt()
+ if prompt or not has_dialog:
+ return Prompt()
+ try:
+ return Dialog()
+ except dialog.ExecutableNotFound:
+ return Prompt()
+
+def join_or(items):
+ if len(items) == 0:
+ raise ValueError
+ elif len(items) == 1:
+ return items[0]
+ return ', '.join(items[:-1]) + ' or ' + items[-1]
+
+@decorator.decorator
+def dialog_wrap(f, self, text, *args, **kwargs):
+ if 'cmdopt' in kwargs: del kwargs['cmdopt']
+ if 'width' not in kwargs and 'height' not in kwargs:
+ kwargs["width"], kwargs["height"] = guess_dimensions(text)
+ result = f(self, text, *args, **kwargs)
+ if not isinstance(result, tuple):
+ exit = result
+ value = None
+ else:
+ exit, value = result
+ if exit == self.dialog.DIALOG_CANCEL or exit == self.dialog.DIALOG_ESC:
+ raise UserCancel
+ elif exit != self.dialog.DIALOG_OK:
+ # XXX: We don't support stuff like DIALOG_EXTRA or DIALOG_HELP
+ raise DialogError(exit)
+ return value
+
+class Dialog(object):
+ interactive = True
+ """Ncurses interface using dialog."""
+ def __init__(self):
+ self.dialog = dialog.Dialog()
+ @dialog_wrap
+ def inputbox(self, *args, **kwargs):
+ kwargs.setdefault('initerror', "You cannot edit initial value; please type characters after it.")
+ initerror = kwargs['initerror']
+ del kwargs['initerror']
+ kwargs['height'] += 5 # for the text box
+ exit, result = self.dialog.inputbox(*args, **kwargs)
+ if exit == self.dialog.DIALOG_OK:
+ # do some funny munging
+ kwargs.setdefault('init', '')
+ if result[0:len(kwargs['init'])] != kwargs['init']:
+ self.msgbox(initerror, height=10, width=50)
+ exit = self.dialog.DIALOG_OK
+ result = self.inputbox(*args, initerror=initerror, **kwargs)
+ else:
+ result = result[len(kwargs['init']):]
+ return (exit, result)
+ @dialog_wrap
+ def menu(self, *args, **kwargs):
+ kwargs['height'] += 6 + len(kwargs['choices']) # for the border and menu entries
+ return self.dialog.menu(*args, **kwargs)
+ @dialog_wrap
+ def msgbox(self, *args, **kwargs):
+ kwargs['height'] += 3
+ return self.dialog.msgbox(*args, **kwargs)
+ @dialog_wrap
+ def passwordbox(self, *args, **kwargs):
+ kwargs['height'] += 6
+ return self.dialog.passwordbox(*args, **kwargs)
+ @dialog_wrap
+ def infobox(self, text, **kwargs):
+ return self.dialog.infobox(text, **kwargs)
+
+@decorator.decorator
+def prompt_wrap(f, self, *args, **kwargs):
+ try:
+ return f(self, *args, **kwargs)
+ except (EOFError, KeyboardInterrupt):
+ raise UserCancel
+
+class Prompt(object):
+ interactive = True
+ @prompt_wrap
+ def inputbox(self, text, init='', **kwargs):
+ print ""
+ return raw_input(fill(text.strip()) + " " + init)
+ @prompt_wrap
+ def menu(self, text, choices=[], **kwargs):
+ print ""
+ print fill(text.strip())
+ values = list(choice[0] for choice in choices)
+ for choice in choices:
+ print "% 4s %s" % (choice[0], choice[1])
+ while 1:
+ out = raw_input("Please enter %s: " % join_or(values))
+ if out not in values:
+ print "'%s' is not a valid value" % out
+ continue
+ return out
+ @prompt_wrap
+ def passwordbox(self, text, **kwargs):
+ print ""
+ return getpass.getpass(text + " ")
+ @prompt_wrap
+ def msgbox(self, text, **kwargs):
+ print ""
+ print fill(text.strip())
+ print "Press <Enter> to continue..."
+ @prompt_wrap
+ def infobox(self, text, **kwargs):
+ print ""
+ print fill(text.strip())
+
+class FailPrompt(object):
+ """
+ Prompt that doesn't actually ask the user; just fails with
+ an error message.
+ """
+ interactive = False
+ def inputbox(self, *args, **kwargs):
+ kwargs.setdefault('cmdopt', '(unknown)')
+ raise MissingRequiredParam(kwargs['cmdopt'])
+ def passwordbox(self, *args, **kwargs):
+ kwargs.setdefault('cmdopt', '(unknown)')
+ raise MissingRequiredParam(kwargs['cmdopt'])
+ def menu(self, *args, **kwargs):
+ kwargs.setdefault('cmdopt', '(unknown)')
+ raise MissingRequiredParam(kwargs['cmdopt'])
+ def msgbox(self, text, **kwargs):
+ print ""
+ print fill(text.strip())
+ def infobox(self, text, **kwargs):
+ print ""
+ print fill(text.strip())
+
+class Error(wizard.Error):
+ pass
+
+class MissingRequiredParam(Error):
+ """Non-interactive, but we needed more info."""
+ def __init__(self, cmdopt):
+ self.cmdopt = cmdopt
+ def __str__(self):
+ return """
+
+ERROR: Missing required parameter, try specifying %s""" % self.cmdopt
+
+class UserCancel(Error):
+ """User canceled the input process."""
+ def __str__(self):
+ return "\nAborting installation process; no changes were made"
+
+class DialogError(Error):
+ """Dialog returned a mysterious error."""
+ def __init__(self, exit):
+ self.exitcode = exit
+ def __str__(self):
+ return """
+
+ERROR: Dialog returned a mysterious exit code %d. Please
+send mail to scripts@mit.edu with the contents of this error
+message and the preceding backtrace.
+""" % self.exitcode
+