Commits

Rob Lanphier committed d8a6b43

Simplified patch. Now just one patch for new API, and second to add Urwid

Comments (0)

Files changed (18)

01-add-basicui-abstraction.patch

+# HG changeset patch
+# Parent 29f1a7a640ab3abadb31cc44bc0a32af5606252c
+Moved many user interface elements into new 'ui'/BasicUI submodule
+
+diff --git a/paste/script/command.py b/paste/script/command.py
+--- a/paste/script/command.py
++++ b/paste/script/command.py
+@@ -14,6 +14,12 @@
+     import subprocess
+ except ImportError:
+     from paste.script.util import subprocess24 as subprocess
++
++import pastevars
++import ui
++
++NoDefault = pastevars.NoDefault
++    
+ difflib = None
+ 
+ if sys.version_info >= (2, 6):
+@@ -46,9 +52,6 @@
+     # precedence.
+     message = property(_get_message, _set_message)
+ 
+-class NoDefault(object):
+-    pass
+-
+ dist = pkg_resources.get_distribution('PasteScript')
+ 
+ python_version = sys.version.splitlines()[0].strip()
+@@ -200,6 +203,8 @@
+         self.verbose += self.options.verbose
+         self.verbose -= self.options.quiet
+         self.simulate = getattr(self.options, 'simulate', False)
++        if self.interactive:
++            self.ui = ui.get_user_interface(use_gui=self.options.use_gui)
+ 
+         # For #! situations:
+         if (os.environ.get('PASTE_CONFIG_FILE')
+@@ -306,23 +311,8 @@
+         """
+         Prompt the user for a variable.
+         """
+-        if default is not NoDefault:
+-            prompt += ' [%r]' % default
+-        prompt += ': '
+-        while 1:
+-            if should_echo:
+-                prompt_method = raw_input
+-            else:
+-                prompt_method = getpass.getpass
+-            response = prompt_method(prompt).strip()
+-            if not response:
+-                if default is not NoDefault:
+-                    return default
+-                else:
+-                    continue
+-            else:
+-                return response
+-        
++        return self.ui.challenge(prompt, default=default, should_echo=should_echo)
++
+     def pad(self, s, length, dir='left'):
+         if len(s) >= length:
+             return s
+@@ -336,7 +326,8 @@
+                         no_interactive=False,
+                         simulate=False,
+                         quiet=False,
+-                        overwrite=False):
++                        overwrite=False,
++                        gui=False):
+         """
+         Create a standard ``OptionParser`` instance.
+         
+@@ -379,6 +370,11 @@
+                               dest="overwrite",
+                               action="store_true",
+                               help="Overwrite files (warnings will be emitted for non-matching files otherwise)")
++        if gui:
++            parser.add_option('--gui',
++                              dest='use_gui',
++                              action='store_true',
++                              help="Use a GUI/TUI if one is available")
+         return parser
+ 
+     standard_parser = classmethod(standard_parser)
+diff --git a/paste/script/create_distro.py b/paste/script/create_distro.py
+--- a/paste/script/create_distro.py
++++ b/paste/script/create_distro.py
+@@ -27,7 +27,8 @@
+     """
+ 
+     parser = Command.standard_parser(
+-        simulate=True, no_interactive=True, quiet=True, overwrite=True)
++        simulate=True, no_interactive=True, quiet=True, overwrite=True, 
++        gui=True)
+     parser.add_option('-t', '--template',
+                       dest='templates',
+                       metavar='TEMPLATE',
+@@ -74,13 +75,18 @@
+         if self.options.list_variables:
+             return self.list_variables(templates)
+         if self.verbose:
+-            print 'Selected and implied templates:'
++            msg = ""
++            msg += "Selected and implied templates:\n"
+             max_tmpl_name = max([len(tmpl_name) for tmpl_name, tmpl in templates])
+             for tmpl_name, tmpl in templates:
+-                print '  %s%s  %s' % (
++                msg += "  %s%s  %s\n" % (
+                     tmpl_name, ' '*(max_tmpl_name-len(tmpl_name)),
+                     tmpl.summary)
+-            print
++            msg += "\n"
++            if self.interactive:
++                self.ui.report(msg)
++            else:
++                print msg
+         if not self.args:
+             if self.interactive:
+                 dist_name = self.challenge('Enter project name')
+@@ -286,13 +292,7 @@
+         return self._entry_points
+ 
+     def display_vars(self, vars):
+-        vars = vars.items()
+-        vars.sort()
+-        print 'Variables:'
+-        max_var = max([len(n) for n, v in vars])
+-        for name, value in vars:
+-            print '  %s:%s  %s' % (
+-                name, ' '*(max_var-len(name)), value)
++        self.ui.display_vars(vars)
+         
+     def list_templates(self):
+         templates = []
+diff --git a/paste/script/pastevars.py b/paste/script/pastevars.py
+new file mode 100644
+--- /dev/null
++++ b/paste/script/pastevars.py
+@@ -0,0 +1,52 @@
++# (c) 2005-2010 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
++# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
++
++class NoDefault(object):
++    pass
++
++class var(object):
++
++    def __init__(self, name, description,
++                 default='', should_echo=True, vartype=None):
++        self.name = name
++        self.description = description
++        self.default = default
++        self.should_echo = should_echo
++        if vartype is not None:
++            self.type = vartype
++        else:
++            if default is NoDefault or default is None:
++                self.type = str
++            else:
++                self.type = type(default)
++        
++
++    def __repr__(self):
++        return '<%s %s default=%r should_echo=%s>' % (
++            self.__class__.__name__,
++            self.name, self.default, self.should_echo)
++
++    def full_description(self):
++        if self.description:
++            return '%s (%s)' % (self.name, self.description)
++        else:
++            return self.name
++
++    def print_vars(cls, vars, indent=0):
++        max_name = max([len(v.name) for v in vars])
++        for var in vars:
++            if var.description:
++                print '%s%s%s  %s' % (
++                    ' '*indent,
++                    var.name,
++                    ' '*(max_name-len(var.name)),
++                    var.description)
++            else:
++                print '  %s' % var.name
++            if var.default is not command.NoDefault:
++                print '      default: %r' % var.default
++            if var.should_echo is True:
++                print '      should_echo: %s' % var.should_echo
++        print
++
++    print_vars = classmethod(print_vars)
+diff --git a/paste/script/templates.py b/paste/script/templates.py
+--- a/paste/script/templates.py
++++ b/paste/script/templates.py
+@@ -5,9 +5,12 @@
+ import inspect
+ import copydir
+ import command
++import pastevars
+ 
+ from paste.util.template import paste_script_template_renderer
+ 
++var = pastevars.var
++
+ class Template(object):
+ 
+     # Subclasses must define:
+@@ -66,12 +69,12 @@
+         converted_vars = {}
+         unused_vars = vars.copy()
+         errors = []
++        if cmd.interactive:
++            challenges = []
+         for var in expect_vars:
+             if var.name not in unused_vars:
+                 if cmd.interactive:
+-                    prompt = 'Enter %s' % var.full_description()
+-                    response = cmd.challenge(prompt, var.default, var.should_echo)
+-                    converted_vars[var.name] = response
++                    challenges.append(var)
+                 elif var.default is command.NoDefault:
+                     errors.append('Required variable missing: %s'
+                                   % var.full_description())
+@@ -79,6 +82,9 @@
+                     converted_vars[var.name] = var.default
+             else:
+                 converted_vars[var.name] = unused_vars.pop(var.name)
++        if cmd.interactive:
++            converted_vars = cmd.ui.challenge_batch(challenges)
++        
+         if errors:
+             raise command.BadCommand(
+                 'Errors in variables:\n%s' % '\n'.join(errors))
+@@ -142,46 +148,6 @@
+         """
+         pass
+ 
+-NoDefault = command.NoDefault
+-
+-class var(object):
+-
+-    def __init__(self, name, description,
+-                 default='', should_echo=True):
+-        self.name = name
+-        self.description = description
+-        self.default = default
+-        self.should_echo = should_echo
+-
+-    def __repr__(self):
+-        return '<%s %s default=%r should_echo=%s>' % (
+-            self.__class__.__name__,
+-            self.name, self.default, self.should_echo)
+-
+-    def full_description(self):
+-        if self.description:
+-            return '%s (%s)' % (self.name, self.description)
+-        else:
+-            return self.name
+-
+-    def print_vars(cls, vars, indent=0):
+-        max_name = max([len(v.name) for v in vars])
+-        for var in vars:
+-            if var.description:
+-                print '%s%s%s  %s' % (
+-                    ' '*indent,
+-                    var.name,
+-                    ' '*(max_name-len(var.name)),
+-                    var.description)
+-            else:
+-                print '  %s' % var.name
+-            if var.default is not command.NoDefault:
+-                print '      default: %r' % var.default
+-            if var.should_echo is True:
+-                print '      should_echo: %s' % var.should_echo
+-        print
+-
+-    print_vars = classmethod(print_vars)
+ 
+ class BasicPackage(Template):
+ 
+diff --git a/paste/script/ui/__init__.py b/paste/script/ui/__init__.py
+new file mode 100644
+--- /dev/null
++++ b/paste/script/ui/__init__.py
+@@ -0,0 +1,70 @@
++# (c) 2005-2010 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
++# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
++
++from paste.script.pastevars import NoDefault, var
++
++
++def get_user_interface(use_gui=False):
++    """
++    Class factory for UserInterface and friends
++    """
++    
++    userinterface = None
++
++    # use_gui variable reserved for future use; for now, always return BasicUI
++    return BasicUI()
++
++
++class UserInterface(object):
++    """
++    Abstract base class for all user interface classes
++    """
++    pass
++
++
++class BasicUI(UserInterface):    
++    def challenge(self, prompt, default=NoDefault, should_echo=True):
++        """
++        Prompt the user for a variable.
++        """
++        if default is not NoDefault:
++            prompt += ' [%r]' % default
++        prompt += ': '
++        while 1:
++            if should_echo:
++                prompt_method = raw_input
++            else:
++                prompt_method = getpass.getpass
++            response = prompt_method(prompt).strip()
++            if not response:
++                if default is not NoDefault:
++                    return default
++                else:
++                    continue
++            else:
++                return response
++
++    def challenge_batch(self, challenges, title=None):
++        """
++        Return a series of responses
++        """
++        converted_vars = {}
++        for var in challenges:
++            prompt = 'Enter %s' % var.full_description()
++            response = self.challenge(prompt, var.default, var.should_echo)
++            converted_vars[var.name] = response
++        return converted_vars
++
++    def display_vars(self, vars, title=None):
++        vars = vars.items()
++        vars.sort()
++        print 'Variables:'
++        max_var = max([len(n) for n, v in vars])
++        for name, value in vars:
++            print '  %s:%s  %s' % (
++                name, ' '*(max_var-len(name)), value)
++
++    def report(self, msg):
++        print msg
++
++

02-add-pasteurwid-ui.patch

+# HG changeset patch
+# Parent a14e3951ada0e7826f4fef639a6afc7996df5066
+Added new Urwid text user interface
+
+diff --git a/paste/script/ui/__init__.py b/paste/script/ui/__init__.py
+--- a/paste/script/ui/__init__.py
++++ b/paste/script/ui/__init__.py
+@@ -11,8 +11,17 @@
+     
+     userinterface = None
+ 
+-    # use_gui variable reserved for future use; for now, always return BasicUI
+-    return BasicUI()
++    if use_gui:
++        try:
++            from paste.script.ui.pasteurwid import get_urwid_interface
++            userinterface = get_urwid_interface()
++        except ImportError:
++            userinterface = None
++
++    if userinterface is not None:
++        return userinterface
++    else:
++        return BasicUI()
+ 
+ 
+ class UserInterface(object):
+diff --git a/paste/script/ui/pasteurwid.py b/paste/script/ui/pasteurwid.py
+new file mode 100644
+--- /dev/null
++++ b/paste/script/ui/pasteurwid.py
+@@ -0,0 +1,399 @@
++# (c) 2010 Rob Lanphier and contributors; written for Paste (http://pythonpaste.org)
++# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
++
++from paste.script.pastevars import NoDefault, var
++
++import urwid
++import urwid.raw_display
++import sys
++
++
++def get_urwid_interface():
++    if urwid.__version__ >= '0.9.9':
++        return UrwidUI()
++    else:
++        print >> sys.stderr, "Text UI requires Urwid 0.9.9 or greater"
++        return None
++
++
++class UrwidUI(object):    
++    def challenge(self, prompt, default=NoDefault, should_echo=True):
++        """
++        Prompt the user for a variable.
++        """
++        
++        while 1:
++            chals = [var('variable', prompt, default=default, should_echo=should_echo)]
++            response = self.challenge_batch(chals, use_names=False)['variable']
++            if not response:
++                if default is not NoDefault:
++                    return default
++                else:
++                    continue
++            else:
++                return response
++
++    def challenge_batch(self, challenges, 
++                        title="'paster create' Configuration",
++                        use_names=True):
++        """
++        Return a series of responses
++        """
++        fieldset = UrwidFieldSet(challenges, use_names=use_names)
++        fieldset.header_text = title
++        fieldset.run()
++        return fieldset.get_value_dict()
++
++    def display_vars(self, vars, title="Variables:"):
++        vars = vars.items()
++        vars.sort()
++        display = UrwidDictDisplay(vars)
++        display.header_text = title
++        display.run()
++        
++    def report(self, msg, title="Message:"):
++        display = UrwidMessageDisplay(msg)
++        display.header_text = title
++        display.run()
++
++
++class UrwidDialogException(Exception):
++    pass
++
++
++class ExitUrwidUI(Exception):
++    def __init__(self, exit_token=None):
++        self.exit_token = exit_token
++
++
++class UrwidMessageDisplay(object):
++    def __init__(self, msg):
++        self.header_text = 'Message:'
++        self.msg = msg
++
++    def get_widgets(self):
++        row = urwid.Text(('field', self.msg))
++        widget = urwid.Padding(row, ('fixed left', 3), ('fixed right', 3))
++        return [widget]
++
++    def get_button_defs(self):
++        return [('ok', {'label':'  OK'}),
++                ('cancel', {'label':'Cancel'})]
++
++    def run(self):
++        dialog = UrwidDialog(self)
++        dialog.header_text = self.header_text
++        UrwidCancelWrapper(dialog).run()
++
++
++class UrwidDictDisplay(object):
++    def __init__(self, showdict):
++        self.showdict = showdict
++        self.header_text = 'Variables:'
++
++    def _get_field(self, name, value):
++        """ Display one name/value pair from the dict """
++        # we don't have hanging indent, but we can stick a bullet out into the 
++        # left column.
++        asterisk = urwid.Text(('label', '* '))
++        label = urwid.Text(('label', name))
++        colon = urwid.Text(('label', ': '))
++        field = urwid.Text(('field', value))
++
++        # put everything together.  Each column is either 'fixed' for a fixed width,
++        # or given a 'weight' to help determine the relative width of the column
++        # such that it can fill the row.
++        row = urwid.Columns([('fixed', 2, asterisk),
++                             ('weight', 3, label),
++                             ('fixed', 2, colon),
++                             ('weight', 4, field)])
++
++        return urwid.Padding(row, ('fixed left', 3), ('fixed right', 3))
++
++    def get_widgets(self):
++        widgets = []
++        for name, value in self.showdict:
++            widgets.append(self._get_field(name, value))
++        return widgets
++
++    def get_button_defs(self):
++        return [('ok', {'label':'  OK'}),
++                ('cancel', {'label':'Cancel'})]
++
++    def run(self):
++        dialog = UrwidDialog(self)
++        dialog.header_text = self.header_text
++        UrwidCancelWrapper(dialog).run()
++
++
++class UrwidYesNo(object):
++    def __init__(self, question):
++        self.question = question
++        self.header_text = 'Question:'
++
++    def get_widgets(self):
++        row = urwid.Text(('field', self.question))
++        widget = urwid.Padding(row, ('fixed left', 3), ('fixed right', 3))
++        return [widget]
++
++    def get_button_defs(self):
++        return [('yes', {'label':'Yes'}),
++                ('no', {'label':'No'})]
++
++    def run(self):
++        dialog = UrwidDialog(self)
++        dialog.header_text = self.header_text
++        dialog.run()
++        self.exit_token = dialog.exit_token
++
++
++class UrwidFieldSet(object):
++    """
++    Takes a set of fields, displays a dialog corresponding to those fields, and
++    returns the values corresponding to those fields.
++    """
++    def __init__(self, challenges, use_names=True):
++        self.getters = {}
++        self.challenges = challenges
++        self.use_names = use_names
++
++    def get_value_dict(self):
++        """
++        Dump everything we've got.
++        """
++        retval = {}
++        for key in self.getters:
++            retval[key] = self.getters[key]()
++        return retval
++
++    def _set_getter(self, name, function):
++        """ 
++        This is where we collect all of the field getter functions.
++        """
++        self.getters[name] = function
++        
++    def _get_field(self, challenge):
++        """ Build a field in our form.  Called from _get_dialog_body()"""
++        # we don't have hanging indent, but we can stick a bullet out into the 
++        # left column.
++        asterisk = urwid.Text(('label', '* '))
++        if self.use_names:
++            label_text = challenge.full_description()
++        else:
++            label_text = challenge.description
++        label = urwid.Text(('label', label_text))
++        colon = urwid.Text(('label', ': '))
++
++        if issubclass(challenge.type, str) and challenge.should_echo == False:
++            if challenge.default is NoDefault:
++                field = PasswordEdit('', '')
++            else:
++                field = PasswordEdit('', challenge.default)
++            def getter():
++                """ 
++                Closure around urwid.Edit.get_edit_text(), which we'll
++                use to scrape the value out when we're all done.
++                """
++                return field.real_edit_text
++            self._set_getter(challenge.name, getter)
++        elif issubclass(challenge.type, str):
++            if challenge.default is NoDefault:
++                field = urwid.Edit('', '')
++            else:
++                field = urwid.Edit('', challenge.default)
++            def getter():
++                """ 
++                Closure around urwid.Edit.get_edit_text(), which we'll
++                use to scrape the value out when we're all done.
++                """
++                return field.get_edit_text()
++            self._set_getter(challenge.name, getter)
++        elif issubclass(challenge.type, bool):
++            field = urwid.CheckBox('')
++            def getter():
++                """ 
++                Closure around urwid.CheckBox.get_state(), which we'll
++                use to scrape the value out when we're all done. 
++                """
++                return field.get_state()
++            self._set_getter(challenge.name, getter)
++        else:
++            raise UrwidDialogException(str(challenge.type))
++
++
++        field = urwid.AttrWrap(field, 'field', 'fieldfocus')
++
++        # put everything together.  Each column is either 'fixed' for a fixed width,
++        # or given a 'weight' to help determine the relative width of the column
++        # such that it can fill the row.
++        editwidget = urwid.Columns([('fixed', 2, asterisk),
++                                    ('weight', 3, label),
++                                    ('fixed', 2, colon),
++                                    ('weight', 4, field)])
++
++        wrapper = urwid.AttrWrap(editwidget, None, {'label':'labelfocus'})
++        return urwid.Padding(wrapper, ('fixed left', 3), ('fixed right', 3))
++
++    def get_widgets(self):
++        fieldwidgets = []
++        for chal in self.challenges:
++            fieldwidgets.append(self._get_field(chal))
++        return fieldwidgets
++
++    def get_button_defs(self):
++        return [('ok', {'label':'  OK'}),
++                ('cancel', {'label':'Cancel'})]
++
++    def run(self):
++        dialog = UrwidDialog(self)
++        dialog.header_text = self.header_text
++        UrwidCancelWrapper(dialog).run()
++
++
++class UrwidCancelWrapper(object):
++    def __init__(self, dialog):
++        self.dialog = dialog
++        self.dialog.header_text += " (select 'OK' to continue, 'Cancel' to abort)"
++        
++    def run(self):
++        keepgoing = True
++        while keepgoing:
++            self.dialog.run()
++            keepgoing = self._check_cancel()
++
++    def _check_cancel(self):
++        if self.dialog.exit_token == 'ok':
++            keepgoing = False
++        elif self.dialog.exit_token == 'cancel':
++            yesno = UrwidYesNo('Do you really wish to cancel?')
++            yesno.header_text = 'Exiting...'
++            yesno.run()
++            if yesno.exit_token == 'yes':
++                print 'Cancelled'
++                raise SystemExit
++            elif yesno.exit_token == 'no':
++                keepgoing = True
++            else:
++                raise UrwidDialogException
++        return keepgoing
++
++
++class UrwidDialog(object):
++    """
++    Takes a set of fields, displays a dialog corresponding to those fields, and
++    returns the values corresponding to those fields.
++    """
++    def __init__(self, bodyobj):
++        self.bodyobj = bodyobj
++        self.header_text = "'paster create' Configuration"
++        self.is_constructed = False
++
++    def construct(self):
++        #  Our main loop is going to need four things: 
++        #  1. frame - the UI with all of its widgets
++        #  2. palette - style information for the UI
++        #  3. screen - the engine used to render everything
++        
++        #  1. frame - the UI with all of its widgets
++        header = self._get_dialog_header()
++        body = self._get_dialog_body()
++        self.frame = urwid.Frame(body, header=header)
++
++        #  2. palette - style information for the UI
++        self.palette = [
++            ('body','black','white', 'standout'),
++            ('header','black','light gray', 'bold'),
++            ('labelfocus','black', 'white', 'bold, underline'),
++            ('label','dark blue', 'white'),
++            ('fieldfocus','black,underline', 'white', 'bold, underline'),
++            ('field','black', 'white'),
++            ('button','black','white'),
++            ('buttonfocus','black','light gray','bold'),
++            ]
++
++        #  3. screen - the engine used to render everything
++        self.screen = urwid.raw_display.Screen()
++        self.is_constructed = True
++
++    def run(self):
++        """
++        Invoke Urwid MainLoop to display the dialog, populating self.getters
++        """
++        if not self.is_constructed:
++            self.construct()
++
++        # Putting it all together and running it
++        try:
++            urwid.MainLoop(self.frame, self.palette, self.screen).run()
++        except ExitUrwidUI as inst:
++            self.exit_token = inst.exit_token
++
++    def _get_button_callback(self, name):
++        def button_callback(button):
++            raise ExitUrwidUI(exit_token=name)
++        return button_callback
++
++    def _get_buttons(self):
++        """ renders the ok and cancel buttons.  Called from get_body() """
++
++        buttonlist = []
++        for name, props in self.bodyobj.get_button_defs():
++            # this is going to be what we actually do when someone clicks the button
++            b = urwid.Button(props['label'], 
++                             on_press=self._get_button_callback(name))
++            buttonlist.append(urwid.AttrWrap(b, 'button', 'buttonfocus'))
++
++        return urwid.GridFlow(buttonlist, 10, 7, 1, 'center')
++
++    def _get_dialog_header(self):
++        """ the header of our form, called from run() """
++        header_text = self.header_text
++        header = urwid.Text(header_text)
++        return urwid.AttrWrap(header, 'header')
++
++    def _get_dialog_body(self):
++        """ the body of our form, called from run() """
++
++        # build the list of field widgets
++        bodywidgets = [urwid.Divider(bottom=2)]
++
++        bodywidgets.extend(self.bodyobj.get_widgets())
++        
++        bodywidgets.append(urwid.Divider(bottom=1)) 
++
++        bodywidgets.append(self._get_buttons())
++
++        # SimpleListWalker provides simple linear navigation between the widgets
++        listwalker = urwid.SimpleListWalker(bodywidgets)
++        
++        # ListBox is a scrollable frame around a list of elements
++        listbox = urwid.ListBox(listwalker)
++        return urwid.AttrWrap(listbox, 'body')
++
++
++class PasswordEdit(urwid.Edit):
++    """Simple password editing widget"""
++    def __init__(self, caption="", edit_text="", **kwargs):
++        self.real_edit_text = edit_text
++        edit_text = self._display_text()
++
++        self.__super.__init__(caption=caption, edit_text=edit_text, **kwargs)
++
++    def _display_text(self):
++        """ 
++        Return the text to display in lieu of showing real_edit_text 
++        """
++        return '*' * len(self.real_edit_text)
++
++    def keypress(self, size, key):
++        """
++        Temporarily set self.edit_text long enough to edit the field, then set
++        it right back
++        """
++        self.edit_text = self.real_edit_text
++        unhandled = urwid.Edit.keypress(self, size, key)
++        self.real_edit_text = self.edit_text
++        self.edit_text = self._display_text()
++
++        return unhandled
++
-urwid1
-urwid2
-urwid3
-urwid4
-urwid5
-urwid6
-urwid7
-urwid8
-urwid9
-urwid10
-urwid11
-urwid12
-urwid13
-urwid14
-urwid15
+01-add-basicui-abstraction.patch
+02-add-pasteurwid-ui.patch

urwid1

-# HG changeset patch
-# Parent 29f1a7a640ab3abadb31cc44bc0a32af5606252c
-moving NoDefault to its own file for now
-
-diff --git a/paste/script/command.py b/paste/script/command.py
---- a/paste/script/command.py
-+++ b/paste/script/command.py
-@@ -14,6 +14,11 @@
-     import subprocess
- except ImportError:
-     from paste.script.util import subprocess24 as subprocess
-+
-+import pastevars
-+
-+NoDefault = pastevars.NoDefault
-+    
- difflib = None
- 
- if sys.version_info >= (2, 6):
-@@ -46,9 +51,6 @@
-     # precedence.
-     message = property(_get_message, _set_message)
- 
--class NoDefault(object):
--    pass
--
- dist = pkg_resources.get_distribution('PasteScript')
- 
- python_version = sys.version.splitlines()[0].strip()
-diff --git a/paste/script/pastevars.py b/paste/script/pastevars.py
-new file mode 100644
---- /dev/null
-+++ b/paste/script/pastevars.py
-@@ -0,0 +1,44 @@
-+# (c) 2005-2010 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-+
-+class NoDefault(object):
-+    pass
-+
-+class var(object):
-+
-+    def __init__(self, name, description,
-+                 default='', should_echo=True):
-+        self.name = name
-+        self.description = description
-+        self.default = default
-+        self.should_echo = should_echo
-+
-+    def __repr__(self):
-+        return '<%s %s default=%r should_echo=%s>' % (
-+            self.__class__.__name__,
-+            self.name, self.default, self.should_echo)
-+
-+    def full_description(self):
-+        if self.description:
-+            return '%s (%s)' % (self.name, self.description)
-+        else:
-+            return self.name
-+
-+    def print_vars(cls, vars, indent=0):
-+        max_name = max([len(v.name) for v in vars])
-+        for var in vars:
-+            if var.description:
-+                print '%s%s%s  %s' % (
-+                    ' '*indent,
-+                    var.name,
-+                    ' '*(max_name-len(var.name)),
-+                    var.description)
-+            else:
-+                print '  %s' % var.name
-+            if var.default is not command.NoDefault:
-+                print '      default: %r' % var.default
-+            if var.should_echo is True:
-+                print '      should_echo: %s' % var.should_echo
-+        print
-+
-+    print_vars = classmethod(print_vars)
-diff --git a/paste/script/templates.py b/paste/script/templates.py
---- a/paste/script/templates.py
-+++ b/paste/script/templates.py
-@@ -5,9 +5,12 @@
- import inspect
- import copydir
- import command
-+import pastevars
- 
- from paste.util.template import paste_script_template_renderer
- 
-+var = pastevars.var
-+
- class Template(object):
- 
-     # Subclasses must define:
-@@ -142,46 +145,6 @@
-         """
-         pass
- 
--NoDefault = command.NoDefault
--
--class var(object):
--
--    def __init__(self, name, description,
--                 default='', should_echo=True):
--        self.name = name
--        self.description = description
--        self.default = default
--        self.should_echo = should_echo
--
--    def __repr__(self):
--        return '<%s %s default=%r should_echo=%s>' % (
--            self.__class__.__name__,
--            self.name, self.default, self.should_echo)
--
--    def full_description(self):
--        if self.description:
--            return '%s (%s)' % (self.name, self.description)
--        else:
--            return self.name
--
--    def print_vars(cls, vars, indent=0):
--        max_name = max([len(v.name) for v in vars])
--        for var in vars:
--            if var.description:
--                print '%s%s%s  %s' % (
--                    ' '*indent,
--                    var.name,
--                    ' '*(max_name-len(var.name)),
--                    var.description)
--            else:
--                print '  %s' % var.name
--            if var.default is not command.NoDefault:
--                print '      default: %r' % var.default
--            if var.should_echo is True:
--                print '      should_echo: %s' % var.should_echo
--        print
--
--    print_vars = classmethod(print_vars)
- 
- class BasicPackage(Template):
- 

urwid10

-# HG changeset patch
-# Parent ad067ddd91065411e9f91e9da2a6ea0c175b7c76
-Created a ui subdirectory, and broke urwid stuff into separate file from the basic ui
-
-diff --git a/paste/script/command.py b/paste/script/command.py
---- a/paste/script/command.py
-+++ b/paste/script/command.py
-@@ -16,7 +16,7 @@
-     from paste.script.util import subprocess24 as subprocess
- 
- import pastevars
--import userinterface
-+import ui
- 
- NoDefault = pastevars.NoDefault
-     
-@@ -150,7 +150,7 @@
- 
-     def __init__(self, name):
-         self.command_name = name
--        self.ui = userinterface.get_user_interface()
-+        self.ui = ui.get_user_interface()
- 
-     max_args = None
-     max_args_error = 'You must provide no more than %(max_args)s arguments'
-diff --git a/paste/script/userinterface.py b/paste/script/ui/__init__.py
-rename from paste/script/userinterface.py
-rename to paste/script/ui/__init__.py
---- a/paste/script/userinterface.py
-+++ b/paste/script/ui/__init__.py
-@@ -1,7 +1,7 @@
- # (c) 2005-2010 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
- # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
- 
--from pastevars import NoDefault, var
-+from paste.script.pastevars import NoDefault, var
- 
- try:
-     import urwid
-@@ -10,6 +10,9 @@
-     pass
- 
- 
-+from paste.script.ui.pasteurwid import UrwidUI
-+
-+
- def get_user_interface():
-     """
-     Class factory for UserInterface and friends
-@@ -77,323 +80,4 @@
-                 name, ' '*(max_var-len(name)), value)
- 
- 
--class UrwidUI(UserInterface):    
--    def challenge(self, prompt, default=NoDefault, should_echo=True):
--        """
--        Prompt the user for a variable.
--        """
--        
--        while 1:
--            chals = [var('variable', prompt, default=default, should_echo=should_echo)]
--            response = self.challenge_batch(chals, use_names=False)['variable']
--            if not response:
--                if default is not NoDefault:
--                    return default
--                else:
--                    continue
--            else:
--                return response
- 
--    def challenge_batch(self, challenges, 
--                        title="'paster create' Configuration",
--                        use_names=True):
--        """
--        Return a series of responses
--        """
--        fieldset = UrwidFieldSet(challenges, use_names=use_names)
--        fieldset.header_text = title
--        fieldset.run()
--        return fieldset.get_value_dict()
--
--    def display_vars(self, vars, title="Variables:"):
--        vars = vars.items()
--        vars.sort()
--        display = UrwidDictDisplay(vars)
--        display.header_text = title
--        display.run()
--
--
--class UrwidDialogException(Exception):
--    pass
--
--
--class ExitUrwidUI(Exception):
--    def __init__(self, exit_token=None):
--        self.exit_token = exit_token
--
--class UrwidDictDisplay(object):
--    def __init__(self, showdict):
--        self.showdict = showdict
--        self.header_text = 'Variables:'
--
--    def _get_field(self, name, value):
--        """ Display one name/value pair from the dict """
--        # we don't have hanging indent, but we can stick a bullet out into the 
--        # left column.
--        asterisk = urwid.Text(('label', '* '))
--        label = urwid.Text(('label', name))
--        colon = urwid.Text(('label', ': '))
--        field = urwid.Text(('field', value))
--
--        # put everything together.  Each column is either 'fixed' for a fixed width,
--        # or given a 'weight' to help determine the relative width of the column
--        # such that it can fill the row.
--        row = urwid.Columns([('fixed', 2, asterisk),
--                             ('weight', 3, label),
--                             ('fixed', 2, colon),
--                             ('weight', 4, field)])
--
--        return urwid.Padding(row, ('fixed left', 3), ('fixed right', 3))
--
--    def get_widgets(self):
--        widgets = []
--        for name, value in self.showdict:
--            widgets.append(self._get_field(name, value))
--        return widgets
--
--    def get_button_defs(self):
--        return [('ok', {'label':'  OK'}),
--                ('cancel', {'label':'Cancel'})]
--
--    def run(self):
--        dialog = UrwidDialog(self)
--        dialog.header_text = self.header_text
--        UrwidCancelWrapper(dialog).run()
--
--
--class UrwidYesNo(object):
--    def __init__(self, question):
--        self.question = question
--        self.header_text = 'Question:'
--
--    def get_widgets(self):
--        row = urwid.Text(('field', self.question))
--        widget = urwid.Padding(row, ('fixed left', 3), ('fixed right', 3))
--        return [widget]
--
--    def get_button_defs(self):
--        return [('yes', {'label':'Yes'}),
--                ('no', {'label':'No'})]
--
--    def run(self):
--        dialog = UrwidDialog(self)
--        dialog.header_text = self.header_text
--        dialog.run()
--        self.exit_token = dialog.exit_token
--
--
--class UrwidFieldSet(object):
--    """
--    Takes a set of fields, displays a dialog corresponding to those fields, and
--    returns the values corresponding to those fields.
--    """
--    def __init__(self, challenges, use_names=True):
--        self.getters = {}
--        self.challenges = challenges
--        self.use_names = use_names
--
--    def get_value_dict(self):
--        """
--        Dump everything we've got.
--        """
--        retval = {}
--        for key in self.getters:
--            retval[key] = self.getters[key]()
--        return retval
--
--    def _set_getter(self, name, function):
--        """ 
--        This is where we collect all of the field getter functions.
--        """
--        self.getters[name] = function
--        
--    def _get_field(self, challenge):
--        """ Build a field in our form.  Called from _get_dialog_body()"""
--        # we don't have hanging indent, but we can stick a bullet out into the 
--        # left column.
--        asterisk = urwid.Text(('label', '* '))
--        if self.use_names:
--            label_text = challenge.full_description()
--        else:
--            label_text = challenge.description
--        label = urwid.Text(('label', label_text))
--        colon = urwid.Text(('label', ': '))
--
--        if issubclass(challenge.type, str):
--            if challenge.default is NoDefault:
--                field = urwid.Edit('', '')
--            else:
--                field = urwid.Edit('', challenge.default)
--            def getter():
--                """ 
--                Closure around urwid.Edit.get_edit_text(), which we'll
--                use to scrape the value out when we're all done.
--                """
--                return field.get_edit_text()
--            self._set_getter(challenge.name, getter)
--        elif issubclass(challenge.type, bool):
--            field = urwid.CheckBox('')
--            def getter():
--                """ 
--                Closure around urwid.CheckBox.get_state(), which we'll
--                use to scrape the value out when we're all done. 
--                """
--                return field.get_state()
--            self._set_getter(challenge.name, getter)
--        else:
--            raise UrwidDialogException(str(challenge.type))
--
--
--        field = urwid.AttrWrap(field, 'field', 'fieldfocus')
--
--        # put everything together.  Each column is either 'fixed' for a fixed width,
--        # or given a 'weight' to help determine the relative width of the column
--        # such that it can fill the row.
--        editwidget = urwid.Columns([('fixed', 2, asterisk),
--                                    ('weight', 3, label),
--                                    ('fixed', 2, colon),
--                                    ('weight', 4, field)])
--
--        wrapper = urwid.AttrWrap(editwidget, None, {'label':'labelfocus'})
--        return urwid.Padding(wrapper, ('fixed left', 3), ('fixed right', 3))
--
--    def get_widgets(self):
--        fieldwidgets = []
--        for chal in self.challenges:
--            fieldwidgets.append(self._get_field(chal))
--        return fieldwidgets
--
--    def get_button_defs(self):
--        return [('ok', {'label':'  OK'}),
--                ('cancel', {'label':'Cancel'})]
--
--    def run(self):
--        dialog = UrwidDialog(self)
--        dialog.header_text = self.header_text
--        UrwidCancelWrapper(dialog).run()
--
--
--class UrwidCancelWrapper(object):
--    def __init__(self, dialog):
--        self.dialog = dialog
--        self.dialog.header_text += " (select 'OK' to continue, 'Cancel' to abort)"
--        
--    def run(self):
--        keepgoing = True
--        while keepgoing:
--            self.dialog.run()
--            keepgoing = self._check_cancel()
--
--    def _check_cancel(self):
--        if self.dialog.exit_token == 'ok':
--            keepgoing = False
--        elif self.dialog.exit_token == 'cancel':
--            yesno = UrwidYesNo('Do you really wish to cancel?')
--            yesno.header_text = 'Exiting...'
--            yesno.run()
--            if yesno.exit_token == 'yes':
--                print 'Cancelled'
--                raise SystemExit
--            elif yesno.exit_token == 'no':
--                keepgoing = True
--            else:
--                raise UrwidDialogException
--        return keepgoing
--
--
--class UrwidDialog(object):
--    """
--    Takes a set of fields, displays a dialog corresponding to those fields, and
--    returns the values corresponding to those fields.
--    """
--    def __init__(self, bodyobj):
--        self.bodyobj = bodyobj
--        self.header_text = "'paster create' Configuration"
--        self.is_constructed = False
--
--    def construct(self):
--        #  Our main loop is going to need four things: 
--        #  1. frame - the UI with all of its widgets
--        #  2. palette - style information for the UI
--        #  3. screen - the engine used to render everything
--        
--        #  1. frame - the UI with all of its widgets
--        header = self._get_dialog_header()
--        body = self._get_dialog_body()
--        self.frame = urwid.Frame(body, header=header)
--
--        #  2. palette - style information for the UI
--        self.palette = [
--            ('body','black','white', 'standout'),
--            ('header','black','light gray', 'bold'),
--            ('labelfocus','black', 'white', 'bold, underline'),
--            ('label','dark blue', 'white'),
--            ('fieldfocus','black,underline', 'white', 'bold, underline'),
--            ('field','black', 'white'),
--            ('button','black','white'),
--            ('buttonfocus','black','light gray','bold'),
--            ]
--
--        #  3. screen - the engine used to render everything
--        self.screen = urwid.raw_display.Screen()
--        self.is_constructed = True
--
--    def run(self):
--        """
--        Invoke Urwid MainLoop to display the dialog, populating self.getters
--        """
--        if not self.is_constructed:
--            self.construct()
--
--        # Putting it all together and running it
--        try:
--            urwid.MainLoop(self.frame, self.palette, self.screen).run()
--        except ExitUrwidUI as inst:
--            self.exit_token = inst.exit_token
--
--    def _get_button_callback(self, name):
--        def button_callback(button):
--            raise ExitUrwidUI(exit_token=name)
--        return button_callback
--
--    def _get_buttons(self):
--        """ renders the ok and cancel buttons.  Called from get_body() """
--
--        buttonlist = []
--        for name, props in self.bodyobj.get_button_defs():
--            # this is going to be what we actually do when someone clicks the button
--            b = urwid.Button(props['label'], 
--                             on_press=self._get_button_callback(name))
--            buttonlist.append(urwid.AttrWrap(b, 'button', 'buttonfocus'))
--
--        return urwid.GridFlow(buttonlist, 10, 7, 1, 'center')
--
--    def _get_dialog_header(self):
--        """ the header of our form, called from run() """
--        header_text = self.header_text
--        header = urwid.Text(header_text)
--        return urwid.AttrWrap(header, 'header')
--
--    def _get_dialog_body(self):
--        """ the body of our form, called from run() """
--
--        # build the list of field widgets
--        bodywidgets = [urwid.Divider(bottom=2)]
--
--        bodywidgets.extend(self.bodyobj.get_widgets())
--        
--        bodywidgets.append(urwid.Divider(bottom=1)) 
--
--        bodywidgets.append(self._get_buttons())
--
--        # SimpleListWalker provides simple linear navigation between the widgets
--        listwalker = urwid.SimpleListWalker(bodywidgets)
--        
--        # ListBox is a scrollable frame around a list of elements
--        listbox = urwid.ListBox(listwalker)
--        return urwid.AttrWrap(listbox, 'body')
--
--
--if '__main__'==__name__:
--    main()
--
-diff --git a/paste/script/ui/pasteurwid.py b/paste/script/ui/pasteurwid.py
-new file mode 100644
---- /dev/null
-+++ b/paste/script/ui/pasteurwid.py
-@@ -0,0 +1,328 @@
-+# (c) 2010 Rob Lanphier and contributors; written for Paste (http://pythonpaste.org)
-+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-+
-+from paste.script.pastevars import NoDefault, var
-+
-+try:
-+    import urwid
-+    import urwid.raw_display
-+except ImportError:
-+    pass
-+
-+
-+class UrwidUI(object):    
-+    def challenge(self, prompt, default=NoDefault, should_echo=True):
-+        """
-+        Prompt the user for a variable.
-+        """
-+        
-+        while 1:
-+            chals = [var('variable', prompt, default=default, should_echo=should_echo)]
-+            response = self.challenge_batch(chals, use_names=False)['variable']
-+            if not response:
-+                if default is not NoDefault:
-+                    return default
-+                else:
-+                    continue
-+            else:
-+                return response
-+
-+    def challenge_batch(self, challenges, 
-+                        title="'paster create' Configuration",
-+                        use_names=True):
-+        """
-+        Return a series of responses
-+        """
-+        fieldset = UrwidFieldSet(challenges, use_names=use_names)
-+        fieldset.header_text = title
-+        fieldset.run()
-+        return fieldset.get_value_dict()
-+
-+    def display_vars(self, vars, title="Variables:"):
-+        vars = vars.items()
-+        vars.sort()
-+        display = UrwidDictDisplay(vars)
-+        display.header_text = title
-+        display.run()
-+
-+
-+class UrwidDialogException(Exception):
-+    pass
-+
-+
-+class ExitUrwidUI(Exception):
-+    def __init__(self, exit_token=None):
-+        self.exit_token = exit_token
-+
-+class UrwidDictDisplay(object):
-+    def __init__(self, showdict):
-+        self.showdict = showdict
-+        self.header_text = 'Variables:'
-+
-+    def _get_field(self, name, value):
-+        """ Display one name/value pair from the dict """
-+        # we don't have hanging indent, but we can stick a bullet out into the 
-+        # left column.
-+        asterisk = urwid.Text(('label', '* '))
-+        label = urwid.Text(('label', name))
-+        colon = urwid.Text(('label', ': '))
-+        field = urwid.Text(('field', value))
-+
-+        # put everything together.  Each column is either 'fixed' for a fixed width,
-+        # or given a 'weight' to help determine the relative width of the column
-+        # such that it can fill the row.
-+        row = urwid.Columns([('fixed', 2, asterisk),
-+                             ('weight', 3, label),
-+                             ('fixed', 2, colon),
-+                             ('weight', 4, field)])
-+
-+        return urwid.Padding(row, ('fixed left', 3), ('fixed right', 3))
-+
-+    def get_widgets(self):
-+        widgets = []
-+        for name, value in self.showdict:
-+            widgets.append(self._get_field(name, value))
-+        return widgets
-+
-+    def get_button_defs(self):
-+        return [('ok', {'label':'  OK'}),
-+                ('cancel', {'label':'Cancel'})]
-+
-+    def run(self):
-+        dialog = UrwidDialog(self)
-+        dialog.header_text = self.header_text
-+        UrwidCancelWrapper(dialog).run()
-+
-+
-+class UrwidYesNo(object):
-+    def __init__(self, question):
-+        self.question = question
-+        self.header_text = 'Question:'
-+
-+    def get_widgets(self):
-+        row = urwid.Text(('field', self.question))
-+        widget = urwid.Padding(row, ('fixed left', 3), ('fixed right', 3))
-+        return [widget]
-+
-+    def get_button_defs(self):
-+        return [('yes', {'label':'Yes'}),
-+                ('no', {'label':'No'})]
-+
-+    def run(self):
-+        dialog = UrwidDialog(self)
-+        dialog.header_text = self.header_text
-+        dialog.run()
-+        self.exit_token = dialog.exit_token
-+
-+
-+class UrwidFieldSet(object):
-+    """
-+    Takes a set of fields, displays a dialog corresponding to those fields, and
-+    returns the values corresponding to those fields.
-+    """
-+    def __init__(self, challenges, use_names=True):
-+        self.getters = {}
-+        self.challenges = challenges
-+        self.use_names = use_names
-+
-+    def get_value_dict(self):
-+        """
-+        Dump everything we've got.
-+        """
-+        retval = {}
-+        for key in self.getters:
-+            retval[key] = self.getters[key]()
-+        return retval
-+
-+    def _set_getter(self, name, function):
-+        """ 
-+        This is where we collect all of the field getter functions.
-+        """
-+        self.getters[name] = function
-+        
-+    def _get_field(self, challenge):
-+        """ Build a field in our form.  Called from _get_dialog_body()"""
-+        # we don't have hanging indent, but we can stick a bullet out into the 
-+        # left column.
-+        asterisk = urwid.Text(('label', '* '))
-+        if self.use_names:
-+            label_text = challenge.full_description()
-+        else:
-+            label_text = challenge.description
-+        label = urwid.Text(('label', label_text))
-+        colon = urwid.Text(('label', ': '))
-+
-+        if issubclass(challenge.type, str):
-+            if challenge.default is NoDefault:
-+                field = urwid.Edit('', '')
-+            else:
-+                field = urwid.Edit('', challenge.default)
-+            def getter():
-+                """ 
-+                Closure around urwid.Edit.get_edit_text(), which we'll
-+                use to scrape the value out when we're all done.
-+                """
-+                return field.get_edit_text()
-+            self._set_getter(challenge.name, getter)
-+        elif issubclass(challenge.type, bool):
-+            field = urwid.CheckBox('')
-+            def getter():
-+                """ 
-+                Closure around urwid.CheckBox.get_state(), which we'll
-+                use to scrape the value out when we're all done. 
-+                """
-+                return field.get_state()
-+            self._set_getter(challenge.name, getter)
-+        else:
-+            raise UrwidDialogException(str(challenge.type))
-+
-+
-+        field = urwid.AttrWrap(field, 'field', 'fieldfocus')
-+
-+        # put everything together.  Each column is either 'fixed' for a fixed width,
-+        # or given a 'weight' to help determine the relative width of the column
-+        # such that it can fill the row.
-+        editwidget = urwid.Columns([('fixed', 2, asterisk),
-+                                    ('weight', 3, label),
-+                                    ('fixed', 2, colon),
-+                                    ('weight', 4, field)])
-+
-+        wrapper = urwid.AttrWrap(editwidget, None, {'label':'labelfocus'})
-+        return urwid.Padding(wrapper, ('fixed left', 3), ('fixed right', 3))
-+
-+    def get_widgets(self):
-+        fieldwidgets = []
-+        for chal in self.challenges:
-+            fieldwidgets.append(self._get_field(chal))
-+        return fieldwidgets
-+
-+    def get_button_defs(self):
-+        return [('ok', {'label':'  OK'}),
-+                ('cancel', {'label':'Cancel'})]
-+
-+    def run(self):
-+        dialog = UrwidDialog(self)
-+        dialog.header_text = self.header_text
-+        UrwidCancelWrapper(dialog).run()
-+
-+
-+class UrwidCancelWrapper(object):
-+    def __init__(self, dialog):
-+        self.dialog = dialog
-+        self.dialog.header_text += " (select 'OK' to continue, 'Cancel' to abort)"
-+        
-+    def run(self):
-+        keepgoing = True
-+        while keepgoing:
-+            self.dialog.run()
-+            keepgoing = self._check_cancel()
-+
-+    def _check_cancel(self):
-+        if self.dialog.exit_token == 'ok':
-+            keepgoing = False
-+        elif self.dialog.exit_token == 'cancel':
-+            yesno = UrwidYesNo('Do you really wish to cancel?')
-+            yesno.header_text = 'Exiting...'
-+            yesno.run()
-+            if yesno.exit_token == 'yes':
-+                print 'Cancelled'
-+                raise SystemExit
-+            elif yesno.exit_token == 'no':
-+                keepgoing = True
-+            else:
-+                raise UrwidDialogException
-+        return keepgoing
-+
-+
-+class UrwidDialog(object):
-+    """
-+    Takes a set of fields, displays a dialog corresponding to those fields, and
-+    returns the values corresponding to those fields.
-+    """
-+    def __init__(self, bodyobj):
-+        self.bodyobj = bodyobj
-+        self.header_text = "'paster create' Configuration"
-+        self.is_constructed = False
-+
-+    def construct(self):
-+        #  Our main loop is going to need four things: 
-+        #  1. frame - the UI with all of its widgets
-+        #  2. palette - style information for the UI
-+        #  3. screen - the engine used to render everything
-+        
-+        #  1. frame - the UI with all of its widgets
-+        header = self._get_dialog_header()
-+        body = self._get_dialog_body()
-+        self.frame = urwid.Frame(body, header=header)
-+
-+        #  2. palette - style information for the UI
-+        self.palette = [
-+            ('body','black','white', 'standout'),
-+            ('header','black','light gray', 'bold'),
-+            ('labelfocus','black', 'white', 'bold, underline'),
-+            ('label','dark blue', 'white'),
-+            ('fieldfocus','black,underline', 'white', 'bold, underline'),
-+            ('field','black', 'white'),
-+            ('button','black','white'),
-+            ('buttonfocus','black','light gray','bold'),
-+            ]
-+
-+        #  3. screen - the engine used to render everything
-+        self.screen = urwid.raw_display.Screen()
-+        self.is_constructed = True
-+
-+    def run(self):
-+        """
-+        Invoke Urwid MainLoop to display the dialog, populating self.getters
-+        """
-+        if not self.is_constructed:
-+            self.construct()
-+
-+        # Putting it all together and running it
-+        try:
-+            urwid.MainLoop(self.frame, self.palette, self.screen).run()
-+        except ExitUrwidUI as inst:
-+            self.exit_token = inst.exit_token
-+
-+    def _get_button_callback(self, name):
-+        def button_callback(button):
-+            raise ExitUrwidUI(exit_token=name)
-+        return button_callback
-+
-+    def _get_buttons(self):
-+        """ renders the ok and cancel buttons.  Called from get_body() """
-+
-+        buttonlist = []
-+        for name, props in self.bodyobj.get_button_defs():
-+            # this is going to be what we actually do when someone clicks the button
-+            b = urwid.Button(props['label'], 
-+                             on_press=self._get_button_callback(name))
-+            buttonlist.append(urwid.AttrWrap(b, 'button', 'buttonfocus'))
-+
-+        return urwid.GridFlow(buttonlist, 10, 7, 1, 'center')
-+
-+    def _get_dialog_header(self):
-+        """ the header of our form, called from run() """
-+        header_text = self.header_text
-+        header = urwid.Text(header_text)
-+        return urwid.AttrWrap(header, 'header')
-+
-+    def _get_dialog_body(self):
-+        """ the body of our form, called from run() """
-+
-+        # build the list of field widgets
-+        bodywidgets = [urwid.Divider(bottom=2)]
-+
-+        bodywidgets.extend(self.bodyobj.get_widgets())
-+        
-+        bodywidgets.append(urwid.Divider(bottom=1)) 
-+
-+        bodywidgets.append(self._get_buttons())
-+
-+        # SimpleListWalker provides simple linear navigation between the widgets
-+        listwalker = urwid.SimpleListWalker(bodywidgets)
-+        
-+        # ListBox is a scrollable frame around a list of elements
-+        listbox = urwid.ListBox(listwalker)
-+        return urwid.AttrWrap(listbox, 'body')
-+

urwid11

-# HG changeset patch
-# Parent 24e70b4ce5a83c6428fdb43126153051f4a7fa71
-added a password handler for when should_echo==False
-
-diff --git a/paste/script/ui/pasteurwid.py b/paste/script/ui/pasteurwid.py
---- a/paste/script/ui/pasteurwid.py
-+++ b/paste/script/ui/pasteurwid.py
-@@ -152,7 +152,19 @@
-         label = urwid.Text(('label', label_text))
-         colon = urwid.Text(('label', ': '))
- 
--        if issubclass(challenge.type, str):
-+        if issubclass(challenge.type, str) and challenge.should_echo == False:
-+            if challenge.default is NoDefault:
-+                field = PasswordEdit('', '')
-+            else:
-+                field = PasswordEdit('', challenge.default)
-+            def getter():
-+                """ 
-+                Closure around urwid.Edit.get_edit_text(), which we'll
-+                use to scrape the value out when we're all done.
-+                """
-+                return field.real_edit_text
-+            self._set_getter(challenge.name, getter)
-+        elif issubclass(challenge.type, str):
-             if challenge.default is NoDefault:
-                 field = urwid.Edit('', '')
-             else:
-@@ -326,3 +338,30 @@
-         listbox = urwid.ListBox(listwalker)
-         return urwid.AttrWrap(listbox, 'body')
- 
-+
-+class PasswordEdit(urwid.Edit):
-+    """Simple password editing widget"""
-+    def __init__(self, caption="", edit_text="", **kwargs):
-+        self.real_edit_text = edit_text
-+        edit_text = self._display_text()
-+
-+        self.__super.__init__(caption=caption, edit_text=edit_text, **kwargs)
-+
-+    def _display_text(self):
-+        """ 
-+        Return the text to display in lieu of showing real_edit_text 
-+        """
-+        return '*' * len(self.real_edit_text)
-+
-+    def keypress(self, size, key):
-+        """
-+        Temporarily set self.edit_text long enough to edit the field, then set
-+        it right back
-+        """
-+        self.edit_text = self.real_edit_text
-+        unhandled = urwid.Edit.keypress(self, size, key)
-+        self.real_edit_text = self.edit_text
-+        self.edit_text = self._display_text()
-+
-+        return unhandled
-+

urwid12

-# HG changeset patch
-# Parent f4e5adc530de4c9de67b2e0752d07a1aadcf3d19
-Added --gui command line switch, and cleaned up urwid import
-
-diff --git a/paste/script/command.py b/paste/script/command.py
---- a/paste/script/command.py
-+++ b/paste/script/command.py
-@@ -150,7 +150,6 @@
- 
-     def __init__(self, name):
-         self.command_name = name
--        self.ui = ui.get_user_interface()
- 
-     max_args = None
-     max_args_error = 'You must provide no more than %(max_args)s arguments'
-@@ -204,6 +203,8 @@
-         self.verbose += self.options.verbose
-         self.verbose -= self.options.quiet
-         self.simulate = getattr(self.options, 'simulate', False)
-+        if self.interactive:
-+            self.ui = ui.get_user_interface(use_gui=self.options.use_gui)
- 
-         # For #! situations:
-         if (os.environ.get('PASTE_CONFIG_FILE')
-@@ -331,7 +332,8 @@
-                         no_interactive=False,
-                         simulate=False,
-                         quiet=False,
--                        overwrite=False):
-+                        overwrite=False,
-+                        gui=False):
-         """
-         Create a standard ``OptionParser`` instance.
-         
-@@ -374,6 +376,11 @@
-                               dest="overwrite",
-                               action="store_true",
-                               help="Overwrite files (warnings will be emitted for non-matching files otherwise)")
-+        if gui:
-+            parser.add_option('--gui',
-+                              dest='use_gui',
-+                              action='store_true',
-+                              help="Use a GUI/TUI if one is available")
-         return parser
- 
-     standard_parser = classmethod(standard_parser)
-diff --git a/paste/script/create_distro.py b/paste/script/create_distro.py
---- a/paste/script/create_distro.py
-+++ b/paste/script/create_distro.py
-@@ -27,7 +27,8 @@
-     """
- 
-     parser = Command.standard_parser(
--        simulate=True, no_interactive=True, quiet=True, overwrite=True)
-+        simulate=True, no_interactive=True, quiet=True, overwrite=True, 
-+        gui=True)
-     parser.add_option('-t', '--template',
-                       dest='templates',
-                       metavar='TEMPLATE',
-diff --git a/paste/script/ui/__init__.py b/paste/script/ui/__init__.py
---- a/paste/script/ui/__init__.py
-+++ b/paste/script/ui/__init__.py
-@@ -3,29 +3,22 @@
- 
- from paste.script.pastevars import NoDefault, var
- 
--try:
--    import urwid
--    import urwid.raw_display
--except ImportError:
--    pass
--
--
--from paste.script.ui.pasteurwid import UrwidUI
--
--
--def get_user_interface():
-+def get_user_interface(use_gui=False):
-     """
-     Class factory for UserInterface and friends
-     """
--    try:
--        import urwid
--        import urwid.raw_display
--        has_urwid = True
--    except ImportError:
--        has_urwid = False
-     
--    if has_urwid:
--        return UrwidUI()
-+    userinterface = None
-+
-+    if use_gui:
-+        try:
-+            from paste.script.ui.pasteurwid import UrwidUI
-+            userinterface = UrwidUI()
-+        except ImportError:
-+            userinterface = None
-+
-+    if userinterface is not None:
-+        return userinterface
-     else:
-         return BasicUI()
- 
-diff --git a/paste/script/ui/pasteurwid.py b/paste/script/ui/pasteurwid.py
---- a/paste/script/ui/pasteurwid.py
-+++ b/paste/script/ui/pasteurwid.py
-@@ -3,11 +3,8 @@
- 
- from paste.script.pastevars import NoDefault, var
- 
--try:
--    import urwid
--    import urwid.raw_display
--except ImportError:
--    pass
-+import urwid
-+import urwid.raw_display
- 
- 
- class UrwidUI(object):    

urwid13

-# HG changeset patch
-# Parent 52d1c2908bc991bd51809ca4a8f2d4c497719496
-Graceful failover when old version of urwid is installed
-
-diff --git a/paste/script/ui/__init__.py b/paste/script/ui/__init__.py
---- a/paste/script/ui/__init__.py
-+++ b/paste/script/ui/__init__.py
-@@ -3,6 +3,7 @@
- 
- from paste.script.pastevars import NoDefault, var
- 
-+
- def get_user_interface(use_gui=False):
-     """
-     Class factory for UserInterface and friends
-@@ -12,8 +13,8 @@
- 
-     if use_gui:
-         try:
--            from paste.script.ui.pasteurwid import UrwidUI
--            userinterface = UrwidUI()
-+            from paste.script.ui.pasteurwid import get_urwid_interface
-+            userinterface = get_urwid_interface()
-         except ImportError:
-             userinterface = None
- 
-diff --git a/paste/script/ui/pasteurwid.py b/paste/script/ui/pasteurwid.py
---- a/paste/script/ui/pasteurwid.py
-+++ b/paste/script/ui/pasteurwid.py
-@@ -5,6 +5,15 @@
- 
- import urwid
- import urwid.raw_display
-+import sys
-+
-+
-+def get_urwid_interface():
-+    if urwid.__version__ >= '0.9.9':
-+        return UrwidUI()
-+    else:
-+        print >> sys.stderr, "Text UI requires Urwid 0.9.9 or greater"
-+        return None
- 
- 
- class UrwidUI(object):    

urwid14

-# HG changeset patch
-# Parent 0a34998e10c744270e2c5f5f949e63ef3fdfcc88
-Added new ui.report method
-
-diff --git a/paste/script/create_distro.py b/paste/script/create_distro.py
---- a/paste/script/create_distro.py
-+++ b/paste/script/create_distro.py
-@@ -75,13 +75,18 @@
-         if self.options.list_variables:
-             return self.list_variables(templates)
-         if self.verbose:
--            print 'Selected and implied templates:'
-+            msg = ""
-+            msg += "Selected and implied templates:\n"
-             max_tmpl_name = max([len(tmpl_name) for tmpl_name, tmpl in templates])
-             for tmpl_name, tmpl in templates:
--                print '  %s%s  %s' % (
-+                msg += "  %s%s  %s\n" % (
-                     tmpl_name, ' '*(max_tmpl_name-len(tmpl_name)),
-                     tmpl.summary)
--            print
-+            msg += "\n"
-+            if self.interactive:
-+                self.ui.report(msg)
-+            else:
-+                print msg
-         if not self.args:
-             if self.interactive:
-                 dist_name = self.challenge('Enter project name')
-diff --git a/paste/script/ui/__init__.py b/paste/script/ui/__init__.py
---- a/paste/script/ui/__init__.py
-+++ b/paste/script/ui/__init__.py
-@@ -73,5 +73,7 @@
-             print '  %s:%s  %s' % (
-                 name, ' '*(max_var-len(name)), value)
- 
-+    def report(self, msg):
-+        print msg
- 
- 
-diff --git a/paste/script/ui/pasteurwid.py b/paste/script/ui/pasteurwid.py
---- a/paste/script/ui/pasteurwid.py
-+++ b/paste/script/ui/pasteurwid.py
-@@ -50,6 +50,11 @@
-         display = UrwidDictDisplay(vars)
-         display.header_text = title
-         display.run()
-+        
-+    def report(self, msg, title="Message:"):
-+        display = UrwidMessageDisplay(msg)
-+        display.header_text = title
-+        display.run()
- 
- 
- class UrwidDialogException(Exception):
-@@ -60,6 +65,27 @@
-     def __init__(self, exit_token=None):
-         self.exit_token = exit_token
- 
-+
-+class UrwidMessageDisplay(object):
-+    def __init__(self, msg):
-+        self.header_text = 'Message:'
-+        self.msg = msg
-+
-+    def get_widgets(self):
-+        row = urwid.Text(('field', self.msg))
-+        widget = urwid.Padding(row, ('fixed left', 3), ('fixed right', 3))
-+        return [widget]
-+
-+    def get_button_defs(self):
-+        return [('ok', {'label':'  OK'}),
-+                ('cancel', {'label':'Cancel'})]
-+
-+    def run(self):
-+        dialog = UrwidDialog(self)
-+        dialog.header_text = self.header_text
-+        UrwidCancelWrapper(dialog).run()
-+
-+
- class UrwidDictDisplay(object):
-     def __init__(self, showdict):
-         self.showdict = showdict

urwid15

-# HG changeset patch
-# Parent 7bb40eccbc769a726bb98c38bfaee7d532d91e51
-Removed challenge_batch from Command
-
-diff --git a/paste/script/command.py b/paste/script/command.py
---- a/paste/script/command.py
-+++ b/paste/script/command.py
-@@ -313,12 +313,6 @@
-         """
-         return self.ui.challenge(prompt, default=default, should_echo=should_echo)
- 
--    def challenge_batch(self, challenges):
--        """
--        Prompt the user for a set of variables
--        """
--        return self.ui.challenge_batch(challenges)
--
-     def pad(self, s, length, dir='left'):
-         if len(s) >= length:
-             return s
-diff --git a/paste/script/templates.py b/paste/script/templates.py
---- a/paste/script/templates.py
-+++ b/paste/script/templates.py
-@@ -83,7 +83,7 @@
-             else:
-                 converted_vars[var.name] = unused_vars.pop(var.name)
-         if cmd.interactive:
--            converted_vars = cmd.challenge_batch(challenges)
-+            converted_vars = cmd.ui.challenge_batch(challenges)
-         
-         if errors:
-             raise command.BadCommand(

urwid2

-# HG changeset patch
-# Parent c549a67072fb299fb7385fec1057fdcd7b042851
-Created new UserInterface class, with BasicUI as first method
-
-diff --git a/paste/script/command.py b/paste/script/command.py
---- a/paste/script/command.py
-+++ b/paste/script/command.py
-@@ -16,6 +16,7 @@
-     from paste.script.util import subprocess24 as subprocess
- 
- import pastevars
-+import userinterface
- 
- NoDefault = pastevars.NoDefault
-     
-@@ -149,6 +150,7 @@
- 
-     def __init__(self, name):
-         self.command_name = name
-+        self.ui = userinterface.get_user_interface()
- 
-     max_args = None
-     max_args_error = 'You must provide no more than %(max_args)s arguments'
-@@ -308,22 +310,8 @@
-         """
-         Prompt the user for a variable.
-         """
--        if default is not NoDefault:
--            prompt += ' [%r]' % default
--        prompt += ': '
--        while 1:
--            if should_echo:
--                prompt_method = raw_input
--            else:
--                prompt_method = getpass.getpass
--            response = prompt_method(prompt).strip()
--            if not response:
--                if default is not NoDefault:
--                    return default
--                else:
--                    continue
--            else:
--                return response
-+        return self.ui.challenge(prompt, default=default, should_echo=should_echo)
-+
-         
-     def pad(self, s, length, dir='left'):
-         if len(s) >= length:
-diff --git a/paste/script/userinterface.py b/paste/script/userinterface.py
-new file mode 100644
---- /dev/null
-+++ b/paste/script/userinterface.py
-@@ -0,0 +1,43 @@
-+# (c) 2005-2010 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
-+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
-+
-+from pastevars import NoDefault
-+
-+def get_user_interface():
-+    """
-+    Class factory for UserInterface and friends
-+    """
-+    return BasicUI()
-+
-+
-+class UserInterface(object):
-+    """
-+    Abstract base class for all user interface classes
-+    """
-+    pass
-+
-+
-+class BasicUI(UserInterface):    
-+    def challenge(self, prompt, default=NoDefault, should_echo=True):
-+        """
-+        Prompt the user for a variable.
-+        """
-+        if default is not NoDefault:
-+            prompt += ' [%r]' % default
-+        prompt += ': '
-+        while 1:
-+            if should_echo:
-+                prompt_method = raw_input
-+            else:
-+                prompt_method = getpass.getpass
-+            response = prompt_method(prompt).strip()
-+            if not response:
-+                if default is not NoDefault:
-+                    return default
-+                else:
-+                    continue
-+            else:
-+                return response
-+
-+
-+

urwid3

-# HG changeset patch
-# Parent 60d93a96e3c1a4971d27b6e9c4b2e95f7cbf0302
-Implementing challenge_batch in BasicUI
-
-diff --git a/paste/script/command.py b/paste/script/command.py
---- a/paste/script/command.py
-+++ b/paste/script/command.py
-@@ -312,7 +312,12 @@
-         """
-         return self.ui.challenge(prompt, default=default, should_echo=should_echo)
- 
--        
-+    def challenge_batch(self, challenges):
-+        """
-+        Prompt the user for a set of variables
-+        """
-+        return self.ui.challenge_batch(challenges)
-+
-     def pad(self, s, length, dir='left'):
-         if len(s) >= length:
-             return s
-diff --git a/paste/script/templates.py b/paste/script/templates.py
---- a/paste/script/templates.py
-+++ b/paste/script/templates.py
-@@ -69,12 +69,12 @@
-         converted_vars = {}
-         unused_vars = vars.copy()
-         errors = []
-+        if cmd.interactive:
-+            challenges = []
-         for var in expect_vars:
-             if var.name not in unused_vars:
-                 if cmd.interactive:
--                    prompt = 'Enter %s' % var.full_description()
--                    response = cmd.challenge(prompt, var.default, var.should_echo)
--                    converted_vars[var.name] = response
-+                    challenges.append(var)
-                 elif var.default is command.NoDefault:
-                     errors.append('Required variable missing: %s'
-                                   % var.full_description())
-@@ -82,6 +82,9 @@
-                     converted_vars[var.name] = var.default
-             else:
-                 converted_vars[var.name] = unused_vars.pop(var.name)
-+        if cmd.interactive:
-+            converted_vars = cmd.challenge_batch(challenges)
-+        
-         if errors:
-             raise command.BadCommand(
-                 'Errors in variables:\n%s' % '\n'.join(errors))
-diff --git a/paste/script/userinterface.py b/paste/script/userinterface.py
---- a/paste/script/userinterface.py
-+++ b/paste/script/userinterface.py
-@@ -1,7 +1,7 @@
- # (c) 2005-2010 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
- # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
- 
--from pastevars import NoDefault
-+from pastevars import NoDefault, var
- 
- def get_user_interface():
-     """
-@@ -39,5 +39,15 @@
-             else:
-                 return response
- 
-+    def challenge_batch(self, challenges):
-+        """
-+        Return a series of responses
-+        """
-+        converted_vars = {}
-+        for var in challenges:
-+            prompt = 'Enter %s' % var.full_description()
-+            response = self.challenge(prompt, var.default, var.should_echo)
-+            converted_vars[var.name] = response
-+        return converted_vars
- 
- 

urwid4

-# HG changeset patch
-# Parent 3ae996a49ffa1fe16fe25b176212e282e7c120e6
-Added UrwidDialog implementation, and added display_vars to UserInterface class
-
-diff --git a/paste/script/create_distro.py b/paste/script/create_distro.py
---- a/paste/script/create_distro.py
-+++ b/paste/script/create_distro.py
-@@ -286,13 +286,7 @@
-         return self._entry_points
- 
-     def display_vars(self, vars):
--        vars = vars.items()
--        vars.sort()
--        print 'Variables:'
--        max_var = max([len(n) for n, v in vars])
--        for name, value in vars:
--            print '  %s:%s  %s' % (
--                name, ' '*(max_var-len(name)), value)
-+        self.ui.display_vars(vars)
-         
-     def list_templates(self):
-         templates = []
-diff --git a/paste/script/userinterface.py b/paste/script/userinterface.py
---- a/paste/script/userinterface.py
-+++ b/paste/script/userinterface.py
-@@ -3,11 +3,28 @@
- 
- from pastevars import NoDefault, var
- 
-+try:
-+    import urwid
-+    import urwid.raw_display
-+except ImportError:
-+    pass
-+
-+
- def get_user_interface():
-     """
-     Class factory for UserInterface and friends
-     """
--    return BasicUI()
-+    try:
-+        import urwid
-+        import urwid.raw_display
-+        has_urwid = True
-+    except ImportError:
-+        has_urwid = False
-+    
-+    if has_urwid:
-+        return UrwidUI()
-+    else:
-+        return BasicUI()
- 
- 
- class UserInterface(object):
-@@ -22,6 +39,8 @@
-         """
-         Prompt the user for a variable.
-         """
-+        
-+        
-         if default is not NoDefault:
-             prompt += ' [%r]' % default
-         prompt += ': '
-@@ -50,4 +69,219 @@
-             converted_vars[var.name] = response
-         return converted_vars
- 
-+    def display_vars(self, vars):
-+        vars = vars.items()
-+        vars.sort()
-+        print 'Variables:'
-+        max_var = max([len(n) for n, v in vars])
-+        for name, value in vars:
-+            print '  %s:%s  %s' % (
-+                name, ' '*(max_var-len(name)), value)
- 
-+