From ae3695f691c6325f1a504ee3df7f22d75c7a0c96 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 29 Apr 2025 07:21:59 -0600 Subject: patman: Move capture_sys_output() into terminal and rename This function is sometimes useful outside tests. Also it can affect how terminal output is done, e.g. whether ANSI characters should be emitted or not. Move it out of the test_util package and into terminal. Signed-off-by: Simon Glass --- tools/u_boot_pylib/terminal.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'tools/u_boot_pylib/terminal.py') diff --git a/tools/u_boot_pylib/terminal.py b/tools/u_boot_pylib/terminal.py index 2cd5a54ab52..84531831760 100644 --- a/tools/u_boot_pylib/terminal.py +++ b/tools/u_boot_pylib/terminal.py @@ -7,6 +7,8 @@ This module handles terminal interaction including ANSI color codes. """ +from contextlib import contextmanager +from io import StringIO import os import re import shutil @@ -271,3 +273,17 @@ class Color(object): base = self.BRIGHT_START if bright else self.NORMAL_START start = base % (color + 30) return start + text + self.RESET + + +# Use this to suppress stdout/stderr output: +# with terminal.capture() as (stdout, stderr) +# ...do something... +@contextmanager +def capture(): + capture_out, capture_err = StringIO(), StringIO() + old_out, old_err = sys.stdout, sys.stderr + try: + sys.stdout, sys.stderr = capture_out, capture_err + yield capture_out, capture_err + finally: + sys.stdout, sys.stderr = old_out, old_err -- cgit v1.2.3 From b697480f19cf690a51bfb3c788aa381de43eac79 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 29 Apr 2025 07:22:01 -0600 Subject: u_boot_pylib: Allow control of capturing Tests often capture output so they can check it. This means that if the test fails it is not easy to see what the output actually was. Add a -N flag which writes out the output after it has been captured. This is not a perfect solution but it is simple and seems to work well in practice. Signed-off-by: Simon Glass --- tools/u_boot_pylib/terminal.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'tools/u_boot_pylib/terminal.py') diff --git a/tools/u_boot_pylib/terminal.py b/tools/u_boot_pylib/terminal.py index 84531831760..4b9a907a547 100644 --- a/tools/u_boot_pylib/terminal.py +++ b/tools/u_boot_pylib/terminal.py @@ -28,6 +28,13 @@ last_print_len = None # stackoverflow.com/questions/14693701/how-can-i-remove-the-ansi-escape-sequences-from-a-string-in-python ansi_escape = re.compile(r'\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') +# True if we are capturing console output +CAPTURING = False + +# Set this to False to disable output-capturing globally +USE_CAPTURE = True + + class PrintLine: """A line of text output @@ -280,10 +287,17 @@ class Color(object): # ...do something... @contextmanager def capture(): + global CAPTURING + capture_out, capture_err = StringIO(), StringIO() old_out, old_err = sys.stdout, sys.stderr try: + CAPTURING = True sys.stdout, sys.stderr = capture_out, capture_err yield capture_out, capture_err finally: sys.stdout, sys.stderr = old_out, old_err + CAPTURING = False + if not USE_CAPTURE: + sys.stdout.write(capture_out.getvalue()) + sys.stderr.write(capture_err.getvalue()) -- cgit v1.2.3 From f74c6f9a81de1c0c04c578b0e6e2d8b1303d6d43 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 29 Apr 2025 07:22:02 -0600 Subject: u_boot_pylib: Improve ANSI-colour output with backgrounds The current implementation does not handle background colours very well: - It outputs an incorrect code in some cases, leading to wrong colours - Some functions lack a control for the background Tidy this up so that background colours can be used in more places. Signed-off-by: Simon Glass --- tools/u_boot_pylib/terminal.py | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) (limited to 'tools/u_boot_pylib/terminal.py') diff --git a/tools/u_boot_pylib/terminal.py b/tools/u_boot_pylib/terminal.py index 4b9a907a547..6bd790acc3f 100644 --- a/tools/u_boot_pylib/terminal.py +++ b/tools/u_boot_pylib/terminal.py @@ -139,7 +139,8 @@ def trim_ascii_len(text, size): return out -def tprint(text='', newline=True, colour=None, limit_to_line=False, bright=True): +def tprint(text='', newline=True, colour=None, limit_to_line=False, + bright=True, back=None, col=None): """Handle a line of output to the terminal. In test mode this is recorded in a list. Otherwise it is output to the @@ -155,9 +156,10 @@ def tprint(text='', newline=True, colour=None, limit_to_line=False, bright=True) if print_test_mode: print_test_list.append(PrintLine(text, colour, newline, bright)) else: - if colour: - col = Color() - text = col.build(colour, text, bright=bright) + if colour is not None: + if not col: + col = Color() + text = col.build(colour, text, bright=bright, back=back) if newline: print(text) last_print_len = None @@ -214,9 +216,10 @@ class Color(object): """Conditionally wraps text in ANSI color escape sequences.""" BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) BOLD = -1 - BRIGHT_START = '\033[1;%dm' - NORMAL_START = '\033[22;%dm' + BRIGHT_START = '\033[1;%d%sm' + NORMAL_START = '\033[22;%d%sm' BOLD_START = '\033[1m' + BACK_EXTRA = ';%d' RESET = '\033[0m' def __init__(self, colored=COLOR_IF_TERMINAL): @@ -233,7 +236,14 @@ class Color(object): except: self._enabled = False - def start(self, color, bright=True): + def enabled(self): + """Check if colour is enabled + + Return: True if enabled, else False + """ + return self._enabled + + def start(self, color, bright=True, back=None): """Returns a start color code. Args: @@ -244,8 +254,11 @@ class Color(object): color, otherwise returns empty string """ if self._enabled: + if color == self.BOLD: + return self.BOLD_START base = self.BRIGHT_START if bright else self.NORMAL_START - return base % (color + 30) + extra = self.BACK_EXTRA % (back + 40) if back else '' + return base % (color + 30, extra) return '' def stop(self): @@ -259,7 +272,7 @@ class Color(object): return self.RESET return '' - def build(self, color, text, bright=True): + def build(self, color, text, bright=True, back=None): """Returns text with conditionally added color escape sequences. Keyword arguments: @@ -274,12 +287,7 @@ class Color(object): """ if not self._enabled: return text - if color == self.BOLD: - start = self.BOLD_START - else: - base = self.BRIGHT_START if bright else self.NORMAL_START - start = base % (color + 30) - return start + text + self.RESET + return self.start(color, bright, back) + text + self.RESET # Use this to suppress stdout/stderr output: -- cgit v1.2.3 From 1f18e8798fe2f89294925ad3cb5acf1754b080ca Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 29 Apr 2025 07:22:03 -0600 Subject: u_boot_pylib: Add a pager It is useful to have a pager when outputting a lot of text. Add support for this in the terminal library, making use of a context manager. Also add a function to indicate whether the output device is a terminal or not, while we are here, to avoid duplicating this code. Signed-off-by: Simon Glass --- tools/u_boot_pylib/terminal.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) (limited to 'tools/u_boot_pylib/terminal.py') diff --git a/tools/u_boot_pylib/terminal.py b/tools/u_boot_pylib/terminal.py index 6bd790acc3f..69c183e85e5 100644 --- a/tools/u_boot_pylib/terminal.py +++ b/tools/u_boot_pylib/terminal.py @@ -12,6 +12,7 @@ from io import StringIO import os import re import shutil +import subprocess import sys # Selection of when we want our output to be colored @@ -211,8 +212,16 @@ def echo_print_test_lines(): if line.newline: print() +def have_terminal(): + """Check if we have an interactive terminal or not -class Color(object): + Returns: + bool: true if an interactive terminal is attached + """ + return os.isatty(sys.stdout.fileno()) + + +class Color(): """Conditionally wraps text in ANSI color escape sequences.""" BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) BOLD = -1 @@ -309,3 +318,29 @@ def capture(): if not USE_CAPTURE: sys.stdout.write(capture_out.getvalue()) sys.stderr.write(capture_err.getvalue()) + + +@contextmanager +def pager(): + """Simple pager for outputting lots of text + + Usage: + with terminal.pager(): + print(...) + """ + proc = None + old_stdout = None + try: + less = os.getenv('PAGER') + if not CAPTURING and less != 'none' and have_terminal(): + if not less: + less = 'less -R --quit-if-one-screen' + proc = subprocess.Popen(less, stdin=subprocess.PIPE, text=True, + shell=True) + old_stdout = sys.stdout + sys.stdout = proc.stdin + yield + finally: + if proc: + sys.stdout = old_stdout + proc.communicate() -- cgit v1.2.3