Py tooling: fix Windows terminal color output and handle color flags in compare.py (fixes #642) (#2297)

Instead of always defaulting to colored output, do so only if the terminal supports them.
Also, for windows, explicitly enable colors in terminal.

*Disclosure: AI assistance (LLM) was used in formulating this patch and tests.*

---------

Co-authored-by: dominic <510002+dmah42@users.noreply.github.com>
diff --git a/AUTHORS b/AUTHORS
index 64adf36..65cad40 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -41,6 +41,7 @@
 Henrique Bucher <hbucher@gmail.com>
 International Business Machines Corporation
 Ismael Jimenez Martinez <ismael.jimenez.martinez@gmail.com>
+jdymitarai <o10040115@gmail.com>
 Jern-Kuan Leong <jernkuan@gmail.com>
 JianXiong Zhou <zhoujianxiong2@gmail.com>
 Joao Paulo Magalhaes <joaoppmagalhaes@gmail.com>
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 91f5247..a77788f 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -62,6 +62,7 @@
 Henrique Bucher <hbucher@gmail.com>
 Ismael Jimenez Martinez <ismael.jimenez.martinez@gmail.com>
 Iakov Sergeev <yahontu@gmail.com>
+jdymitarai <o10040115@gmail.com>
 Jern-Kuan Leong <jernkuan@gmail.com>
 JianXiong Zhou <zhoujianxiong2@gmail.com>
 Joao Paulo Magalhaes <joaoppmagalhaes@gmail.com>
diff --git a/tools/compare.py b/tools/compare.py
index 1a65634..8b6011d 100755
--- a/tools/compare.py
+++ b/tools/compare.py
@@ -12,6 +12,7 @@
 import sys
 import unittest
 from argparse import ArgumentParser
+from unittest.mock import patch
 
 import gbench
 from gbench import report, util
@@ -56,6 +57,45 @@
         sys.exit(1)
 
 
+def enable_virtual_terminal_processing():
+    """
+    On Windows, enable ENABLE_VIRTUAL_TERMINAL_PROCESSING on the console
+    output handle to allow ANSI escape sequences to be rendered natively.
+    Returns True if VT processing is enabled or on non-Windows; False otherwise.
+    """
+    if sys.platform == "win32":
+        try:
+            import ctypes
+
+            kernel32 = ctypes.windll.kernel32
+            handle = kernel32.GetStdHandle(-11)  # STD_OUTPUT_HANDLE
+            mode = ctypes.c_ulong()
+            if kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
+                # ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
+                return bool(
+                    kernel32.SetConsoleMode(handle, mode.value | 0x0004)
+                )
+            return False
+        except Exception:
+            return False
+    return True
+
+
+def should_use_color():
+    """
+    Determine whether terminal color output should be enabled by default.
+    Follows the NO_COLOR specification (https://no-color.org/), verifies that
+    stdout is a TTY, and checks that the Windows console supports VT processing.
+    """
+    if "NO_COLOR" in os.environ:
+        return False
+    if not hasattr(sys.stdout, "isatty") or not sys.stdout.isatty():
+        return False
+    if sys.platform == "win32":
+        return enable_virtual_terminal_processing()
+    return os.environ.get("TERM", "") != "dumb"
+
+
 def create_parser():
     parser = ArgumentParser(
         description="versatile benchmark output compare tool"
@@ -76,7 +116,7 @@
     parser.add_argument(
         "--no-color",
         dest="color",
-        default=True,
+        default=should_use_color(),
         action="store_false",
         help="Do not use colors in the terminal output",
     )
@@ -534,6 +574,39 @@
         self.assertEqual(parsed.filter_contender[0], "e")
         self.assertEqual(parsed.benchmark_options[0], "g")
 
+    def test_benchmarks_no_color_flag(self):
+        parsed = self.parser.parse_args(
+            ["--no-color", "benchmarks", self.testInput0, self.testInput1]
+        )
+        self.assertFalse(parsed.color)
+
+    @patch.dict(os.environ, {"NO_COLOR": "1"})
+    def test_benchmarks_color_default_no_color_env(self):
+        parser = create_parser()
+        parsed = parser.parse_args(
+            ["benchmarks", self.testInput0, self.testInput1]
+        )
+        self.assertFalse(parsed.color)
+
+    @patch.dict(os.environ, {}, clear=True)
+    @patch("sys.stdout.isatty", return_value=False)
+    def test_benchmarks_color_default_not_a_tty(self, mock_isatty):
+        parser = create_parser()
+        parsed = parser.parse_args(
+            ["benchmarks", self.testInput0, self.testInput1]
+        )
+        self.assertFalse(parsed.color)
+
+    @patch.dict(os.environ, {}, clear=True)
+    @patch("compare.enable_virtual_terminal_processing", return_value=True)
+    @patch("sys.stdout.isatty", return_value=True)
+    def test_benchmarks_color_default_tty(self, mock_isatty, mock_vt):
+        parser = create_parser()
+        parsed = parser.parse_args(
+            ["benchmarks", self.testInput0, self.testInput1]
+        )
+        self.assertTrue(parsed.color)
+
 
 if __name__ == "__main__":
     # unittest.main()