From 6529eb61f8dc35a9eab3fdb3099f9d7513ce68db Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Fri, 3 Feb 2023 13:31:07 -0500 Subject: [PATCH] xfstestsdb: Create a ColorScheme class This will be used by tools to print out text with colored outut. I provide 3 options: * A NoneScheme for printing text without any formatting * A DarkScheme with colors chosen for a dark terminal * A LightScheme with colors chosen for a light terminal Users can set the XFSTESTSDB_COLORS environment variable to either "none", "dark", or "light". If this environment variable isn't set, or if it is set to an invalid value, then xfstestsdb will use dark mode by default. Signed-off-by: Anna Schumaker --- tests/test_colors.py | 105 +++++++++++++++++++++++++++++++++++++++++++ xfstestsdb/colors.py | 102 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 tests/test_colors.py create mode 100644 xfstestsdb/colors.py diff --git a/tests/test_colors.py b/tests/test_colors.py new file mode 100644 index 0000000..c5538fd --- /dev/null +++ b/tests/test_colors.py @@ -0,0 +1,105 @@ +# Copyright 2023 (c) Anna Schumaker. +"""Tests our color handling code.""" +import os +import termcolor +import unittest +import unittest.mock +import xfstestsdb.colors + + +class TestColors(unittest.TestCase): + """Test color handling code.""" + + def test_get_default_colors(self): + """Test getting the default color scheme.""" + os.unsetenv("XFSTESTSDB_COLORS") + self.assertEqual(xfstestsdb.colors.get_default_colors(), "dark") + os.environ["XFSTESTSDB_COLORS"] = "light" + self.assertEqual(xfstestsdb.colors.get_default_colors(), "light") + os.environ["XFSTESTSDB_COLORS"] = "none" + self.assertEqual(xfstestsdb.colors.get_default_colors(), "none") + os.environ["XFSTESTSDB_COLORS"] = "dark" + self.assertEqual(xfstestsdb.colors.get_default_colors(), "dark") + os.environ["XFSTESTSDB_COLORS"] = "invalid" + self.assertEqual(xfstestsdb.colors.get_default_colors(), "dark") + + @unittest.mock.patch.dict("os.environ", {"FORCE_COLOR": "1"}) + def test_dark_colors(self): + """Test the dark color scheme.""" + scheme = xfstestsdb.colors.get_colors("dark") + self.assertIsInstance(scheme, xfstestsdb.colors.ColorScheme) + self.assertIsInstance(scheme, xfstestsdb.colors.DarkScheme) + self.assertEqual(scheme.name, "dark") + + self.assertEqual(scheme["diff-+"], "green") + self.assertEqual(scheme["diff--"], "red") + self.assertEqual(scheme["diff-@"], "magenta") + self.assertEqual(scheme["diff- "], "light_grey") + + self.assertEqual(scheme["fg"], "light_grey") + self.assertEqual(scheme["fg-odd"], "light_grey") + self.assertEqual(scheme["fg-even"], "cyan") + self.assertEqual(scheme["fg-header"], "yellow") + self.assertEqual(scheme["fg-passed"], "green") + self.assertEqual(scheme["fg-failure"], "red") + self.assertEqual(scheme["fg-skipped"], "dark_grey") + self.assertEqual(scheme["fg-testcase"], "cyan") + self.assertFalse(scheme["fg-dark"]) + + self.assertEqual(scheme["table-bg"], "black") + self.assertEqual(scheme["table-border"], "dark_grey") + + self.assertEqual(scheme["any-key"], None) + + self.assertEqual(scheme.format("text", bgcolor="table-bg", + bold=True, dark="fg-dark"), + termcolor.colored("text", on_color="on_black", + attrs=["bold"])) + self.assertEqual(scheme.format("text", bgcolor="other", bold=False), + termcolor.colored("text", on_color=None)) + + @unittest.mock.patch.dict("os.environ", {"FORCE_COLOR": "1"}) + def test_light_colors(self): + """Test the light color scheme.""" + scheme = xfstestsdb.colors.get_colors("light") + self.assertIsInstance(scheme, xfstestsdb.colors.ColorScheme) + self.assertIsInstance(scheme, xfstestsdb.colors.LightScheme) + self.assertEqual(scheme.name, "light") + + self.assertEqual(scheme["diff-+"], "green") + self.assertEqual(scheme["diff--"], "red") + self.assertEqual(scheme["diff-@"], "magenta") + self.assertEqual(scheme["diff- "], "light_grey") + + self.assertEqual(scheme["fg"], "dark_grey") + self.assertEqual(scheme["fg-odd"], "dark_grey") + self.assertEqual(scheme["fg-even"], "green") + self.assertEqual(scheme["fg-header"], "blue") + self.assertEqual(scheme["fg-passed"], "green") + self.assertEqual(scheme["fg-failure"], "red") + self.assertEqual(scheme["fg-skipped"], "light_grey") + self.assertEqual(scheme["fg-testcase"], "cyan") + self.assertTrue(scheme["fg-dark"]) + + self.assertEqual(scheme["table-bg"], "white") + self.assertEqual(scheme["table-border"], "light_grey") + + self.assertEqual(scheme["any-key"], None) + + self.assertEqual(scheme.format("text", dark="fg-dark"), + termcolor.colored("text", attrs=["dark"])) + self.assertEqual(scheme.format("text", dark=True), + termcolor.colored("text", attrs=["dark"])) + + @unittest.mock.patch.dict("os.environ", {"FORCE_COLOR": "1"}) + def test_none_colors(self): + """Test the fallback color scheme.""" + scheme = xfstestsdb.colors.get_colors("none") + self.assertIsInstance(scheme, xfstestsdb.colors.ColorScheme) + self.assertIsInstance(scheme, xfstestsdb.colors.NoneScheme) + self.assertEqual(scheme.name, "none") + self.assertFalse(scheme["fg-dark"]) + self.assertEqual(scheme["any-key"], None) + + self.assertEqual(scheme.format("text", bgcolor="table-bg", bold=True), + "text") diff --git a/xfstestsdb/colors.py b/xfstestsdb/colors.py new file mode 100644 index 0000000..959e005 --- /dev/null +++ b/xfstestsdb/colors.py @@ -0,0 +1,102 @@ +# Copyright 2023 (c) Anna Schumaker. +"""Color handling functions.""" +import os +import termcolor + + +class ColorScheme: + """Base class for color scheme handling.""" + + def __init__(self, name: str) -> None: + """Initialize a ColorScheme.""" + self.name = name + + def __getitem__(self, key: str | bool | None) -> None: + """Get the color for the specific situation.""" + return None + + def format(self, text: str, color: str | None = None, + bgcolor: str | None = None, bold: bool = False, + dark: bool | str = False) -> str: + """Format the given text based on the provided attributes.""" + on_color = self[bgcolor] + on_color = f"on_{on_color}" if on_color else None + + attrs = [] + if bold: + attrs.append("bold") + if dark is True or self[dark]: + attrs.append("dark") + + return termcolor.colored(text, color=self[color], on_color=on_color, + attrs=attrs) + + +class DarkScheme(ColorScheme): + """Dark mode color scheme.""" + + def __getitem__(self, key: str) -> str | bool | None: + """Get dark mode colors.""" + match key: + case "diff-+": return "green" + case "diff--": return "red" + case "diff-@": return "magenta" + case "diff- ": return "light_grey" + case "fg" | "fg-odd": return "light_grey" + case "fg-even": return "cyan" + case "fg-dark": return False + case "fg-header": return "yellow" + case "fg-passed": return "green" + case "fg-failure": return "red" + case "fg-skipped": return "dark_grey" + case "fg-testcase": return "cyan" + case "table-bg": return "black" + case "table-border": return "dark_grey" + return None + + +class LightScheme(ColorScheme): + """Light mode color scheme.""" + + def __getitem__(self, key: str) -> str | bool | None: + """Get light mode colors.""" + match key: + case "diff-+": return "green" + case "diff--": return "red" + case "diff-@": return "magenta" + case "diff- ": return "light_grey" + case "fg" | "fg-odd": return "dark_grey" + case "fg-even": return "green" + case "fg-dark": return True + case "fg-header": return "blue" + case "fg-passed": return "green" + case "fg-failure": return "red" + case "fg-skipped": return "light_grey" + case "fg-testcase": return "cyan" + case "table-bg": return "white" + case "table-border": return "light_grey" + return None + + +class NoneScheme(ColorScheme): + """Color scheme for no colors.""" + + def format(self, text, *args, **kwargs) -> str: + """Simply return the text with no formatting.""" + return text + + +def get_default_colors() -> str: + """Check the environment to determine the default color scheme.""" + match os.getenv("XFSTESTSDB_COLORS"): + case "light": return "light" + case "none": return "none" + case _: return "dark" + + +def get_colors(color_scheme: str) -> ColorScheme: + """Get the current color scheme.""" + match color_scheme: + case "dark": return DarkScheme("dark") + case "light": return LightScheme("light") + case _: return NoneScheme("none")