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