mirror of
https://github.com/httpie/cli.git
synced 2024-11-24 16:53:35 +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 .utils import repr_dict
|
||||
from httpie.output.ui import rich_palette as palette
|
||||
from .output.ui.palette import GenericColor
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from rich.console import Console
|
||||
|
||||
|
||||
class Levels(str, Enum):
|
||||
class LogLevel(str, Enum):
|
||||
INFO = 'info'
|
||||
WARNING = 'warning'
|
||||
ERROR = 'error'
|
||||
|
||||
|
||||
DISPLAY_THRESHOLDS = {
|
||||
Levels.WARNING: 2,
|
||||
Levels.ERROR: float('inf'), # Never hide errors.
|
||||
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.
|
||||
}
|
||||
|
||||
|
||||
@ -159,16 +167,22 @@ class Environment:
|
||||
self.stdout = original_stdout
|
||||
self.stderr = original_stderr
|
||||
|
||||
def log_error(self, msg: str, level: Levels = Levels.ERROR) -> None:
|
||||
if self.stdout_isatty and self.quiet >= DISPLAY_THRESHOLDS[level]:
|
||||
def log_error(self, msg: str, level: LogLevel = LogLevel.ERROR) -> None:
|
||||
if self.stdout_isatty and self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[level]:
|
||||
stderr = self.stderr # Not directly /dev/null, since stderr might be mocked
|
||||
else:
|
||||
stderr = self._orig_stderr
|
||||
|
||||
stderr.write(f'\n{self.program_name}: {level}: {msg}\n\n')
|
||||
rich_console = self._make_rich_console(file=stderr, force_terminal=stderr.isatty())
|
||||
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:
|
||||
if self.quiet >= DISPLAY_THRESHOLDS[Levels.WARNING]:
|
||||
if self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[LogLevel.WARNING]:
|
||||
warnings.simplefilter("ignore")
|
||||
|
||||
def _make_rich_console(
|
||||
@ -177,32 +191,17 @@ class Environment:
|
||||
force_terminal: bool
|
||||
) -> 'Console':
|
||||
from rich.console import Console
|
||||
from rich.theme import 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
|
||||
})
|
||||
from httpie.output.ui.rich_palette import _make_rich_color_theme
|
||||
|
||||
style = getattr(self.args, 'style', None)
|
||||
theme = _make_rich_color_theme(style)
|
||||
# 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),
|
||||
theme=Theme(theme)
|
||||
theme=theme
|
||||
)
|
||||
|
||||
# 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.nested_json import HTTPieSyntaxError
|
||||
from .client import collect_messages
|
||||
from .context import Environment, Levels
|
||||
from .context import Environment, LogLevel
|
||||
from .downloads import Downloader
|
||||
from .models import (
|
||||
RequestsMessageKind,
|
||||
@ -223,7 +223,7 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
|
||||
if args.check_status or downloader:
|
||||
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):
|
||||
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(
|
||||
requests_message=message,
|
||||
env=env,
|
||||
|
@ -17,13 +17,15 @@ from pygments.util import ClassNotFound
|
||||
|
||||
from ..lexers.json import EnhancedJsonLexer
|
||||
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 ...plugins import FormatterPlugin
|
||||
|
||||
|
||||
DEFAULT_STYLE = AUTO_STYLE
|
||||
SOLARIZED_STYLE = 'solarized' # Bundled here
|
||||
PYGMENTS_BOLD = ColorString('bold')
|
||||
PYGMENTS_ITALIC = ColorString('italic')
|
||||
|
||||
BUNDLED_STYLES = {
|
||||
SOLARIZED_STYLE,
|
||||
@ -253,11 +255,11 @@ class Solarized256Style(pygments.style.Style):
|
||||
pygments.token.Comment.Preproc: GREEN,
|
||||
pygments.token.Comment.Special: GREEN,
|
||||
pygments.token.Generic.Deleted: CYAN,
|
||||
pygments.token.Generic.Emph: 'italic',
|
||||
pygments.token.Generic.Emph: PYGMENTS_ITALIC,
|
||||
pygments.token.Generic.Error: RED,
|
||||
pygments.token.Generic.Heading: ORANGE,
|
||||
pygments.token.Generic.Inserted: GREEN,
|
||||
pygments.token.Generic.Strong: 'bold',
|
||||
pygments.token.Generic.Strong: PYGMENTS_BOLD,
|
||||
pygments.token.Generic.Subheading: ORANGE,
|
||||
pygments.token.Token: BASE1,
|
||||
pygments.token.Token.Other: ORANGE,
|
||||
@ -266,86 +268,86 @@ class Solarized256Style(pygments.style.Style):
|
||||
|
||||
PIE_HEADER_STYLE = {
|
||||
# HTTP line / Headers / Etc.
|
||||
pygments.token.Name.Namespace: 'bold primary',
|
||||
pygments.token.Keyword.Reserved: 'bold grey',
|
||||
pygments.token.Operator: 'bold grey',
|
||||
pygments.token.Number: 'bold grey',
|
||||
pygments.token.Name.Function.Magic: 'bold green',
|
||||
pygments.token.Name.Exception: 'bold green',
|
||||
pygments.token.Name.Attribute: 'blue',
|
||||
pygments.token.String: 'primary',
|
||||
pygments.token.Name.Namespace: PYGMENTS_BOLD | PieColor.PRIMARY,
|
||||
pygments.token.Keyword.Reserved: PYGMENTS_BOLD | PieColor.GREY,
|
||||
pygments.token.Operator: PYGMENTS_BOLD | PieColor.GREY,
|
||||
pygments.token.Number: PYGMENTS_BOLD | PieColor.GREY,
|
||||
pygments.token.Name.Function.Magic: PYGMENTS_BOLD | PieColor.GREEN,
|
||||
pygments.token.Name.Exception: PYGMENTS_BOLD | PieColor.GREEN,
|
||||
pygments.token.Name.Attribute: PieColor.BLUE,
|
||||
pygments.token.String: PieColor.PRIMARY,
|
||||
|
||||
# HTTP Methods
|
||||
pygments.token.Name.Function: 'bold grey',
|
||||
pygments.token.Name.Function.HTTP.GET: 'bold green',
|
||||
pygments.token.Name.Function.HTTP.HEAD: 'bold green',
|
||||
pygments.token.Name.Function.HTTP.POST: 'bold yellow',
|
||||
pygments.token.Name.Function.HTTP.PUT: 'bold orange',
|
||||
pygments.token.Name.Function.HTTP.PATCH: 'bold orange',
|
||||
pygments.token.Name.Function.HTTP.DELETE: 'bold red',
|
||||
pygments.token.Name.Function: PYGMENTS_BOLD | PieColor.GREY,
|
||||
pygments.token.Name.Function.HTTP.GET: PYGMENTS_BOLD | PieColor.GREEN,
|
||||
pygments.token.Name.Function.HTTP.HEAD: PYGMENTS_BOLD | PieColor.GREEN,
|
||||
pygments.token.Name.Function.HTTP.POST: PYGMENTS_BOLD | PieColor.YELLOW,
|
||||
pygments.token.Name.Function.HTTP.PUT: PYGMENTS_BOLD | PieColor.ORANGE,
|
||||
pygments.token.Name.Function.HTTP.PATCH: PYGMENTS_BOLD | PieColor.ORANGE,
|
||||
pygments.token.Name.Function.HTTP.DELETE: PYGMENTS_BOLD | PieColor.RED,
|
||||
|
||||
# HTTP status codes
|
||||
pygments.token.Number.HTTP.INFO: 'bold aqua',
|
||||
pygments.token.Number.HTTP.OK: 'bold green',
|
||||
pygments.token.Number.HTTP.REDIRECT: 'bold yellow',
|
||||
pygments.token.Number.HTTP.CLIENT_ERR: 'bold orange',
|
||||
pygments.token.Number.HTTP.SERVER_ERR: 'bold red',
|
||||
pygments.token.Number.HTTP.INFO: PYGMENTS_BOLD | PieColor.AQUA,
|
||||
pygments.token.Number.HTTP.OK: PYGMENTS_BOLD | PieColor.GREEN,
|
||||
pygments.token.Number.HTTP.REDIRECT: PYGMENTS_BOLD | PieColor.YELLOW,
|
||||
pygments.token.Number.HTTP.CLIENT_ERR: PYGMENTS_BOLD | PieColor.ORANGE,
|
||||
pygments.token.Number.HTTP.SERVER_ERR: PYGMENTS_BOLD | PieColor.RED,
|
||||
|
||||
# Metadata
|
||||
pygments.token.Name.Decorator: 'grey',
|
||||
pygments.token.Number.SPEED.FAST: 'bold green',
|
||||
pygments.token.Number.SPEED.AVG: 'bold yellow',
|
||||
pygments.token.Number.SPEED.SLOW: 'bold orange',
|
||||
pygments.token.Number.SPEED.VERY_SLOW: 'bold red',
|
||||
pygments.token.Name.Decorator: PieColor.GREY,
|
||||
pygments.token.Number.SPEED.FAST: PYGMENTS_BOLD | PieColor.GREEN,
|
||||
pygments.token.Number.SPEED.AVG: PYGMENTS_BOLD | PieColor.YELLOW,
|
||||
pygments.token.Number.SPEED.SLOW: PYGMENTS_BOLD | PieColor.ORANGE,
|
||||
pygments.token.Number.SPEED.VERY_SLOW: PYGMENTS_BOLD | PieColor.RED,
|
||||
}
|
||||
|
||||
PIE_BODY_STYLE = {
|
||||
# {}[]:
|
||||
pygments.token.Punctuation: 'grey',
|
||||
pygments.token.Punctuation: PieColor.GREY,
|
||||
|
||||
# Keys
|
||||
pygments.token.Name.Tag: 'pink',
|
||||
pygments.token.Name.Tag: PieColor.PINK,
|
||||
|
||||
# Values
|
||||
pygments.token.Literal.String: 'green',
|
||||
pygments.token.Literal.String.Double: 'green',
|
||||
pygments.token.Literal.Number: 'aqua',
|
||||
pygments.token.Keyword: 'orange',
|
||||
pygments.token.Literal.String: PieColor.GREEN,
|
||||
pygments.token.Literal.String.Double: PieColor.GREEN,
|
||||
pygments.token.Literal.Number: PieColor.AQUA,
|
||||
pygments.token.Keyword: PieColor.ORANGE,
|
||||
|
||||
# Other stuff
|
||||
pygments.token.Text: 'primary',
|
||||
pygments.token.Name.Attribute: 'primary',
|
||||
pygments.token.Name.Builtin: 'blue',
|
||||
pygments.token.Name.Builtin.Pseudo: 'blue',
|
||||
pygments.token.Name.Class: 'blue',
|
||||
pygments.token.Name.Constant: 'orange',
|
||||
pygments.token.Name.Decorator: 'blue',
|
||||
pygments.token.Name.Entity: 'orange',
|
||||
pygments.token.Name.Exception: 'yellow',
|
||||
pygments.token.Name.Function: 'blue',
|
||||
pygments.token.Name.Variable: 'blue',
|
||||
pygments.token.String: 'aqua',
|
||||
pygments.token.String.Backtick: 'secondary',
|
||||
pygments.token.String.Char: 'aqua',
|
||||
pygments.token.String.Doc: 'aqua',
|
||||
pygments.token.String.Escape: 'red',
|
||||
pygments.token.String.Heredoc: 'aqua',
|
||||
pygments.token.String.Regex: 'red',
|
||||
pygments.token.Number: 'aqua',
|
||||
pygments.token.Operator: 'primary',
|
||||
pygments.token.Operator.Word: 'green',
|
||||
pygments.token.Comment: 'secondary',
|
||||
pygments.token.Comment.Preproc: 'green',
|
||||
pygments.token.Comment.Special: 'green',
|
||||
pygments.token.Generic.Deleted: 'aqua',
|
||||
pygments.token.Generic.Emph: 'italic',
|
||||
pygments.token.Generic.Error: 'red',
|
||||
pygments.token.Generic.Heading: 'orange',
|
||||
pygments.token.Generic.Inserted: 'green',
|
||||
pygments.token.Generic.Strong: 'bold',
|
||||
pygments.token.Generic.Subheading: 'orange',
|
||||
pygments.token.Token: 'primary',
|
||||
pygments.token.Token.Other: 'orange',
|
||||
pygments.token.Text: PieColor.PRIMARY,
|
||||
pygments.token.Name.Attribute: PieColor.PRIMARY,
|
||||
pygments.token.Name.Builtin: PieColor.BLUE,
|
||||
pygments.token.Name.Builtin.Pseudo: PieColor.BLUE,
|
||||
pygments.token.Name.Class: PieColor.BLUE,
|
||||
pygments.token.Name.Constant: PieColor.ORANGE,
|
||||
pygments.token.Name.Decorator: PieColor.BLUE,
|
||||
pygments.token.Name.Entity: PieColor.ORANGE,
|
||||
pygments.token.Name.Exception: PieColor.YELLOW,
|
||||
pygments.token.Name.Function: PieColor.BLUE,
|
||||
pygments.token.Name.Variable: PieColor.BLUE,
|
||||
pygments.token.String: PieColor.AQUA,
|
||||
pygments.token.String.Backtick: PieColor.SECONDARY,
|
||||
pygments.token.String.Char: PieColor.AQUA,
|
||||
pygments.token.String.Doc: PieColor.AQUA,
|
||||
pygments.token.String.Escape: PieColor.RED,
|
||||
pygments.token.String.Heredoc: PieColor.AQUA,
|
||||
pygments.token.String.Regex: PieColor.RED,
|
||||
pygments.token.Number: PieColor.AQUA,
|
||||
pygments.token.Operator: PieColor.PRIMARY,
|
||||
pygments.token.Operator.Word: PieColor.GREEN,
|
||||
pygments.token.Comment: PieColor.SECONDARY,
|
||||
pygments.token.Comment.Preproc: PieColor.GREEN,
|
||||
pygments.token.Comment.Special: PieColor.GREEN,
|
||||
pygments.token.Generic.Deleted: PieColor.AQUA,
|
||||
pygments.token.Generic.Emph: PYGMENTS_ITALIC,
|
||||
pygments.token.Generic.Error: PieColor.RED,
|
||||
pygments.token.Generic.Heading: PieColor.ORANGE,
|
||||
pygments.token.Generic.Inserted: PieColor.GREEN,
|
||||
pygments.token.Generic.Strong: PYGMENTS_BOLD,
|
||||
pygments.token.Generic.Subheading: PieColor.ORANGE,
|
||||
pygments.token.Token: PieColor.PRIMARY,
|
||||
pygments.token.Token.Other: PieColor.ORANGE,
|
||||
}
|
||||
|
||||
|
||||
@ -369,7 +371,7 @@ def make_style(name, raw_styles, shade):
|
||||
def make_styles():
|
||||
styles = {}
|
||||
|
||||
for shade, name in SHADE_NAMES.items():
|
||||
for shade, name in SHADE_TO_PIE_STYLE.items():
|
||||
styles[name] = [
|
||||
make_style(name, style_map, shade)
|
||||
for style_name, style_map in [
|
||||
|
@ -18,7 +18,7 @@ def is_available(program: str) -> bool:
|
||||
[MAN_COMMAND, program],
|
||||
shell=False,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
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)."""
|
||||
|
||||
subprocess.run(
|
||||
[MAN_COMMAND, program],
|
||||
stdout=env.stdout,
|
||||
stderr=env.stderr
|
||||
[MAN_COMMAND, program], 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
|
||||
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 = {
|
||||
# Copy the brand palette
|
||||
'white': '#F5F5F0',
|
||||
'black': '#1C1818',
|
||||
'grey': {
|
||||
PieColor.WHITE: '#F5F5F0',
|
||||
PieColor.BLACK: '#1C1818',
|
||||
PieColor.GREY: {
|
||||
'50': '#F5F5F0',
|
||||
'100': '#EDEDEB',
|
||||
'200': '#D1D1CF',
|
||||
@ -23,7 +104,7 @@ COLOR_PALETTE = {
|
||||
'900': '#1C1818',
|
||||
'DEFAULT': '#7D7D7D',
|
||||
},
|
||||
'aqua': {
|
||||
PieColor.AQUA: {
|
||||
'50': '#E8F0F5',
|
||||
'100': '#D6E3ED',
|
||||
'200': '#C4D9E5',
|
||||
@ -36,7 +117,7 @@ COLOR_PALETTE = {
|
||||
'900': '#455966',
|
||||
'DEFAULT': '#8CB4CD',
|
||||
},
|
||||
'purple': {
|
||||
PieColor.PURPLE: {
|
||||
'50': '#F0E0FC',
|
||||
'100': '#E3C7FA',
|
||||
'200': '#D9ADF7',
|
||||
@ -49,7 +130,7 @@ COLOR_PALETTE = {
|
||||
'900': '#5C2982',
|
||||
'DEFAULT': '#B464F0',
|
||||
},
|
||||
'orange': {
|
||||
PieColor.ORANGE: {
|
||||
'50': '#FFEDDB',
|
||||
'100': '#FFDEBF',
|
||||
'200': '#FFCFA3',
|
||||
@ -62,7 +143,7 @@ COLOR_PALETTE = {
|
||||
'900': '#C75E0A',
|
||||
'DEFAULT': '#FFA24E',
|
||||
},
|
||||
'red': {
|
||||
PieColor.RED: {
|
||||
'50': '#FFE0DE',
|
||||
'100': '#FFC7C4',
|
||||
'200': '#FFB0AB',
|
||||
@ -75,7 +156,7 @@ COLOR_PALETTE = {
|
||||
'900': '#910A00',
|
||||
'DEFAULT': '#FF665B',
|
||||
},
|
||||
'blue': {
|
||||
PieColor.BLUE: {
|
||||
'50': '#DBE3FA',
|
||||
'100': '#BFCFF5',
|
||||
'200': '#A1B8F2',
|
||||
@ -88,7 +169,7 @@ COLOR_PALETTE = {
|
||||
'900': '#2B478F',
|
||||
'DEFAULT': '#4B78E6',
|
||||
},
|
||||
'pink': {
|
||||
PieColor.PINK: {
|
||||
'50': '#FFEBFF',
|
||||
'100': '#FCDBFC',
|
||||
'200': '#FCCCFC',
|
||||
@ -101,7 +182,7 @@ COLOR_PALETTE = {
|
||||
'900': '#8C3D8A',
|
||||
'DEFAULT': '#FA9BFA',
|
||||
},
|
||||
'green': {
|
||||
PieColor.GREEN: {
|
||||
'50': '#E3F7E8',
|
||||
'100': '#CCF2D6',
|
||||
'200': '#B5EDC4',
|
||||
@ -114,7 +195,7 @@ COLOR_PALETTE = {
|
||||
'900': '#307842',
|
||||
'DEFAULT': '#73DC8C',
|
||||
},
|
||||
'yellow': {
|
||||
PieColor.YELLOW: {
|
||||
'50': '#F7F7DB',
|
||||
'100': '#F2F2BF',
|
||||
'200': '#EDEDA6',
|
||||
@ -128,47 +209,38 @@ COLOR_PALETTE = {
|
||||
'DEFAULT': '#DBDE52',
|
||||
},
|
||||
}
|
||||
|
||||
COLOR_PALETTE.update(
|
||||
{
|
||||
# Terminal-specific palette customizations.
|
||||
PieColor.GREY: {
|
||||
# Grey is the same no matter shade for the colors
|
||||
COLOR_PALETTE['grey'] = {
|
||||
shade: COLOR_PALETTE['grey']['500'] for shade in COLOR_PALETTE['grey'].keys()
|
||||
shade: COLOR_PALETTE[PieColor.GREY]['500']
|
||||
for shade in COLOR_PALETTE[PieColor.GREY].keys()
|
||||
},
|
||||
PieColor.PRIMARY: {
|
||||
'700': COLOR_PALETTE[PieColor.BLACK],
|
||||
'600': PYGMENTS_BRIGHT_BLACK,
|
||||
'500': COLOR_PALETTE[PieColor.WHITE],
|
||||
},
|
||||
PieColor.SECONDARY: {
|
||||
'700': '#37523C',
|
||||
'600': '#6c6969',
|
||||
'500': '#6c6969',
|
||||
},
|
||||
}
|
||||
|
||||
COLOR_PALETTE['primary'] = {
|
||||
'700': COLOR_PALETTE['black'],
|
||||
'600': 'ansibrightblack',
|
||||
'500': COLOR_PALETTE['white'],
|
||||
}
|
||||
|
||||
COLOR_PALETTE['secondary'] = {'700': '#37523C', '600': '#6c6969', '500': '#6c6969'}
|
||||
)
|
||||
|
||||
|
||||
SHADE_NAMES = {
|
||||
'500': STYLE_PIE_DARK,
|
||||
'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))
|
||||
]
|
||||
def boldify(color: PieColor) -> str:
|
||||
return f'bold {color}'
|
||||
|
||||
|
||||
# noinspection PyDefaultArgument
|
||||
def get_color(
|
||||
color: str,
|
||||
shade: str,
|
||||
*,
|
||||
palette: Dict[str, Dict[str, str]] = COLOR_PALETTE
|
||||
color: PieColor, shade: str, *, palette=COLOR_PALETTE
|
||||
) -> Optional[str]:
|
||||
if color not in palette:
|
||||
return None
|
||||
|
||||
color_code = palette[color]
|
||||
if isinstance(color_code, dict) and shade in color_code:
|
||||
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.options import Argument, ParserSpec, Qualifiers
|
||||
from httpie.output.ui.palette import GenericColor
|
||||
|
||||
SEPARATORS = '|'.join(map(re.escape, SEPARATOR_GROUP_ALL_ITEMS))
|
||||
|
||||
STYLE_METAVAR = 'yellow'
|
||||
STYLE_SWITCH = 'green'
|
||||
STYLE_PROGRAM_NAME = 'bold green'
|
||||
STYLE_USAGE_OPTIONAL = 'grey46'
|
||||
STYLE_USAGE_REGULAR = 'white'
|
||||
STYLE_USAGE_ERROR = 'red'
|
||||
STYLE_USAGE_MISSING = 'yellow'
|
||||
STYLE_METAVAR = GenericColor.YELLOW
|
||||
STYLE_SWITCH = GenericColor.GREEN
|
||||
STYLE_PROGRAM_NAME = GenericColor.GREEN # .boldify()
|
||||
STYLE_USAGE_OPTIONAL = GenericColor.GREY
|
||||
STYLE_USAGE_REGULAR = GenericColor.WHITE
|
||||
STYLE_USAGE_ERROR = GenericColor.RED
|
||||
STYLE_USAGE_MISSING = GenericColor.YELLOW
|
||||
STYLE_BOLD = 'bold'
|
||||
|
||||
MAX_CHOICE_CHARS = 80
|
||||
|
||||
@ -77,7 +79,7 @@ def to_usage(
|
||||
# shown first
|
||||
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:
|
||||
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
|
||||
# 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 = {
|
||||
'progress.description': 'white',
|
||||
'progress.data.speed': 'green',
|
||||
'progress.percentage': 'aqua',
|
||||
'progress.download': 'aqua',
|
||||
'progress.remaining': 'orange',
|
||||
'bar.complete': 'purple',
|
||||
'bar.finished': 'green',
|
||||
'bar.pulse': 'purple',
|
||||
'option': 'pink'
|
||||
'progress.description': GenericColor.WHITE,
|
||||
'progress.data.speed': GenericColor.GREEN,
|
||||
'progress.percentage': GenericColor.AQUA,
|
||||
'progress.download': GenericColor.AQUA,
|
||||
'progress.remaining': GenericColor.ORANGE,
|
||||
'bar.complete': GenericColor.PURPLE,
|
||||
'bar.finished': GenericColor.GREEN,
|
||||
'bar.pulse': GenericColor.PURPLE,
|
||||
'option': GenericColor.PINK,
|
||||
}
|
||||
|
||||
RICH_THEME_PALETTE = COLOR_PALETTE.copy() # noqa
|
||||
RICH_THEME_PALETTE.update(
|
||||
{
|
||||
custom_style: RICH_THEME_PALETTE[color]
|
||||
for custom_style, color in CUSTOM_STYLES.items()
|
||||
}
|
||||
|
||||
class _GenericColorCaster(dict):
|
||||
"""
|
||||
Translate GenericColor to a regular string on the attribute access
|
||||
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
|
||||
|
||||
def _print_summary(
|
||||
self,
|
||||
is_finished: bool,
|
||||
observed_steps: int,
|
||||
time_spent: float
|
||||
self, is_finished: bool, observed_steps: int, time_spent: float
|
||||
):
|
||||
from rich import filesize
|
||||
|
||||
@ -50,7 +47,9 @@ class BaseDisplay:
|
||||
else:
|
||||
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):
|
||||
@ -65,7 +64,9 @@ class StatusDisplay(BaseDisplay):
|
||||
self, *, total: Optional[float], at: float, description: str
|
||||
) -> None:
|
||||
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.start()
|
||||
@ -75,8 +76,12 @@ class StatusDisplay(BaseDisplay):
|
||||
|
||||
self.observed += steps
|
||||
|
||||
observed_amount, observed_unit = filesize.decimal(self.observed).split()
|
||||
self.status.update(status=f'{self.description} [progress.download]{observed_amount}/? {observed_unit}[/progress.download]')
|
||||
observed_amount, observed_unit = filesize.decimal(
|
||||
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:
|
||||
self.status.stop()
|
||||
@ -85,7 +90,7 @@ class StatusDisplay(BaseDisplay):
|
||||
self._print_summary(
|
||||
is_finished=True,
|
||||
observed_steps=self.observed,
|
||||
time_spent=time_spent
|
||||
time_spent=time_spent,
|
||||
)
|
||||
|
||||
|
||||
@ -114,7 +119,7 @@ class ProgressDisplay(BaseDisplay):
|
||||
TimeRemainingColumn(),
|
||||
TransferSpeedColumn(),
|
||||
console=self.console,
|
||||
transient=True
|
||||
transient=True,
|
||||
)
|
||||
self.progress_bar.start()
|
||||
self.transfer_task = self.progress_bar.add_task(
|
||||
@ -132,5 +137,5 @@ class ProgressDisplay(BaseDisplay):
|
||||
self._print_summary(
|
||||
is_finished=task.finished,
|
||||
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
|
||||
return a *style-less* version of it as a string."""
|
||||
|
||||
with open(os.devnull, "w") as null_stream:
|
||||
fake_console = Console(
|
||||
file=null_stream,
|
||||
record=True
|
||||
)
|
||||
with open(os.devnull, 'w') as null_stream:
|
||||
fake_console = Console(file=null_stream, record=True)
|
||||
fake_console.print(renderable)
|
||||
return fake_console.export_text()
|
||||
|
||||
|
@ -13,7 +13,7 @@ from typing import Any, Dict, List, Optional, Union
|
||||
from requests.auth import AuthBase
|
||||
from requests.cookies import RequestsCookieJar, remove_cookie_by_name
|
||||
|
||||
from .context import Environment, Levels
|
||||
from .context import Environment, LogLevel
|
||||
from .cookies import HTTPieCookiePolicy
|
||||
from .cli.dicts import HTTPHeadersDict
|
||||
from .config import BaseConfigDict, DEFAULT_CONFIG_DIR
|
||||
@ -313,7 +313,7 @@ class Session(BaseConfigDict):
|
||||
|
||||
self.env.log_error(
|
||||
warning,
|
||||
level=Levels.WARNING
|
||||
level=LogLevel.WARNING
|
||||
)
|
||||
|
||||
# We don't want to spam multiple warnings on each usage,
|
||||
|
Loading…
Reference in New Issue
Block a user