summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBatuhan Taskaya <isidentical@gmail.com>2022-05-05 18:17:05 +0300
committerGitHub <noreply@github.com>2022-05-05 08:17:05 -0700
commitf7c1bb269e4ef422e6151d4a20c37051f9e74dc2 (patch)
tree06941dbfb58ade1a1d87e9ab884188a0e252652e
parent0f9fd7685288f2f1b2cea4f0ccb4f5f479e563a4 (diff)
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>
-rw-r--r--httpie/context.py57
-rw-r--r--httpie/core.py4
-rw-r--r--httpie/output/formatters/colors.py138
-rw-r--r--httpie/output/ui/man_pages.py6
-rw-r--r--httpie/output/ui/palette.py168
-rw-r--r--httpie/output/ui/rich_help.py18
-rw-r--r--httpie/output/ui/rich_palette.py78
-rw-r--r--httpie/output/ui/rich_progress.py27
-rw-r--r--httpie/output/ui/rich_utils.py7
-rw-r--r--httpie/sessions.py4
10 files changed, 312 insertions, 195 deletions
diff --git a/httpie/context.py b/httpie/context.py
index da4d1925..086cfa4f 100644
--- a/httpie/context.py
+++ b/httpie/context.py
@@ -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
diff --git a/httpie/core.py b/httpie/core.py
index 71ecfa08..2259c4ad 100644
--- a/httpie/core.py
+++ b/httpie/core.py
@@ -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,
diff --git a/httpie/output/formatters/colors.py b/httpie/output/formatters/colors.py
index 4bafe732..0d9bd12e 100644
--- a/httpie/output/formatters/colors.py
+++ b/httpie/output/formatters/colors.py
@@ -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 [
diff --git a/httpie/output/ui/man_pages.py b/httpie/output/ui/man_pages.py
index 5871e21d..5ab0f2d7 100644
--- a/httpie/output/ui/man_pages.py
+++ b/httpie/output/ui/man_pages.py
@@ -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
)
diff --git a/httpie/output/ui/palette.py b/httpie/output/ui/palette.py
index 87b962e4..2b10a0b7 100644
--- a/httpie/output/ui/palette.py
+++ b/httpie/output/ui/palette.py
@@ -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
+ 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',
+ },
+ }
+)
-# 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()
-}
-
-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]
diff --git a/httpie/output/ui/rich_help.py b/httpie/output/ui/rich_help.py
index 7faa337a..0782b747 100644
--- a/httpie/output/ui/rich_help.py
+++ b/httpie/output/ui/rich_help.py
@@ -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(' ')
diff --git a/httpie/output/ui/rich_palette.py b/httpie/output/ui/rich_palette.py
index c3b6b424..3d140210 100644
--- a/httpie/output/ui/rich_palette.py
+++ b/httpie/output/ui/rich_palette.py
@@ -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
diff --git a/httpie/output/ui/rich_progress.py b/httpie/output/ui/rich_progress.py
index 84109c9f..d2cfd38c 100644
--- a/httpie/output/ui/rich_progress.py
+++ b/httpie/output/ui/rich_progress.py
@@ -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,
)
diff --git a/httpie/output/ui/rich_utils.py b/httpie/output/ui/rich_utils.py
index 82567ba5..4cdd2695 100644
--- a/httpie/output/ui/rich_utils.py
+++ b/httpie/output/ui/rich_utils.py
@@ -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()
diff --git a/httpie/sessions.py b/httpie/sessions.py
index ca40b575..99dcdba9 100644
--- a/httpie/sessions.py
+++ b/httpie/sessions.py
@@ -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,