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

Remove top-level dependency on ansicolors #19283

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 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
2 changes: 1 addition & 1 deletion src/python/pants/backend/python/typecheck/mypy/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ async def mypy_typecheck_partition(
"MYPY_FORCE_COLOR": "1",
# Mypy needs to know the terminal so it can use appropriate escape sequences. ansi is a
# reasonable lowest common denominator for the sort of escapes mypy uses (NB. TERM=xterm
# uses some additional codes that colors.strip_color doesn't remove).
# uses some additional codes that pants.util.colors.strip_color doesn't remove).
"TERM": "ansi",
# Force a fixed terminal width. This is effectively infinite, disabling mypy's
# builtin truncation and line wrapping. Terminals do an acceptable job of soft-wrapping
Expand Down
5 changes: 2 additions & 3 deletions src/python/pants/core/goals/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
from dataclasses import dataclass
from typing import Any, ClassVar, Generic, Iterable, TypeVar, cast

import colors

from pants.core.goals.lint import REPORT_DIR as REPORT_DIR # noqa: F401
from pants.core.goals.multi_tool_goal_helper import (
OnlyOption,
Expand All @@ -28,6 +26,7 @@
from pants.engine.rules import Get, MultiGet, QueryRule, collect_rules, goal_rule
from pants.engine.target import FieldSet, FilteredTargets
from pants.engine.unions import UnionMembership, union
from pants.util.colors import strip_color
from pants.util.logging import LogLevel
from pants.util.memo import memoized_property
from pants.util.meta import classproperty
Expand Down Expand Up @@ -57,7 +56,7 @@ def from_fallible_process_result(
) -> CheckResult:
def prep_output(s: bytes) -> str:
chroot = strip_v2_chroot_path(s) if strip_chroot_path else s.decode()
formatting = cast(str, colors.strip_color(chroot)) if strip_formatting else chroot
formatting = strip_color(chroot) if strip_formatting else chroot
return formatting

return CheckResult(
Expand Down
42 changes: 21 additions & 21 deletions src/python/pants/core/goals/generate_lockfiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,33 +218,35 @@ def print(self, diff: LockfileDiff) -> None:
if not output:
return
self.console.print_stderr(
self.style(" " * 66, style="underline")
self.style(" " * 66, color="default", underline=True)
+ f"\nLockfile diff: {diff.path} [{diff.resolve_name}]\n"
+ output
)

def output_sections(self, diff: LockfileDiff) -> Iterator[str]:
if self.include_unchanged:
yield from self.output_reqs("Unchanged dependencies", diff.unchanged, fg="blue")
yield from self.output_reqs("Unchanged dependencies", diff.unchanged, color="blue")
yield from self.output_changed("Upgraded dependencies", diff.upgraded)
yield from self.output_changed("!! Downgraded dependencies !!", diff.downgraded)
yield from self.output_reqs("Added dependencies", diff.added, fg="green", style="bold")
yield from self.output_reqs("Removed dependencies", diff.removed, fg="magenta")
yield from self.output_reqs("Added dependencies", diff.added, color="green", bold=True)
yield from self.output_reqs("Removed dependencies", diff.removed, color="magenta")

def style(self, text: str, **kwargs) -> str:
return cast(str, self.maybe_color(text, **kwargs))
def style(self, text: str, *, color: str, **kwargs: bool) -> str:
return cast(str, getattr(self, f"maybe_{color}")(text, **kwargs))

def title(self, text: str) -> str:
heading = f"== {text:^60} =="
return self.style("\n".join((" " * len(heading), heading, "")), style="underline")
return self.style(
"\n".join((" " * len(heading), heading, "")), color="default", underline=True
)

def output_reqs(self, heading: str, reqs: LockfilePackages, **kwargs) -> Iterator[str]:
if not reqs:
return

yield self.title(heading)
for name, version in reqs.items():
name_s = self.style(f"{name:30}", fg="yellow")
name_s = self.style(f"{name:30}", color="yellow")
version_s = self.style(str(version), **kwargs)
yield f" {name_s} {version_s}"

Expand All @@ -255,26 +257,24 @@ def output_changed(self, title: str, reqs: ChangedPackages) -> Iterator[str]:
yield self.title(title)
label = "-->"
for name, (prev, curr) in reqs.items():
bump_attrs = self.get_bump_attrs(prev, curr)
name_s = self.style(f"{name:30}", fg="yellow")
prev_s = self.style(f"{str(prev):10}", fg="cyan")
bump_s = self.style(f"{label:^7}", **bump_attrs)
curr_s = self.style(str(curr), **bump_attrs)
bump_color, bump_bold = self.get_bump_attrs(prev, curr)
name_s = self.style(f"{name:30}", color="yellow")
prev_s = self.style(f"{str(prev):10}", color="cyan")
bump_s = self.style(f"{label:^7}", color=bump_color, bump=bump_bold)
curr_s = self.style(str(curr), color=bump_color, bump=bump_bold)
yield f" {name_s} {prev_s} {bump_s} {curr_s}"

_BUMPS = (
("major", dict(fg="red", style="bold")),
("minor", dict(fg="yellow")),
("micro", dict(fg="green")),
# Default style
(None, dict(fg="magenta")),
("major", ("red", True)),
("minor", ("yellow", False)),
("micro", ("green", False)),
)

def get_bump_attrs(self, prev: PackageVersion, curr: PackageVersion) -> dict[str, str]:
def get_bump_attrs(self, prev: PackageVersion, curr: PackageVersion) -> tuple[str, bool]:
for key, attrs in self._BUMPS:
if key is None or getattr(prev, key, None) != getattr(curr, key, None):
if getattr(prev, key, None) != getattr(curr, key, None):
return attrs
return {} # Should never happen, but let's be safe.
return ("magenta", False)


DEFAULT_TOOL_LOCKFILE = "<default>"
Expand Down
9 changes: 4 additions & 5 deletions src/python/pants/core/goals/update_build_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@
from dataclasses import dataclass
from enum import Enum
from io import BytesIO
from typing import DefaultDict, cast

from colors import green, red
from typing import DefaultDict

from pants.backend.build_files.fix.deprecations import renamed_fields_rules, renamed_targets_rules
from pants.backend.build_files.fix.deprecations.base import FixedBUILDFile
Expand Down Expand Up @@ -47,6 +45,7 @@
from pants.engine.rules import Get, MultiGet, collect_rules, goal_rule, rule
from pants.engine.unions import UnionMembership, UnionRule, union
from pants.option.option_types import BoolOption, EnumOption
from pants.util.colors import green, red
from pants.util.docutil import bin_name, doc_url
from pants.util.logging import LogLevel
from pants.util.memo import memoized
Expand Down Expand Up @@ -94,10 +93,10 @@ def tokenize(self) -> list[tokenize.TokenInfo]:
raise ParseError(f"Failed to parse {self.path}: {e}")

def red(self, s: str) -> str:
return cast(str, red(s)) if self.colors_enabled else s
return red(s) if self.colors_enabled else s

def green(self, s: str) -> str:
return cast(str, green(s)) if self.colors_enabled else s
return green(s) if self.colors_enabled else s


class DeprecationFixerRequest(RewrittenBuildFileRequest):
Expand Down
3 changes: 1 addition & 2 deletions src/python/pants/engine/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
import sys
from typing import Callable, TextIO

from colors import blue, cyan, green, magenta, red, yellow

from pants.engine.engine_aware import SideEffecting
from pants.engine.internals.scheduler import SchedulerSession
from pants.util.colors import blue, cyan, green, magenta, red, yellow


class Console(SideEffecting):
Expand Down
13 changes: 4 additions & 9 deletions src/python/pants/help/maybe_color.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@

from typing import List

from colors import color as ansicolor
from colors import cyan, green, magenta, red, yellow


def _orange(s: str, **kwargs):
return ansicolor(s, "orange", **kwargs)
from pants.util.colors import cyan, default, green, magenta, orange, red, yellow


class MaybeColor:
Expand All @@ -17,13 +12,13 @@ class MaybeColor:
def __init__(self, color: bool) -> None:
self._color = color

def noop(x, **_):
def noop(x, bold: bool = False, underline: bool = False):
return x

self.maybe_color = ansicolor if color else noop
self.maybe_default = default if color else noop
self.maybe_cyan = cyan if color else noop
self.maybe_green = green if color else noop
self.maybe_orange = _orange if color else noop
self.maybe_orange = orange if color else noop
self.maybe_red = red if color else noop
self.maybe_magenta = magenta if color else noop
self.maybe_yellow = yellow if color else noop
Expand Down
72 changes: 72 additions & 0 deletions src/python/pants/util/colors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).
# See https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit

import functools
import re
from typing import Callable

from typing_extensions import ParamSpec


def strip_color(s: str) -> str:
"""Remove ANSI color/style sequences from a string."""
return re.sub("\x1b\\[(.*?m)", "", s)


_P = ParamSpec("_P")


def _ansi_color(*rgb) -> Callable[[Callable[_P, None]], Callable[_P, str]]:
codes = ""
if rgb:
codes = "2;" + ";".join(str(n) for n in rgb)

def decorator(func: Callable[_P, None]) -> Callable[_P, str]:
@functools.wraps(func)
def wrapper(s: str, *, bold: bool = False, underline: bool = False) -> str:
return f"\x1b[{'1;' if bold else ''}{'4;' if underline else ''}38;{codes}m{s}\x1b[0m"

return wrapper # type: ignore

return decorator


@_ansi_color(0, 0, 255)
def blue(s: str, *, bold: bool = False, underline: bool = False):
"""Clear skies, tranquil oceans, and sapphires gleaming with brilliance."""


@_ansi_color(0, 255, 255)
def cyan(s: str, *, bold: bool = False, underline: bool = False):
"""Tropical waters, verdant foliage, and the vibrant plumage of exotic birds."""


@_ansi_color(0, 128, 0)
def green(s: str, *, bold: bool = False, underline: bool = False):
"""Fresh leaves, lush meadows, and emerald gemstones."""


@_ansi_color(255, 0, 255)
def magenta(s: str, *, bold: bool = False, underline: bool = False):
"""Blooming flowers, radiant sunsets, and the bold intensity of fuchsia."""


@_ansi_color(255, 165, 0)
def orange(s: str, *, bold: bool = False, underline: bool = False):
"""Zest of ripe citrus fruits, fiery autumn leaves, and the energetic glow of a setting sun."""


@_ansi_color(255, 0, 0)
def red(s: str, *, bold: bool = False, underline: bool = False):
"""Fiery sunsets, vibrant roses, and the exhilarating energy of blazing flames."""


@_ansi_color(255, 255, 0)
def yellow(s: str, *, bold: bool = False, underline: bool = False):
"""Sunshine, golden fields of daffodils, and the cheerful vibrancy of lemon zest."""


@_ansi_color()
def default(s: str, *, bold: bool = False, underline: bool = False):
"""Initial state, conventional choice, and the pre-established configuration."""