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):