This fixes https://fedorahosted.org/freeipa/ticket/2345 (^D on password input returns traceback), with common code in all the prompt methods refactored out, so they will all behave the same on ^D and ^C.

--
PetrĀ³
>From 0ab03b780303aac1f4ae0d727715daf4832b23c4 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pvikt...@redhat.com>
Date: Thu, 9 Feb 2012 09:09:03 -0500
Subject: [PATCH] Add common helper for interactive prompts

This patch adds a common method, textui.prompt_helper, that handles
encoding, decoding and error handling for interactive prompts.
On EOFError (Ctrl+D) or  KeyboardInterrupt (Ctrl+C), it raises
a new InvocationError, PromptFailed.

The helper is used in prompt, prompt_yesno, and prompt_password,
each of which originally only handled one of Ctrl+C and Ctrl+D.
This fixes https://fedorahosted.org/freeipa/ticket/2345
And it means prompt_yesno will no longer return None on error.

A minor fix restores errors.py's ability print out the list of
errors when run as a script.
---
 ipalib/cli.py    |   59 +++++++++++++++++++++++++++--------------------------
 ipalib/errors.py |   11 +++++++++-
 2 files changed, 40 insertions(+), 30 deletions(-)

diff --git a/ipalib/cli.py b/ipalib/cli.py
index 5d07cb1b5113852be68043eb2c814a1d0609702c..dac468ce6a1647ed1f727aae08482302efe7d636 100644
--- a/ipalib/cli.py
+++ b/ipalib/cli.py
@@ -46,7 +46,9 @@ import frontend
 import backend
 import plugable
 import util
-from errors import PublicError, CommandError, HelpError, InternalError, NoSuchNamespaceError, ValidationError, NotFound, NotConfiguredError
+from errors import (PublicError, CommandError, HelpError, InternalError,
+        NoSuchNamespaceError, ValidationError, NotFound, NotConfiguredError,
+        PromptFailed)
 from constants import CLI_TAB
 from parameters import Password, Bytes, File, Str, StrEnum
 from text import _
@@ -515,6 +517,18 @@ class textui(backend.Backend):
     def print_error(self, text):
         print '  ** %s **' % unicode(text)
 
+    def prompt_helper(self, prompt, label, prompt_func=raw_input):
+        """Prompt user for input
+
+        Handles encoding the prompt and decoding the input.
+        On end of stream or ctrl+c, raise PromptFailed.
+        """
+        try:
+            return self.decode(prompt_func(self.encode(prompt)))
+        except (KeyboardInterrupt, EOFError):
+            print
+            raise PromptFailed(name=label)
+
     def prompt(self, label, default=None, get_values=None, optional=False):
         """
         Prompt user for input.
@@ -528,11 +542,7 @@ class textui(backend.Backend):
             prompt = u'%s: ' % prompt
         else:
             prompt = u'%s [%s]: ' % (prompt, default)
-        try:
-            data = raw_input(self.encode(prompt))
-        except EOFError:
-            return None
-        return self.decode(data)
+        return self.prompt_helper(prompt, label)
 
     def prompt_yesno(self, label, default=None):
         """
@@ -546,8 +556,6 @@ class textui(backend.Backend):
 
         If Default parameter is None, user is asked for Yes/No answer until
         a correct answer is provided. Answer is then returned.
-
-        In case of an error, a None value may returned
         """
 
         default_prompt = None
@@ -563,10 +571,7 @@ class textui(backend.Backend):
             prompt = u'%s Yes/No: ' % label
 
         while True:
-            try:
-                data = raw_input(self.encode(prompt)).lower()
-            except EOFError:
-                return None
+            data = self.prompt_helper(prompt, label)
 
             if data in (u'yes', u'y'):
                 return True
@@ -580,23 +585,19 @@ class textui(backend.Backend):
         Prompt user for a password or read it in via stdin depending
         on whether there is a tty or not.
         """
-        try:
-            if sys.stdin.isatty():
-                while True:
-                    pw1 = getpass.getpass(u'%s: ' % unicode(label))
-                    if not confirm:
-                        return self.decode(pw1)
-                    pw2 = getpass.getpass(
-                        unicode(_('Enter %(label)s again to verify: ') % dict(label=label))
-                    )
-                    if pw1 == pw2:
-                        return self.decode(pw1)
-                    self.print_error( _('Passwords do not match!'))
-            else:
-                return self.decode(sys.stdin.readline().strip())
-        except KeyboardInterrupt:
-            print ''
-            self.print_error(_('Cancelled.'))
+        if sys.stdin.isatty():
+            prompt = u'%s: ' % unicode(label)
+            repeat_prompt = unicode(_('Enter %(label)s again to verify: ') % dict(label=label))
+            while True:
+                pw1 = self.prompt_helper(prompt, label, prompt_func=getpass.getpass)
+                if not confirm:
+                    return pw1
+                pw2 = self.prompt_helper(repeat_prompt, label, prompt_func=getpass.getpass)
+                if pw1 == pw2:
+                    return pw1
+                self.print_error( _('Passwords do not match!'))
+        else:
+            return self.decode(sys.stdin.readline().strip())
 
     def select_entry(self, entries, format, attrs, display_count=True):
         """
diff --git a/ipalib/errors.py b/ipalib/errors.py
index f115f0c4ec206812b9e2532449c0ed9008e3a068..bc46a33a5d3f6b69dff94a09e09acb3a03d64db5 100644
--- a/ipalib/errors.py
+++ b/ipalib/errors.py
@@ -802,6 +802,15 @@ class NotConfiguredError(InvocationError):
     format = _('Client is not configured. Run ipa-client-install.')
 
 
+class PromptFailed(InvocationError):
+    """
+    **3014** Raise when an interactive prompt failed.
+    """
+
+    errno = 3014
+    format = _('Could not get %(name)s interactively')
+
+
 ##############################################################################
 # 4000 - 4999: Execution errors
 
@@ -1573,5 +1582,5 @@ public_errors = tuple(
 
 if __name__ == '__main__':
     for klass in public_errors:
-        print '%d\t%s' % (klass.code, klass.__name__)
+        print '%d\t%s' % (klass.errno, klass.__name__)
     print '(%d public errors)' % len(public_errors)
-- 
1.7.7.6

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to