summaryrefslogtreecommitdiffstats
path: root/sphinx/util/console.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/util/console.py')
-rw-r--r--sphinx/util/console.py129
1 files changed, 129 insertions, 0 deletions
diff --git a/sphinx/util/console.py b/sphinx/util/console.py
new file mode 100644
index 0000000..0fc9450
--- /dev/null
+++ b/sphinx/util/console.py
@@ -0,0 +1,129 @@
+"""Format colored console output."""
+
+from __future__ import annotations
+
+import os
+import re
+import shutil
+import sys
+
+try:
+ # check if colorama is installed to support color on Windows
+ import colorama
+except ImportError:
+ colorama = None
+
+
+_ansi_re: re.Pattern[str] = re.compile('\x1b\\[(\\d\\d;){0,2}\\d\\dm')
+codes: dict[str, str] = {}
+
+
+def terminal_safe(s: str) -> str:
+ """Safely encode a string for printing to the terminal."""
+ return s.encode('ascii', 'backslashreplace').decode('ascii')
+
+
+def get_terminal_width() -> int:
+ """Return the width of the terminal in columns."""
+ return shutil.get_terminal_size().columns - 1
+
+
+_tw: int = get_terminal_width()
+
+
+def term_width_line(text: str) -> str:
+ if not codes:
+ # if no coloring, don't output fancy backspaces
+ return text + '\n'
+ else:
+ # codes are not displayed, this must be taken into account
+ return text.ljust(_tw + len(text) - len(_ansi_re.sub('', text))) + '\r'
+
+
+def color_terminal() -> bool:
+ if 'NO_COLOR' in os.environ:
+ return False
+ if sys.platform == 'win32' and colorama is not None:
+ colorama.init()
+ return True
+ if 'FORCE_COLOR' in os.environ:
+ return True
+ if not hasattr(sys.stdout, 'isatty'):
+ return False
+ if not sys.stdout.isatty():
+ return False
+ if 'COLORTERM' in os.environ:
+ return True
+ term = os.environ.get('TERM', 'dumb').lower()
+ if term in ('xterm', 'linux') or 'color' in term:
+ return True
+ return False
+
+
+def nocolor() -> None:
+ if sys.platform == 'win32' and colorama is not None:
+ colorama.deinit()
+ codes.clear()
+
+
+def coloron() -> None:
+ codes.update(_orig_codes)
+
+
+def colorize(name: str, text: str, input_mode: bool = False) -> str:
+ def escseq(name: str) -> str:
+ # Wrap escape sequence with ``\1`` and ``\2`` to let readline know
+ # it is non-printable characters
+ # ref: https://tiswww.case.edu/php/chet/readline/readline.html
+ #
+ # Note: This hack does not work well in Windows (see #5059)
+ escape = codes.get(name, '')
+ if input_mode and escape and sys.platform != 'win32':
+ return '\1' + escape + '\2'
+ else:
+ return escape
+
+ return escseq(name) + text + escseq('reset')
+
+
+def strip_colors(s: str) -> str:
+ return re.compile('\x1b.*?m').sub('', s)
+
+
+def create_color_func(name: str) -> None:
+ def inner(text: str) -> str:
+ return colorize(name, text)
+ globals()[name] = inner
+
+
+_attrs = {
+ 'reset': '39;49;00m',
+ 'bold': '01m',
+ 'faint': '02m',
+ 'standout': '03m',
+ 'underline': '04m',
+ 'blink': '05m',
+}
+
+for _name, _value in _attrs.items():
+ codes[_name] = '\x1b[' + _value
+
+_colors = [
+ ('black', 'darkgray'),
+ ('darkred', 'red'),
+ ('darkgreen', 'green'),
+ ('brown', 'yellow'),
+ ('darkblue', 'blue'),
+ ('purple', 'fuchsia'),
+ ('turquoise', 'teal'),
+ ('lightgray', 'white'),
+]
+
+for i, (dark, light) in enumerate(_colors, 30):
+ codes[dark] = '\x1b[%im' % i
+ codes[light] = '\x1b[%im' % (i + 60)
+
+_orig_codes = codes.copy()
+
+for _name in codes:
+ create_color_func(_name)