# 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)