# Copyright 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Terminal utilities
This module handles terminal interaction including ANSI color codes.
"""
import os
from pathlib
import Path
import sys
from typing
import List, Optional
THIS_FILE = Path(__file__).resolve()
THIS_DIR = THIS_FILE.parent
sys.path.insert(
0, str(THIS_DIR.parent))
# pylint: disable=wrong-import-position
import rh.shell
# This will erase all content in the current line after the cursor. This is
# useful for partial updates & progress messages as the terminal can display
# it better.
CSI_ERASE_LINE_AFTER =
"\x1b[K"
class Color(object):
"""Conditionally wraps text in ANSI color escape sequences."""
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(
8)
BOLD = -
1
COLOR_START =
"\033[1;%dm"
BOLD_START =
"\033[1m"
RESET =
"\033[m"
def __init__(self, enabled=
None):
"""Create a new Color object, optionally disabling color output.
Args:
enabled:
True if color output should be enabled.
If False then this
class will
not add color codes at all.
"""
self._enabled = enabled
def start(self, color):
"""Returns a start color code.
Args:
color: Color to use, e.g. BLACK, RED, etc...
Returns:
If color
is enabled, returns an ANSI sequence to start the given
color, otherwise returns empty string
"""
if self.enabled:
return self.COLOR_START % (color +
30)
return ""
def stop(self):
"""Returns a stop color code.
Returns:
If color
is enabled, returns an ANSI color reset sequence, otherwise
returns empty string
"""
if self.enabled:
return self.RESET
return ""
def color(self, color, text):
"""Returns text with conditionally added color escape sequences.
Args:
color: Text color -- one of the color constants defined
in this
class.
text: The text to color.
Returns:
If self._enabled
is False, returns the original text.
If it
's True,
returns text
with color escape sequences based on the value of color.
"""
if not self.enabled:
return text
if color == self.BOLD:
start = self.BOLD_START
else:
start = self.COLOR_START % (color +
30)
return start + text + self.RESET
@property
def enabled(self):
"""See if the colorization is enabled."""
if self._enabled
is None:
if "NOCOLOR" in os.environ:
self._enabled =
not rh.shell.boolean_shell_value(
os.environ[
"NOCOLOR"],
False
)
else:
self._enabled = sys.stderr.isatty()
return self._enabled
def print_status_line(line, print_newline=
False):
"""Clears the current terminal line, and prints |line|.
Args:
line: String to print.
print_newline: Print a newline at the end,
if sys.stderr
is a TTY.
"""
if sys.stderr.isatty():
output =
"\r" + line + CSI_ERASE_LINE_AFTER
if print_newline:
output +=
"\n"
else:
output = line +
"\n"
sys.stderr.write(output)
sys.stderr.flush()
def str_prompt(
prompt: str,
choices: List[str],
lower: bool =
True,
) -> Optional[str]:
"""Helper function for processing user input.
Args:
prompt: The question to present to the user.
lower: Whether to lowercase the response.
Returns:
The string the user entered,
or None if EOF (e.g. Ctrl+D).
"""
prompt = f
'{prompt} ({"/".join(choices)})? '
try:
result = input(prompt)
return result.lower()
if lower
else result
except EOFError:
# If the user hits Ctrl+D, or stdin is disabled, use the default.
print()
return None
except KeyboardInterrupt:
# If the user hits Ctrl+C, just exit the process.
print()
raise
def boolean_prompt(
prompt=
"Do you want to continue?",
default=
True,
true_value=
"yes",
false_value=
"no",
prolog=
None,
):
"""Helper function for processing boolean choice prompts.
Args:
prompt: The question to present to the user.
default: Boolean to
return if the user just presses enter.
true_value: The text to display that represents a
True returned.
false_value: The text to display that represents a
False returned.
prolog: The text to display before prompt.
Returns:
True or False.
"""
true_value, false_value = true_value.lower(), false_value.lower()
true_text, false_text = true_value, false_value
if true_value == false_value:
raise ValueError(
f
"true_value and false_value must differ: got {true_value!r}"
)
if default:
true_text = true_text[
0].upper() + true_text[
1:]
else:
false_text = false_text[
0].upper() + false_text[
1:]
if prolog:
prompt = f
"\n{prolog}\n{prompt}"
prompt =
"\n" + prompt
while True:
response = str_prompt(prompt, choices=(true_text, false_text))
if not response:
return default
if true_value.startswith(response):
if not false_value.startswith(response):
return True
# common prefix between the two...
elif false_value.startswith(response):
return False