From 909c97feda9b07d0985fb8bc4ed48a912c325d9d Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 22 Nov 2024 01:47:21 +0100 Subject: [PATCH 1/2] Add ANSI colours to log output --- novelwriter/__init__.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py index c07350a28..efc539d84 100644 --- a/novelwriter/__init__.py +++ b/novelwriter/__init__.py @@ -3,7 +3,8 @@ ======================= File History: -Created: 2018-09-22 [0.0.1] +Created: 2018-09-22 [0.0.1] main +Created: 2024-11-22 [2.6b2] ColorFormatter This file is a part of novelWriter Copyright 2018–2024, Veronica Berglyd Olsen @@ -77,6 +78,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None: "version", "info", "debug", + "color", "style=", "config=", "data=", @@ -98,6 +100,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None: " -v, --version Print program version and exit.\n" " --info Print additional runtime information.\n" " --debug Print debug output. Includes --info.\n" + " --color Add ANSI colors to log output.\n" " --meminfo Show memory usage information in the status bar.\n" " --style= Sets Qt5 style flag. Defaults to 'Fusion'.\n" " --config= Alternative config file.\n" @@ -107,6 +110,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None: # Defaults logLevel = logging.WARN logFormat = "{levelname:8} {message:}" + logFormatter = logging.Formatter confPath = None dataPath = None testMode = False @@ -137,6 +141,8 @@ def main(sysArgs: list | None = None) -> GuiMain | None: CONFIG.isDebug = True logLevel = logging.DEBUG logFormat = "[{asctime:}] {filename:>18}:{lineno:<4d} {levelname:8} {message:}" + elif inOpt == "--color": + logFormatter = ColorFormatter elif inOpt == "--style": qtStyle = inArg elif inOpt == "--config": @@ -154,7 +160,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None: if len(pkgLogger.handlers) == 0: # Make sure we only create one logger (mostly an issue with tests) cHandle = logging.StreamHandler() - cHandle.setFormatter(logging.Formatter(fmt=logFormat, style="{")) + cHandle.setFormatter(logFormatter(fmt=logFormat, style="{")) pkgLogger.addHandler(cHandle) logger.info("Starting novelWriter %s (%s) %s", __version__, __hexversion__, __date__) @@ -241,3 +247,22 @@ def main(sysArgs: list | None = None) -> GuiMain | None: nwGUI.postLaunchTasks(cmdOpen) sys.exit(app.exec()) + + +class ColorFormatter(logging.Formatter): + + def __init__(self, fmt: str, style: str) -> None: + super().__init__(fmt, style="{") + self._formats = { + logging.DEBUG: f"\033[1;34m{fmt}\033[0m", + logging.INFO: f"\033[1;32m{fmt}\033[0m", + logging.WARNING: f"\033[1;33m{fmt}\033[0m", + logging.ERROR: f"\033[1;31m{fmt}\033[0m", + logging.CRITICAL: f"\033[1;31m{fmt}\033[0m", + } + return + + def format(self, record: logging.LogRecord) -> str: + """Overload the format string for each record.""" + self._style._fmt = self._formats.get(record.levelno, "ERR") + return super().format(record) From 893f18c7adaaefd7f729bc92a156801d03f24f80 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 22 Nov 2024 02:18:31 +0100 Subject: [PATCH 2/2] Add test coverage --- tests/test_base/test_base_init.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/tests/test_base/test_base_init.py b/tests/test_base/test_base_init.py index 6fb691a0a..0d05d078e 100644 --- a/tests/test_base/test_base_init.py +++ b/tests/test_base/test_base_init.py @@ -25,7 +25,7 @@ import pytest -from novelwriter import CONFIG, logger, main +from novelwriter import CONFIG, ColorFormatter, logger, main from tests.mocked import MockGuiMain @@ -99,14 +99,14 @@ def testBaseInit_Options(monkeypatch, fncPath): # Log Levels nwGUI = main( - ["--testmode", "--info", f"--config={fncPath}", f"--data={fncPath}"] + ["--testmode", "--info", "--color", f"--config={fncPath}", f"--data={fncPath}"] ) assert nwGUI is not None assert logger.getEffectiveLevel() == logging.INFO assert nwGUI.closeMain() == "closeMain" nwGUI = main( - ["--testmode", "--debug", f"--config={fncPath}", f"--data={fncPath}"] + ["--testmode", "--debug", "--color", f"--config={fncPath}", f"--data={fncPath}"] ) assert nwGUI is not None assert logger.getEffectiveLevel() == logging.DEBUG @@ -171,3 +171,24 @@ def testBaseInit_Imports(caplog, monkeypatch, fncPath): assert "At least Python" in caplog.messages[0] assert "At least Qt5" in caplog.messages[1] assert "At least PyQt5" in caplog.messages[2] + + +@pytest.mark.base +def testBaseInit_ColorFormatter(qtbot, monkeypatch, fncPath, tstPaths): + """Check launching the main GUI.""" + formatter = ColorFormatter(fmt="<{message:}>", style="{") + + record = logging.LogRecord("", logging.INFO, "", 1, "Info", None, None) + assert formatter.format(record) == "\x1b[1;32m\x1b[0m" + + record = logging.LogRecord("", logging.DEBUG, "", 1, "Debug", None, None) + assert formatter.format(record) == "\x1b[1;34m\x1b[0m" + + record = logging.LogRecord("", logging.WARNING, "", 1, "Warning", None, None) + assert formatter.format(record) == "\x1b[1;33m\x1b[0m" + + record = logging.LogRecord("", logging.ERROR, "", 1, "Error", None, None) + assert formatter.format(record) == "\x1b[1;31m\x1b[0m" + + record = logging.LogRecord("", logging.CRITICAL, "", 1, "Critical", None, None) + assert formatter.format(record) == "\x1b[1;31m\x1b[0m"