diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-03-12 23:07:32 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-03-12 23:07:32 +0000 |
commit | 47562fa92998f8f4289ae9a8048349067754d52e (patch) | |
tree | c1643be8ab17fc607cea748a8bb1d621a5964873 /pw_ide/py/pw_ide/status_reporter.py | |
parent | eeec55b65fe2c3c7647bb70ea44b3c839eb1267c (diff) | |
parent | 646563934a3e2ee26f50171f94d95173a1662e2c (diff) | |
download | pigweed-47562fa92998f8f4289ae9a8048349067754d52e.tar.gz |
Snap for 11566117 from 646563934a3e2ee26f50171f94d95173a1662e2c to sdk-releaseplatform-tools-35.0.1
Change-Id: Iec629b181a2c6905754a4c340e334884e13fd3b4
Diffstat (limited to 'pw_ide/py/pw_ide/status_reporter.py')
-rw-r--r-- | pw_ide/py/pw_ide/status_reporter.py | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/pw_ide/py/pw_ide/status_reporter.py b/pw_ide/py/pw_ide/status_reporter.py new file mode 100644 index 000000000..f0e25788c --- /dev/null +++ b/pw_ide/py/pw_ide/status_reporter.py @@ -0,0 +1,143 @@ +# Copyright 2023 The Pigweed Authors +# +# 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 +# +# https://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. +"""Attractive status output to the terminal (and other places if you want).""" + +import logging +from typing import Callable, List, Tuple, Union + +from pw_cli.color import colors + + +def _no_color(msg: str) -> str: + return msg + + +def _split_lines(msg: Union[str, List[str]]) -> Tuple[str, List[str]]: + """Turn a list of strings into a tuple of the first and list of rest.""" + if isinstance(msg, str): + return (msg, []) + + return (msg[0], msg[1:]) + + +class StatusReporter: + """Print user-friendly status reports to the terminal for CLI tools. + + You can instead redirect these lines to logs without formatting by + substituting ``LoggingStatusReporter``. Consumers of this should be + designed to take any subclass and not make assumptions about where the + output will go. But the reason you would choose this over plain logging is + because you want to support pretty-printing to the terminal. + + This is also "themable" in the sense that you can subclass this, override + the methods with whatever formatting you want, and supply the subclass to + anything that expects an instance of this. + + Key: + + - info: Plain ol' informational status. + - ok: Something was checked and it was okay. + - new: Something needed to be changed/updated and it was successfully. + - wrn: Warning, non-critical. + - err: Error, critical. + + This doesn't expose the %-style string formatting that is used in idiomatic + Python logging, but this shouldn't be used for performance-critical logging + situations anyway. + """ + + def _report( # pylint: disable=no-self-use + self, + msg: Union[str, List[str]], + color: Callable[[str], str], + char: str, + func: Callable, + silent: bool, + ) -> None: + """Actually print/log/whatever the status lines.""" + first_line, rest_lines = _split_lines(msg) + first_line = color(f'{char} {first_line}') + spaces = ' ' * len(char) + rest_lines = [color(f'{spaces} {line}') for line in rest_lines] + + if not silent: + for line in [first_line, *rest_lines]: + func(line) + + def demo(self): + """Run this to see what your status reporter output looks like.""" + self.info( + [ + 'FYI, here\'s some information:', + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + 'Donec condimentum metus molestie metus maximus ultricies ' + 'ac id dolor.', + ] + ) + self.ok('This is okay, no changes needed.') + self.new('We changed some things successfully!') + self.wrn('Uh oh, you might want to be aware of this.') + self.err('This is bad! Things might be broken!') + + def info(self, msg: Union[str, List[str]], silent: bool = False) -> None: + self._report(msg, _no_color, '\u2022', print, silent) + + def ok(self, msg: Union[str, List[str]], silent: bool = False) -> None: + self._report(msg, colors().blue, '\u2713', print, silent) + + def new(self, msg: Union[str, List[str]], silent: bool = False) -> None: + self._report(msg, colors().green, '\u2713', print, silent) + + def wrn(self, msg: Union[str, List[str]], silent: bool = False) -> None: + self._report(msg, colors().yellow, '\u26A0\uFE0F ', print, silent) + + def err(self, msg: Union[str, List[str]], silent: bool = False) -> None: + self._report(msg, colors().red, '\U0001F525', print, silent) + + +class LoggingStatusReporter(StatusReporter): + """Print status lines to logs instead of to the terminal.""" + + def __init__(self, logger: logging.Logger) -> None: + self.logger = logger + super().__init__() + + def _report( + self, + msg: Union[str, List[str]], + color: Callable[[str], str], + char: str, + func: Callable, + silent: bool, + ) -> None: + first_line, rest_lines = _split_lines(msg) + + if not silent: + for line in [first_line, *rest_lines]: + func(line) + + def info(self, msg: Union[str, List[str]], silent: bool = False) -> None: + self._report(msg, _no_color, '', self.logger.info, silent) + + def ok(self, msg: Union[str, List[str]], silent: bool = False) -> None: + self._report(msg, _no_color, '', self.logger.info, silent) + + def new(self, msg: Union[str, List[str]], silent: bool = False) -> None: + self._report(msg, _no_color, '', self.logger.info, silent) + + def wrn(self, msg: Union[str, List[str]], silent: bool = False) -> None: + self._report(msg, _no_color, '', self.logger.warning, silent) + + def err(self, msg: Union[str, List[str]], silent: bool = False) -> None: + self._report(msg, _no_color, '', self.logger.error, silent) |