2022-04-14 16:43:10 +02:00
|
|
|
|
import argparse
|
2014-04-27 00:07:13 +02:00
|
|
|
|
import sys
|
2020-06-26 18:28:03 +02:00
|
|
|
|
import os
|
2022-03-07 13:40:35 +01:00
|
|
|
|
import warnings
|
2021-11-30 09:12:51 +01:00
|
|
|
|
from contextlib import contextmanager
|
2019-08-30 11:32:14 +02:00
|
|
|
|
from pathlib import Path
|
2022-04-14 16:43:10 +02:00
|
|
|
|
from typing import Iterator, IO, Optional, TYPE_CHECKING
|
2022-03-07 13:40:35 +01:00
|
|
|
|
from enum import Enum
|
2019-08-29 13:39:42 +02:00
|
|
|
|
|
|
|
|
|
|
2016-09-06 12:07:52 +02:00
|
|
|
|
try:
|
|
|
|
|
import curses
|
|
|
|
|
except ImportError:
|
|
|
|
|
curses = None # Compiled w/o curses
|
2014-04-27 00:07:13 +02:00
|
|
|
|
|
2022-04-14 16:43:10 +02:00
|
|
|
|
from .compat import is_windows, cached_property
|
2021-05-05 14:13:39 +02:00
|
|
|
|
from .config import DEFAULT_CONFIG_DIR, Config, ConfigFileError
|
2021-10-06 17:27:07 +02:00
|
|
|
|
from .encoding import UTF8
|
2014-04-27 00:07:13 +02:00
|
|
|
|
|
2021-05-05 14:13:39 +02:00
|
|
|
|
from .utils import repr_dict
|
2022-05-05 17:17:05 +02:00
|
|
|
|
from .output.ui.palette import GenericColor
|
2022-04-14 16:43:10 +02:00
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from rich.console import Console
|
2016-03-04 18:42:13 +01:00
|
|
|
|
|
2014-04-27 00:07:13 +02:00
|
|
|
|
|
2022-05-05 17:17:05 +02:00
|
|
|
|
class LogLevel(str, Enum):
|
|
|
|
|
INFO = 'info'
|
2022-03-07 13:40:35 +01:00
|
|
|
|
WARNING = 'warning'
|
|
|
|
|
ERROR = 'error'
|
|
|
|
|
|
|
|
|
|
|
2022-05-05 17:17:05 +02:00
|
|
|
|
LOG_LEVEL_COLORS = {
|
|
|
|
|
LogLevel.INFO: GenericColor.PINK,
|
|
|
|
|
LogLevel.WARNING: GenericColor.ORANGE,
|
|
|
|
|
LogLevel.ERROR: GenericColor.RED,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_LEVEL_DISPLAY_THRESHOLDS = {
|
|
|
|
|
LogLevel.INFO: 1,
|
|
|
|
|
LogLevel.WARNING: 2,
|
|
|
|
|
LogLevel.ERROR: float('inf'), # Never hide errors.
|
2022-03-07 13:40:35 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-08-30 11:32:14 +02:00
|
|
|
|
class Environment:
|
2014-04-27 00:07:13 +02:00
|
|
|
|
"""
|
|
|
|
|
Information about the execution context
|
|
|
|
|
(standard streams, config directory, etc).
|
|
|
|
|
|
|
|
|
|
By default, it represents the actual environment.
|
|
|
|
|
All of the attributes can be overwritten though, which
|
|
|
|
|
is used by the test suite to simulate various scenarios.
|
|
|
|
|
|
|
|
|
|
"""
|
2022-04-14 16:43:10 +02:00
|
|
|
|
args = argparse.Namespace()
|
2019-08-30 11:32:14 +02:00
|
|
|
|
is_windows: bool = is_windows
|
|
|
|
|
config_dir: Path = DEFAULT_CONFIG_DIR
|
2019-08-29 13:39:42 +02:00
|
|
|
|
stdin: Optional[IO] = sys.stdin # `None` when closed fd (#791)
|
2019-08-30 11:32:14 +02:00
|
|
|
|
stdin_isatty: bool = stdin.isatty() if stdin else False
|
|
|
|
|
stdin_encoding: str = None
|
|
|
|
|
stdout: IO = sys.stdout
|
|
|
|
|
stdout_isatty: bool = stdout.isatty()
|
|
|
|
|
stdout_encoding: str = None
|
|
|
|
|
stderr: IO = sys.stderr
|
|
|
|
|
stderr_isatty: bool = stderr.isatty()
|
2014-10-20 14:40:55 +02:00
|
|
|
|
colors = 256
|
2019-12-02 17:43:16 +01:00
|
|
|
|
program_name: str = 'http'
|
2022-04-14 16:43:10 +02:00
|
|
|
|
|
|
|
|
|
# Whether to show progress bars / status spinners etc.
|
|
|
|
|
show_displays: bool = True
|
|
|
|
|
|
2014-09-08 07:46:53 +02:00
|
|
|
|
if not is_windows:
|
2016-09-06 12:07:52 +02:00
|
|
|
|
if curses:
|
2015-01-23 22:19:02 +01:00
|
|
|
|
try:
|
2016-09-06 12:07:52 +02:00
|
|
|
|
curses.setupterm()
|
2015-01-23 22:19:02 +01:00
|
|
|
|
colors = curses.tigetnum('colors')
|
2016-09-06 12:07:52 +02:00
|
|
|
|
except curses.error:
|
|
|
|
|
pass
|
2014-09-08 07:46:53 +02:00
|
|
|
|
else:
|
2014-04-27 00:07:13 +02:00
|
|
|
|
# noinspection PyUnresolvedReferences
|
2020-06-27 18:08:44 +02:00
|
|
|
|
import colorama.initialise
|
2014-09-08 07:46:53 +02:00
|
|
|
|
stdout = colorama.initialise.wrap_stream(
|
|
|
|
|
stdout, convert=None, strip=None,
|
|
|
|
|
autoreset=True, wrap=True
|
|
|
|
|
)
|
|
|
|
|
stderr = colorama.initialise.wrap_stream(
|
|
|
|
|
stderr, convert=None, strip=None,
|
|
|
|
|
autoreset=True, wrap=True
|
|
|
|
|
)
|
|
|
|
|
del colorama
|
2014-04-27 00:07:13 +02:00
|
|
|
|
|
2020-08-15 15:25:05 +02:00
|
|
|
|
def __init__(self, devnull=None, **kwargs):
|
2014-04-27 00:07:13 +02:00
|
|
|
|
"""
|
|
|
|
|
Use keyword arguments to overwrite
|
|
|
|
|
any of the class attributes for this instance.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
assert all(hasattr(type(self), attr) for attr in kwargs.keys())
|
|
|
|
|
self.__dict__.update(**kwargs)
|
|
|
|
|
|
2020-08-15 15:25:05 +02:00
|
|
|
|
# The original STDERR unaffected by --quiet’ing.
|
|
|
|
|
self._orig_stderr = self.stderr
|
|
|
|
|
self._devnull = devnull
|
|
|
|
|
|
2021-08-05 20:58:43 +02:00
|
|
|
|
# Keyword arguments > stream.encoding > default UTF-8
|
2019-07-18 06:58:37 +02:00
|
|
|
|
if self.stdin and self.stdin_encoding is None:
|
2014-04-27 00:07:13 +02:00
|
|
|
|
self.stdin_encoding = getattr(
|
2021-08-05 20:58:43 +02:00
|
|
|
|
self.stdin, 'encoding', None) or UTF8
|
2014-04-27 00:07:13 +02:00
|
|
|
|
if self.stdout_encoding is None:
|
|
|
|
|
actual_stdout = self.stdout
|
|
|
|
|
if is_windows:
|
2014-09-08 07:46:53 +02:00
|
|
|
|
# noinspection PyUnresolvedReferences
|
2014-04-27 00:07:13 +02:00
|
|
|
|
from colorama import AnsiToWin32
|
|
|
|
|
if isinstance(self.stdout, AnsiToWin32):
|
2019-08-30 11:32:14 +02:00
|
|
|
|
# noinspection PyUnresolvedReferences
|
2014-04-27 00:07:13 +02:00
|
|
|
|
actual_stdout = self.stdout.wrapped
|
|
|
|
|
self.stdout_encoding = getattr(
|
2021-08-05 20:58:43 +02:00
|
|
|
|
actual_stdout, 'encoding', None) or UTF8
|
2014-04-27 00:07:13 +02:00
|
|
|
|
|
2022-03-07 13:40:35 +01:00
|
|
|
|
self.quiet = kwargs.pop('quiet', 0)
|
|
|
|
|
|
2016-03-04 18:42:13 +01:00
|
|
|
|
def __str__(self):
|
|
|
|
|
defaults = dict(type(self).__dict__)
|
|
|
|
|
actual = dict(defaults)
|
|
|
|
|
actual.update(self.__dict__)
|
|
|
|
|
actual['config'] = self.config
|
2019-08-31 18:00:03 +02:00
|
|
|
|
return repr_dict({
|
|
|
|
|
key: value
|
2016-03-04 18:42:13 +01:00
|
|
|
|
for key, value in actual.items()
|
2019-08-31 18:00:03 +02:00
|
|
|
|
if not key.startswith('_')
|
|
|
|
|
})
|
2016-03-04 18:42:13 +01:00
|
|
|
|
|
|
|
|
|
def __repr__(self):
|
2019-08-31 15:17:10 +02:00
|
|
|
|
return f'<{type(self).__name__} {self}>'
|
2019-12-02 17:43:16 +01:00
|
|
|
|
|
|
|
|
|
_config: Config = None
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def config(self) -> Config:
|
|
|
|
|
config = self._config
|
|
|
|
|
if not config:
|
|
|
|
|
self._config = config = Config(directory=self.config_dir)
|
|
|
|
|
if not config.is_new():
|
|
|
|
|
try:
|
|
|
|
|
config.load()
|
|
|
|
|
except ConfigFileError as e:
|
2023-05-19 21:51:32 +02:00
|
|
|
|
self.log_error(e, level=LogLevel.WARNING)
|
2019-12-02 17:43:16 +01:00
|
|
|
|
return config
|
|
|
|
|
|
2020-07-15 00:21:57 +02:00
|
|
|
|
@property
|
2020-07-14 23:31:46 +02:00
|
|
|
|
def devnull(self) -> IO:
|
|
|
|
|
if self._devnull is None:
|
|
|
|
|
self._devnull = open(os.devnull, 'w+')
|
|
|
|
|
return self._devnull
|
|
|
|
|
|
2021-11-30 09:12:51 +01:00
|
|
|
|
@contextmanager
|
|
|
|
|
def as_silent(self) -> Iterator[None]:
|
|
|
|
|
original_stdout = self.stdout
|
|
|
|
|
original_stderr = self.stderr
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
self.stdout = self.devnull
|
|
|
|
|
self.stderr = self.devnull
|
|
|
|
|
yield
|
|
|
|
|
finally:
|
|
|
|
|
self.stdout = original_stdout
|
|
|
|
|
self.stderr = original_stderr
|
|
|
|
|
|
2022-05-05 17:17:05 +02:00
|
|
|
|
def log_error(self, msg: str, level: LogLevel = LogLevel.ERROR) -> None:
|
|
|
|
|
if self.stdout_isatty and self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[level]:
|
2022-03-07 13:40:35 +01:00
|
|
|
|
stderr = self.stderr # Not directly /dev/null, since stderr might be mocked
|
|
|
|
|
else:
|
|
|
|
|
stderr = self._orig_stderr
|
2022-05-05 17:17:05 +02:00
|
|
|
|
rich_console = self._make_rich_console(file=stderr, force_terminal=stderr.isatty())
|
|
|
|
|
rich_console.print(
|
2023-05-19 21:51:32 +02:00
|
|
|
|
f'\n{self.program_name}: {level.value}: {msg}\n\n',
|
2022-05-05 17:17:05 +02:00
|
|
|
|
style=LOG_LEVEL_COLORS[level],
|
|
|
|
|
markup=False,
|
|
|
|
|
highlight=False,
|
|
|
|
|
soft_wrap=True
|
|
|
|
|
)
|
2022-03-07 13:40:35 +01:00
|
|
|
|
|
|
|
|
|
def apply_warnings_filter(self) -> None:
|
2022-05-05 17:17:05 +02:00
|
|
|
|
if self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[LogLevel.WARNING]:
|
2022-03-07 13:40:35 +01:00
|
|
|
|
warnings.simplefilter("ignore")
|
2022-04-14 16:43:10 +02:00
|
|
|
|
|
|
|
|
|
def _make_rich_console(
|
|
|
|
|
self,
|
|
|
|
|
file: IO[str],
|
|
|
|
|
force_terminal: bool
|
|
|
|
|
) -> 'Console':
|
|
|
|
|
from rich.console import Console
|
2022-05-05 17:17:05 +02:00
|
|
|
|
from httpie.output.ui.rich_palette import _make_rich_color_theme
|
2022-04-14 16:43:10 +02:00
|
|
|
|
|
2022-05-05 17:17:05 +02:00
|
|
|
|
style = getattr(self.args, 'style', None)
|
|
|
|
|
theme = _make_rich_color_theme(style)
|
2022-04-14 16:43:10 +02:00
|
|
|
|
# Rich infers the rest of the knowledge (e.g encoding)
|
|
|
|
|
# dynamically by looking at the file/stderr.
|
|
|
|
|
return Console(
|
|
|
|
|
file=file,
|
|
|
|
|
force_terminal=force_terminal,
|
|
|
|
|
no_color=(self.colors == 0),
|
2022-05-05 17:17:05 +02:00
|
|
|
|
theme=theme
|
2022-04-14 16:43:10 +02:00
|
|
|
|
)
|
|
|
|
|
|
2022-04-16 01:06:34 +02:00
|
|
|
|
# Rich recommends separating the actual console (stdout) from
|
2022-04-14 16:43:10 +02:00
|
|
|
|
# the error (stderr) console for better isolation between parts.
|
|
|
|
|
# https://rich.readthedocs.io/en/stable/console.html#error-console
|
|
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
|
def rich_console(self):
|
|
|
|
|
return self._make_rich_console(self.stdout, self.stdout_isatty)
|
|
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
|
def rich_error_console(self):
|
|
|
|
|
return self._make_rich_console(self.stderr, self.stderr_isatty)
|