mirror of
https://github.com/httpie/cli.git
synced 2024-11-25 01:03:27 +01:00
Refactor palette (#1378)
* Refactor palette * Modifiers / change static strings to colors * Colors... * Error-based tests * Styling linting Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
This commit is contained in:
parent
0f9fd76852
commit
f7c1bb269e
@ -18,20 +18,28 @@ from .config import DEFAULT_CONFIG_DIR, Config, ConfigFileError
|
|||||||
from .encoding import UTF8
|
from .encoding import UTF8
|
||||||
|
|
||||||
from .utils import repr_dict
|
from .utils import repr_dict
|
||||||
from httpie.output.ui import rich_palette as palette
|
from .output.ui.palette import GenericColor
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
|
|
||||||
class Levels(str, Enum):
|
class LogLevel(str, Enum):
|
||||||
|
INFO = 'info'
|
||||||
WARNING = 'warning'
|
WARNING = 'warning'
|
||||||
ERROR = 'error'
|
ERROR = 'error'
|
||||||
|
|
||||||
|
|
||||||
DISPLAY_THRESHOLDS = {
|
LOG_LEVEL_COLORS = {
|
||||||
Levels.WARNING: 2,
|
LogLevel.INFO: GenericColor.PINK,
|
||||||
Levels.ERROR: float('inf'), # Never hide errors.
|
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.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -159,16 +167,22 @@ class Environment:
|
|||||||
self.stdout = original_stdout
|
self.stdout = original_stdout
|
||||||
self.stderr = original_stderr
|
self.stderr = original_stderr
|
||||||
|
|
||||||
def log_error(self, msg: str, level: Levels = Levels.ERROR) -> None:
|
def log_error(self, msg: str, level: LogLevel = LogLevel.ERROR) -> None:
|
||||||
if self.stdout_isatty and self.quiet >= DISPLAY_THRESHOLDS[level]:
|
if self.stdout_isatty and self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[level]:
|
||||||
stderr = self.stderr # Not directly /dev/null, since stderr might be mocked
|
stderr = self.stderr # Not directly /dev/null, since stderr might be mocked
|
||||||
else:
|
else:
|
||||||
stderr = self._orig_stderr
|
stderr = self._orig_stderr
|
||||||
|
rich_console = self._make_rich_console(file=stderr, force_terminal=stderr.isatty())
|
||||||
stderr.write(f'\n{self.program_name}: {level}: {msg}\n\n')
|
rich_console.print(
|
||||||
|
f'\n{self.program_name}: {level}: {msg}\n\n',
|
||||||
|
style=LOG_LEVEL_COLORS[level],
|
||||||
|
markup=False,
|
||||||
|
highlight=False,
|
||||||
|
soft_wrap=True
|
||||||
|
)
|
||||||
|
|
||||||
def apply_warnings_filter(self) -> None:
|
def apply_warnings_filter(self) -> None:
|
||||||
if self.quiet >= DISPLAY_THRESHOLDS[Levels.WARNING]:
|
if self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[LogLevel.WARNING]:
|
||||||
warnings.simplefilter("ignore")
|
warnings.simplefilter("ignore")
|
||||||
|
|
||||||
def _make_rich_console(
|
def _make_rich_console(
|
||||||
@ -177,32 +191,17 @@ class Environment:
|
|||||||
force_terminal: bool
|
force_terminal: bool
|
||||||
) -> 'Console':
|
) -> 'Console':
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.theme import Theme
|
from httpie.output.ui.rich_palette import _make_rich_color_theme
|
||||||
from rich.style import Style
|
|
||||||
|
|
||||||
style = getattr(self.args, 'style', palette.AUTO_STYLE)
|
|
||||||
theme = {}
|
|
||||||
if style in palette.STYLE_SHADES:
|
|
||||||
shade = palette.STYLE_SHADES[style]
|
|
||||||
theme.update({
|
|
||||||
color: Style(
|
|
||||||
color=palette.get_color(
|
|
||||||
color,
|
|
||||||
shade,
|
|
||||||
palette=palette.RICH_THEME_PALETTE
|
|
||||||
),
|
|
||||||
bold=True
|
|
||||||
)
|
|
||||||
for color in palette.RICH_THEME_PALETTE
|
|
||||||
})
|
|
||||||
|
|
||||||
|
style = getattr(self.args, 'style', None)
|
||||||
|
theme = _make_rich_color_theme(style)
|
||||||
# Rich infers the rest of the knowledge (e.g encoding)
|
# Rich infers the rest of the knowledge (e.g encoding)
|
||||||
# dynamically by looking at the file/stderr.
|
# dynamically by looking at the file/stderr.
|
||||||
return Console(
|
return Console(
|
||||||
file=file,
|
file=file,
|
||||||
force_terminal=force_terminal,
|
force_terminal=force_terminal,
|
||||||
no_color=(self.colors == 0),
|
no_color=(self.colors == 0),
|
||||||
theme=Theme(theme)
|
theme=theme
|
||||||
)
|
)
|
||||||
|
|
||||||
# Rich recommends separating the actual console (stdout) from
|
# Rich recommends separating the actual console (stdout) from
|
||||||
|
@ -13,7 +13,7 @@ from . import __version__ as httpie_version
|
|||||||
from .cli.constants import OUT_REQ_BODY
|
from .cli.constants import OUT_REQ_BODY
|
||||||
from .cli.nested_json import HTTPieSyntaxError
|
from .cli.nested_json import HTTPieSyntaxError
|
||||||
from .client import collect_messages
|
from .client import collect_messages
|
||||||
from .context import Environment, Levels
|
from .context import Environment, LogLevel
|
||||||
from .downloads import Downloader
|
from .downloads import Downloader
|
||||||
from .models import (
|
from .models import (
|
||||||
RequestsMessageKind,
|
RequestsMessageKind,
|
||||||
@ -223,7 +223,7 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
|
|||||||
if args.check_status or downloader:
|
if args.check_status or downloader:
|
||||||
exit_status = http_status_to_exit_status(http_status=message.status_code, follow=args.follow)
|
exit_status = http_status_to_exit_status(http_status=message.status_code, follow=args.follow)
|
||||||
if exit_status != ExitStatus.SUCCESS and (not env.stdout_isatty or args.quiet == 1):
|
if exit_status != ExitStatus.SUCCESS and (not env.stdout_isatty or args.quiet == 1):
|
||||||
env.log_error(f'HTTP {message.raw.status} {message.raw.reason}', level=Levels.WARNING)
|
env.log_error(f'HTTP {message.raw.status} {message.raw.reason}', level=LogLevel.WARNING)
|
||||||
write_message(
|
write_message(
|
||||||
requests_message=message,
|
requests_message=message,
|
||||||
env=env,
|
env=env,
|
||||||
|
@ -17,13 +17,15 @@ from pygments.util import ClassNotFound
|
|||||||
|
|
||||||
from ..lexers.json import EnhancedJsonLexer
|
from ..lexers.json import EnhancedJsonLexer
|
||||||
from ..lexers.metadata import MetadataLexer
|
from ..lexers.metadata import MetadataLexer
|
||||||
from ..ui.palette import AUTO_STYLE, SHADE_NAMES, get_color
|
from ..ui.palette import AUTO_STYLE, SHADE_TO_PIE_STYLE, PieColor, ColorString, get_color
|
||||||
from ...context import Environment
|
from ...context import Environment
|
||||||
from ...plugins import FormatterPlugin
|
from ...plugins import FormatterPlugin
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_STYLE = AUTO_STYLE
|
DEFAULT_STYLE = AUTO_STYLE
|
||||||
SOLARIZED_STYLE = 'solarized' # Bundled here
|
SOLARIZED_STYLE = 'solarized' # Bundled here
|
||||||
|
PYGMENTS_BOLD = ColorString('bold')
|
||||||
|
PYGMENTS_ITALIC = ColorString('italic')
|
||||||
|
|
||||||
BUNDLED_STYLES = {
|
BUNDLED_STYLES = {
|
||||||
SOLARIZED_STYLE,
|
SOLARIZED_STYLE,
|
||||||
@ -253,11 +255,11 @@ class Solarized256Style(pygments.style.Style):
|
|||||||
pygments.token.Comment.Preproc: GREEN,
|
pygments.token.Comment.Preproc: GREEN,
|
||||||
pygments.token.Comment.Special: GREEN,
|
pygments.token.Comment.Special: GREEN,
|
||||||
pygments.token.Generic.Deleted: CYAN,
|
pygments.token.Generic.Deleted: CYAN,
|
||||||
pygments.token.Generic.Emph: 'italic',
|
pygments.token.Generic.Emph: PYGMENTS_ITALIC,
|
||||||
pygments.token.Generic.Error: RED,
|
pygments.token.Generic.Error: RED,
|
||||||
pygments.token.Generic.Heading: ORANGE,
|
pygments.token.Generic.Heading: ORANGE,
|
||||||
pygments.token.Generic.Inserted: GREEN,
|
pygments.token.Generic.Inserted: GREEN,
|
||||||
pygments.token.Generic.Strong: 'bold',
|
pygments.token.Generic.Strong: PYGMENTS_BOLD,
|
||||||
pygments.token.Generic.Subheading: ORANGE,
|
pygments.token.Generic.Subheading: ORANGE,
|
||||||
pygments.token.Token: BASE1,
|
pygments.token.Token: BASE1,
|
||||||
pygments.token.Token.Other: ORANGE,
|
pygments.token.Token.Other: ORANGE,
|
||||||
@ -266,86 +268,86 @@ class Solarized256Style(pygments.style.Style):
|
|||||||
|
|
||||||
PIE_HEADER_STYLE = {
|
PIE_HEADER_STYLE = {
|
||||||
# HTTP line / Headers / Etc.
|
# HTTP line / Headers / Etc.
|
||||||
pygments.token.Name.Namespace: 'bold primary',
|
pygments.token.Name.Namespace: PYGMENTS_BOLD | PieColor.PRIMARY,
|
||||||
pygments.token.Keyword.Reserved: 'bold grey',
|
pygments.token.Keyword.Reserved: PYGMENTS_BOLD | PieColor.GREY,
|
||||||
pygments.token.Operator: 'bold grey',
|
pygments.token.Operator: PYGMENTS_BOLD | PieColor.GREY,
|
||||||
pygments.token.Number: 'bold grey',
|
pygments.token.Number: PYGMENTS_BOLD | PieColor.GREY,
|
||||||
pygments.token.Name.Function.Magic: 'bold green',
|
pygments.token.Name.Function.Magic: PYGMENTS_BOLD | PieColor.GREEN,
|
||||||
pygments.token.Name.Exception: 'bold green',
|
pygments.token.Name.Exception: PYGMENTS_BOLD | PieColor.GREEN,
|
||||||
pygments.token.Name.Attribute: 'blue',
|
pygments.token.Name.Attribute: PieColor.BLUE,
|
||||||
pygments.token.String: 'primary',
|
pygments.token.String: PieColor.PRIMARY,
|
||||||
|
|
||||||
# HTTP Methods
|
# HTTP Methods
|
||||||
pygments.token.Name.Function: 'bold grey',
|
pygments.token.Name.Function: PYGMENTS_BOLD | PieColor.GREY,
|
||||||
pygments.token.Name.Function.HTTP.GET: 'bold green',
|
pygments.token.Name.Function.HTTP.GET: PYGMENTS_BOLD | PieColor.GREEN,
|
||||||
pygments.token.Name.Function.HTTP.HEAD: 'bold green',
|
pygments.token.Name.Function.HTTP.HEAD: PYGMENTS_BOLD | PieColor.GREEN,
|
||||||
pygments.token.Name.Function.HTTP.POST: 'bold yellow',
|
pygments.token.Name.Function.HTTP.POST: PYGMENTS_BOLD | PieColor.YELLOW,
|
||||||
pygments.token.Name.Function.HTTP.PUT: 'bold orange',
|
pygments.token.Name.Function.HTTP.PUT: PYGMENTS_BOLD | PieColor.ORANGE,
|
||||||
pygments.token.Name.Function.HTTP.PATCH: 'bold orange',
|
pygments.token.Name.Function.HTTP.PATCH: PYGMENTS_BOLD | PieColor.ORANGE,
|
||||||
pygments.token.Name.Function.HTTP.DELETE: 'bold red',
|
pygments.token.Name.Function.HTTP.DELETE: PYGMENTS_BOLD | PieColor.RED,
|
||||||
|
|
||||||
# HTTP status codes
|
# HTTP status codes
|
||||||
pygments.token.Number.HTTP.INFO: 'bold aqua',
|
pygments.token.Number.HTTP.INFO: PYGMENTS_BOLD | PieColor.AQUA,
|
||||||
pygments.token.Number.HTTP.OK: 'bold green',
|
pygments.token.Number.HTTP.OK: PYGMENTS_BOLD | PieColor.GREEN,
|
||||||
pygments.token.Number.HTTP.REDIRECT: 'bold yellow',
|
pygments.token.Number.HTTP.REDIRECT: PYGMENTS_BOLD | PieColor.YELLOW,
|
||||||
pygments.token.Number.HTTP.CLIENT_ERR: 'bold orange',
|
pygments.token.Number.HTTP.CLIENT_ERR: PYGMENTS_BOLD | PieColor.ORANGE,
|
||||||
pygments.token.Number.HTTP.SERVER_ERR: 'bold red',
|
pygments.token.Number.HTTP.SERVER_ERR: PYGMENTS_BOLD | PieColor.RED,
|
||||||
|
|
||||||
# Metadata
|
# Metadata
|
||||||
pygments.token.Name.Decorator: 'grey',
|
pygments.token.Name.Decorator: PieColor.GREY,
|
||||||
pygments.token.Number.SPEED.FAST: 'bold green',
|
pygments.token.Number.SPEED.FAST: PYGMENTS_BOLD | PieColor.GREEN,
|
||||||
pygments.token.Number.SPEED.AVG: 'bold yellow',
|
pygments.token.Number.SPEED.AVG: PYGMENTS_BOLD | PieColor.YELLOW,
|
||||||
pygments.token.Number.SPEED.SLOW: 'bold orange',
|
pygments.token.Number.SPEED.SLOW: PYGMENTS_BOLD | PieColor.ORANGE,
|
||||||
pygments.token.Number.SPEED.VERY_SLOW: 'bold red',
|
pygments.token.Number.SPEED.VERY_SLOW: PYGMENTS_BOLD | PieColor.RED,
|
||||||
}
|
}
|
||||||
|
|
||||||
PIE_BODY_STYLE = {
|
PIE_BODY_STYLE = {
|
||||||
# {}[]:
|
# {}[]:
|
||||||
pygments.token.Punctuation: 'grey',
|
pygments.token.Punctuation: PieColor.GREY,
|
||||||
|
|
||||||
# Keys
|
# Keys
|
||||||
pygments.token.Name.Tag: 'pink',
|
pygments.token.Name.Tag: PieColor.PINK,
|
||||||
|
|
||||||
# Values
|
# Values
|
||||||
pygments.token.Literal.String: 'green',
|
pygments.token.Literal.String: PieColor.GREEN,
|
||||||
pygments.token.Literal.String.Double: 'green',
|
pygments.token.Literal.String.Double: PieColor.GREEN,
|
||||||
pygments.token.Literal.Number: 'aqua',
|
pygments.token.Literal.Number: PieColor.AQUA,
|
||||||
pygments.token.Keyword: 'orange',
|
pygments.token.Keyword: PieColor.ORANGE,
|
||||||
|
|
||||||
# Other stuff
|
# Other stuff
|
||||||
pygments.token.Text: 'primary',
|
pygments.token.Text: PieColor.PRIMARY,
|
||||||
pygments.token.Name.Attribute: 'primary',
|
pygments.token.Name.Attribute: PieColor.PRIMARY,
|
||||||
pygments.token.Name.Builtin: 'blue',
|
pygments.token.Name.Builtin: PieColor.BLUE,
|
||||||
pygments.token.Name.Builtin.Pseudo: 'blue',
|
pygments.token.Name.Builtin.Pseudo: PieColor.BLUE,
|
||||||
pygments.token.Name.Class: 'blue',
|
pygments.token.Name.Class: PieColor.BLUE,
|
||||||
pygments.token.Name.Constant: 'orange',
|
pygments.token.Name.Constant: PieColor.ORANGE,
|
||||||
pygments.token.Name.Decorator: 'blue',
|
pygments.token.Name.Decorator: PieColor.BLUE,
|
||||||
pygments.token.Name.Entity: 'orange',
|
pygments.token.Name.Entity: PieColor.ORANGE,
|
||||||
pygments.token.Name.Exception: 'yellow',
|
pygments.token.Name.Exception: PieColor.YELLOW,
|
||||||
pygments.token.Name.Function: 'blue',
|
pygments.token.Name.Function: PieColor.BLUE,
|
||||||
pygments.token.Name.Variable: 'blue',
|
pygments.token.Name.Variable: PieColor.BLUE,
|
||||||
pygments.token.String: 'aqua',
|
pygments.token.String: PieColor.AQUA,
|
||||||
pygments.token.String.Backtick: 'secondary',
|
pygments.token.String.Backtick: PieColor.SECONDARY,
|
||||||
pygments.token.String.Char: 'aqua',
|
pygments.token.String.Char: PieColor.AQUA,
|
||||||
pygments.token.String.Doc: 'aqua',
|
pygments.token.String.Doc: PieColor.AQUA,
|
||||||
pygments.token.String.Escape: 'red',
|
pygments.token.String.Escape: PieColor.RED,
|
||||||
pygments.token.String.Heredoc: 'aqua',
|
pygments.token.String.Heredoc: PieColor.AQUA,
|
||||||
pygments.token.String.Regex: 'red',
|
pygments.token.String.Regex: PieColor.RED,
|
||||||
pygments.token.Number: 'aqua',
|
pygments.token.Number: PieColor.AQUA,
|
||||||
pygments.token.Operator: 'primary',
|
pygments.token.Operator: PieColor.PRIMARY,
|
||||||
pygments.token.Operator.Word: 'green',
|
pygments.token.Operator.Word: PieColor.GREEN,
|
||||||
pygments.token.Comment: 'secondary',
|
pygments.token.Comment: PieColor.SECONDARY,
|
||||||
pygments.token.Comment.Preproc: 'green',
|
pygments.token.Comment.Preproc: PieColor.GREEN,
|
||||||
pygments.token.Comment.Special: 'green',
|
pygments.token.Comment.Special: PieColor.GREEN,
|
||||||
pygments.token.Generic.Deleted: 'aqua',
|
pygments.token.Generic.Deleted: PieColor.AQUA,
|
||||||
pygments.token.Generic.Emph: 'italic',
|
pygments.token.Generic.Emph: PYGMENTS_ITALIC,
|
||||||
pygments.token.Generic.Error: 'red',
|
pygments.token.Generic.Error: PieColor.RED,
|
||||||
pygments.token.Generic.Heading: 'orange',
|
pygments.token.Generic.Heading: PieColor.ORANGE,
|
||||||
pygments.token.Generic.Inserted: 'green',
|
pygments.token.Generic.Inserted: PieColor.GREEN,
|
||||||
pygments.token.Generic.Strong: 'bold',
|
pygments.token.Generic.Strong: PYGMENTS_BOLD,
|
||||||
pygments.token.Generic.Subheading: 'orange',
|
pygments.token.Generic.Subheading: PieColor.ORANGE,
|
||||||
pygments.token.Token: 'primary',
|
pygments.token.Token: PieColor.PRIMARY,
|
||||||
pygments.token.Token.Other: 'orange',
|
pygments.token.Token.Other: PieColor.ORANGE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -369,7 +371,7 @@ def make_style(name, raw_styles, shade):
|
|||||||
def make_styles():
|
def make_styles():
|
||||||
styles = {}
|
styles = {}
|
||||||
|
|
||||||
for shade, name in SHADE_NAMES.items():
|
for shade, name in SHADE_TO_PIE_STYLE.items():
|
||||||
styles[name] = [
|
styles[name] = [
|
||||||
make_style(name, style_map, shade)
|
make_style(name, style_map, shade)
|
||||||
for style_name, style_map in [
|
for style_name, style_map in [
|
||||||
|
@ -18,7 +18,7 @@ def is_available(program: str) -> bool:
|
|||||||
[MAN_COMMAND, program],
|
[MAN_COMMAND, program],
|
||||||
shell=False,
|
shell=False,
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL
|
stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
return process.returncode == 0
|
return process.returncode == 0
|
||||||
|
|
||||||
@ -27,7 +27,5 @@ def display_for(env: Environment, program: str) -> None:
|
|||||||
"""Display the man page for the given command (http/https)."""
|
"""Display the man page for the given command (http/https)."""
|
||||||
|
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
[MAN_COMMAND, program],
|
[MAN_COMMAND, program], stdout=env.stdout, stderr=env.stderr
|
||||||
stdout=env.stdout,
|
|
||||||
stderr=env.stderr
|
|
||||||
)
|
)
|
||||||
|
@ -1,16 +1,97 @@
|
|||||||
from typing import Dict, Optional
|
from enum import Enum, auto
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
PYGMENTS_BRIGHT_BLACK = 'ansibrightblack'
|
||||||
|
|
||||||
AUTO_STYLE = 'auto' # Follows terminal ANSI color styles
|
AUTO_STYLE = 'auto' # Follows terminal ANSI color styles
|
||||||
STYLE_PIE = 'pie'
|
|
||||||
STYLE_PIE_DARK = 'pie-dark'
|
|
||||||
STYLE_PIE_LIGHT = 'pie-light'
|
|
||||||
|
|
||||||
|
|
||||||
|
class Styles(Enum):
|
||||||
|
PIE = auto()
|
||||||
|
ANSI = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class PieStyle(str, Enum):
|
||||||
|
UNIVERSAL = 'pie'
|
||||||
|
DARK = 'pie-dark'
|
||||||
|
LIGHT = 'pie-light'
|
||||||
|
|
||||||
|
|
||||||
|
PIE_STYLE_TO_SHADE = {
|
||||||
|
PieStyle.DARK: '500',
|
||||||
|
PieStyle.UNIVERSAL: '600',
|
||||||
|
PieStyle.LIGHT: '700',
|
||||||
|
}
|
||||||
|
SHADE_TO_PIE_STYLE = {
|
||||||
|
shade: style for style, shade in PIE_STYLE_TO_SHADE.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ColorString(str):
|
||||||
|
def __or__(self, other: str) -> 'ColorString':
|
||||||
|
"""Combine a style with a property.
|
||||||
|
|
||||||
|
E.g: PieColor.BLUE | BOLD | ITALIC
|
||||||
|
"""
|
||||||
|
return ColorString(self + ' ' + other)
|
||||||
|
|
||||||
|
|
||||||
|
class PieColor(ColorString, Enum):
|
||||||
|
"""Styles that are available only in Pie themes."""
|
||||||
|
|
||||||
|
PRIMARY = 'primary'
|
||||||
|
SECONDARY = 'secondary'
|
||||||
|
|
||||||
|
WHITE = 'white'
|
||||||
|
BLACK = 'black'
|
||||||
|
GREY = 'grey'
|
||||||
|
AQUA = 'aqua'
|
||||||
|
PURPLE = 'purple'
|
||||||
|
ORANGE = 'orange'
|
||||||
|
RED = 'red'
|
||||||
|
BLUE = 'blue'
|
||||||
|
PINK = 'pink'
|
||||||
|
GREEN = 'green'
|
||||||
|
YELLOW = 'yellow'
|
||||||
|
|
||||||
|
|
||||||
|
class GenericColor(Enum):
|
||||||
|
"""Generic colors that are safe to use everywhere."""
|
||||||
|
|
||||||
|
# <https://rich.readthedocs.io/en/stable/appendix/colors.html>
|
||||||
|
|
||||||
|
WHITE = {Styles.PIE: PieColor.WHITE, Styles.ANSI: 'white'}
|
||||||
|
BLACK = {Styles.PIE: PieColor.BLACK, Styles.ANSI: 'black'}
|
||||||
|
GREEN = {Styles.PIE: PieColor.GREEN, Styles.ANSI: 'green'}
|
||||||
|
ORANGE = {Styles.PIE: PieColor.ORANGE, Styles.ANSI: 'yellow'}
|
||||||
|
YELLOW = {Styles.PIE: PieColor.YELLOW, Styles.ANSI: 'bright_yellow'}
|
||||||
|
BLUE = {Styles.PIE: PieColor.BLUE, Styles.ANSI: 'blue'}
|
||||||
|
PINK = {Styles.PIE: PieColor.PINK, Styles.ANSI: 'bright_magenta'}
|
||||||
|
PURPLE = {Styles.PIE: PieColor.PURPLE, Styles.ANSI: 'magenta'}
|
||||||
|
RED = {Styles.PIE: PieColor.RED, Styles.ANSI: 'red'}
|
||||||
|
AQUA = {Styles.PIE: PieColor.AQUA, Styles.ANSI: 'cyan'}
|
||||||
|
GREY = {Styles.PIE: PieColor.GREY, Styles.ANSI: 'bright_black'}
|
||||||
|
|
||||||
|
def apply_style(
|
||||||
|
self, style: Styles, *, style_name: Optional[str] = None
|
||||||
|
) -> str:
|
||||||
|
"""Apply the given style to a particular value."""
|
||||||
|
exposed_color = self.value[style]
|
||||||
|
if style is Styles.PIE:
|
||||||
|
assert style_name is not None
|
||||||
|
shade = PIE_STYLE_TO_SHADE[PieStyle(style_name)]
|
||||||
|
return get_color(exposed_color, shade)
|
||||||
|
else:
|
||||||
|
return exposed_color
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyDictCreation
|
||||||
COLOR_PALETTE = {
|
COLOR_PALETTE = {
|
||||||
# Copy the brand palette
|
# Copy the brand palette
|
||||||
'white': '#F5F5F0',
|
PieColor.WHITE: '#F5F5F0',
|
||||||
'black': '#1C1818',
|
PieColor.BLACK: '#1C1818',
|
||||||
'grey': {
|
PieColor.GREY: {
|
||||||
'50': '#F5F5F0',
|
'50': '#F5F5F0',
|
||||||
'100': '#EDEDEB',
|
'100': '#EDEDEB',
|
||||||
'200': '#D1D1CF',
|
'200': '#D1D1CF',
|
||||||
@ -23,7 +104,7 @@ COLOR_PALETTE = {
|
|||||||
'900': '#1C1818',
|
'900': '#1C1818',
|
||||||
'DEFAULT': '#7D7D7D',
|
'DEFAULT': '#7D7D7D',
|
||||||
},
|
},
|
||||||
'aqua': {
|
PieColor.AQUA: {
|
||||||
'50': '#E8F0F5',
|
'50': '#E8F0F5',
|
||||||
'100': '#D6E3ED',
|
'100': '#D6E3ED',
|
||||||
'200': '#C4D9E5',
|
'200': '#C4D9E5',
|
||||||
@ -36,7 +117,7 @@ COLOR_PALETTE = {
|
|||||||
'900': '#455966',
|
'900': '#455966',
|
||||||
'DEFAULT': '#8CB4CD',
|
'DEFAULT': '#8CB4CD',
|
||||||
},
|
},
|
||||||
'purple': {
|
PieColor.PURPLE: {
|
||||||
'50': '#F0E0FC',
|
'50': '#F0E0FC',
|
||||||
'100': '#E3C7FA',
|
'100': '#E3C7FA',
|
||||||
'200': '#D9ADF7',
|
'200': '#D9ADF7',
|
||||||
@ -49,7 +130,7 @@ COLOR_PALETTE = {
|
|||||||
'900': '#5C2982',
|
'900': '#5C2982',
|
||||||
'DEFAULT': '#B464F0',
|
'DEFAULT': '#B464F0',
|
||||||
},
|
},
|
||||||
'orange': {
|
PieColor.ORANGE: {
|
||||||
'50': '#FFEDDB',
|
'50': '#FFEDDB',
|
||||||
'100': '#FFDEBF',
|
'100': '#FFDEBF',
|
||||||
'200': '#FFCFA3',
|
'200': '#FFCFA3',
|
||||||
@ -62,7 +143,7 @@ COLOR_PALETTE = {
|
|||||||
'900': '#C75E0A',
|
'900': '#C75E0A',
|
||||||
'DEFAULT': '#FFA24E',
|
'DEFAULT': '#FFA24E',
|
||||||
},
|
},
|
||||||
'red': {
|
PieColor.RED: {
|
||||||
'50': '#FFE0DE',
|
'50': '#FFE0DE',
|
||||||
'100': '#FFC7C4',
|
'100': '#FFC7C4',
|
||||||
'200': '#FFB0AB',
|
'200': '#FFB0AB',
|
||||||
@ -75,7 +156,7 @@ COLOR_PALETTE = {
|
|||||||
'900': '#910A00',
|
'900': '#910A00',
|
||||||
'DEFAULT': '#FF665B',
|
'DEFAULT': '#FF665B',
|
||||||
},
|
},
|
||||||
'blue': {
|
PieColor.BLUE: {
|
||||||
'50': '#DBE3FA',
|
'50': '#DBE3FA',
|
||||||
'100': '#BFCFF5',
|
'100': '#BFCFF5',
|
||||||
'200': '#A1B8F2',
|
'200': '#A1B8F2',
|
||||||
@ -88,7 +169,7 @@ COLOR_PALETTE = {
|
|||||||
'900': '#2B478F',
|
'900': '#2B478F',
|
||||||
'DEFAULT': '#4B78E6',
|
'DEFAULT': '#4B78E6',
|
||||||
},
|
},
|
||||||
'pink': {
|
PieColor.PINK: {
|
||||||
'50': '#FFEBFF',
|
'50': '#FFEBFF',
|
||||||
'100': '#FCDBFC',
|
'100': '#FCDBFC',
|
||||||
'200': '#FCCCFC',
|
'200': '#FCCCFC',
|
||||||
@ -101,7 +182,7 @@ COLOR_PALETTE = {
|
|||||||
'900': '#8C3D8A',
|
'900': '#8C3D8A',
|
||||||
'DEFAULT': '#FA9BFA',
|
'DEFAULT': '#FA9BFA',
|
||||||
},
|
},
|
||||||
'green': {
|
PieColor.GREEN: {
|
||||||
'50': '#E3F7E8',
|
'50': '#E3F7E8',
|
||||||
'100': '#CCF2D6',
|
'100': '#CCF2D6',
|
||||||
'200': '#B5EDC4',
|
'200': '#B5EDC4',
|
||||||
@ -114,7 +195,7 @@ COLOR_PALETTE = {
|
|||||||
'900': '#307842',
|
'900': '#307842',
|
||||||
'DEFAULT': '#73DC8C',
|
'DEFAULT': '#73DC8C',
|
||||||
},
|
},
|
||||||
'yellow': {
|
PieColor.YELLOW: {
|
||||||
'50': '#F7F7DB',
|
'50': '#F7F7DB',
|
||||||
'100': '#F2F2BF',
|
'100': '#F2F2BF',
|
||||||
'200': '#EDEDA6',
|
'200': '#EDEDA6',
|
||||||
@ -128,47 +209,38 @@ COLOR_PALETTE = {
|
|||||||
'DEFAULT': '#DBDE52',
|
'DEFAULT': '#DBDE52',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
COLOR_PALETTE.update(
|
||||||
# Grey is the same no matter shade for the colors
|
{
|
||||||
COLOR_PALETTE['grey'] = {
|
# Terminal-specific palette customizations.
|
||||||
shade: COLOR_PALETTE['grey']['500'] for shade in COLOR_PALETTE['grey'].keys()
|
PieColor.GREY: {
|
||||||
}
|
# Grey is the same no matter shade for the colors
|
||||||
|
shade: COLOR_PALETTE[PieColor.GREY]['500']
|
||||||
COLOR_PALETTE['primary'] = {
|
for shade in COLOR_PALETTE[PieColor.GREY].keys()
|
||||||
'700': COLOR_PALETTE['black'],
|
},
|
||||||
'600': 'ansibrightblack',
|
PieColor.PRIMARY: {
|
||||||
'500': COLOR_PALETTE['white'],
|
'700': COLOR_PALETTE[PieColor.BLACK],
|
||||||
}
|
'600': PYGMENTS_BRIGHT_BLACK,
|
||||||
|
'500': COLOR_PALETTE[PieColor.WHITE],
|
||||||
COLOR_PALETTE['secondary'] = {'700': '#37523C', '600': '#6c6969', '500': '#6c6969'}
|
},
|
||||||
|
PieColor.SECONDARY: {
|
||||||
|
'700': '#37523C',
|
||||||
|
'600': '#6c6969',
|
||||||
|
'500': '#6c6969',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
SHADE_NAMES = {
|
def boldify(color: PieColor) -> str:
|
||||||
'500': STYLE_PIE_DARK,
|
return f'bold {color}'
|
||||||
'600': STYLE_PIE,
|
|
||||||
'700': STYLE_PIE_LIGHT
|
|
||||||
}
|
|
||||||
|
|
||||||
STYLE_SHADES = {
|
|
||||||
style: shade
|
|
||||||
for shade, style in SHADE_NAMES.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
SHADES = [
|
|
||||||
'50',
|
|
||||||
*map(str, range(100, 1000, 100))
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyDefaultArgument
|
||||||
def get_color(
|
def get_color(
|
||||||
color: str,
|
color: PieColor, shade: str, *, palette=COLOR_PALETTE
|
||||||
shade: str,
|
|
||||||
*,
|
|
||||||
palette: Dict[str, Dict[str, str]] = COLOR_PALETTE
|
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
if color not in palette:
|
if color not in palette:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
color_code = palette[color]
|
color_code = palette[color]
|
||||||
if isinstance(color_code, dict) and shade in color_code:
|
if isinstance(color_code, dict) and shade in color_code:
|
||||||
return color_code[shade]
|
return color_code[shade]
|
||||||
|
@ -10,16 +10,18 @@ from rich.text import Text
|
|||||||
|
|
||||||
from httpie.cli.constants import SEPARATOR_GROUP_ALL_ITEMS
|
from httpie.cli.constants import SEPARATOR_GROUP_ALL_ITEMS
|
||||||
from httpie.cli.options import Argument, ParserSpec, Qualifiers
|
from httpie.cli.options import Argument, ParserSpec, Qualifiers
|
||||||
|
from httpie.output.ui.palette import GenericColor
|
||||||
|
|
||||||
SEPARATORS = '|'.join(map(re.escape, SEPARATOR_GROUP_ALL_ITEMS))
|
SEPARATORS = '|'.join(map(re.escape, SEPARATOR_GROUP_ALL_ITEMS))
|
||||||
|
|
||||||
STYLE_METAVAR = 'yellow'
|
STYLE_METAVAR = GenericColor.YELLOW
|
||||||
STYLE_SWITCH = 'green'
|
STYLE_SWITCH = GenericColor.GREEN
|
||||||
STYLE_PROGRAM_NAME = 'bold green'
|
STYLE_PROGRAM_NAME = GenericColor.GREEN # .boldify()
|
||||||
STYLE_USAGE_OPTIONAL = 'grey46'
|
STYLE_USAGE_OPTIONAL = GenericColor.GREY
|
||||||
STYLE_USAGE_REGULAR = 'white'
|
STYLE_USAGE_REGULAR = GenericColor.WHITE
|
||||||
STYLE_USAGE_ERROR = 'red'
|
STYLE_USAGE_ERROR = GenericColor.RED
|
||||||
STYLE_USAGE_MISSING = 'yellow'
|
STYLE_USAGE_MISSING = GenericColor.YELLOW
|
||||||
|
STYLE_BOLD = 'bold'
|
||||||
|
|
||||||
MAX_CHOICE_CHARS = 80
|
MAX_CHOICE_CHARS = 80
|
||||||
|
|
||||||
@ -77,7 +79,7 @@ def to_usage(
|
|||||||
# shown first
|
# shown first
|
||||||
shown_arguments.sort(key=lambda argument: argument.aliases, reverse=True)
|
shown_arguments.sort(key=lambda argument: argument.aliases, reverse=True)
|
||||||
|
|
||||||
text = Text(program_name or spec.program, style='bold')
|
text = Text(program_name or spec.program, style=STYLE_BOLD)
|
||||||
for argument in shown_arguments:
|
for argument in shown_arguments:
|
||||||
text.append(' ')
|
text.append(' ')
|
||||||
|
|
||||||
|
@ -1,23 +1,65 @@
|
|||||||
from httpie.output.ui.palette import * # noqa
|
from collections import ChainMap
|
||||||
|
from typing import TYPE_CHECKING, Any, Optional
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from rich.theme import Theme
|
||||||
|
|
||||||
|
from httpie.output.ui.palette import GenericColor, PieStyle, Styles # noqa
|
||||||
|
|
||||||
# Rich-specific color code declarations
|
# Rich-specific color code declarations
|
||||||
# https://github.com/Textualize/rich/blob/fcd684dd3a482977cab620e71ccaebb94bf13ac9/rich/default_styles.py#L5
|
# <https://github.com/Textualize/rich/blob/fcd684dd3a482977cab620e71ccaebb94bf13ac9/rich/default_styles.py>
|
||||||
CUSTOM_STYLES = {
|
CUSTOM_STYLES = {
|
||||||
'progress.description': 'white',
|
'progress.description': GenericColor.WHITE,
|
||||||
'progress.data.speed': 'green',
|
'progress.data.speed': GenericColor.GREEN,
|
||||||
'progress.percentage': 'aqua',
|
'progress.percentage': GenericColor.AQUA,
|
||||||
'progress.download': 'aqua',
|
'progress.download': GenericColor.AQUA,
|
||||||
'progress.remaining': 'orange',
|
'progress.remaining': GenericColor.ORANGE,
|
||||||
'bar.complete': 'purple',
|
'bar.complete': GenericColor.PURPLE,
|
||||||
'bar.finished': 'green',
|
'bar.finished': GenericColor.GREEN,
|
||||||
'bar.pulse': 'purple',
|
'bar.pulse': GenericColor.PURPLE,
|
||||||
'option': 'pink'
|
'option': GenericColor.PINK,
|
||||||
}
|
}
|
||||||
|
|
||||||
RICH_THEME_PALETTE = COLOR_PALETTE.copy() # noqa
|
|
||||||
RICH_THEME_PALETTE.update(
|
class _GenericColorCaster(dict):
|
||||||
{
|
"""
|
||||||
custom_style: RICH_THEME_PALETTE[color]
|
Translate GenericColor to a regular string on the attribute access
|
||||||
for custom_style, color in CUSTOM_STYLES.items()
|
phase.
|
||||||
}
|
"""
|
||||||
)
|
|
||||||
|
def _translate(self, key: Any) -> Any:
|
||||||
|
if isinstance(key, GenericColor):
|
||||||
|
return key.name.lower()
|
||||||
|
else:
|
||||||
|
return key
|
||||||
|
|
||||||
|
def __getitem__(self, key: Any) -> Any:
|
||||||
|
return super().__getitem__(self._translate(key))
|
||||||
|
|
||||||
|
def get(self, key: Any) -> Any:
|
||||||
|
return super().get(self._translate(key))
|
||||||
|
|
||||||
|
|
||||||
|
def _make_rich_color_theme(style_name: Optional[str]) -> 'Theme':
|
||||||
|
from rich.style import Style
|
||||||
|
from rich.theme import Theme
|
||||||
|
|
||||||
|
try:
|
||||||
|
PieStyle(style_name)
|
||||||
|
except ValueError:
|
||||||
|
style = Styles.ANSI
|
||||||
|
else:
|
||||||
|
style = Styles.PIE
|
||||||
|
|
||||||
|
theme = Theme()
|
||||||
|
for color, color_set in ChainMap(
|
||||||
|
GenericColor.__members__, CUSTOM_STYLES
|
||||||
|
).items():
|
||||||
|
theme.styles[color.lower()] = Style(
|
||||||
|
color=color_set.apply_style(style, style_name=style_name),
|
||||||
|
bold=style is Styles.PIE,
|
||||||
|
)
|
||||||
|
|
||||||
|
# E.g translate GenericColor.BLUE into blue on key access
|
||||||
|
theme.styles = _GenericColorCaster(theme.styles)
|
||||||
|
return theme
|
||||||
|
@ -28,10 +28,7 @@ class BaseDisplay:
|
|||||||
return self.env.rich_error_console
|
return self.env.rich_error_console
|
||||||
|
|
||||||
def _print_summary(
|
def _print_summary(
|
||||||
self,
|
self, is_finished: bool, observed_steps: int, time_spent: float
|
||||||
is_finished: bool,
|
|
||||||
observed_steps: int,
|
|
||||||
time_spent: float
|
|
||||||
):
|
):
|
||||||
from rich import filesize
|
from rich import filesize
|
||||||
|
|
||||||
@ -50,7 +47,9 @@ class BaseDisplay:
|
|||||||
else:
|
else:
|
||||||
total_time = f'{minutes:02d}:{seconds:0.5f}'
|
total_time = f'{minutes:02d}:{seconds:0.5f}'
|
||||||
|
|
||||||
self.console.print(f'[progress.description]{verb}. {total_size} in {total_time} ({avg_speed}/s)')
|
self.console.print(
|
||||||
|
f'[progress.description]{verb}. {total_size} in {total_time} ({avg_speed}/s)'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DummyDisplay(BaseDisplay):
|
class DummyDisplay(BaseDisplay):
|
||||||
@ -65,7 +64,9 @@ class StatusDisplay(BaseDisplay):
|
|||||||
self, *, total: Optional[float], at: float, description: str
|
self, *, total: Optional[float], at: float, description: str
|
||||||
) -> None:
|
) -> None:
|
||||||
self.observed = at
|
self.observed = at
|
||||||
self.description = f'[progress.description]{description}[/progress.description]'
|
self.description = (
|
||||||
|
f'[progress.description]{description}[/progress.description]'
|
||||||
|
)
|
||||||
|
|
||||||
self.status = self.console.status(self.description, spinner='line')
|
self.status = self.console.status(self.description, spinner='line')
|
||||||
self.status.start()
|
self.status.start()
|
||||||
@ -75,8 +76,12 @@ class StatusDisplay(BaseDisplay):
|
|||||||
|
|
||||||
self.observed += steps
|
self.observed += steps
|
||||||
|
|
||||||
observed_amount, observed_unit = filesize.decimal(self.observed).split()
|
observed_amount, observed_unit = filesize.decimal(
|
||||||
self.status.update(status=f'{self.description} [progress.download]{observed_amount}/? {observed_unit}[/progress.download]')
|
self.observed
|
||||||
|
).split()
|
||||||
|
self.status.update(
|
||||||
|
status=f'{self.description} [progress.download]{observed_amount}/? {observed_unit}[/progress.download]'
|
||||||
|
)
|
||||||
|
|
||||||
def stop(self, time_spent: float) -> None:
|
def stop(self, time_spent: float) -> None:
|
||||||
self.status.stop()
|
self.status.stop()
|
||||||
@ -85,7 +90,7 @@ class StatusDisplay(BaseDisplay):
|
|||||||
self._print_summary(
|
self._print_summary(
|
||||||
is_finished=True,
|
is_finished=True,
|
||||||
observed_steps=self.observed,
|
observed_steps=self.observed,
|
||||||
time_spent=time_spent
|
time_spent=time_spent,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -114,7 +119,7 @@ class ProgressDisplay(BaseDisplay):
|
|||||||
TimeRemainingColumn(),
|
TimeRemainingColumn(),
|
||||||
TransferSpeedColumn(),
|
TransferSpeedColumn(),
|
||||||
console=self.console,
|
console=self.console,
|
||||||
transient=True
|
transient=True,
|
||||||
)
|
)
|
||||||
self.progress_bar.start()
|
self.progress_bar.start()
|
||||||
self.transfer_task = self.progress_bar.add_task(
|
self.transfer_task = self.progress_bar.add_task(
|
||||||
@ -132,5 +137,5 @@ class ProgressDisplay(BaseDisplay):
|
|||||||
self._print_summary(
|
self._print_summary(
|
||||||
is_finished=task.finished,
|
is_finished=task.finished,
|
||||||
observed_steps=task.completed,
|
observed_steps=task.completed,
|
||||||
time_spent=time_spent
|
time_spent=time_spent,
|
||||||
)
|
)
|
||||||
|
@ -11,11 +11,8 @@ def render_as_string(renderable: RenderableType) -> str:
|
|||||||
"""Render any `rich` object in a fake console and
|
"""Render any `rich` object in a fake console and
|
||||||
return a *style-less* version of it as a string."""
|
return a *style-less* version of it as a string."""
|
||||||
|
|
||||||
with open(os.devnull, "w") as null_stream:
|
with open(os.devnull, 'w') as null_stream:
|
||||||
fake_console = Console(
|
fake_console = Console(file=null_stream, record=True)
|
||||||
file=null_stream,
|
|
||||||
record=True
|
|
||||||
)
|
|
||||||
fake_console.print(renderable)
|
fake_console.print(renderable)
|
||||||
return fake_console.export_text()
|
return fake_console.export_text()
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ from typing import Any, Dict, List, Optional, Union
|
|||||||
from requests.auth import AuthBase
|
from requests.auth import AuthBase
|
||||||
from requests.cookies import RequestsCookieJar, remove_cookie_by_name
|
from requests.cookies import RequestsCookieJar, remove_cookie_by_name
|
||||||
|
|
||||||
from .context import Environment, Levels
|
from .context import Environment, LogLevel
|
||||||
from .cookies import HTTPieCookiePolicy
|
from .cookies import HTTPieCookiePolicy
|
||||||
from .cli.dicts import HTTPHeadersDict
|
from .cli.dicts import HTTPHeadersDict
|
||||||
from .config import BaseConfigDict, DEFAULT_CONFIG_DIR
|
from .config import BaseConfigDict, DEFAULT_CONFIG_DIR
|
||||||
@ -313,7 +313,7 @@ class Session(BaseConfigDict):
|
|||||||
|
|
||||||
self.env.log_error(
|
self.env.log_error(
|
||||||
warning,
|
warning,
|
||||||
level=Levels.WARNING
|
level=LogLevel.WARNING
|
||||||
)
|
)
|
||||||
|
|
||||||
# We don't want to spam multiple warnings on each usage,
|
# We don't want to spam multiple warnings on each usage,
|
||||||
|
Loading…
Reference in New Issue
Block a user