Title: [280640] trunk/Tools
Revision
280640
Author
jbed...@apple.com
Date
2021-08-04 10:58:51 -0700 (Wed, 04 Aug 2021)

Log Message

[webkitcorepy] Add shared terminal input code
https://bugs.webkit.org/show_bug.cgi?id=226024
<rdar://problem/78261645>

Reviewed by Dewei Zhu.

* Scripts/libraries/webkitcorepy/setup.py: Bump version.
* Scripts/libraries/webkitcorepy/webkitcorepy/__init__.py: Export Terminal object, bump version.
* Scripts/libraries/webkitcorepy/webkitcorepy/credentials.py:
(credentials): Use Terminal.input.
* Scripts/libraries/webkitcorepy/webkitcorepy/mocks/__init__.py:
* Scripts/libraries/webkitcorepy/webkitcorepy/mocks/terminal.py: Added.
(Terminal):
(Terminal.input): Mocking input and raw_input requires so specialized knowledge,
so we should generalize it.
* Scripts/libraries/webkitcorepy/webkitcorepy/terminal.py: Added.
(Terminal):
(Terminal.input): Python 2/3 compatible input function.
(Terminal.choose): Generic multiple-choice input prompt.
* Scripts/libraries/webkitcorepy/webkitcorepy/tests/terminal_unittest.py: Added.
(TerminalTests):
(TerminalTests.test_choose_basic):
(TerminalTests.test_choose_strict):
(TerminalTests.test_choose_default):
(TerminalTests.test_choose_triple):
(TerminalTests.test_choose_number):
* Scripts/webkitpy/common/system/user.py:
(User.prompt): Use Terminal.input.
(User.prompt_with_multiple_lists): Ditto.
(User.prompt_with_list): Ditto.
(User.confirm): Ditto.
* Scripts/webkitpy/common/system/user_mock.py:
(MockUser.prompt): Use Terminal.input.
(MockUser.prompt_with_list): Ditto.

Modified Paths

Added Paths

Diff

Modified: trunk/Tools/ChangeLog (280639 => 280640)


--- trunk/Tools/ChangeLog	2021-08-04 17:43:14 UTC (rev 280639)
+++ trunk/Tools/ChangeLog	2021-08-04 17:58:51 UTC (rev 280640)
@@ -1,3 +1,40 @@
+2021-08-04  Jonathan Bedard  <jbed...@apple.com>
+
+        [webkitcorepy] Add shared terminal input code
+        https://bugs.webkit.org/show_bug.cgi?id=226024
+        <rdar://problem/78261645>
+
+        Reviewed by Dewei Zhu.
+
+        * Scripts/libraries/webkitcorepy/setup.py: Bump version.
+        * Scripts/libraries/webkitcorepy/webkitcorepy/__init__.py: Export Terminal object, bump version.
+        * Scripts/libraries/webkitcorepy/webkitcorepy/credentials.py:
+        (credentials): Use Terminal.input.
+        * Scripts/libraries/webkitcorepy/webkitcorepy/mocks/__init__.py:
+        * Scripts/libraries/webkitcorepy/webkitcorepy/mocks/terminal.py: Added.
+        (Terminal):
+        (Terminal.input): Mocking input and raw_input requires so specialized knowledge,
+        so we should generalize it.
+        * Scripts/libraries/webkitcorepy/webkitcorepy/terminal.py: Added.
+        (Terminal):
+        (Terminal.input): Python 2/3 compatible input function.
+        (Terminal.choose): Generic multiple-choice input prompt.
+        * Scripts/libraries/webkitcorepy/webkitcorepy/tests/terminal_unittest.py: Added.
+        (TerminalTests):
+        (TerminalTests.test_choose_basic):
+        (TerminalTests.test_choose_strict):
+        (TerminalTests.test_choose_default):
+        (TerminalTests.test_choose_triple):
+        (TerminalTests.test_choose_number):
+        * Scripts/webkitpy/common/system/user.py:
+        (User.prompt): Use Terminal.input.
+        (User.prompt_with_multiple_lists): Ditto.
+        (User.prompt_with_list): Ditto.
+        (User.confirm): Ditto.
+        * Scripts/webkitpy/common/system/user_mock.py:
+        (MockUser.prompt): Use Terminal.input.
+        (MockUser.prompt_with_list): Ditto.
+
 2021-08-04  Andres Gonzalez  <andresg...@apple.com>
 
         Add contributor to accessibility watchlist.

Modified: trunk/Tools/Scripts/libraries/webkitcorepy/setup.py (280639 => 280640)


--- trunk/Tools/Scripts/libraries/webkitcorepy/setup.py	2021-08-04 17:43:14 UTC (rev 280639)
+++ trunk/Tools/Scripts/libraries/webkitcorepy/setup.py	2021-08-04 17:58:51 UTC (rev 280640)
@@ -30,7 +30,7 @@
 
 setup(
     name='webkitcorepy',
-    version='0.7.4',
+    version='0.8.0',
     description='Library containing various Python support classes and functions.',
     long_description=readme(),
     classifiers=[

Modified: trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/__init__.py (280639 => 280640)


--- trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/__init__.py	2021-08-04 17:43:14 UTC (rev 280639)
+++ trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/__init__.py	2021-08-04 17:58:51 UTC (rev 280640)
@@ -35,11 +35,12 @@
 from webkitcorepy.subprocess_utils import TimeoutExpired, CompletedProcess, run
 from webkitcorepy.output_capture import LoggerCapture, OutputCapture, OutputDuplicate
 from webkitcorepy.task_pool import TaskPool
+from webkitcorepy.terminal import Terminal
 from webkitcorepy.credentials import credentials
 from webkitcorepy.measure_time import MeasureTime
 from webkitcorepy.nested_fuzzy_dict import NestedFuzzyDict
 
-version = Version(0, 7, 4)
+version = Version(0, 8, 0)
 
 from webkitcorepy.autoinstall import Package, AutoInstall
 if sys.version_info > (3, 0):

Modified: trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/credentials.py (280639 => 280640)


--- trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/credentials.py	2021-08-04 17:43:14 UTC (rev 280639)
+++ trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/credentials.py	2021-08-04 17:58:51 UTC (rev 280640)
@@ -25,7 +25,7 @@
 import sys
 
 from subprocess import CalledProcessError
-from webkitcorepy import OutputCapture
+from webkitcorepy import OutputCapture, Terminal
 
 _cache = dict()
 
@@ -64,7 +64,7 @@
                 raise OSError('No tty to prompt user for username')
             sys.stderr.write("Authentication required to use {}\n".format(prompt or name))
             sys.stderr.write('Username: ')
-            username = (input if sys.version_info > (3, 0) else raw_input)()
+            username = Terminal.input()
             username_prompted = True
 
     if not key:
@@ -85,7 +85,7 @@
 
     if keyring and (username_prompted or key_prompted):
         sys.stderr.write('Store username and {} in system keyring for {}? (Y/N): '.format(key_name, url))
-        response = (input if sys.version_info > (3, 0) else raw_input)()
+        response = Terminal.input()
         if response.lower() in ['y', 'yes', 'ok']:
             sys.stderr.write('Storing credentials...\n')
             keyring.set_password(url, 'username', username)

Modified: trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/mocks/__init__.py (280639 => 280640)


--- trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/mocks/__init__.py	2021-08-04 17:43:14 UTC (rev 280639)
+++ trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/mocks/__init__.py	2021-08-04 17:58:51 UTC (rev 280640)
@@ -25,3 +25,4 @@
 from webkitcorepy.mocks.subprocess import ProcessCompletion, Subprocess
 
 from webkitcorepy.mocks.requests_ import Response, Requests
+from webkitcorepy.mocks.terminal import Terminal

Copied: trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/mocks/terminal.py (from rev 280639, trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/mocks/__init__.py) (0 => 280640)


--- trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/mocks/terminal.py	                        (rev 0)
+++ trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/mocks/terminal.py	2021-08-04 17:58:51 UTC (rev 280640)
@@ -0,0 +1,44 @@
+# Copyright (C) 2021 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1.  Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import sys
+
+
+class Terminal(object):
+    index = 0
+
+    @classmethod
+    def input(cls, *args):
+        from mock import patch
+
+        cls.index = 0
+
+        def function(output):
+            print(output)
+            cls.index += 1
+            return args[cls.index - 1]
+
+        if sys.version_info > (3, 0):
+            return patch('builtins.input', new=function)
+
+        import __builtin__
+        return patch.object(__builtin__, 'raw_input', new=function)

Added: trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/terminal.py (0 => 280640)


--- trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/terminal.py	                        (rev 0)
+++ trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/terminal.py	2021-08-04 17:58:51 UTC (rev 280640)
@@ -0,0 +1,59 @@
+# Copyright (C) 2021 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1.  Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import sys
+
+
+class Terminal(object):
+    @classmethod
+    def input(cls, *args, **kwargs):
+        return (input if sys.version_info > (3, 0) else raw_input)(*args, **kwargs)
+
+    @classmethod
+    def choose(cls, prompt, options=None, default=None, strict=False, numbered=False):
+        options = options or ('Yes', 'No')
+
+        response = None
+        while response is None:
+            if numbered:
+                numbered_options = ['{}) {}'.format(i + 1, options[i]) for i in range(len(options))]
+                response = cls.input('{}:\n    {}\n: '.format(prompt, '\n    '.join(numbered_options)))
+            else:
+                response = cls.input('{} ({}): '.format(prompt, '/'.join(options)))
+
+            if numbered and response.isdigit():
+                index = int(response) - 1
+                if index >= 0 and index < len(options):
+                    response = options[index]
+
+            if not strict:
+                for option in options:
+                    if option.lower().startswith(response.lower()):
+                        response = option
+                        break
+
+            if response not in options:
+                if not default:
+                    print("'{}' is not an option".format(response))
+                response = default
+
+        return response

Added: trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/tests/terminal_unittest.py (0 => 280640)


--- trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/tests/terminal_unittest.py	                        (rev 0)
+++ trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/tests/terminal_unittest.py	2021-08-04 17:58:51 UTC (rev 280640)
@@ -0,0 +1,93 @@
+# Copyright (C) 2021 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1.  Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import unittest
+
+from mock import patch
+from webkitcorepy import mocks, OutputCapture, Terminal
+
+
+class TerminalTests(unittest.TestCase):
+    def test_choose_basic(self):
+        with mocks.Terminal.input('y'), OutputCapture() as captured:
+            self.assertEqual('Yes', Terminal.choose('Continue'))
+        self.assertEqual(captured.stdout.getvalue(), 'Continue (Yes/No): \n')
+
+        with mocks.Terminal.input('n'), OutputCapture() as captured:
+            self.assertEqual('No', Terminal.choose('Continue'))
+        self.assertEqual(captured.stdout.getvalue(), 'Continue (Yes/No): \n')
+
+        with mocks.Terminal.input('huh', 'y'), OutputCapture() as captured:
+            self.assertEqual('Yes', Terminal.choose('Continue'))
+        self.assertEqual(captured.stdout.getvalue(), "Continue (Yes/No): \n'huh' is not an option\nContinue (Yes/No): \n")
+
+    def test_choose_strict(self):
+        with mocks.Terminal.input('Yes'), OutputCapture() as captured:
+            self.assertEqual('Yes', Terminal.choose('Continue', strict=True))
+        self.assertEqual(captured.stdout.getvalue(), 'Continue (Yes/No): \n')
+
+        with mocks.Terminal.input('No'), OutputCapture() as captured:
+            self.assertEqual('No', Terminal.choose('Continue', strict=True))
+        self.assertEqual(captured.stdout.getvalue(), 'Continue (Yes/No): \n')
+
+        with mocks.Terminal.input('n', 'Yes'), OutputCapture() as captured:
+            self.assertEqual('Yes', Terminal.choose('Continue', strict=True))
+        self.assertEqual(captured.stdout.getvalue(), "Continue (Yes/No): \n'n' is not an option\nContinue (Yes/No): \n")
+
+        with mocks.Terminal.input('y', 'No'), OutputCapture() as captured:
+            self.assertEqual('No', Terminal.choose('Continue', strict=True))
+        self.assertEqual(captured.stdout.getvalue(), "Continue (Yes/No): \n'y' is not an option\nContinue (Yes/No): \n")
+
+    def test_choose_default(self):
+        with mocks.Terminal.input('y'), OutputCapture() as captured:
+            self.assertEqual('Yes', Terminal.choose('Continue', default='Other'))
+        self.assertEqual(captured.stdout.getvalue(), 'Continue (Yes/No): \n')
+
+        with mocks.Terminal.input('n'), OutputCapture() as captured:
+            self.assertEqual('No', Terminal.choose('Continue', default='Other'))
+        self.assertEqual(captured.stdout.getvalue(), 'Continue (Yes/No): \n')
+
+        with mocks.Terminal.input('huh'), OutputCapture() as captured:
+            self.assertEqual('Other', Terminal.choose('Continue', default='Other'))
+        self.assertEqual(captured.stdout.getvalue(), 'Continue (Yes/No): \n')
+
+    def test_choose_triple(self):
+        with mocks.Terminal.input('y'), OutputCapture() as captured:
+            self.assertEqual('Yes', Terminal.choose('Continue', options=('Yes', 'No', 'Maybe')))
+        self.assertEqual(captured.stdout.getvalue(), 'Continue (Yes/No/Maybe): \n')
+
+        with mocks.Terminal.input('n'), OutputCapture() as captured:
+            self.assertEqual('No', Terminal.choose('Continue', options=('Yes', 'No', 'Maybe')))
+        self.assertEqual(captured.stdout.getvalue(), 'Continue (Yes/No/Maybe): \n')
+
+        with mocks.Terminal.input('may'), OutputCapture() as captured:
+            self.assertEqual('Maybe', Terminal.choose('Continue', options=('Yes', 'No', 'Maybe')))
+        self.assertEqual(captured.stdout.getvalue(), 'Continue (Yes/No/Maybe): \n')
+
+        with mocks.Terminal.input('huh'), OutputCapture() as captured:
+            self.assertEqual('No', Terminal.choose('Continue', options=('Yes', 'No', 'Maybe'), default='No'))
+        self.assertEqual(captured.stdout.getvalue(), 'Continue (Yes/No/Maybe): \n')
+
+    def test_choose_number(self):
+        with mocks.Terminal.input('2'), OutputCapture() as captured:
+            self.assertEqual('Beta', Terminal.choose('Pick', options=('Alpha', 'Beta', 'Charlie', 'Delta'), numbered=True))
+        self.assertEqual(captured.stdout.getvalue(), 'Pick:\n    1) Alpha\n    2) Beta\n    3) Charlie\n    4) Delta\n: \n')

Modified: trunk/Tools/Scripts/webkitpy/common/system/user.py (280639 => 280640)


--- trunk/Tools/Scripts/webkitpy/common/system/user.py	2021-08-04 17:43:14 UTC (rev 280639)
+++ trunk/Tools/Scripts/webkitpy/common/system/user.py	2021-08-04 17:58:51 UTC (rev 280640)
@@ -36,6 +36,8 @@
 import subprocess
 import webbrowser
 
+from webkitcorepy import Terminal
+
 from webkitpy.common.system.executive import Executive
 from webkitpy.common.system.platforminfo import PlatformInfo
 
@@ -50,12 +52,7 @@
         # There is no readline module for win32, not much to do except cry.
         _log.warn("Unable to import readline.")
 
-if sys.version_info > (3, 0):
-    input_func = input
-else:
-    input_func = raw_input
 
-
 class User(object):
     DEFAULT_NO = 'n'
     DEFAULT_YES = 'y'
@@ -67,7 +64,7 @@
 
     # FIXME: These are @classmethods because bugzilla.py doesn't have a Tool object (thus no User instance).
     @classmethod
-    def prompt(cls, message, repeat=1, raw_input=input_func):
+    def prompt(cls, message, repeat=1, raw_input=Terminal.input):
         response = None
         while (repeat and not response):
             repeat -= 1
@@ -79,7 +76,7 @@
         return cls.prompt(message, repeat=repeat, raw_input=getpass.getpass)
 
     @classmethod
-    def prompt_with_multiple_lists(cls, list_title, subtitles, lists, can_choose_multiple=False, raw_input=input_func):
+    def prompt_with_multiple_lists(cls, list_title, subtitles, lists, can_choose_multiple=False, raw_input=Terminal.input):
         item_index = 0
         cumulated_list = []
         print(list_title)
@@ -119,7 +116,7 @@
                 return list_items[result]
 
     @classmethod
-    def prompt_with_list(cls, list_title, list_items, can_choose_multiple=False, raw_input=input_func):
+    def prompt_with_list(cls, list_title, list_items, can_choose_multiple=False, raw_input=Terminal.input):
         print(list_title)
         i = 0
         for item in list_items:
@@ -161,7 +158,7 @@
         except IOError:
             pass
 
-    def confirm(self, message=None, default=DEFAULT_YES, raw_input=input_func):
+    def confirm(self, message=None, default=DEFAULT_YES, raw_input=Terminal.input):
         if not message:
             message = "Continue?"
         choice = {'y': 'Y/n', 'n': 'y/N'}[default]

Modified: trunk/Tools/Scripts/webkitpy/common/system/user_mock.py (280639 => 280640)


--- trunk/Tools/Scripts/webkitpy/common/system/user_mock.py	2021-08-04 17:43:14 UTC (rev 280639)
+++ trunk/Tools/Scripts/webkitpy/common/system/user_mock.py	2021-08-04 17:58:51 UTC (rev 280640)
@@ -29,20 +29,19 @@
 import logging
 import sys
 
+from webkitcorepy import Terminal
+
 _log = logging.getLogger(__name__)
 
-if sys.version_info < (3, 0):
-    input = raw_input
 
-
 class MockUser(object):
 
     @classmethod
-    def prompt(cls, message, repeat=1, raw_input=input):
+    def prompt(cls, message, repeat=1, raw_input=Terminal.input):
         return "Mock user response"
 
     @classmethod
-    def prompt_with_list(cls, list_title, list_items, can_choose_multiple=False, raw_input=input):
+    def prompt_with_list(cls, list_title, list_items, can_choose_multiple=False, raw_input=Terminal.input):
         pass
 
     def __init__(self):
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to