mirror of
https://github.com/httpie/cli.git
synced 2025-01-06 05:39:08 +01:00
Implement new pie
and pie-light
styles (#1238)
* Implement new `pie` and `pie-light` styles * Change some pallete * Integrate the color palette * some docs * some docs * Rework on code generation * Apply suggestions from code review Co-authored-by: Jakub Roztocil <jakub@roztocil.co> Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
This commit is contained in:
parent
1bd8422fb5
commit
8dc6c0df77
@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
||||
- Added support for _receiving_ multiple HTTP headers lines with the same name. ([#1207](https://github.com/httpie/httpie/issues/1207))
|
||||
- Added support for basic JSON types on `--form`/`--multipart` when using JSON only operators (`:=`/`:=@`). ([#1212](https://github.com/httpie/httpie/issues/1212))
|
||||
- Added support for automatically enabling `--stream` when `Content-Type` is `text/event-stream`. ([#376](https://github.com/httpie/httpie/issues/376))
|
||||
- Added new `pie-dark`/`pie-light` (and `pie`) styles that match with [HTTPie for Web and Desktop](https://httpie.io/product). ([#1237](https://github.com/httpie/httpie/issues/1237))
|
||||
- Broken plugins will no longer crash the whole application. ([#1204](https://github.com/httpie/httpie/issues/1204))
|
||||
- Fixed auto addition of XML declaration to every formatted XML response. ([#1156](https://github.com/httpie/httpie/issues/1156))
|
||||
- Fixed highlighting when `Content-Type` specifies `charset`. ([#1242](https://github.com/httpie/httpie/issues/1242))
|
||||
|
@ -1717,13 +1717,16 @@ Syntax highlighting is applied to HTTP headers and bodies (where it makes sense)
|
||||
You can choose your preferred color scheme via the `--style` option if you don’t like the default one.
|
||||
There are dozens of styles available, here are just a few notable ones:
|
||||
|
||||
| Style | Description |
|
||||
| --------: | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `auto` | Follows your terminal ANSI color styles. This is the default style used by HTTPie |
|
||||
| `default` | Default styles of the underlying Pygments library. Not actually used by default by HTTPie. You can enable it with `--style=default` |
|
||||
| `monokai` | A popular color scheme. Enable with `--style=monokai` |
|
||||
| `fruity` | A bold, colorful scheme. Enable with `--style=fruity` |
|
||||
| … | See `$ http --help` for all the possible `--style` values |
|
||||
| Style | Description |
|
||||
| ---------: | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `auto` | Follows your terminal ANSI color styles. This is the default style used by HTTPie |
|
||||
| `default` | Default styles of the underlying Pygments library. Not actually used by default by HTTPie. You can enable it with `--style=default` |
|
||||
| `pie-dark` | HTTPie’s original brand style. Also used in [HTTPie for Web and Desktop](https://httpie.io/product). |
|
||||
|`pie-light` | Like `pie-dark`, but for terminals with light background colors. |
|
||||
| `pie` | A generic version of `pie-dark` and `pie-light` themes that can work with any terminal background. Its universality requires compromises in terms of legibility, but it’s useful if you frequently switch your terminal between dark and light backgrounds. |
|
||||
| `monokai` | A popular color scheme. Enable with `--style=monokai` |
|
||||
| `fruity` | A bold, colorful scheme. Enable with `--style=fruity` |
|
||||
| … | See `$ http --help` for all the possible `--style` values |
|
||||
|
||||
Use one of these options to control output processing:
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import json
|
||||
from typing import Optional, Type
|
||||
from typing import Optional, Type, Tuple
|
||||
|
||||
import pygments.formatters
|
||||
import pygments.lexer
|
||||
import pygments.lexers
|
||||
import pygments.style
|
||||
@ -15,6 +16,7 @@ from pygments.lexers.text import HttpLexer as PygmentsHttpLexer
|
||||
from pygments.util import ClassNotFound
|
||||
|
||||
from ..lexers.json import EnhancedJsonLexer
|
||||
from ..ui.palette import SHADE_NAMES, get_color
|
||||
from ...compat import is_windows
|
||||
from ...context import Environment
|
||||
from ...plugins import FormatterPlugin
|
||||
@ -23,6 +25,7 @@ from ...plugins import FormatterPlugin
|
||||
AUTO_STYLE = 'auto' # Follows terminal ANSI color styles
|
||||
DEFAULT_STYLE = AUTO_STYLE
|
||||
SOLARIZED_STYLE = 'solarized' # Bundled here
|
||||
|
||||
if is_windows:
|
||||
# Colors on Windows via colorama don't look that
|
||||
# great and fruity seems to give the best result there.
|
||||
@ -66,22 +69,23 @@ class ColorFormatter(FormatterPlugin):
|
||||
if use_auto_style or not has_256_colors:
|
||||
http_lexer = PygmentsHttpLexer()
|
||||
formatter = TerminalFormatter()
|
||||
body_formatter = formatter
|
||||
header_formatter = formatter
|
||||
else:
|
||||
from ..lexers.http import SimplifiedHTTPLexer
|
||||
http_lexer = SimplifiedHTTPLexer()
|
||||
formatter = Terminal256Formatter(
|
||||
style=self.get_style_class(color_scheme)
|
||||
)
|
||||
header_formatter, body_formatter, precise = self.get_formatters(color_scheme)
|
||||
http_lexer = SimplifiedHTTPLexer(precise=precise)
|
||||
|
||||
self.explicit_json = explicit_json # --json
|
||||
self.formatter = formatter
|
||||
self.header_formatter = header_formatter
|
||||
self.body_formatter = body_formatter
|
||||
self.http_lexer = http_lexer
|
||||
|
||||
def format_headers(self, headers: str) -> str:
|
||||
return pygments.highlight(
|
||||
code=headers,
|
||||
lexer=self.http_lexer,
|
||||
formatter=self.formatter,
|
||||
formatter=self.header_formatter,
|
||||
).strip()
|
||||
|
||||
def format_body(self, body: str, mime: str) -> str:
|
||||
@ -90,7 +94,7 @@ class ColorFormatter(FormatterPlugin):
|
||||
body = pygments.highlight(
|
||||
code=body,
|
||||
lexer=lexer,
|
||||
formatter=self.formatter,
|
||||
formatter=self.body_formatter,
|
||||
)
|
||||
return body
|
||||
|
||||
@ -104,6 +108,25 @@ class ColorFormatter(FormatterPlugin):
|
||||
body=body,
|
||||
)
|
||||
|
||||
def get_formatters(self, color_scheme: str) -> Tuple[
|
||||
pygments.formatter.Formatter,
|
||||
pygments.formatter.Formatter,
|
||||
bool
|
||||
]:
|
||||
if color_scheme in PIE_STYLES:
|
||||
header_style, body_style = PIE_STYLES[color_scheme]
|
||||
precise = True
|
||||
else:
|
||||
header_style = self.get_style_class(color_scheme)
|
||||
body_style = header_style
|
||||
precise = False
|
||||
|
||||
return (
|
||||
Terminal256Formatter(style=header_style),
|
||||
Terminal256Formatter(style=body_style),
|
||||
precise
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_style_class(color_scheme: str) -> Type[pygments.style.Style]:
|
||||
try:
|
||||
@ -237,3 +260,117 @@ class Solarized256Style(pygments.style.Style):
|
||||
pygments.token.Token: BASE1,
|
||||
pygments.token.Token.Other: ORANGE,
|
||||
}
|
||||
|
||||
|
||||
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',
|
||||
|
||||
# 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',
|
||||
|
||||
# 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',
|
||||
}
|
||||
|
||||
PIE_BODY_STYLE = {
|
||||
# {}[]:
|
||||
pygments.token.Punctuation: 'grey',
|
||||
|
||||
# Keys
|
||||
pygments.token.Name.Tag: 'pink',
|
||||
|
||||
# Values
|
||||
pygments.token.Literal.String: 'green',
|
||||
pygments.token.Literal.String.Double: 'green',
|
||||
pygments.token.Literal.Number: 'aqua',
|
||||
pygments.token.Keyword: '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',
|
||||
}
|
||||
|
||||
|
||||
def make_style(name, raw_styles, shade):
|
||||
def format_value(value):
|
||||
return ' '.join(
|
||||
get_color(part, shade) or part
|
||||
for part in value.split()
|
||||
)
|
||||
|
||||
bases = (pygments.style.Style,)
|
||||
data = {
|
||||
'styles': {
|
||||
key: format_value(value)
|
||||
for key, value in raw_styles.items()
|
||||
}
|
||||
}
|
||||
return type(name, bases, data)
|
||||
|
||||
|
||||
def make_styles():
|
||||
styles = {}
|
||||
|
||||
for shade, name in SHADE_NAMES.items():
|
||||
styles[name] = [
|
||||
make_style(name, style_map, shade)
|
||||
for style_name, style_map in [
|
||||
(f'Pie{name}HeaderStyle', PIE_HEADER_STYLE),
|
||||
(f'Pie{name}BodyStyle', PIE_BODY_STYLE),
|
||||
]
|
||||
]
|
||||
|
||||
return styles
|
||||
|
||||
|
||||
PIE_STYLES = make_styles()
|
||||
BUNDLED_STYLES |= PIE_STYLES.keys()
|
||||
|
@ -1,6 +1,70 @@
|
||||
import re
|
||||
import pygments
|
||||
|
||||
|
||||
RE_STATUS_LINE = re.compile(r'(\d{3})( +)(.+)')
|
||||
|
||||
STATUS_TYPES = {
|
||||
'1': pygments.token.Number.HTTP.INFO,
|
||||
'2': pygments.token.Number.HTTP.OK,
|
||||
'3': pygments.token.Number.HTTP.REDIRECT,
|
||||
'4': pygments.token.Number.HTTP.CLIENT_ERR,
|
||||
'5': pygments.token.Number.HTTP.SERVER_ERR,
|
||||
}
|
||||
|
||||
RESPONSE_TYPES = {
|
||||
'GET': pygments.token.Name.Function.HTTP.GET,
|
||||
'HEAD': pygments.token.Name.Function.HTTP.HEAD,
|
||||
'POST': pygments.token.Name.Function.HTTP.POST,
|
||||
'PUT': pygments.token.Name.Function.HTTP.PUT,
|
||||
'PATCH': pygments.token.Name.Function.HTTP.PATCH,
|
||||
'DELETE': pygments.token.Name.Function.HTTP.DELETE,
|
||||
}
|
||||
|
||||
|
||||
def precise(lexer, precise_token, parent_token):
|
||||
# Due to a pygments bug*, custom tokens will look bad
|
||||
# on outside styles. Until it is fixed on upstream, we'll
|
||||
# convey whether the client is using pie style or not
|
||||
# through precise option and return more precise tokens
|
||||
# depending on it's value.
|
||||
#
|
||||
# [0]: https://github.com/pygments/pygments/issues/1986
|
||||
if precise_token is None or not lexer.options.get("precise"):
|
||||
return parent_token
|
||||
else:
|
||||
return precise_token
|
||||
|
||||
|
||||
def http_response_type(lexer, match, ctx):
|
||||
status_match = RE_STATUS_LINE.match(match.group())
|
||||
if status_match is None:
|
||||
return None
|
||||
|
||||
status_code, text, reason = status_match.groups()
|
||||
status_type = precise(
|
||||
lexer,
|
||||
STATUS_TYPES.get(status_code[0]),
|
||||
pygments.token.Number
|
||||
)
|
||||
|
||||
groups = pygments.lexer.bygroups(
|
||||
status_type,
|
||||
pygments.token.Text,
|
||||
status_type
|
||||
)
|
||||
yield from groups(lexer, status_match, ctx)
|
||||
|
||||
|
||||
def request_method(lexer, match, ctx):
|
||||
response_type = precise(
|
||||
lexer,
|
||||
RESPONSE_TYPES.get(match.group()),
|
||||
pygments.token.Name.Function
|
||||
)
|
||||
yield match.start(), response_type, match.group()
|
||||
|
||||
|
||||
class SimplifiedHTTPLexer(pygments.lexer.RegexLexer):
|
||||
"""Simplified HTTP lexer for Pygments.
|
||||
|
||||
@ -18,7 +82,7 @@ class SimplifiedHTTPLexer(pygments.lexer.RegexLexer):
|
||||
# Request-Line
|
||||
(r'([A-Z]+)( +)([^ ]+)( +)(HTTP)(/)(\d+\.\d+)',
|
||||
pygments.lexer.bygroups(
|
||||
pygments.token.Name.Function,
|
||||
request_method,
|
||||
pygments.token.Text,
|
||||
pygments.token.Name.Namespace,
|
||||
pygments.token.Text,
|
||||
@ -27,15 +91,13 @@ class SimplifiedHTTPLexer(pygments.lexer.RegexLexer):
|
||||
pygments.token.Number
|
||||
)),
|
||||
# Response Status-Line
|
||||
(r'(HTTP)(/)(\d+\.\d+)( +)(\d{3})( +)(.+)',
|
||||
(r'(HTTP)(/)(\d+\.\d+)( +)(.+)',
|
||||
pygments.lexer.bygroups(
|
||||
pygments.token.Keyword.Reserved, # 'HTTP'
|
||||
pygments.token.Operator, # '/'
|
||||
pygments.token.Number, # Version
|
||||
pygments.token.Text,
|
||||
pygments.token.Number, # Status code
|
||||
pygments.token.Text,
|
||||
pygments.token.Name.Exception, # Reason
|
||||
http_response_type, # Status code and Reason
|
||||
)),
|
||||
# Header
|
||||
(r'(.*?)( *)(:)( *)(.+)', pygments.lexer.bygroups(
|
||||
|
0
httpie/output/ui/__init__.py
Normal file
0
httpie/output/ui/__init__.py
Normal file
161
httpie/output/ui/palette.py
Normal file
161
httpie/output/ui/palette.py
Normal file
@ -0,0 +1,161 @@
|
||||
# Copy the brand palette
|
||||
from typing import Optional
|
||||
|
||||
COLOR_PALETTE = {
|
||||
'transparent': 'transparent',
|
||||
'current': 'currentColor',
|
||||
'white': '#F5F5F0',
|
||||
'black': '#1C1818',
|
||||
'grey': {
|
||||
'50': '#F5F5F0',
|
||||
'100': '#EDEDEB',
|
||||
'200': '#D1D1CF',
|
||||
'300': '#B5B5B2',
|
||||
'400': '#999999',
|
||||
'500': '#7D7D7D',
|
||||
'600': '#666663',
|
||||
'700': '#4F4D4D',
|
||||
'800': '#363636',
|
||||
'900': '#1C1818',
|
||||
'DEFAULT': '#7D7D7D',
|
||||
},
|
||||
'aqua': {
|
||||
'50': '#E8F0F5',
|
||||
'100': '#D6E3ED',
|
||||
'200': '#C4D9E5',
|
||||
'300': '#B0CCDE',
|
||||
'400': '#9EBFD6',
|
||||
'500': '#8CB4CD',
|
||||
'600': '#7A9EB5',
|
||||
'700': '#698799',
|
||||
'800': '#597082',
|
||||
'900': '#455966',
|
||||
'DEFAULT': '#8CB4CD',
|
||||
},
|
||||
'purple': {
|
||||
'50': '#F0E0FC',
|
||||
'100': '#E3C7FA',
|
||||
'200': '#D9ADF7',
|
||||
'300': '#CC96F5',
|
||||
'400': '#BF7DF2',
|
||||
'500': '#B464F0',
|
||||
'600': '#9E54D6',
|
||||
'700': '#8745BA',
|
||||
'800': '#70389E',
|
||||
'900': '#5C2982',
|
||||
'DEFAULT': '#B464F0',
|
||||
},
|
||||
'orange': {
|
||||
'50': '#FFEDDB',
|
||||
'100': '#FFDEBF',
|
||||
'200': '#FFCFA3',
|
||||
'300': '#FFBF87',
|
||||
'400': '#FFB06B',
|
||||
'500': '#FFA24E',
|
||||
'600': '#F2913D',
|
||||
'700': '#E3822B',
|
||||
'800': '#D6701C',
|
||||
'900': '#C75E0A',
|
||||
'DEFAULT': '#FFA24E',
|
||||
},
|
||||
'red': {
|
||||
'50': '#FFE0DE',
|
||||
'100': '#FFC7C4',
|
||||
'200': '#FFB0AB',
|
||||
'300': '#FF968F',
|
||||
'400': '#FF8075',
|
||||
'500': '#FF665B',
|
||||
'600': '#E34F45',
|
||||
'700': '#C7382E',
|
||||
'800': '#AD2117',
|
||||
'900': '#910A00',
|
||||
'DEFAULT': '#FF665B',
|
||||
},
|
||||
'blue': {
|
||||
'50': '#DBE3FA',
|
||||
'100': '#BFCFF5',
|
||||
'200': '#A1B8F2',
|
||||
'300': '#85A3ED',
|
||||
'400': '#698FEB',
|
||||
'500': '#4B78E6',
|
||||
'600': '#426BD1',
|
||||
'700': '#3B5EBA',
|
||||
'800': '#3354A6',
|
||||
'900': '#2B478F',
|
||||
'DEFAULT': '#4B78E6',
|
||||
},
|
||||
'pink': {
|
||||
'50': '#FFEBFF',
|
||||
'100': '#FCDBFC',
|
||||
'200': '#FCCCFC',
|
||||
'300': '#FCBAFC',
|
||||
'400': '#FAABFA',
|
||||
'500': '#FA9BFA',
|
||||
'600': '#DE85DE',
|
||||
'700': '#C26EC2',
|
||||
'800': '#A854A6',
|
||||
'900': '#8C3D8A',
|
||||
'DEFAULT': '#FA9BFA',
|
||||
},
|
||||
'green': {
|
||||
'50': '#E3F7E8',
|
||||
'100': '#CCF2D6',
|
||||
'200': '#B5EDC4',
|
||||
'300': '#A1E8B0',
|
||||
'400': '#8AE09E',
|
||||
'500': '#73DC8C',
|
||||
'600': '#63C27A',
|
||||
'700': '#52AB66',
|
||||
'800': '#429154',
|
||||
'900': '#307842',
|
||||
'DEFAULT': '#73DC8C',
|
||||
},
|
||||
'yellow': {
|
||||
'50': '#F7F7DB',
|
||||
'100': '#F2F2BF',
|
||||
'200': '#EDEDA6',
|
||||
'300': '#E5E88A',
|
||||
'400': '#E0E36E',
|
||||
'500': '#DBDE52',
|
||||
'600': '#CCCC3D',
|
||||
'700': '#BABA29',
|
||||
'800': '#ABA614',
|
||||
'900': '#999400',
|
||||
'DEFAULT': '#DBDE52',
|
||||
},
|
||||
}
|
||||
|
||||
# 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': 'pie-dark',
|
||||
'600': 'pie',
|
||||
'700': 'pie-light'
|
||||
}
|
||||
|
||||
SHADES = [
|
||||
'50',
|
||||
*map(str, range(100, 1000, 100))
|
||||
]
|
||||
|
||||
|
||||
def get_color(color: str, shade: str) -> Optional[str]:
|
||||
if color not in COLOR_PALETTE:
|
||||
return None
|
||||
|
||||
color_code = COLOR_PALETTE[color]
|
||||
if isinstance(color_code, dict) and shade in color_code:
|
||||
return color_code[shade]
|
||||
else:
|
||||
return color_code
|
Loading…
Reference in New Issue
Block a user