This commit is contained in:
parent
f9af0c34dd
commit
309b0fafb0
3 changed files with 132 additions and 34 deletions
|
@ -16,15 +16,15 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from PySide6.QtCore import QtMsgType, qInstallMessageHandler
|
from PySide6.QtCore import QtMsgType, qInstallMessageHandler, QMessageLogContext
|
||||||
from math import ceil, log10
|
from math import ceil, log10
|
||||||
from sourcemap import loads
|
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
CURRENT_PATH = path.dirname(path.realpath(__file__))
|
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
|
SOURCEMAP_INDEX = None
|
||||||
|
|
||||||
|
|
||||||
class LOG_COLORS:
|
class LOG_COLORS:
|
||||||
GRAY = "\033[90m"
|
GRAY = "\033[90m"
|
||||||
BLUE = "\033[94m"
|
BLUE = "\033[94m"
|
||||||
|
@ -35,7 +35,6 @@ class LOG_COLORS:
|
||||||
RESET = "\033[0m"
|
RESET = "\033[0m"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
MODES = {
|
MODES = {
|
||||||
QtMsgType.QtInfoMsg: ['info', LOG_COLORS.BLUE],
|
QtMsgType.QtInfoMsg: ['info', LOG_COLORS.BLUE],
|
||||||
QtMsgType.QtWarningMsg: ['warning', LOG_COLORS.ORANGE],
|
QtMsgType.QtWarningMsg: ['warning', LOG_COLORS.ORANGE],
|
||||||
|
@ -45,36 +44,59 @@ MODES = {
|
||||||
|
|
||||||
DEFAULT_MODE = ['debug', LOG_COLORS.GRAY]
|
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:
|
try:
|
||||||
mode = MODES[mode]
|
if SOURCEMAP_INDEX is not None:
|
||||||
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:
|
|
||||||
token = SOURCEMAP_INDEX.lookup(line, 20)
|
token = SOURCEMAP_INDEX.lookup(line, 20)
|
||||||
source_file = source_file[:-len("index.mjs")] + token.src
|
source_file = source_file[:-len("index.mjs")] + token.src
|
||||||
line = token.src_line
|
line = token.src_line
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass # Unable to find source, leave as is.
|
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})")
|
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():
|
def setup():
|
||||||
global SOURCEMAP_INDEX
|
global SOURCEMAP_INDEX
|
||||||
with open(SOURECMAP_PATH, "r") as f:
|
try:
|
||||||
SOURCEMAP_INDEX = loads(f.read())
|
with open(SOURCEMAP_PATH, "r") as f:
|
||||||
qInstallMessageHandler(log_qt_debug)
|
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)
|
||||||
|
|
18
README.md
18
README.md
|
@ -15,9 +15,19 @@
|
||||||
|
|
||||||
You can find more screenshots on the [app's website](https://apps.ad5001.eu/logarithmplotter/).
|
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=<lang code>` commandline option to force the locale.
|
In order to test translations, you can use the `--lang=<lang code>` commandline option to force the locale.
|
||||||
|
|
||||||
|
@ -27,9 +37,7 @@ In order to test translations, you can use the `--lang=<lang code>` commandline
|
||||||
|
|
||||||
All scripts noted here can be found in the `scripts` directory.
|
All scripts noted here can be found in the `scripts` directory.
|
||||||
|
|
||||||
You can generate installers for LogarithmPlotter after installing all the dependencies:
|
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`.
|
|
||||||
|
|
||||||
- Windows installer:
|
- Windows installer:
|
||||||
- Run the `build-windows.bat` script (or `build-wine.sh` if you're cross-compiling with wine on Linux) to build an
|
- Run the `build-windows.bat` script (or `build-wine.sh` if you're cross-compiling with wine on Linux) to build an
|
||||||
|
|
68
tests/python/test_debug.py
Normal file
68
tests/python/test_debug.py
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
Loading…
Reference in a new issue