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

Fix wrapping of colored values #229

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: python -m pip install --upgrade pip
- run: pip install --user ruff
- run: ruff --format=github .

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • run: ruff -- format= gitHub

- run: ruff --output-format=github .

pyright:
runs-on: ubuntu-latest
Expand Down
7 changes: 6 additions & 1 deletion src/wily/commands/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from wily.commands.build import run_operator
from wily.config import DEFAULT_PATH
from wily.config.types import WilyConfig
from wily.helper import get_maxcolwidth, get_style
from wily.helper import get_maxcolwidth, get_style, handle_long_word
from wily.operators import (
BAD_COLORS,
GOOD_COLORS,
Expand All @@ -28,6 +28,11 @@
)
from wily.state import State

# Monkeypatch tabulate to fix wrapping bug (https://github.com/astanin/python-tabulate/issues/307):
if not hasattr(tabulate._CustomTextWrap, "original_handle_long_word"): # type: ignore
tabulate._CustomTextWrap.original_handle_long_word = tabulate._CustomTextWrap._handle_long_word # type: ignore
tabulate._CustomTextWrap._handle_long_word = handle_long_word # type: ignore


def diff(
config: WilyConfig,
Expand Down
7 changes: 6 additions & 1 deletion src/wily/commands/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,17 @@

from wily import MAX_MESSAGE_WIDTH, format_date, format_revision, logger
from wily.config.types import WilyConfig
from wily.helper import get_maxcolwidth
from wily.helper import get_maxcolwidth, handle_long_word
from wily.helper.custom_enums import ReportFormat
from wily.lang import _
from wily.operators import MetricType, resolve_metric_as_tuple
from wily.state import State

# Monkeypatch tabulate to fix wrapping bug (https://github.com/astanin/python-tabulate/issues/307):
if not hasattr(tabulate._CustomTextWrap, "original_handle_long_word"): # type: ignore
tabulate._CustomTextWrap.original_handle_long_word = tabulate._CustomTextWrap._handle_long_word # type: ignore
tabulate._CustomTextWrap._handle_long_word = handle_long_word # type: ignore

ANSI_RED = 31
ANSI_GREEN = 32
ANSI_YELLOW = 33
Expand Down
61 changes: 60 additions & 1 deletion src/wily/helper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import shutil
import sys
from functools import lru_cache
from typing import Optional, Sized, Union
from typing import List, Optional, Sized, Union

import tabulate

from wily.defaults import DEFAULT_GRID_STYLE

Expand Down Expand Up @@ -54,3 +56,60 @@ def generate_cache_path(path: Union[pathlib.Path, str]) -> str:
cache_path = str(HOME / ".wily" / sha)
logger.debug("Cache path is %s", cache_path)
return cache_path


strip_ansi = tabulate._strip_ansi # type: ignore
ansi_codes = tabulate._ansi_codes # type: ignore


def handle_long_word(
self, reversed_chunks: List[str], cur_line: List[str], cur_len: int, width: int
):
"""
Handle a chunk of text that is too long to fit in any line.

Fixed version of tabulate._CustomTextWrap._handle_long_word that avoids a
wrapping bug (https://github.com/astanin/python-tabulate/issues/307) where
ANSI escape codes would be broken up in the middle.
"""
# Figure out when indent is larger than the specified width, and make
# sure at least one character is stripped off on every pass
if width < 1:
space_left = 1
else:
space_left = width - cur_len

# If we're allowed to break long words, then do so: put as much
# of the next chunk onto the current line as will fit.
if self.break_long_words:
# Tabulate Custom: Build the string up piece-by-piece in order to
# take each character's width into account
chunk = reversed_chunks[-1]
i = 1
# Only count printable characters, so strip_ansi first, index later.
while len(strip_ansi(chunk)[:i]) <= space_left:
i = i + 1
# Consider escape codes when breaking words up
total_escape_len = 0
last_group = 0
if ansi_codes.search(chunk) is not None:
for group, _, _, _ in ansi_codes.findall(chunk):
escape_len = len(group)
if group in chunk[last_group : i + total_escape_len + escape_len - 1]:
total_escape_len += escape_len
found = ansi_codes.search(chunk[last_group:])
last_group += found.end()
cur_line.append(chunk[: i + total_escape_len - 1])
reversed_chunks[-1] = chunk[i + total_escape_len - 1 :]

# Otherwise, we have to preserve the long word intact. Only add
# it to the current line if there's nothing already there --
# that minimizes how much we violate the width constraint.
elif not cur_line:
cur_line.append(reversed_chunks.pop())

# If we're not allowed to break long words, and there's already
# text on the current line, do nothing. Next time through the
# main loop of _wrap_chunks(), we'll wind up here again, but
# cur_len will be zero, so the next line will be entirely
# devoted to the long word that we can't handle right now.
23 changes: 22 additions & 1 deletion test/unit/test_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import tabulate

from wily.defaults import DEFAULT_GRID_STYLE
from wily.helper import get_maxcolwidth, get_style
from wily.helper import get_maxcolwidth, get_style, handle_long_word

SHORT_DATA = [list("abcdefgh"), list("abcdefgh")]

Expand Down Expand Up @@ -156,3 +156,24 @@ def test_get_style_none():
with mock.patch("sys.stdout", output):
style = get_style()
assert style == "fancy_grid"


def test_handle_long_word():
data = "This_is_a_\033[31mtest_string_for_testing_TextWrap\033[0m_with_colors"
expected = [
"This_is_a_\033[31mte\033[0m",
"\033[31mst_string_fo\033[0m",
"\033[31mr_testing_Te\033[0m",
"\033[31mxtWrap\033[0m_with_",
"colors",
]
if hasattr(tabulate._CustomTextWrap, "original_handle_long_word"): # type: ignore
# We've already monkeypatched _CustomTextWrap, undo it.
tabulate._CustomTextWrap._handle_long_word = tabulate._CustomTextWrap.original_handle_long_word # type: ignore
wrapper = tabulate._CustomTextWrap(width=12) # type: ignore
result = wrapper.wrap(data)
assert result != expected
tabulate._CustomTextWrap._handle_long_word = handle_long_word # type: ignore
wrapper = tabulate._CustomTextWrap(width=12) # type: ignore
result = wrapper.wrap(data)
assert result == expected
Loading