[git_auth] Print empty line after all read input

Small UX improvement

Bug: 404613530
Change-Id: Ie1cb79279a41cd80f1992e2e18107d4a15e16edb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/6418884
Reviewed-by: Yiwei Zhang <yiwzhang@google.com>
Commit-Queue: Allen Li <ayatane@chromium.org>
changes/84/6418884/4
Allen Li 4 weeks ago committed by LUCI CQ
parent 5dcf92389b
commit e32bcc7be0

@ -359,6 +359,97 @@ class _GitcookiesSituation(NamedTuple):
divergent_cookiefiles: bool
_InputChecker = Callable[['UserInterface', str], bool]
def _check_any(ui: UserInterface, line: str) -> bool:
"""Allow any input."""
return True
def _check_nonempty(ui: UserInterface, line: str) -> bool:
"""Reject nonempty input."""
if line:
return True
ui.write('Input cannot be empty.\n')
return False
def _check_choice(choices: Collection[str]) -> _InputChecker:
"""Allow specified choices."""
def func(ui: UserInterface, line: str) -> bool:
if line in choices:
return True
ui.write('Invalid choice.\n')
return False
return func
class UserInterface(object):
"""Abstracts user interaction for ConfigWizard.
This implementation supports regular terminals.
"""
_prompts = {
None: 'y/n',
True: 'Y/n',
False: 'y/N',
}
def __init__(self, stdin: TextIO, stdout: TextIO):
self._stdin = stdin
self._stdout = stdout
def read_yn(self, prompt: str, *, default: bool | None = None) -> bool:
"""Reads a yes/no response.
The prompt should end in '?'.
"""
prompt = f'{prompt} [{self._prompts[default]}]: '
while True:
self._stdout.write(prompt)
self._stdout.flush()
response = self._stdin.readline().strip().lower()
if response in ('y', 'yes'):
return True
if response in ('n', 'no'):
return False
if not response and default is not None:
return default
self._stdout.write('Type y or n.\n')
def read_line(self,
prompt: str,
*,
check: _InputChecker = _check_any) -> str:
"""Reads a line of input.
Trailing whitespace is stripped from the read string.
The prompt should not end in any special indicator like a colon.
Optionally, an input check function may be provided. This
method will continue to prompt for input until it passes the
check. The check should print some explanation for rejected
inputs.
"""
while True:
self._stdout.write(f'{prompt}: ')
self._stdout.flush()
s = self._stdin.readline().rstrip()
if check(self, s):
return s
def write(self, s: str) -> None:
"""Write string as-is.
The string should usually end in a newline.
"""
self._stdout.write(s)
class ConfigWizard(object):
"""Wizard for setting up user's Git config Gerrit authentication."""
@ -407,8 +498,8 @@ class ConfigWizard(object):
self._println(
'You can re-run this command inside a Gerrit repository,'
' or we can try to set up some commonly used Gerrit hosts.')
if not self._ui.read_yn('Set up commonly used Gerrit hosts?',
default=True):
if not self._read_yn('Set up commonly used Gerrit hosts?',
default=True):
self._println('Okay, skipping Gerrit host setup.')
self._println(
'You can re-run this command later or follow the instructions for manual configuration.'
@ -508,7 +599,7 @@ class ConfigWizard(object):
'This won"t affect Git authentication, but may cause issues for'
)
self._println('other Gerrit operations in depot_tools.')
if self._ui.read_yn(
if self._read_yn(
'Shall we move your .gitcookies file (to a backup location)?',
default=True):
self._move_file(self._gitcookies())
@ -527,7 +618,7 @@ class ConfigWizard(object):
self._println(
'This will not affect anything, but we suggest removing the http.cookiefile from your Git config.'
)
if self._ui.read_yn('Shall we remove it for you?', default=True):
if self._read_yn('Shall we remove it for you?', default=True):
self._set_config('http.cookiefile', None, scope='global')
return
@ -567,7 +658,7 @@ class ConfigWizard(object):
self._println(
'Cookie auth is deprecated, and these cookies may interfere with Gerrit authentication.'
)
if not self._ui.read_yn(
if not self._read_yn(
'Shall we move your cookie file (to a backup location)?',
default=True):
self._println(
@ -606,15 +697,15 @@ class ConfigWizard(object):
return email
self._println(
'You do not have an email configured in your global Git config.')
if not self._ui.read_yn('Do you want to set one now?', default=True):
if not self._read_yn('Do you want to set one now?', default=True):
self._println('Will attempt to continue without a global email.')
return ''
name = scm.GIT.GetConfig(os.getcwd(), 'user.name', scope='global') or ''
if not name:
name = self._ui.read_line('Enter your name (e.g., John Doe)',
check=_check_nonempty)
name = self._read_line('Enter your name (e.g., John Doe)',
check=_check_nonempty)
self._set_config('user.name', name, scope='global')
email = self._ui.read_line('Enter your email', check=_check_nonempty)
email = self._read_line('Enter your email', check=_check_nonempty)
self._set_config('user.email', email, scope='global')
return email
@ -769,6 +860,19 @@ class ConfigWizard(object):
self._ui.write(s)
self._ui.write('\n')
def _read_yn(self, prompt: str, *, default: bool | None = None) -> bool:
ret = self._ui.read_yn(prompt, default=default)
self._ui.write('\n')
return ret
def _read_line(self,
prompt: str,
*,
check: _InputChecker = _check_any) -> str:
ret = self._ui.read_line(prompt, check=check)
self._ui.write('\n')
return ret
@staticmethod
def _gitcookies() -> str:
"""Path to user's gitcookies.
@ -778,97 +882,6 @@ class ConfigWizard(object):
return os.path.expanduser('~/.gitcookies')
_InputChecker = Callable[['UserInterface', str], bool]
def _check_any(ui: UserInterface, line: str) -> bool:
"""Allow any input."""
return True
def _check_nonempty(ui: UserInterface, line: str) -> bool:
"""Reject nonempty input."""
if line:
return True
ui.write('Input cannot be empty.\n')
return False
def _check_choice(choices: Collection[str]) -> _InputChecker:
"""Allow specified choices."""
def func(ui: UserInterface, line: str) -> bool:
if line in choices:
return True
ui.write('Invalid choice.\n')
return False
return func
class UserInterface(object):
"""Abstracts user interaction for ConfigWizard.
This implementation supports regular terminals.
"""
_prompts = {
None: 'y/n',
True: 'Y/n',
False: 'y/N',
}
def __init__(self, stdin: TextIO, stdout: TextIO):
self._stdin = stdin
self._stdout = stdout
def read_yn(self, prompt: str, *, default: bool | None = None) -> bool:
"""Reads a yes/no response.
The prompt should end in '?'.
"""
prompt = f'{prompt} [{self._prompts[default]}]: '
while True:
self._stdout.write(prompt)
self._stdout.flush()
response = self._stdin.readline().strip().lower()
if response in ('y', 'yes'):
return True
if response in ('n', 'no'):
return False
if not response and default is not None:
return default
self._stdout.write('Type y or n.\n')
def read_line(self,
prompt: str,
*,
check: _InputChecker = _check_any) -> str:
"""Reads a line of input.
Trailing whitespace is stripped from the read string.
The prompt should not end in any special indicator like a colon.
Optionally, an input check function may be provided. This
method will continue to prompt for input until it passes the
check. The check should print some explanation for rejected
inputs.
"""
while True:
self._stdout.write(f'{prompt}: ')
self._stdout.flush()
s = self._stdin.readline().rstrip()
if check(self, s):
return s
def write(self, s: str) -> None:
"""Write string as-is.
The string should usually end in a newline.
"""
self._stdout.write(s)
class _CookiefileInfo(NamedTuple):
"""Result for _parse_cookiefile."""
contains_gerrit: bool

Loading…
Cancel
Save