From 7fce478d6a3b74595300ecb43e14b22008618070 Mon Sep 17 00:00:00 2001 From: Ofek Lev Date: Sun, 8 Oct 2023 13:51:46 -0400 Subject: [PATCH] Improve terminal logic --- src/hatch/cli/__init__.py | 4 +- src/hatch/cli/application.py | 2 +- src/hatch/cli/terminal.py | 72 ++++++++++++++++++++++++------------ tests/conftest.py | 3 +- 4 files changed, 53 insertions(+), 28 deletions(-) diff --git a/src/hatch/cli/__init__.py b/src/hatch/cli/__init__.py index bc07b49e4..1c7a2cfa4 100644 --- a/src/hatch/cli/__init__.py +++ b/src/hatch/cli/__init__.py @@ -102,8 +102,8 @@ def hatch(ctx: click.Context, env_name, project, verbose, quiet, color, interact elif os.environ.get(AppEnvVars.FORCE_COLOR) == '1': color = True - if interactive is None: - interactive = not running_in_ci() + if interactive is None and running_in_ci(): + interactive = False app = Application(ctx.exit, verbose - quiet, color, interactive) diff --git a/src/hatch/cli/application.py b/src/hatch/cli/application.py index 32296c357..349f1cfeb 100644 --- a/src/hatch/cli/application.py +++ b/src/hatch/cli/application.py @@ -18,7 +18,7 @@ class Application(Terminal): def __init__(self, exit_func, *args, **kwargs): super().__init__(*args, **kwargs) - self.platform = Platform(self.display_raw) + self.platform = Platform(self.output) self.__exit_func = exit_func self.config_file = ConfigFile() diff --git a/src/hatch/cli/terminal.py b/src/hatch/cli/terminal.py index 44522ce7b..c70c9c609 100644 --- a/src/hatch/cli/terminal.py +++ b/src/hatch/cli/terminal.py @@ -40,7 +40,7 @@ def __init__( self, console: Console, *, - tty: bool, + is_interactive: bool, verbosity: int, spinner_style: str, waiting_style: Style, @@ -49,7 +49,7 @@ def __init__( finalizer: Callable, ): self.__console = console - self.__tty = tty + self.__is_interactive = is_interactive self.__verbosity = verbosity self.__spinner_style = spinner_style self.__waiting_style = waiting_style @@ -85,7 +85,7 @@ def __enter__(self) -> BorrowedStatus: return self message, _ = self.__messages[-1] - if not self.__tty: + if not self.__is_interactive: self.__output(message) return self @@ -108,7 +108,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.__output(Text(final_text, style=self.__success_style)) - if not self.__tty: + if not self.__is_interactive: return self.__status.stop() @@ -134,9 +134,9 @@ def __output(self, text): class Terminal: def __init__(self, verbosity, enable_color, interactive): self.verbosity = verbosity - self.interactive = interactive self.console = Console( force_terminal=enable_color, + force_interactive=interactive, no_color=enable_color is False, markup=False, emoji=False, @@ -157,6 +157,28 @@ def __init__(self, verbosity, enable_color, interactive): # Chosen as the default since it's compatible everywhere and looks nice self._style_spinner = 'simpleDotsScrolling' + @cached_property + def kv_separator(self) -> Style: + return self.style_warning('->') + + def style_success(self, text: str) -> Text: + return Text(text, style=self._style_level_success) + + def style_error(self, text: str) -> Text: + return Text(text, style=self._style_level_error) + + def style_warning(self, text: str) -> Text: + return Text(text, style=self._style_level_warning) + + def style_waiting(self, text: str) -> Text: + return Text(text, style=self._style_level_waiting) + + def style_info(self, text: str) -> Text: + return Text(text, style=self._style_level_info) + + def style_debug(self, text: str) -> Text: + return Text(text, style=self._style_level_debug) + def initialize_styles(self, styles: dict): # no cov # Lazily display errors so that they use the correct style errors = [] @@ -194,31 +216,31 @@ def display_error(self, text='', *, stderr=True, indent=None, link=None, **kwarg if self.verbosity < -2: # noqa: PLR2004 return - self.output(text, self._style_level_error, stderr=stderr, indent=indent, link=link, **kwargs) + self._output(text, self._style_level_error, stderr=stderr, indent=indent, link=link, **kwargs) def display_warning(self, text='', *, stderr=True, indent=None, link=None, **kwargs): if self.verbosity < -1: return - self.output(text, self._style_level_warning, stderr=stderr, indent=indent, link=link, **kwargs) + self._output(text, self._style_level_warning, stderr=stderr, indent=indent, link=link, **kwargs) def display_info(self, text='', *, stderr=True, indent=None, link=None, **kwargs): if self.verbosity < 0: return - self.output(text, self._style_level_info, stderr=stderr, indent=indent, link=link, **kwargs) + self._output(text, self._style_level_info, stderr=stderr, indent=indent, link=link, **kwargs) def display_success(self, text='', *, stderr=True, indent=None, link=None, **kwargs): if self.verbosity < 0: return - self.output(text, self._style_level_success, stderr=stderr, indent=indent, link=link, **kwargs) + self._output(text, self._style_level_success, stderr=stderr, indent=indent, link=link, **kwargs) def display_waiting(self, text='', *, stderr=True, indent=None, link=None, **kwargs): if self.verbosity < 0: return - self.output(text, self._style_level_waiting, stderr=stderr, indent=indent, link=link, **kwargs) + self._output(text, self._style_level_waiting, stderr=stderr, indent=indent, link=link, **kwargs) def display_debug(self, text='', level=1, *, stderr=True, indent=None, link=None, **kwargs): if not 1 <= level <= 3: # noqa: PLR2004 @@ -227,7 +249,7 @@ def display_debug(self, text='', level=1, *, stderr=True, indent=None, link=None elif self.verbosity < level: return - self.output(text, self._style_level_debug, stderr=stderr, indent=indent, link=link, **kwargs) + self._output(text, self._style_level_debug, stderr=stderr, indent=indent, link=link, **kwargs) def display_mini_header(self, text, *, stderr=False, indent=None, link=None): if self.verbosity < 0: @@ -243,7 +265,10 @@ def display_header(self, title='', *, stderr=False): def display_markdown(self, text, **kwargs): # no cov from rich.markdown import Markdown - self.display_raw(Markdown(text), **kwargs) + self.output(Markdown(text), **kwargs) + + def display_pair(self, key, value): + self.output(self.style_success(key), self.kv_separator, value) def display_table(self, title, columns, *, show_lines=False, column_options=None, force_ascii=False, num_rows=0): from rich.table import Table @@ -284,7 +309,7 @@ def display_table(self, title, columns, *, show_lines=False, column_options=None def status(self) -> BorrowedStatus: return BorrowedStatus( self.console, - tty=self.interactive and self.console.is_terminal, + is_interactive=self.console.is_interactive, verbosity=self.verbosity, spinner_style=self._style_spinner, waiting_style=self._style_level_waiting, @@ -296,30 +321,29 @@ def status(self) -> BorrowedStatus: def status_if(self, *args, condition: bool, **kwargs) -> TerminalStatus: return self.status(*args, **kwargs) if condition else NullStatus() - def output(self, text='', style=None, *, stderr=False, indent=None, link=None, **kwargs): - kwargs.setdefault('overflow', 'ignore') - kwargs.setdefault('no_wrap', True) - kwargs.setdefault('crop', False) - + def _output(self, text='', style=None, *, stderr=False, indent=None, link=None, **kwargs): if indent: text = indent_text(text, indent) if link: style = style.update_link(self.platform.format_file_uri(link)) + self.output(text, stderr=stderr, style=style, **kwargs) + + def output(self, *args, stderr=False, **kwargs): + kwargs.setdefault('overflow', 'ignore') + kwargs.setdefault('no_wrap', True) + kwargs.setdefault('crop', False) + if not stderr: - self.console.print(text, style=style, **kwargs) + self.console.print(*args, **kwargs) else: self.console.stderr = True try: - self.console.print(text, style=style, **kwargs) + self.console.print(*args, **kwargs) finally: self.console.stderr = False - def display_raw(self, text, **kwargs): - # No styling - self.console.print(text, overflow='ignore', no_wrap=True, crop=False, **kwargs) - @staticmethod def prompt(text, **kwargs): return click.prompt(text, **kwargs) diff --git a/tests/conftest.py b/tests/conftest.py index b0d791b16..00e6660ab 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -90,6 +90,7 @@ def isolation() -> Generator[Path, None, None]: with d.as_cwd(default_env_vars): os.environ.pop(AppEnvVars.ENV_ACTIVE, None) + os.environ.pop(AppEnvVars.FORCE_COLOR, None) yield d @@ -251,7 +252,7 @@ def devpi(tmp_path_factory, worker_id): for _ in range(60): output = subprocess.check_output(['docker', 'logs', 'hatch-devpi']).decode('utf-8') if f'Serving index {dp.user}/{dp.index}' in output: - time.sleep(2) + time.sleep(5) break time.sleep(1)