diff --git a/LogarithmPlotter/util/debug.py b/LogarithmPlotter/util/debug.py index d6474ce..b093ee0 100644 --- a/LogarithmPlotter/util/debug.py +++ b/LogarithmPlotter/util/debug.py @@ -16,15 +16,15 @@ * along with this program. If not, see . """ -from PySide6.QtCore import QtMsgType, qInstallMessageHandler +from PySide6.QtCore import QtMsgType, qInstallMessageHandler, QMessageLogContext from math import ceil, log10 -from sourcemap import loads from os import path CURRENT_PATH = path.dirname(path.realpath(__file__)) -SOURECMAP_PATH = path.realpath(f"{CURRENT_PATH}/../qml/eu/ad5001/LogarithmPlotter/js/index.mjs.map") +SOURCEMAP_PATH = path.realpath(f"{CURRENT_PATH}/../qml/eu/ad5001/LogarithmPlotter/js/index.mjs.map") SOURCEMAP_INDEX = None + class LOG_COLORS: GRAY = "\033[90m" BLUE = "\033[94m" @@ -35,7 +35,6 @@ class LOG_COLORS: RESET = "\033[0m" - MODES = { QtMsgType.QtInfoMsg: ['info', LOG_COLORS.BLUE], QtMsgType.QtWarningMsg: ['warning', LOG_COLORS.ORANGE], @@ -45,36 +44,59 @@ MODES = { DEFAULT_MODE = ['debug', LOG_COLORS.GRAY] -def log_qt_debug(mode, context, message): + +def map_javascript_source(source_file: str, line: str) -> tuple[str, str]: """ - Parses and renders qt log messages. + Maps a line from the compiled javascript to its source. """ - if mode in MODES: - mode = MODES[mode] - else: - mode = DEFAULT_MODE - line = context.line - source_file = context.file - # Remove source and line from emssage - if source_file is not None: - if message.startswith(source_file): - message = message[len(source_file) + 1:] - source_file = source_file.split("/qml")[-1] # We're only interested in that part. - if line is not None and message.startswith(str(line)): - line_length = ceil(log10((line+1) if line > 0 else 1)) - message = message[line_length + 2:] - # Check MJS - if line is not None and source_file is not None and source_file.endswith("index.mjs"): - try: + try: + if SOURCEMAP_INDEX is not None: token = SOURCEMAP_INDEX.lookup(line, 20) source_file = source_file[:-len("index.mjs")] + token.src line = token.src_line - except IndexError: - pass # Unable to find source, leave as is. - print(f"{LOG_COLORS.INVERT}{mode[1]}[{mode[0].upper()}]{LOG_COLORS.RESET_INVERT} {message}{LOG_COLORS.RESET} ({context.function} at {source_file}:{line})") + except IndexError: + pass # Unable to find source, leave as is. + return source_file, line + + +def create_log_terminal_message(mode: QtMsgType, context: QMessageLogContext, message: str): + """ + Parses a qt log message and returns it. + """ + mode = MODES[mode] if mode in MODES else DEFAULT_MODE + line = context.line + source_file = context.file + # Remove source and line from message + if source_file is not None: + if message.startswith(source_file): + message = message[len(source_file) + 1:] + source_file = "LogarithmPlotter/qml/" + source_file.split("/qml/")[-1] # We're only interested in that part. + if line is not None and message.startswith(str(line)): + line_length = ceil(log10((line + 1) if line > 0 else 1)) + message = message[line_length + 2:] + # Check MJS + if line is not None and source_file is not None and source_file.endswith("index.mjs"): + source_file, line = map_javascript_source(source_file, line) + prefix = f"{LOG_COLORS.INVERT}{mode[1]}[{mode[0].upper()}]{LOG_COLORS.RESET_INVERT}" + message = message + LOG_COLORS.RESET + context = f"{context.function} at {source_file}:{line}" + return f"{prefix} {message} ({context})" + + +def log_qt_debug(mode: QtMsgType, context: QMessageLogContext, message: str): + """ + Parses and renders qt log messages. + """ + print(create_log_terminal_message(mode, context, message)) + def setup(): global SOURCEMAP_INDEX - with open(SOURECMAP_PATH, "r") as f: - SOURCEMAP_INDEX = loads(f.read()) - qInstallMessageHandler(log_qt_debug) + try: + with open(SOURCEMAP_PATH, "r") as f: + from sourcemap import loads + SOURCEMAP_INDEX = loads(f.read()) + except Exception as e: + log_qt_debug(QtMsgType.QtWarningMsg, QMessageLogContext(), + f"Could not setup JavaScript source mapper in logs: {repr(e)}") + qInstallMessageHandler(log_qt_debug) diff --git a/README.md b/README.md index e368435..8402402 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,19 @@ You can find more screenshots on the [app's website](https://apps.ad5001.eu/logarithmplotter/). -## Run +## Build & Run -You can simply run LogarithmPlotter using `python3 run.py`. +First, you'll need to install all the required dependencies: + +- [Python 3](https://python.org) with [poetry](https://python-poetry.org/), and setup a virtual environment and call + `poetry install`. +- [npm](https://npmjs.com) (or [yarn](https://yarnpkg.com/)), and run `npm install` (or `yarn install`). + +You can simply run LogarithmPlotter using `python3 run.py`. It automatically compiles the language files (requires +`lrelease` to be installed and in path), and the JavaScript modules. + +If you do not wish do recompile the files again on every run, you can use +`python3 LogarithmPlotter/logarithmplotter.py`. In order to test translations, you can use the `--lang=` commandline option to force the locale. @@ -27,9 +37,7 @@ In order to test translations, you can use the `--lang=` commandline All scripts noted here can be found in the `scripts` directory. -You can generate installers for LogarithmPlotter after installing all the dependencies: -For all builds, you will need [Python 3](https://python.org) with [poetry](https://python-poetry.org/), and -`poetry install --with packaging`. +You can generate installers for LogarithmPlotter after installing all the dependencies. - Windows installer: - Run the `build-windows.bat` script (or `build-wine.sh` if you're cross-compiling with wine on Linux) to build an diff --git a/tests/python/test_debug.py b/tests/python/test_debug.py new file mode 100644 index 0000000..24261aa --- /dev/null +++ b/tests/python/test_debug.py @@ -0,0 +1,68 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2024 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +import pytest + +from os.path import exists +from PySide6.QtCore import QtMsgType, QMessageLogContext + +from LogarithmPlotter.util import debug + + +def test_setup(): + sourcemap_installed = False + try: + import sourcemap + sourcemap_installed = True + except: + pass + if sourcemap_installed: + file = debug.SOURCEMAP_PATH + debug.SOURCEMAP_PATH = None + debug.setup() # Nothing could be setup. + debug.SOURCEMAP_PATH = file + debug.setup() + assert (sourcemap_installed and exists(debug.SOURCEMAP_PATH)) == (debug.SOURCEMAP_INDEX is not None) + + +def test_map_source(): + sourcemap_available = debug.SOURCEMAP_INDEX is not None + if sourcemap_available: + assert debug.map_javascript_source("js/index.mjs", 21) == ("js/module/interface.mjs", 21) + assert debug.map_javascript_source("js/index.mjs", 100000) == ("js/index.mjs", 100000) # Too long, not found + debug.SOURCEMAP_INDEX = None + assert debug.map_javascript_source("js/index.mjs", 21) == ("js/index.mjs", 21) + + +def test_log_terminal_message(): + msg1 = debug.create_log_terminal_message( + QtMsgType.QtWarningMsg, QMessageLogContext(), + "a long and random message" + ) + assert "[WARNING]" in msg1 + assert "a long and random message" in msg1 + msg2 = debug.create_log_terminal_message( + QtMsgType.QtCriticalMsg, + QMessageLogContext("LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Index.qml", 15, "anotherFunctionName", + "aCategoryDifferent"), + "LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Index.qml:15: a potentially potential error message" + ) + assert "[CRITICAL]" in msg2 + assert "Index.qml" in msg2 + assert "a potentially potential error message" in msg2 + assert "anotherFunctionName" in msg2