mirror of
https://github.com/httpie/cli.git
synced 2025-08-14 02:18:47 +02:00
Compare commits
9 Commits
isidentica
...
fix-initia
Author | SHA1 | Date | |
---|---|---|---|
9d2e2afede | |||
418b12bbd6 | |||
ecff53f2d5 | |||
41da87f7c8 | |||
4f172a61b4 | |||
542a2d35de | |||
d9e1dc08c9 | |||
3b734fb0bc | |||
8abe47969e |
@ -59,8 +59,10 @@ $ git checkout -b my_topical_branch
|
||||
|
||||
#### Setup
|
||||
|
||||
The [Makefile](https://github.com/httpie/httpie/blob/master/Makefile) contains a bunch of tasks to get you started. Just run
|
||||
the following command, which:
|
||||
The [Makefile](https://github.com/httpie/httpie/blob/master/Makefile) contains a bunch of tasks to get you started.
|
||||
You can run `$ make` to see all the available tasks.
|
||||
|
||||
To get started, run the command below, which:
|
||||
|
||||
- Creates an isolated Python virtual environment inside `./venv`
|
||||
(via the standard library [venv](https://docs.python.org/3/library/venv.html) tool);
|
||||
@ -70,7 +72,7 @@ the following command, which:
|
||||
- and runs tests (It is the same as running `make install test`).
|
||||
|
||||
```bash
|
||||
$ make
|
||||
$ make all
|
||||
```
|
||||
|
||||
#### Python virtual environment
|
||||
|
22
Makefile
22
Makefile
@ -22,6 +22,26 @@ VENV_PYTHON=$(VENV_BIN)/python
|
||||
export PATH := $(VENV_BIN):$(PATH)
|
||||
|
||||
|
||||
|
||||
default: list-tasks
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Default task to get a list of tasks when `make' is run without args.
|
||||
# <https://stackoverflow.com/questions/4219255>
|
||||
###############################################################################
|
||||
|
||||
list-tasks:
|
||||
@echo Available tasks:
|
||||
@echo ----------------
|
||||
@$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$'
|
||||
@echo
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Installation
|
||||
###############################################################################
|
||||
|
||||
all: uninstall-httpie install test
|
||||
|
||||
|
||||
@ -33,7 +53,7 @@ install-reqs:
|
||||
$(VENV_PIP) install --upgrade pip wheel build
|
||||
|
||||
@echo $(H1)Installing dev requirements$(H1END)
|
||||
$(VENV_PIP) install --upgrade --editable '.[dev]'
|
||||
$(VENV_PIP) install --upgrade '.[dev]' '.[test]'
|
||||
|
||||
@echo $(H1)Installing HTTPie$(H1END)
|
||||
$(VENV_PIP) install --upgrade --editable .
|
||||
|
@ -217,7 +217,7 @@ $ pacman -Syu
|
||||
|
||||
#### Single binary executables
|
||||
|
||||
Have a standalone HTTPie executable when you don't want to go through the full installation process
|
||||
Get the standalone HTTPie Linux executables when you don't want to go through the full installation process
|
||||
|
||||
```bash
|
||||
# Install httpie
|
||||
@ -2425,7 +2425,7 @@ You can check whether a new update is available for your system by running `http
|
||||
|
||||
In the past `pip` was used to install/uninstall plugins, but on some environments (e.g., brew installed
|
||||
packages) it wasn’t working properly. The new interface is a very simple overlay on top of `pip` to allow
|
||||
plugin installations on every installation method.
|
||||
plugin installations on every installation method.
|
||||
|
||||
By default, the plugins (and their missing dependencies) will be stored under the configuration directory,
|
||||
but this can be modified through `plugins_dir` variable on the config.
|
||||
|
@ -17,13 +17,13 @@ docs-structure:
|
||||
Windows:
|
||||
- chocolatey
|
||||
Linux:
|
||||
- snap-linux
|
||||
- brew-linux
|
||||
- apt
|
||||
- dnf
|
||||
- yum
|
||||
- pacman
|
||||
- single-binary
|
||||
- snap-linux
|
||||
- brew-linux
|
||||
- pacman
|
||||
FreeBSD:
|
||||
- pkg
|
||||
|
||||
@ -186,11 +186,12 @@ tools:
|
||||
single-binary:
|
||||
title: Single binary executables
|
||||
name: Single binary executables
|
||||
note: Have a standalone HTTPie executable when you don't want to go through the full installation process.
|
||||
note: Get the standalone HTTPie Linux executables when you don't want to go through the full installation process.
|
||||
links:
|
||||
commands:
|
||||
install:
|
||||
- https --download packages.httpie.io/binaries/linux/http-latest -o http
|
||||
- chmod +x ./http
|
||||
- ln -ls ./http ./https
|
||||
- chmod +x ./http ./https
|
||||
upgrade:
|
||||
- https --download packages.httpie.io/binaries/linux/http-latest -o http
|
||||
|
@ -6,6 +6,9 @@ from typing import Iterator, Tuple
|
||||
BUILD_DIR = Path(__file__).parent
|
||||
HTTPIE_DIR = BUILD_DIR.parent.parent.parent
|
||||
|
||||
EXTRAS_DIR = HTTPIE_DIR / 'extras'
|
||||
MAN_PAGES_DIR = EXTRAS_DIR / 'man'
|
||||
|
||||
SCRIPT_DIR = BUILD_DIR / Path('scripts')
|
||||
HOOKS_DIR = SCRIPT_DIR / 'hooks'
|
||||
|
||||
@ -50,6 +53,11 @@ def build_packages(http_binary: Path, httpie_binary: Path) -> None:
|
||||
(http_binary, '/usr/bin/https'),
|
||||
(httpie_binary, '/usr/bin/httpie'),
|
||||
]
|
||||
files.extend(
|
||||
(man_page, f'/usr/share/man/man1/{man_page.name}')
|
||||
for man_page in MAN_PAGES_DIR.glob('*.1')
|
||||
)
|
||||
|
||||
# A list of additional dependencies
|
||||
deps = [
|
||||
'python3 >= 3.7',
|
||||
|
@ -132,10 +132,3 @@ class RequestType(enum.Enum):
|
||||
FORM = enum.auto()
|
||||
MULTIPART = enum.auto()
|
||||
JSON = enum.auto()
|
||||
|
||||
|
||||
EMPTY_STRING = ''
|
||||
OPEN_BRACKET = '['
|
||||
CLOSE_BRACKET = ']'
|
||||
BACKSLASH = '\\'
|
||||
HIGHLIGHTER = '^'
|
||||
|
@ -9,8 +9,14 @@ from typing import (
|
||||
Type,
|
||||
Union,
|
||||
)
|
||||
from httpie.cli.dicts import NestedJSONArray
|
||||
from httpie.cli.constants import EMPTY_STRING, OPEN_BRACKET, CLOSE_BRACKET, BACKSLASH, HIGHLIGHTER
|
||||
from .dicts import NestedJSONArray
|
||||
|
||||
|
||||
EMPTY_STRING = ''
|
||||
HIGHLIGHTER = '^'
|
||||
OPEN_BRACKET = '['
|
||||
CLOSE_BRACKET = ']'
|
||||
BACKSLASH = '\\'
|
||||
|
||||
|
||||
class HTTPieSyntaxError(ValueError):
|
||||
@ -31,7 +37,7 @@ class HTTPieSyntaxError(ValueError):
|
||||
if self.token is not None:
|
||||
lines.append(self.source)
|
||||
lines.append(
|
||||
' ' * (self.token.start)
|
||||
' ' * self.token.start
|
||||
+ HIGHLIGHTER * (self.token.end - self.token.start)
|
||||
)
|
||||
return '\n'.join(lines)
|
||||
@ -51,9 +57,15 @@ class TokenKind(Enum):
|
||||
return 'a ' + self.name.lower()
|
||||
|
||||
|
||||
OPERATORS = {OPEN_BRACKET: TokenKind.LEFT_BRACKET, CLOSE_BRACKET: TokenKind.RIGHT_BRACKET}
|
||||
OPERATORS = {
|
||||
OPEN_BRACKET: TokenKind.LEFT_BRACKET,
|
||||
CLOSE_BRACKET: TokenKind.RIGHT_BRACKET,
|
||||
}
|
||||
SPECIAL_CHARS = OPERATORS.keys() | {BACKSLASH}
|
||||
LITERAL_TOKENS = [TokenKind.TEXT, TokenKind.NUMBER]
|
||||
LITERAL_TOKENS = [
|
||||
TokenKind.TEXT,
|
||||
TokenKind.NUMBER,
|
||||
]
|
||||
|
||||
|
||||
class Token(NamedTuple):
|
||||
|
@ -13,7 +13,8 @@ import urllib3
|
||||
from . import __version__
|
||||
from .adapters import HTTPieHTTPAdapter
|
||||
from .context import Environment
|
||||
from .cli.constants import EMPTY_STRING, HTTP_OPTIONS
|
||||
from .cli.constants import HTTP_OPTIONS
|
||||
from .cli.nested_json import EMPTY_STRING
|
||||
from .cli.dicts import HTTPHeadersDict, NestedJSONArray
|
||||
from .encoding import UTF8
|
||||
from .models import RequestsMessage
|
||||
|
@ -18,7 +18,7 @@ from .config import DEFAULT_CONFIG_DIR, Config, ConfigFileError
|
||||
from .encoding import UTF8
|
||||
|
||||
from .utils import repr_dict
|
||||
from .output.ui.palette import RichColor
|
||||
from .output.ui.palette import GenericColor
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from rich.console import Console
|
||||
@ -31,9 +31,9 @@ class LogLevel(str, Enum):
|
||||
|
||||
|
||||
LOG_LEVEL_COLORS = {
|
||||
LogLevel.INFO: RichColor.PINK,
|
||||
LogLevel.WARNING: RichColor.ORANGE,
|
||||
LogLevel.ERROR: RichColor.RED,
|
||||
LogLevel.INFO: GenericColor.PINK,
|
||||
LogLevel.WARNING: GenericColor.ORANGE,
|
||||
LogLevel.ERROR: GenericColor.RED,
|
||||
}
|
||||
|
||||
LOG_LEVEL_DISPLAY_THRESHOLDS = {
|
||||
@ -191,10 +191,10 @@ class Environment:
|
||||
force_terminal: bool
|
||||
) -> 'Console':
|
||||
from rich.console import Console
|
||||
from httpie.output.ui.palette import make_rich_theme_from_style
|
||||
from httpie.output.ui.rich_palette import _make_rich_color_theme
|
||||
|
||||
style = getattr(self.args, 'style', None)
|
||||
theme = make_rich_theme_from_style(style)
|
||||
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(
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""
|
||||
This module provides an interface to spawn a detached task to be
|
||||
runned with httpie.internal.daemon_runner on a separate process. It is
|
||||
run with httpie.internal.daemon_runner on a separate process. It is
|
||||
based on DVC's daemon system.
|
||||
https://github.com/iterative/dvc/blob/main/dvc/daemon.py
|
||||
"""
|
||||
|
@ -1,12 +1,18 @@
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from httpie.output.ui.palette.utils import ColorString
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum, auto
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
PYGMENTS_BRIGHT_BLACK = 'ansibrightblack'
|
||||
|
||||
AUTO_STYLE = 'auto' # Follows terminal ANSI color styles
|
||||
|
||||
|
||||
class Styles(Enum):
|
||||
PIE = auto()
|
||||
ANSI = auto()
|
||||
|
||||
|
||||
class PieStyle(str, Enum):
|
||||
UNIVERSAL = 'pie'
|
||||
DARK = 'pie-dark'
|
||||
@ -18,11 +24,34 @@ PIE_STYLE_TO_SHADE = {
|
||||
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
|
||||
"""
|
||||
if isinstance(other, str):
|
||||
# In case of PieColor.BLUE | SOMETHING
|
||||
# we just create a new string.
|
||||
return ColorString(self + ' ' + other)
|
||||
elif isinstance(other, GenericColor):
|
||||
# If we see a GenericColor, then we'll wrap it
|
||||
# in with the desired property in a different class.
|
||||
return _StyledGenericColor(other, styles=self.split())
|
||||
elif isinstance(other, _StyledGenericColor):
|
||||
# And if it is already wrapped, we'll just extend the
|
||||
# list of properties.
|
||||
other.styles.extend(self.split())
|
||||
return other
|
||||
else:
|
||||
return NotImplemented
|
||||
|
||||
|
||||
class PieColor(ColorString, Enum):
|
||||
"""Styles that are available only in Pie themes."""
|
||||
|
||||
@ -42,6 +71,42 @@ class PieColor(ColorString, Enum):
|
||||
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
|
||||
|
||||
|
||||
@dataclass
|
||||
class _StyledGenericColor:
|
||||
color: 'GenericColor'
|
||||
styles: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
# noinspection PyDictCreation
|
||||
COLOR_PALETTE = {
|
||||
# Copy the brand palette
|
||||
@ -187,6 +252,10 @@ COLOR_PALETTE.update(
|
||||
)
|
||||
|
||||
|
||||
def boldify(color: PieColor) -> str:
|
||||
return f'bold {color}'
|
||||
|
||||
|
||||
# noinspection PyDefaultArgument
|
||||
def get_color(
|
||||
color: PieColor, shade: str, *, palette=COLOR_PALETTE
|
@ -1,3 +0,0 @@
|
||||
from httpie.output.ui.palette.pie import AUTO_STYLE, SHADE_TO_PIE_STYLE, PieStyle, PieColor, ColorString, get_color # noqa
|
||||
from httpie.output.ui.palette.rich import RichColor, make_rich_theme_from_style
|
||||
from httpie.output.ui.palette.utils import ColorString
|
@ -1,18 +0,0 @@
|
||||
from httpie.output.ui.palette.rich import RichColor
|
||||
from httpie.output.ui.palette.utils import ColorString
|
||||
|
||||
RICH_BOLD = ColorString('bold')
|
||||
|
||||
# Rich-specific color code declarations
|
||||
# <https://github.com/Textualize/rich/blob/fcd684dd3a482977cab620e71ccaebb94bf13ac9/rich/default_styles.py>
|
||||
RICH_CUSTOM_STYLES = {
|
||||
'progress.description': RICH_BOLD | RichColor.WHITE,
|
||||
'progress.data.speed': RICH_BOLD | RichColor.GREEN,
|
||||
'progress.percentage': RICH_BOLD | RichColor.AQUA,
|
||||
'progress.download': RICH_BOLD | RichColor.AQUA,
|
||||
'progress.remaining': RICH_BOLD | RichColor.ORANGE,
|
||||
'bar.complete': RICH_BOLD | RichColor.PURPLE,
|
||||
'bar.finished': RICH_BOLD | RichColor.GREEN,
|
||||
'bar.pulse': RICH_BOLD | RichColor.PURPLE,
|
||||
'option': RICH_BOLD | RichColor.PINK,
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
from collections import ChainMap
|
||||
from typing import TYPE_CHECKING, Any, Optional, List
|
||||
from enum import Enum, auto
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from rich.theme import Theme
|
||||
|
||||
from httpie.output.ui.palette.pie import (
|
||||
PIE_STYLE_TO_SHADE,
|
||||
PieStyle,
|
||||
PieColor,
|
||||
get_color,
|
||||
) # noqa
|
||||
|
||||
|
||||
class RichTheme(Enum):
|
||||
"""Represents the color theme to use within rich."""
|
||||
PIE = auto()
|
||||
ANSI = auto()
|
||||
|
||||
@classmethod
|
||||
def from_style_name(cls, style_name: str) -> 'RichTheme':
|
||||
try:
|
||||
PieStyle(style_name)
|
||||
except ValueError:
|
||||
return RichTheme.ANSI
|
||||
else:
|
||||
return RichTheme.PIE
|
||||
|
||||
|
||||
class RichColor(Enum):
|
||||
"""Generic colors that are safe to use everywhere within rich."""
|
||||
|
||||
# <https://rich.readthedocs.io/en/stable/appendix/colors.html>
|
||||
|
||||
WHITE = {RichTheme.PIE: PieColor.WHITE, RichTheme.ANSI: 'white'}
|
||||
BLACK = {RichTheme.PIE: PieColor.BLACK, RichTheme.ANSI: 'black'}
|
||||
GREEN = {RichTheme.PIE: PieColor.GREEN, RichTheme.ANSI: 'green'}
|
||||
ORANGE = {RichTheme.PIE: PieColor.ORANGE, RichTheme.ANSI: 'yellow'}
|
||||
YELLOW = {RichTheme.PIE: PieColor.YELLOW, RichTheme.ANSI: 'bright_yellow'}
|
||||
BLUE = {RichTheme.PIE: PieColor.BLUE, RichTheme.ANSI: 'blue'}
|
||||
PINK = {RichTheme.PIE: PieColor.PINK, RichTheme.ANSI: 'bright_magenta'}
|
||||
PURPLE = {RichTheme.PIE: PieColor.PURPLE, RichTheme.ANSI: 'magenta'}
|
||||
RED = {RichTheme.PIE: PieColor.RED, RichTheme.ANSI: 'red'}
|
||||
AQUA = {RichTheme.PIE: PieColor.AQUA, RichTheme.ANSI: 'cyan'}
|
||||
GREY = {RichTheme.PIE: PieColor.GREY, RichTheme.ANSI: 'bright_black'}
|
||||
|
||||
def apply_theme(
|
||||
self, style: RichTheme, *, style_name: Optional[str] = None
|
||||
) -> str:
|
||||
"""Apply the given style to a particular value."""
|
||||
exposed_color = self.value[style]
|
||||
if style is RichTheme.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
|
||||
|
||||
|
||||
@dataclass
|
||||
class _StyledRichColor:
|
||||
color: RichColor
|
||||
styles: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
class _RichColorCaster(dict):
|
||||
"""
|
||||
Translate RichColor to a regular string on the attribute access
|
||||
phase.
|
||||
"""
|
||||
|
||||
def _translate(self, key: Any) -> Any:
|
||||
if isinstance(key, RichColor):
|
||||
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_theme_from_style(style_name: Optional[str] = None) -> 'Theme':
|
||||
from rich.style import Style
|
||||
from rich.theme import Theme
|
||||
from httpie.output.ui.palette.custom_styles import RICH_CUSTOM_STYLES
|
||||
|
||||
rich_theme = RichTheme.from_style_name(style_name)
|
||||
|
||||
theme = Theme()
|
||||
for color, color_set in ChainMap(
|
||||
RichColor.__members__, RICH_CUSTOM_STYLES
|
||||
).items():
|
||||
if isinstance(color_set, _StyledRichColor):
|
||||
properties = dict.fromkeys(color_set.styles, True)
|
||||
color_set = color_set.color
|
||||
else:
|
||||
properties = {}
|
||||
|
||||
theme.styles[color.lower()] = Style(
|
||||
color=color_set.apply_theme(rich_theme, style_name=style_name),
|
||||
**properties,
|
||||
)
|
||||
|
||||
# E.g translate RichColor.BLUE into blue on key access
|
||||
theme.styles = _RichColorCaster(theme.styles)
|
||||
return theme
|
@ -1,23 +0,0 @@
|
||||
class ColorString(str):
|
||||
def __or__(self, other: str) -> 'ColorString':
|
||||
"""Combine a style with a property.
|
||||
|
||||
E.g: PieColor.BLUE | BOLD | ITALIC
|
||||
"""
|
||||
from httpie.output.ui.palette.rich import RichColor, _StyledRichColor
|
||||
|
||||
if isinstance(other, str):
|
||||
# In case of PieColor.BLUE | SOMETHING
|
||||
# we just create a new string.
|
||||
return ColorString(self + ' ' + other)
|
||||
elif isinstance(other, RichColor):
|
||||
# If we see a GenericColor, then we'll wrap it
|
||||
# in with the desired property in a different class.
|
||||
return _StyledRichColor(other, styles=self.split())
|
||||
elif isinstance(other, _StyledRichColor):
|
||||
# And if it is already wrapped, we'll just extend the
|
||||
# list of properties.
|
||||
other.styles.extend(self.split())
|
||||
return other
|
||||
else:
|
||||
return NotImplemented
|
@ -10,17 +10,17 @@ 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 RichColor
|
||||
from httpie.output.ui.palette import GenericColor
|
||||
|
||||
SEPARATORS = '|'.join(map(re.escape, SEPARATOR_GROUP_ALL_ITEMS))
|
||||
|
||||
STYLE_METAVAR = RichColor.YELLOW
|
||||
STYLE_SWITCH = RichColor.GREEN
|
||||
STYLE_PROGRAM_NAME = RichColor.GREEN # .boldify()
|
||||
STYLE_USAGE_OPTIONAL = RichColor.GREY
|
||||
STYLE_USAGE_REGULAR = RichColor.WHITE
|
||||
STYLE_USAGE_ERROR = RichColor.RED
|
||||
STYLE_USAGE_MISSING = RichColor.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
|
||||
|
73
httpie/output/ui/rich_palette.py
Normal file
73
httpie/output/ui/rich_palette.py
Normal file
@ -0,0 +1,73 @@
|
||||
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, ColorString, _StyledGenericColor # noqa
|
||||
|
||||
RICH_BOLD = ColorString('bold')
|
||||
|
||||
# Rich-specific color code declarations
|
||||
# <https://github.com/Textualize/rich/blob/fcd684dd3a482977cab620e71ccaebb94bf13ac9/rich/default_styles.py>
|
||||
CUSTOM_STYLES = {
|
||||
'progress.description': RICH_BOLD | GenericColor.WHITE,
|
||||
'progress.data.speed': RICH_BOLD | GenericColor.GREEN,
|
||||
'progress.percentage': RICH_BOLD | GenericColor.AQUA,
|
||||
'progress.download': RICH_BOLD | GenericColor.AQUA,
|
||||
'progress.remaining': RICH_BOLD | GenericColor.ORANGE,
|
||||
'bar.complete': RICH_BOLD | GenericColor.PURPLE,
|
||||
'bar.finished': RICH_BOLD | GenericColor.GREEN,
|
||||
'bar.pulse': RICH_BOLD | GenericColor.PURPLE,
|
||||
'option': RICH_BOLD | GenericColor.PINK,
|
||||
}
|
||||
|
||||
|
||||
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] = None) -> '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():
|
||||
if isinstance(color_set, _StyledGenericColor):
|
||||
properties = dict.fromkeys(color_set.styles, True)
|
||||
color_set = color_set.color
|
||||
else:
|
||||
properties = {}
|
||||
|
||||
theme.styles[color.lower()] = Style(
|
||||
color=color_set.apply_style(style, style_name=style_name),
|
||||
**properties,
|
||||
)
|
||||
|
||||
# E.g translate GenericColor.BLUE into blue on key access
|
||||
theme.styles = _GenericColorCaster(theme.styles)
|
||||
return theme
|
@ -6,7 +6,7 @@ from contextlib import contextmanager
|
||||
from rich.console import Console, RenderableType
|
||||
from rich.highlighter import Highlighter
|
||||
|
||||
from httpie.output.ui.palette import make_rich_theme_from_style
|
||||
from httpie.output.ui.rich_palette import _make_rich_color_theme
|
||||
|
||||
|
||||
def render_as_string(renderable: RenderableType) -> str:
|
||||
@ -14,7 +14,7 @@ def render_as_string(renderable: RenderableType) -> str:
|
||||
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, theme=make_rich_theme_from_style())
|
||||
fake_console = Console(file=null_stream, record=True, theme=_make_rich_color_theme())
|
||||
fake_console.print(renderable)
|
||||
return fake_console.export_text()
|
||||
|
||||
|
@ -277,7 +277,7 @@ def open_with_lockfile(file: Path, *args, **kwargs) -> Generator[IO[Any], None,
|
||||
target_file = Path(tempfile.gettempdir()) / file_id
|
||||
|
||||
# Have an atomic-like touch here, so we'll tighten the possibility of
|
||||
# a race occuring between multiple processes accessing the same file.
|
||||
# a race occurring between multiple processes accessing the same file.
|
||||
try:
|
||||
target_file.touch(exist_ok=False)
|
||||
except FileExistsError as exc:
|
||||
|
@ -1,9 +1,12 @@
|
||||
"""Miscellaneous regression tests"""
|
||||
import pytest
|
||||
|
||||
from httpie.cli.argtypes import KeyValueArgType
|
||||
from httpie.cli.constants import SEPARATOR_HEADER, SEPARATOR_QUERY_PARAM, SEPARATOR_DATA_STRING
|
||||
from httpie.cli.requestitems import RequestItems
|
||||
from httpie.compat import is_windows
|
||||
from .utils.matching import assert_output_matches, Expect
|
||||
from .utils import HTTP_OK, MockEnvironment, http
|
||||
from .utils.matching import assert_output_matches, Expect
|
||||
|
||||
|
||||
def test_Host_header_overwrite(httpbin):
|
||||
@ -47,3 +50,21 @@ def test_verbose_redirected_stdout_separator(httpbin):
|
||||
Expect.RESPONSE_HEADERS,
|
||||
Expect.BODY,
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(['separator', 'target'], [
|
||||
(SEPARATOR_HEADER, 'headers'),
|
||||
(SEPARATOR_QUERY_PARAM, 'params'),
|
||||
(SEPARATOR_DATA_STRING, 'data'),
|
||||
])
|
||||
def test_initial_backslash_number(separator, target):
|
||||
"""
|
||||
<https://github.com/httpie/httpie/issues/1408>
|
||||
"""
|
||||
back_digit = r'\0'
|
||||
raw_arg = back_digit + separator + back_digit
|
||||
expected_parsed_data = {back_digit: back_digit}
|
||||
parsed_arg = KeyValueArgType(separator)(raw_arg)
|
||||
items = RequestItems.from_args([parsed_arg])
|
||||
parsed_data = getattr(items, target)
|
||||
assert parsed_data == expected_parsed_data
|
||||
|
Reference in New Issue
Block a user