mirror of
https://gerrit.googlesource.com/git-repo
synced 2026-11-13 15:57:35 +00:00
Replace the bare `object()` sentinel used for `_CHECK_CONSOLE` with an instance of a dedicated `_CheckConsoleSentinel` class. This gives the sentinel a meaningful repr and type, making it easier to identify in debugging output and type checks compared to an opaque `<object>`. Change-Id: I916521d47ba13207e29a2412e00f3576aff8afff Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/605021 Commit-Queue: Brian Gan <brgan@google.com> Tested-by: Brian Gan <brgan@google.com> Reviewed-by: Gavin Mak <gavinmak@google.com>
231 lines
5.2 KiB
Python
231 lines
5.2 KiB
Python
# Copyright (C) 2008 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import os
|
|
import sys
|
|
from typing import Optional
|
|
|
|
import pager
|
|
|
|
|
|
COLORS = {
|
|
None: -1,
|
|
"normal": -1,
|
|
"black": 0,
|
|
"red": 1,
|
|
"green": 2,
|
|
"yellow": 3,
|
|
"blue": 4,
|
|
"magenta": 5,
|
|
"cyan": 6,
|
|
"white": 7,
|
|
}
|
|
|
|
ATTRS = {None: -1, "bold": 1, "dim": 2, "ul": 4, "blink": 5, "reverse": 7}
|
|
|
|
RESET = "\033[m"
|
|
|
|
|
|
def is_color(s):
|
|
return s in COLORS
|
|
|
|
|
|
def is_attr(s):
|
|
return s in ATTRS
|
|
|
|
|
|
def _Color(fg=None, bg=None, attr=None):
|
|
fg = COLORS[fg]
|
|
bg = COLORS[bg]
|
|
attr = ATTRS[attr]
|
|
|
|
if attr >= 0 or fg >= 0 or bg >= 0:
|
|
need_sep = False
|
|
code = "\033["
|
|
|
|
if attr >= 0:
|
|
code += chr(ord("0") + attr)
|
|
need_sep = True
|
|
|
|
if fg >= 0:
|
|
if need_sep:
|
|
code += ";"
|
|
need_sep = True
|
|
|
|
if fg < 8:
|
|
code += "3%c" % (ord("0") + fg)
|
|
else:
|
|
code += "38;5;%d" % fg
|
|
|
|
if bg >= 0:
|
|
if need_sep:
|
|
code += ";"
|
|
|
|
if bg < 8:
|
|
code += "4%c" % (ord("0") + bg)
|
|
else:
|
|
code += "48;5;%d" % bg
|
|
code += "m"
|
|
else:
|
|
code = ""
|
|
return code
|
|
|
|
|
|
DEFAULT = None
|
|
|
|
|
|
class _CheckConsoleSentinel:
|
|
"""Sentinel for checking console coloring."""
|
|
|
|
|
|
# Placholder value that indicates we need to check if the user is in an
|
|
# interactive terminal session to determine if we turn on color or not.
|
|
_CHECK_CONSOLE = _CheckConsoleSentinel()
|
|
|
|
# https://git-scm.com/docs/git-config#Documentation/git-config.txt-colorui
|
|
_CONFIG_TO_COLOR_SETTING = {
|
|
"false": False,
|
|
"never": False,
|
|
"no": False,
|
|
"auto": _CHECK_CONSOLE,
|
|
"true": _CHECK_CONSOLE,
|
|
"yes": _CHECK_CONSOLE,
|
|
"always": True,
|
|
}
|
|
|
|
|
|
def SetDefaultColoring(state: Optional[str]) -> None:
|
|
"""Set coloring behavior to |state|.
|
|
|
|
This is useful for overriding config options via the command line.
|
|
"""
|
|
|
|
global DEFAULT
|
|
|
|
if isinstance(state, str):
|
|
state = state.lower()
|
|
if state in _CONFIG_TO_COLOR_SETTING:
|
|
DEFAULT = state
|
|
|
|
|
|
class Coloring:
|
|
def __init__(self, config, section_type):
|
|
self._section = f"color.{section_type}"
|
|
self._config = config
|
|
self._out = sys.stdout
|
|
|
|
on = DEFAULT
|
|
if on is None:
|
|
on = self._config.GetString(self._section)
|
|
if on is None:
|
|
on = self._config.GetString("color.ui")
|
|
if isinstance(on, str):
|
|
on = on.lower()
|
|
|
|
self._on = _CONFIG_TO_COLOR_SETTING.get(on, _CHECK_CONSOLE)
|
|
if self._on is _CHECK_CONSOLE:
|
|
self._on = pager.active or os.isatty(1)
|
|
|
|
def redirect(self, out):
|
|
self._out = out
|
|
|
|
@property
|
|
def is_on(self):
|
|
return self._on
|
|
|
|
def write(self, fmt, *args):
|
|
self._out.write(fmt % args)
|
|
|
|
def flush(self):
|
|
self._out.flush()
|
|
|
|
def nl(self):
|
|
self._out.write("\n")
|
|
|
|
def printer(self, opt=None, fg=None, bg=None, attr=None):
|
|
s = self
|
|
c = self.colorer(opt, fg, bg, attr)
|
|
|
|
def f(fmt, *args):
|
|
s._out.write(c(fmt, *args))
|
|
|
|
return f
|
|
|
|
def nofmt_printer(self, opt=None, fg=None, bg=None, attr=None):
|
|
s = self
|
|
c = self.nofmt_colorer(opt, fg, bg, attr)
|
|
|
|
def f(fmt):
|
|
s._out.write(c(fmt))
|
|
|
|
return f
|
|
|
|
def colorer(self, opt=None, fg=None, bg=None, attr=None):
|
|
if self._on:
|
|
c = self._parse(opt, fg, bg, attr)
|
|
|
|
def f(fmt, *args):
|
|
output = fmt % args
|
|
return "".join([c, output, RESET])
|
|
|
|
return f
|
|
else:
|
|
|
|
def f(fmt, *args):
|
|
return fmt % args
|
|
|
|
return f
|
|
|
|
def nofmt_colorer(self, opt=None, fg=None, bg=None, attr=None):
|
|
if self._on:
|
|
c = self._parse(opt, fg, bg, attr)
|
|
|
|
def f(fmt):
|
|
return "".join([c, fmt, RESET])
|
|
|
|
return f
|
|
else:
|
|
|
|
def f(fmt):
|
|
return fmt
|
|
|
|
return f
|
|
|
|
def _parse(self, opt, fg, bg, attr):
|
|
if not opt:
|
|
return _Color(fg, bg, attr)
|
|
|
|
v = self._config.GetString(f"{self._section}.{opt}")
|
|
if v is None:
|
|
return _Color(fg, bg, attr)
|
|
|
|
v = v.strip().lower()
|
|
if v == "reset":
|
|
return RESET
|
|
elif v == "":
|
|
return _Color(fg, bg, attr)
|
|
|
|
have_fg = False
|
|
for a in v.split(" "):
|
|
if is_color(a):
|
|
if have_fg:
|
|
bg = a
|
|
else:
|
|
have_fg = True
|
|
fg = a
|
|
elif is_attr(a):
|
|
attr = a
|
|
|
|
return _Color(fg, bg, attr)
|