Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new pie and pie-light styles #1238

Merged
merged 7 commits into from
Dec 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))

## [2.6.0](https://github.com/httpie/httpie/compare/2.5.0...2.6.0) (2021-10-14)
Expand Down
17 changes: 10 additions & 7 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
153 changes: 145 additions & 8 deletions httpie/output/formatters/colors.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -15,6 +16,7 @@
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
Expand All @@ -23,6 +25,7 @@
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.
Expand Down Expand Up @@ -66,22 +69,23 @@ def __init__(
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:
Expand All @@ -90,7 +94,7 @@ def format_body(self, body: str, mime: str) -> str:
body = pygments.highlight(
code=body,
lexer=lexer,
formatter=self.formatter,
formatter=self.body_formatter,
)
return body

Expand All @@ -104,6 +108,25 @@ def get_lexer_for_body(
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:
Expand Down Expand Up @@ -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()
72 changes: 67 additions & 5 deletions httpie/output/lexers/http.py
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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,
Expand All @@ -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(
Expand Down
Empty file added httpie/output/ui/__init__.py
Empty file.