pvl-dotview 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pvl_dotview-0.1.0/.github/workflows/license-check.yml +35 -0
- pvl_dotview-0.1.0/.github/workflows/release.yml +56 -0
- pvl_dotview-0.1.0/.github/workflows/test.yml +28 -0
- pvl_dotview-0.1.0/.gitignore +10 -0
- pvl_dotview-0.1.0/LICENSE +21 -0
- pvl_dotview-0.1.0/NOTICE +24 -0
- pvl_dotview-0.1.0/PKG-INFO +65 -0
- pvl_dotview-0.1.0/README.md +42 -0
- pvl_dotview-0.1.0/pyproject.toml +54 -0
- pvl_dotview-0.1.0/src/pvl_dotview/__init__.py +0 -0
- pvl_dotview-0.1.0/src/pvl_dotview/__main__.py +15 -0
- pvl_dotview-0.1.0/src/pvl_dotview/app.py +54 -0
- pvl_dotview-0.1.0/src/pvl_dotview/renderer.py +35 -0
- pvl_dotview-0.1.0/src/pvl_dotview/window.py +262 -0
- pvl_dotview-0.1.0/uv.lock +171 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: License check
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main, develop]
|
|
6
|
+
push:
|
|
7
|
+
branches: [main, develop]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
licenses:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v6
|
|
17
|
+
|
|
18
|
+
- name: Install uv
|
|
19
|
+
uses: astral-sh/setup-uv@v8.1.0
|
|
20
|
+
|
|
21
|
+
- name: Sync deps (including dev for pip-licenses)
|
|
22
|
+
run: uv sync
|
|
23
|
+
|
|
24
|
+
# Strict fail-list keeps copyleft (especially AGPL/SSPL) out of the
|
|
25
|
+
# transitive tree. PySide6 family is explicitly allowlisted because
|
|
26
|
+
# we use it as a dynamically-linked LGPL-3.0 dep (vetted in NOTICE);
|
|
27
|
+
# any *other* LGPL/GPL/AGPL/SSPL coming in transitively is a real
|
|
28
|
+
# signal worth investigating.
|
|
29
|
+
- name: Check transitive licenses
|
|
30
|
+
run: |
|
|
31
|
+
uv run pip-licenses \
|
|
32
|
+
--from=mixed \
|
|
33
|
+
--ignore-packages PySide6 shiboken6 PySide6_Addons PySide6_Essentials \
|
|
34
|
+
--fail-on="GPL;GPL v2;GPL v3;GNU General Public License;GNU General Public License v2 (GPLv2);GNU General Public License v3 (GPLv3);AGPL;Affero;GNU Affero General Public License v3;GNU Affero General Public License v3 (AGPLv3);LGPL;GNU Lesser General Public License;GNU Lesser General Public License v2 (LGPLv2);GNU Lesser General Public License v3 (LGPLv3);GNU Library or Lesser General Public License (LGPL);SSPL;Server Side Public License" \
|
|
35
|
+
--order=license
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ['v*']
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
id-token: write # PyPI trusted publishing
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
# Pre-flight checks. Publish depends on this, so a failed gate
|
|
13
|
+
# prevents any artifact from shipping.
|
|
14
|
+
gate:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v6
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0 # full history needed for the ancestor check below
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v8.1.0
|
|
23
|
+
|
|
24
|
+
- name: Verify tag matches pyproject version
|
|
25
|
+
run: |
|
|
26
|
+
TAG="${GITHUB_REF#refs/tags/v}"
|
|
27
|
+
PROJECT_VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
28
|
+
if [ "$TAG" != "$PROJECT_VERSION" ]; then
|
|
29
|
+
echo "tag $TAG != pyproject $PROJECT_VERSION"
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
- name: Verify tag is reachable from origin/main
|
|
34
|
+
run: |
|
|
35
|
+
git fetch --no-tags origin main
|
|
36
|
+
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then
|
|
37
|
+
echo "tagged commit $GITHUB_SHA is not on origin/main"
|
|
38
|
+
echo "release tags must come from main — back-merge through, then re-tag from main"
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
pypi:
|
|
43
|
+
needs: gate
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
environment: pypi # matches the trusted-publisher config on PyPI
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v6
|
|
48
|
+
|
|
49
|
+
- name: Install uv
|
|
50
|
+
uses: astral-sh/setup-uv@v8.1.0
|
|
51
|
+
|
|
52
|
+
- name: Build wheel + sdist
|
|
53
|
+
run: uv build
|
|
54
|
+
|
|
55
|
+
- name: Publish to PyPI
|
|
56
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main, develop]
|
|
6
|
+
push:
|
|
7
|
+
branches: [main, develop]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
lint:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v6
|
|
17
|
+
|
|
18
|
+
- name: Install uv
|
|
19
|
+
uses: astral-sh/setup-uv@v8.1.0
|
|
20
|
+
|
|
21
|
+
- name: Sync deps
|
|
22
|
+
run: uv sync
|
|
23
|
+
|
|
24
|
+
- name: Ruff
|
|
25
|
+
run: uv run ruff check src/
|
|
26
|
+
|
|
27
|
+
- name: Ty
|
|
28
|
+
run: uv run ty check src/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 GaryCoding, Claude Code
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pvl_dotview-0.1.0/NOTICE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
pvl-dotview
|
|
2
|
+
Copyright (c) 2026 GaryCoding, Claude Code
|
|
3
|
+
|
|
4
|
+
This product is licensed under the MIT License (see LICENSE).
|
|
5
|
+
|
|
6
|
+
This product bundles or dynamically links against the following third-party
|
|
7
|
+
software, each under its own license:
|
|
8
|
+
|
|
9
|
+
-------------------------------------------------------------------------------
|
|
10
|
+
PySide6
|
|
11
|
+
Copyright (c) The Qt Company Ltd.
|
|
12
|
+
Licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0).
|
|
13
|
+
Source: https://code.qt.io/cgit/pyside/pyside-setup.git/
|
|
14
|
+
License text: https://www.gnu.org/licenses/lgpl-3.0.html
|
|
15
|
+
|
|
16
|
+
PySide6 is used as a dynamically-linked Python import, in accordance with
|
|
17
|
+
section 4 of the LGPL-3.0. Users may replace the PySide6 installation with a
|
|
18
|
+
modified version by installing a different version into the Python environment.
|
|
19
|
+
|
|
20
|
+
-------------------------------------------------------------------------------
|
|
21
|
+
Graphviz (system dependency, invoked via the `dot` binary)
|
|
22
|
+
Copyright (c) AT&T Corp. and contributors.
|
|
23
|
+
Licensed under the Common Public License 1.0 / Eclipse Public License 1.0.
|
|
24
|
+
Source: https://gitlab.com/graphviz/graphviz
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pvl-dotview
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interactive Graphviz DOT file viewer with pan, zoom, and multi-window support.
|
|
5
|
+
Project-URL: Homepage, https://github.com/ParkviewLab/pvl-dotview
|
|
6
|
+
Project-URL: Source, https://github.com/ParkviewLab/pvl-dotview
|
|
7
|
+
Project-URL: Issues, https://github.com/ParkviewLab/pvl-dotview/issues
|
|
8
|
+
Author: GaryCoding, Claude Code
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: diagram,dot,graphviz,gui,viewer,visualization
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: pyside6>=6.7
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# pvl-dotview
|
|
25
|
+
|
|
26
|
+
Interactive Graphviz DOT file viewer. Drop a `.dot` file onto a blank window
|
|
27
|
+
and the graph is rendered as crisp, pannable, zoomable vector graphics
|
|
28
|
+
(via embedded Chromium / Qt WebEngine for browser-grade SVG fidelity). Open
|
|
29
|
+
additional windows with **⌘N** (macOS) or **Ctrl+N** (Linux/Windows).
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
`pvl-dotview` shells out to the `dot` binary at runtime, so Graphviz must be
|
|
34
|
+
installed system-wide before running.
|
|
35
|
+
|
|
36
|
+
### As a tool (recommended for end users)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# macOS
|
|
40
|
+
brew install graphviz
|
|
41
|
+
uv tool install pvl-dotview
|
|
42
|
+
|
|
43
|
+
# Debian/Ubuntu
|
|
44
|
+
sudo apt install graphviz
|
|
45
|
+
uv tool install pvl-dotview
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then run `pvl-dotview` from any terminal.
|
|
49
|
+
|
|
50
|
+
### From a checkout (for development)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
brew install graphviz # or: apt install graphviz
|
|
54
|
+
uv sync
|
|
55
|
+
uv run pvl-dotview
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
This project is released under the [MIT License](LICENSE).
|
|
61
|
+
|
|
62
|
+
Third-party components and their licenses are listed in [NOTICE](NOTICE):
|
|
63
|
+
|
|
64
|
+
- **PySide6** (LGPL-3.0) — used as a dynamically-linked dependency
|
|
65
|
+
- **Graphviz** (EPL-1.0, system dependency, invoked via the `dot` binary)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# pvl-dotview
|
|
2
|
+
|
|
3
|
+
Interactive Graphviz DOT file viewer. Drop a `.dot` file onto a blank window
|
|
4
|
+
and the graph is rendered as crisp, pannable, zoomable vector graphics
|
|
5
|
+
(via embedded Chromium / Qt WebEngine for browser-grade SVG fidelity). Open
|
|
6
|
+
additional windows with **⌘N** (macOS) or **Ctrl+N** (Linux/Windows).
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
`pvl-dotview` shells out to the `dot` binary at runtime, so Graphviz must be
|
|
11
|
+
installed system-wide before running.
|
|
12
|
+
|
|
13
|
+
### As a tool (recommended for end users)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# macOS
|
|
17
|
+
brew install graphviz
|
|
18
|
+
uv tool install pvl-dotview
|
|
19
|
+
|
|
20
|
+
# Debian/Ubuntu
|
|
21
|
+
sudo apt install graphviz
|
|
22
|
+
uv tool install pvl-dotview
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then run `pvl-dotview` from any terminal.
|
|
26
|
+
|
|
27
|
+
### From a checkout (for development)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
brew install graphviz # or: apt install graphviz
|
|
31
|
+
uv sync
|
|
32
|
+
uv run pvl-dotview
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
This project is released under the [MIT License](LICENSE).
|
|
38
|
+
|
|
39
|
+
Third-party components and their licenses are listed in [NOTICE](NOTICE):
|
|
40
|
+
|
|
41
|
+
- **PySide6** (LGPL-3.0) — used as a dynamically-linked dependency
|
|
42
|
+
- **Graphviz** (EPL-1.0, system dependency, invoked via the `dot` binary)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pvl-dotview"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Interactive Graphviz DOT file viewer with pan, zoom, and multi-window support."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "GaryCoding" },
|
|
11
|
+
{ name = "Claude Code" },
|
|
12
|
+
]
|
|
13
|
+
keywords = ["graphviz", "dot", "viewer", "visualization", "gui", "diagram"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"PySide6>=6.7",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/ParkviewLab/pvl-dotview"
|
|
30
|
+
Source = "https://github.com/ParkviewLab/pvl-dotview"
|
|
31
|
+
Issues = "https://github.com/ParkviewLab/pvl-dotview/issues"
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
pvl-dotview = "pvl_dotview.__main__:main"
|
|
35
|
+
|
|
36
|
+
[dependency-groups]
|
|
37
|
+
dev = ["ruff", "ty", "pip-licenses"]
|
|
38
|
+
|
|
39
|
+
[build-system]
|
|
40
|
+
requires = ["hatchling"]
|
|
41
|
+
build-backend = "hatchling.build"
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.wheel]
|
|
44
|
+
packages = ["src/pvl_dotview"]
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
line-length = 100
|
|
48
|
+
src = ["src"]
|
|
49
|
+
|
|
50
|
+
[tool.ruff.lint]
|
|
51
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
52
|
+
|
|
53
|
+
[tool.ty.environment]
|
|
54
|
+
root = ["./src"]
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Entry point: ``python -m pvl_dotview`` or ``pvl-dotview`` script."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from .app import PvlDotApp
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main() -> None:
|
|
11
|
+
sys.exit(PvlDotApp().run())
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if __name__ == "__main__":
|
|
15
|
+
main()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""PvlDotApp — application singleton owning the QApplication and window registry."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import contextlib
|
|
6
|
+
import sys
|
|
7
|
+
import traceback
|
|
8
|
+
from types import TracebackType
|
|
9
|
+
|
|
10
|
+
from PySide6.QtWidgets import QApplication, QMessageBox
|
|
11
|
+
|
|
12
|
+
from .window import PvlDotWindow
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PvlDotApp:
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
self._qapp = QApplication.instance() or QApplication(sys.argv)
|
|
18
|
+
self._windows: list[PvlDotWindow] = []
|
|
19
|
+
sys.excepthook = self._on_uncaught_exception
|
|
20
|
+
self.new_window()
|
|
21
|
+
|
|
22
|
+
def new_window(self) -> PvlDotWindow:
|
|
23
|
+
window = PvlDotWindow(self)
|
|
24
|
+
window.show()
|
|
25
|
+
self._windows.append(window)
|
|
26
|
+
return window
|
|
27
|
+
|
|
28
|
+
def remove_window(self, window: PvlDotWindow) -> None:
|
|
29
|
+
if window in self._windows:
|
|
30
|
+
self._windows.remove(window)
|
|
31
|
+
|
|
32
|
+
def run(self) -> int:
|
|
33
|
+
return self._qapp.exec()
|
|
34
|
+
|
|
35
|
+
def _on_uncaught_exception(
|
|
36
|
+
self,
|
|
37
|
+
exc_type: type[BaseException],
|
|
38
|
+
exc_value: BaseException,
|
|
39
|
+
exc_tb: TracebackType | None,
|
|
40
|
+
) -> None:
|
|
41
|
+
# Always print to stderr so the trace lands in the terminal too.
|
|
42
|
+
sys.__excepthook__(exc_type, exc_value, exc_tb)
|
|
43
|
+
# Don't pop a dialog for KeyboardInterrupt — let it propagate cleanly.
|
|
44
|
+
if issubclass(exc_type, KeyboardInterrupt):
|
|
45
|
+
return
|
|
46
|
+
tb = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
|
|
47
|
+
parent = self._windows[0] if self._windows else None
|
|
48
|
+
# If the dialog itself fails, we've already printed to stderr.
|
|
49
|
+
with contextlib.suppress(Exception):
|
|
50
|
+
QMessageBox.critical(
|
|
51
|
+
parent,
|
|
52
|
+
"Unexpected error",
|
|
53
|
+
f"{exc_type.__name__}: {exc_value}\n\n{tb}",
|
|
54
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Render a DOT source string to SVG bytes via the graphviz `dot` binary."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GraphvizNotFoundError(RuntimeError):
|
|
10
|
+
"""The `dot` binary is not on PATH."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class GraphvizRenderError(RuntimeError):
|
|
14
|
+
"""`dot` exited non-zero (DOT syntax error or other failure)."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def render_dot_to_svg(dot_source: str) -> bytes:
|
|
18
|
+
"""Invoke `dot -Tsvg` on the DOT source and return the SVG bytes."""
|
|
19
|
+
if not shutil.which("dot"):
|
|
20
|
+
raise GraphvizNotFoundError(
|
|
21
|
+
"Graphviz `dot` binary not found on PATH. "
|
|
22
|
+
"Install with `brew install graphviz` (macOS) or "
|
|
23
|
+
"`apt install graphviz` (Linux)."
|
|
24
|
+
)
|
|
25
|
+
try:
|
|
26
|
+
result = subprocess.run(
|
|
27
|
+
["dot", "-Tsvg"],
|
|
28
|
+
input=dot_source.encode("utf-8"),
|
|
29
|
+
capture_output=True,
|
|
30
|
+
check=True,
|
|
31
|
+
)
|
|
32
|
+
except subprocess.CalledProcessError as exc:
|
|
33
|
+
stderr = exc.stderr.decode("utf-8", errors="replace").strip()
|
|
34
|
+
raise GraphvizRenderError(stderr or f"dot exited {exc.returncode}") from exc
|
|
35
|
+
return result.stdout
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""PvlDotWindow — viewer window backed by QWebEngineView for browser-grade rendering."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import TYPE_CHECKING, cast
|
|
8
|
+
|
|
9
|
+
from PySide6.QtCore import QChildEvent, QEvent, QObject
|
|
10
|
+
from PySide6.QtGui import (
|
|
11
|
+
QAction,
|
|
12
|
+
QCloseEvent,
|
|
13
|
+
QDragEnterEvent,
|
|
14
|
+
QDragMoveEvent,
|
|
15
|
+
QDropEvent,
|
|
16
|
+
QKeySequence,
|
|
17
|
+
)
|
|
18
|
+
from PySide6.QtWebEngineWidgets import QWebEngineView
|
|
19
|
+
from PySide6.QtWidgets import QMainWindow, QMessageBox, QWidget
|
|
20
|
+
|
|
21
|
+
from .renderer import GraphvizNotFoundError, GraphvizRenderError, render_dot_to_svg
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from .app import PvlDotApp
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
HTML_TEMPLATE = """<!DOCTYPE html>
|
|
28
|
+
<html>
|
|
29
|
+
<head>
|
|
30
|
+
<meta charset="utf-8">
|
|
31
|
+
<style>
|
|
32
|
+
html, body {
|
|
33
|
+
margin: 0; padding: 0; width: 100%; height: 100%;
|
|
34
|
+
overflow: hidden; background: #ffffff;
|
|
35
|
+
-webkit-user-select: none; user-select: none;
|
|
36
|
+
}
|
|
37
|
+
#viewport {
|
|
38
|
+
position: fixed; inset: 0; overflow: hidden;
|
|
39
|
+
cursor: grab;
|
|
40
|
+
}
|
|
41
|
+
#viewport.panning { cursor: grabbing; }
|
|
42
|
+
#content {
|
|
43
|
+
position: absolute; top: 0; left: 0;
|
|
44
|
+
transform-origin: 0 0;
|
|
45
|
+
will-change: transform;
|
|
46
|
+
}
|
|
47
|
+
#content svg { display: block; }
|
|
48
|
+
.placeholder {
|
|
49
|
+
display: flex; align-items: center; justify-content: center;
|
|
50
|
+
height: 100vh; padding: 0 24px;
|
|
51
|
+
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
|
|
52
|
+
text-align: center; color: #888; font-size: 16px;
|
|
53
|
+
}
|
|
54
|
+
</style>
|
|
55
|
+
</head>
|
|
56
|
+
<body>
|
|
57
|
+
<div id="viewport"><div id="content">__CONTENT__</div></div>
|
|
58
|
+
<script>
|
|
59
|
+
(function(){
|
|
60
|
+
const viewport = document.getElementById('viewport');
|
|
61
|
+
const content = document.getElementById('content');
|
|
62
|
+
|
|
63
|
+
let scale = 1, tx = 0, ty = 0;
|
|
64
|
+
const ZOOM_BASE = 1.0015;
|
|
65
|
+
const ZOOM_MIN = 0.05;
|
|
66
|
+
const ZOOM_MAX = 50;
|
|
67
|
+
|
|
68
|
+
function applyTransform() {
|
|
69
|
+
content.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function fitToViewport() {
|
|
73
|
+
const svg = content.querySelector('svg');
|
|
74
|
+
if (!svg) {
|
|
75
|
+
tx = 0; ty = 0; scale = 1;
|
|
76
|
+
applyTransform();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
let svgW = 0, svgH = 0;
|
|
80
|
+
if (svg.viewBox && svg.viewBox.baseVal && svg.viewBox.baseVal.width) {
|
|
81
|
+
svgW = svg.viewBox.baseVal.width;
|
|
82
|
+
svgH = svg.viewBox.baseVal.height;
|
|
83
|
+
} else if (svg.width && svg.width.baseVal) {
|
|
84
|
+
svgW = svg.width.baseVal.value;
|
|
85
|
+
svgH = svg.height.baseVal.value;
|
|
86
|
+
}
|
|
87
|
+
if (!svgW || !svgH) return;
|
|
88
|
+
svg.style.width = svgW + 'px';
|
|
89
|
+
svg.style.height = svgH + 'px';
|
|
90
|
+
const vw = viewport.clientWidth, vh = viewport.clientHeight;
|
|
91
|
+
scale = Math.min(vw / svgW, vh / svgH) * 0.95;
|
|
92
|
+
tx = (vw - svgW * scale) / 2;
|
|
93
|
+
ty = (vh - svgH * scale) / 2;
|
|
94
|
+
applyTransform();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
viewport.addEventListener('wheel', (e) => {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
const delta = e.deltaY;
|
|
100
|
+
let factor = Math.pow(ZOOM_BASE, -delta);
|
|
101
|
+
let newScale = scale * factor;
|
|
102
|
+
if (newScale < ZOOM_MIN) { factor = ZOOM_MIN / scale; newScale = ZOOM_MIN; }
|
|
103
|
+
if (newScale > ZOOM_MAX) { factor = ZOOM_MAX / scale; newScale = ZOOM_MAX; }
|
|
104
|
+
const rect = viewport.getBoundingClientRect();
|
|
105
|
+
const cx = e.clientX - rect.left;
|
|
106
|
+
const cy = e.clientY - rect.top;
|
|
107
|
+
tx = cx - (cx - tx) * factor;
|
|
108
|
+
ty = cy - (cy - ty) * factor;
|
|
109
|
+
scale = newScale;
|
|
110
|
+
applyTransform();
|
|
111
|
+
}, { passive: false });
|
|
112
|
+
|
|
113
|
+
let panning = false, lastX = 0, lastY = 0;
|
|
114
|
+
viewport.addEventListener('mousedown', (e) => {
|
|
115
|
+
if (e.button !== 0) return;
|
|
116
|
+
panning = true;
|
|
117
|
+
lastX = e.clientX; lastY = e.clientY;
|
|
118
|
+
viewport.classList.add('panning');
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
});
|
|
121
|
+
document.addEventListener('mousemove', (e) => {
|
|
122
|
+
if (!panning) return;
|
|
123
|
+
tx += e.clientX - lastX;
|
|
124
|
+
ty += e.clientY - lastY;
|
|
125
|
+
lastX = e.clientX; lastY = e.clientY;
|
|
126
|
+
applyTransform();
|
|
127
|
+
});
|
|
128
|
+
document.addEventListener('mouseup', () => {
|
|
129
|
+
if (!panning) return;
|
|
130
|
+
panning = false;
|
|
131
|
+
viewport.classList.remove('panning');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Initial fit. setTimeout 0 lets the SVG layout settle first.
|
|
135
|
+
setTimeout(fitToViewport, 0);
|
|
136
|
+
window.addEventListener('load', fitToViewport);
|
|
137
|
+
})();
|
|
138
|
+
</script>
|
|
139
|
+
</body>
|
|
140
|
+
</html>
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
PLACEHOLDER_BODY = '<div class="placeholder">Drop a .dot file here</div>'
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _build_html(content: str) -> str:
|
|
148
|
+
return HTML_TEMPLATE.replace("__CONTENT__", content)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _strip_svg_prologue(svg_text: str) -> str:
|
|
152
|
+
"""Drop <?xml?> / <!DOCTYPE> for clean HTML embedding."""
|
|
153
|
+
idx = svg_text.find("<svg")
|
|
154
|
+
return svg_text[idx:] if idx >= 0 else svg_text
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class _DropFilter(QObject):
|
|
158
|
+
"""Intercept drag-drop events on the webview and any of its inner widgets.
|
|
159
|
+
|
|
160
|
+
QWebEngineView has internal Chromium widgets that consume drag-drop events;
|
|
161
|
+
a filter installed on the view and each of its children catches drops
|
|
162
|
+
regardless of which inner widget receives the OS event first.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def __init__(self, on_file_dropped: Callable[[str], None]) -> None:
|
|
166
|
+
super().__init__()
|
|
167
|
+
self._on_file_dropped = on_file_dropped
|
|
168
|
+
|
|
169
|
+
def eventFilter(self, obj: QObject, event: QEvent) -> bool: # noqa: N802
|
|
170
|
+
t = event.type()
|
|
171
|
+
if t == QEvent.Type.DragEnter:
|
|
172
|
+
ev = cast(QDragEnterEvent, event)
|
|
173
|
+
if ev.mimeData().hasUrls():
|
|
174
|
+
ev.acceptProposedAction()
|
|
175
|
+
return True
|
|
176
|
+
elif t == QEvent.Type.DragMove:
|
|
177
|
+
mv = cast(QDragMoveEvent, event)
|
|
178
|
+
if mv.mimeData().hasUrls():
|
|
179
|
+
mv.acceptProposedAction()
|
|
180
|
+
return True
|
|
181
|
+
elif t == QEvent.Type.Drop:
|
|
182
|
+
drop = cast(QDropEvent, event)
|
|
183
|
+
for url in drop.mimeData().urls():
|
|
184
|
+
path = url.toLocalFile()
|
|
185
|
+
if path:
|
|
186
|
+
self._on_file_dropped(path)
|
|
187
|
+
drop.acceptProposedAction()
|
|
188
|
+
return True
|
|
189
|
+
elif t == QEvent.Type.ChildAdded:
|
|
190
|
+
# A new internal widget was just attached — make sure it accepts
|
|
191
|
+
# drops and routes through this filter too.
|
|
192
|
+
child_ev = cast(QChildEvent, event)
|
|
193
|
+
child = child_ev.child()
|
|
194
|
+
if isinstance(child, QWidget):
|
|
195
|
+
child.setAcceptDrops(True)
|
|
196
|
+
child.installEventFilter(self)
|
|
197
|
+
return super().eventFilter(obj, event)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class PvlDotWindow(QMainWindow):
|
|
201
|
+
def __init__(self, app: PvlDotApp) -> None:
|
|
202
|
+
super().__init__()
|
|
203
|
+
self._app = app
|
|
204
|
+
self.setWindowTitle("pvl-dotview")
|
|
205
|
+
self.resize(900, 700)
|
|
206
|
+
|
|
207
|
+
self._view = QWebEngineView(self)
|
|
208
|
+
self.setCentralWidget(self._view)
|
|
209
|
+
|
|
210
|
+
self._drop_filter = _DropFilter(on_file_dropped=self._load)
|
|
211
|
+
self.setAcceptDrops(True)
|
|
212
|
+
self.installEventFilter(self._drop_filter)
|
|
213
|
+
self._view.setAcceptDrops(True)
|
|
214
|
+
self._view.installEventFilter(self._drop_filter)
|
|
215
|
+
for child in self._view.findChildren(QWidget):
|
|
216
|
+
child.setAcceptDrops(True)
|
|
217
|
+
child.installEventFilter(self._drop_filter)
|
|
218
|
+
|
|
219
|
+
self._show_placeholder()
|
|
220
|
+
self._install_shortcuts()
|
|
221
|
+
|
|
222
|
+
def _install_shortcuts(self) -> None:
|
|
223
|
+
new_action = QAction("New Window", self)
|
|
224
|
+
new_action.setShortcut(QKeySequence.StandardKey.New)
|
|
225
|
+
new_action.triggered.connect(self._app.new_window)
|
|
226
|
+
self.addAction(new_action)
|
|
227
|
+
|
|
228
|
+
def _show_placeholder(self) -> None:
|
|
229
|
+
self._view.setHtml(_build_html(PLACEHOLDER_BODY))
|
|
230
|
+
|
|
231
|
+
def _load(self, path: str) -> None:
|
|
232
|
+
filename = os.path.basename(path)
|
|
233
|
+
try:
|
|
234
|
+
with open(path) as f:
|
|
235
|
+
dot_source = f.read()
|
|
236
|
+
svg_body = _strip_svg_prologue(render_dot_to_svg(dot_source).decode("utf-8"))
|
|
237
|
+
except GraphvizNotFoundError as exc:
|
|
238
|
+
QMessageBox.warning(self, "Graphviz not installed", str(exc))
|
|
239
|
+
return
|
|
240
|
+
except GraphvizRenderError as exc:
|
|
241
|
+
QMessageBox.warning(self, "Cannot render DOT file", f"{filename}\n\n{exc}")
|
|
242
|
+
return
|
|
243
|
+
except OSError as exc:
|
|
244
|
+
QMessageBox.warning(self, "Cannot read file", f"{filename}\n\n{exc}")
|
|
245
|
+
return
|
|
246
|
+
except Exception as exc:
|
|
247
|
+
# Catch-all so unexpected failures (UnicodeDecodeError, MemoryError,
|
|
248
|
+
# anything from the subprocess layer we didn't anticipate) show a
|
|
249
|
+
# dialog and the app keeps running instead of crashing.
|
|
250
|
+
QMessageBox.warning(
|
|
251
|
+
self,
|
|
252
|
+
"Unexpected error loading file",
|
|
253
|
+
f"{filename}\n\n{type(exc).__name__}: {exc}",
|
|
254
|
+
)
|
|
255
|
+
return
|
|
256
|
+
|
|
257
|
+
self._view.setHtml(_build_html(svg_body))
|
|
258
|
+
self.setWindowTitle(f"pvl-dotview — {filename}")
|
|
259
|
+
|
|
260
|
+
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802
|
|
261
|
+
self._app.remove_window(self)
|
|
262
|
+
super().closeEvent(event)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.11"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "pip-licenses"
|
|
7
|
+
version = "5.5.5"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
dependencies = [
|
|
10
|
+
{ name = "prettytable" },
|
|
11
|
+
]
|
|
12
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7d/18/ddd93af610a04f56a51a27095ddfe55238e1ec236f6758730a0d2c0b49f2/pip_licenses-5.5.5.tar.gz", hash = "sha256:60750c006adf7a0910347b726e8ee9fee3bc8d2e7c8307a5c4ec0776c8e2a276", size = 54955, upload-time = "2026-03-28T22:12:56.48Z" }
|
|
13
|
+
wheels = [
|
|
14
|
+
{ url = "https://files.pythonhosted.org/packages/2a/9a/6acfdb8d463eac7cdae7534d35d72237eca63f5fbafe797289d8a5fae447/pip_licenses-5.5.5-py3-none-any.whl", hash = "sha256:f4c4c6d9e6a03612cf59f29f19dc8ab54904d82e055b8e191498f2279a224e14", size = 23247, upload-time = "2026-03-28T22:12:54.89Z" },
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[[package]]
|
|
18
|
+
name = "prettytable"
|
|
19
|
+
version = "3.17.0"
|
|
20
|
+
source = { registry = "https://pypi.org/simple" }
|
|
21
|
+
dependencies = [
|
|
22
|
+
{ name = "wcwidth" },
|
|
23
|
+
]
|
|
24
|
+
sdist = { url = "https://files.pythonhosted.org/packages/79/45/b0847d88d6cfeb4413566738c8bbf1e1995fad3d42515327ff32cc1eb578/prettytable-3.17.0.tar.gz", hash = "sha256:59f2590776527f3c9e8cf9fe7b66dd215837cca96a9c39567414cbc632e8ddb0", size = 67892, upload-time = "2025-11-14T17:33:20.212Z" }
|
|
25
|
+
wheels = [
|
|
26
|
+
{ url = "https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl", hash = "sha256:aad69b294ddbe3e1f95ef8886a060ed1666a0b83018bbf56295f6f226c43d287", size = 34433, upload-time = "2025-11-14T17:33:19.093Z" },
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[[package]]
|
|
30
|
+
name = "pvl-dotview"
|
|
31
|
+
version = "0.1.0"
|
|
32
|
+
source = { editable = "." }
|
|
33
|
+
dependencies = [
|
|
34
|
+
{ name = "pyside6" },
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[package.dev-dependencies]
|
|
38
|
+
dev = [
|
|
39
|
+
{ name = "pip-licenses" },
|
|
40
|
+
{ name = "ruff" },
|
|
41
|
+
{ name = "ty" },
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[package.metadata]
|
|
45
|
+
requires-dist = [{ name = "pyside6", specifier = ">=6.7" }]
|
|
46
|
+
|
|
47
|
+
[package.metadata.requires-dev]
|
|
48
|
+
dev = [
|
|
49
|
+
{ name = "pip-licenses" },
|
|
50
|
+
{ name = "ruff" },
|
|
51
|
+
{ name = "ty" },
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[[package]]
|
|
55
|
+
name = "pyside6"
|
|
56
|
+
version = "6.11.1"
|
|
57
|
+
source = { registry = "https://pypi.org/simple" }
|
|
58
|
+
dependencies = [
|
|
59
|
+
{ name = "pyside6-addons" },
|
|
60
|
+
{ name = "pyside6-essentials" },
|
|
61
|
+
{ name = "shiboken6" },
|
|
62
|
+
]
|
|
63
|
+
wheels = [
|
|
64
|
+
{ url = "https://files.pythonhosted.org/packages/da/a6/27ba5947ed48918f7b74b7c43a1e280aac069e36f25adeb4c9adfac835c4/pyside6-6.11.1-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:537682c3b7530817203e667c1f5a2f00486b37bf52c52eeab438544c7a0917f6", size = 571921, upload-time = "2026-05-13T09:47:36.402Z" },
|
|
65
|
+
{ url = "https://files.pythonhosted.org/packages/d8/de/af89d71410c83b10654d86ff9aff2a4f87c30163658f1cc145242e222526/pyside6-6.11.1-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b1fc521ba2bb5109425ab8add06bddbdd524abcad06cfa012cc39a22a189feb2", size = 572102, upload-time = "2026-05-13T09:47:38.249Z" },
|
|
66
|
+
{ url = "https://files.pythonhosted.org/packages/b6/0e/d583bd3f7bf5046a4497b36f3902cfb64aa29554489a5a25c18e6b4ac0ac/pyside6-6.11.1-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:75f0005c3eb95c07cfb65522ec50d0815ac007a96482c21dc3cb4b4c04895d84", size = 572098, upload-time = "2026-05-13T09:47:39.44Z" },
|
|
67
|
+
{ url = "https://files.pythonhosted.org/packages/57/f2/d9d8ce1373dabb37e5919f63cd18446556079631d3f2eea3ada03c29f6b8/pyside6-6.11.1-cp310-abi3-win_amd64.whl", hash = "sha256:0968877ab1fb4ef3587a284da6fe05e8647ada56a6a3750b6395188e01f4aba6", size = 578377, upload-time = "2026-05-13T09:47:40.76Z" },
|
|
68
|
+
{ url = "https://files.pythonhosted.org/packages/96/02/a6057d8bd2bdb1940820fff2d627fdf4013148c9c57adf69fa40d3452ac3/pyside6-6.11.1-cp310-abi3-win_arm64.whl", hash = "sha256:acee467cb5f256cc47ebb9d815a054c1d8416da380c191b247a76d164aa3f805", size = 561765, upload-time = "2026-05-13T09:47:41.9Z" },
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
[[package]]
|
|
72
|
+
name = "pyside6-addons"
|
|
73
|
+
version = "6.11.1"
|
|
74
|
+
source = { registry = "https://pypi.org/simple" }
|
|
75
|
+
dependencies = [
|
|
76
|
+
{ name = "pyside6-essentials" },
|
|
77
|
+
{ name = "shiboken6" },
|
|
78
|
+
]
|
|
79
|
+
wheels = [
|
|
80
|
+
{ url = "https://files.pythonhosted.org/packages/3f/6b/8bc94aff48b63f788f2d84e5467c12362d68906ba742c0942f46cb04c879/pyside6_addons-6.11.1-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:54733c77f789bef5f03c6aff4ad3bec8b2eff021f0cfcbc53d5e6c250ded24f9", size = 331714589, upload-time = "2026-05-13T09:39:12.36Z" },
|
|
81
|
+
{ url = "https://files.pythonhosted.org/packages/dd/62/fb1428a523b2a4541e232aab50d9e789e6b4526f37fd9593452a7ea5b6b3/pyside6_addons-6.11.1-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8e6c65fbd73a512d6f72cda8d8277444a85a34dc99dd1dae9c21d35b8671bb1f", size = 175063224, upload-time = "2026-05-13T09:39:34.185Z" },
|
|
82
|
+
{ url = "https://files.pythonhosted.org/packages/ee/9b/2ccd52f66db55c06de65d0501170a1935d04d64d0a230c0d892284a02ce3/pyside6_addons-6.11.1-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:bf1c6c4e954e5eba3d2a7c661ad4b9689e8f09c7f4a16bdf29713371d11af993", size = 170553429, upload-time = "2026-05-13T09:39:54.424Z" },
|
|
83
|
+
{ url = "https://files.pythonhosted.org/packages/9a/bd/8adc4d350b3b363f3dfc8fccdcf5bfed25f7e36c2fff30c64e106f4f1572/pyside6_addons-6.11.1-cp310-abi3-win_amd64.whl", hash = "sha256:0d13c4dfd671b050a48e4f8d8ddc724b7248f9c0437e7fc47fdf316278572923", size = 168816308, upload-time = "2026-05-13T09:40:13.541Z" },
|
|
84
|
+
{ url = "https://files.pythonhosted.org/packages/65/b7/9a840d97f0f0f04e372a87e205dd30ee285b4e3b021b188459a917c9dc76/pyside6_addons-6.11.1-cp310-abi3-win_arm64.whl", hash = "sha256:3494f480dee92f415be2f2d989c0b3f4755ac332b28045cbf4ba0f5c5a22ba37", size = 35759347, upload-time = "2026-05-13T09:40:21.199Z" },
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
[[package]]
|
|
88
|
+
name = "pyside6-essentials"
|
|
89
|
+
version = "6.11.1"
|
|
90
|
+
source = { registry = "https://pypi.org/simple" }
|
|
91
|
+
dependencies = [
|
|
92
|
+
{ name = "shiboken6" },
|
|
93
|
+
]
|
|
94
|
+
wheels = [
|
|
95
|
+
{ url = "https://files.pythonhosted.org/packages/b3/da/10d9197e7370eb4fed8df5fc547b7548dec88e5c5949e2d450db4ae96feb/pyside6_essentials-6.11.1-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:228de53c2bc26b07e5021fbe3614fc44ca08e4dab9999af08c2b389d2c239957", size = 110352945, upload-time = "2026-05-13T09:43:08.006Z" },
|
|
96
|
+
{ url = "https://files.pythonhosted.org/packages/5c/49/0e1237c4400bec7e335d2c4eeb49bc40d9fd88a9ac44ca9083ce1abdc308/pyside6_essentials-6.11.1-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:e3ef7027b41e4e55fadb56e3b3257dc8ee92154b639fe67fc4c8e05e9d976c60", size = 79908535, upload-time = "2026-05-13T09:43:24.836Z" },
|
|
97
|
+
{ url = "https://files.pythonhosted.org/packages/4c/c5/da4c5f23c6540ac5211a1f60177c8dee84b1bf40f2719479587ab8c60731/pyside6_essentials-6.11.1-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:a039b6da68a3a4b9d243217b2b98d475eed3f617159ef6be925badab53c11b0d", size = 78960051, upload-time = "2026-05-13T09:43:35.423Z" },
|
|
98
|
+
{ url = "https://files.pythonhosted.org/packages/64/0e/b663ecc96ca57b5c91b83b6615d6b174380b0faf30338125c26e053d6aa7/pyside6_essentials-6.11.1-cp310-abi3-win_amd64.whl", hash = "sha256:63311bd48e32c584599ab04b9ef7c324082374cd2c9fa533f978fb893bb47e40", size = 77549267, upload-time = "2026-05-13T09:43:44.92Z" },
|
|
99
|
+
{ url = "https://files.pythonhosted.org/packages/f1/12/eb6723faf5cb7fa581145da1c15f40d641b96e080f0491af2f1859fdeedb/pyside6_essentials-6.11.1-cp310-abi3-win_arm64.whl", hash = "sha256:11253ea52aabecefe9febddbbe78b43a824129e3af1cec98431028fba7fa954f", size = 57964512, upload-time = "2026-05-13T09:43:52.968Z" },
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
[[package]]
|
|
103
|
+
name = "ruff"
|
|
104
|
+
version = "0.15.14"
|
|
105
|
+
source = { registry = "https://pypi.org/simple" }
|
|
106
|
+
sdist = { url = "https://files.pythonhosted.org/packages/dc/8a/8bce2894573e9dae6ff4d77fe34ad727d79b9e6238ad288c5638990d90f6/ruff-0.15.14.tar.gz", hash = "sha256:48e866b165be4a9bdbf310f7d3c9a07edef2fe8cd63ffeb4e00bb590506ebf9f", size = 4700910, upload-time = "2026-05-21T14:34:55.177Z" }
|
|
107
|
+
wheels = [
|
|
108
|
+
{ url = "https://files.pythonhosted.org/packages/b9/c8/74a92c6ff9fcfb4f1f947126d3ebee8389276e161ecc85de5bda7cda51bd/ruff-0.15.14-py3-none-linux_armv6l.whl", hash = "sha256:8dd2db9416e487c8d4b01fa7056bb02c4d05969d4f8d17a08c229c2f4ff3c108", size = 10739177, upload-time = "2026-05-21T14:34:37.332Z" },
|
|
109
|
+
{ url = "https://files.pythonhosted.org/packages/45/91/254a35c20acc38a7223c9d2d594af12e794432464f2cdeb52af1dc4a892d/ruff-0.15.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:be4ff55af755bd71a00ab3dc6bd7ffc467bd76e0df6881e286c2e3d23e8fb43b", size = 11144969, upload-time = "2026-05-21T14:34:43.978Z" },
|
|
110
|
+
{ url = "https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:48d5909d7d06276ce7dde6d32bfa4b0d4cb2651145cd8ee4b440722cbc77832f", size = 10478207, upload-time = "2026-05-21T14:34:48.378Z" },
|
|
111
|
+
{ url = "https://files.pythonhosted.org/packages/8d/f1/b15a7839fa4f332f8acec78e20564f26bb2d866e3d21710b877fd0263000/ruff-0.15.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca8cbfa94c4f90984a67561978602746d4cd27103568f745fa90eee3f0d4107d", size = 10818459, upload-time = "2026-05-21T14:34:22.318Z" },
|
|
112
|
+
{ url = "https://files.pythonhosted.org/packages/45/33/53d651177f84f94b400a0e27f8824eeada3dddc9d5ee8aeb048f4352a520/ruff-0.15.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a6bbc0333f1ab053423bcbf6226477d266ca7cec7738c4c8e3f55647803f3c4", size = 10541800, upload-time = "2026-05-21T14:34:20.209Z" },
|
|
113
|
+
{ url = "https://files.pythonhosted.org/packages/b8/a6/868f87e0bf9786ed24b5d0d0ad8676b8a94fd1912f42cddf9cfc7857818a/ruff-0.15.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a24a4f7605d7003a6674d4387651effd939dead3fddd0f36561eb77a9a2e542", size = 11342149, upload-time = "2026-05-21T14:34:46.365Z" },
|
|
114
|
+
{ url = "https://files.pythonhosted.org/packages/a7/8b/38cd5c19faffdcc05a408d2b78edccc69492ab9720eadb49ea15ef80d768/ruff-0.15.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:049b5326e53ed80978f2fc041a280603f69dd6b0c95464342a2bb4572d9d9e2f", size = 12212563, upload-time = "2026-05-21T14:34:28.579Z" },
|
|
115
|
+
{ url = "https://files.pythonhosted.org/packages/3e/4d/a3c5b874a556d5731e3e657aaf04311bb76f0a5c3ec220ed43051be6b64b/ruff-0.15.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4ed42e6696c8dfa5f06728e6441993901f548eb92d73bc472cb5a38d1395fbf", size = 11493299, upload-time = "2026-05-21T14:34:41.836Z" },
|
|
116
|
+
{ url = "https://files.pythonhosted.org/packages/1e/c0/56472c251d09858a53e51efbd485b09e1995d8731668b76d52e5dd6ee0f1/ruff-0.15.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715c543cf450c4888251f91c52f1942a800541d9bddd7ac060aa4e6b77ae7cba", size = 11455931, upload-time = "2026-05-21T14:34:57.276Z" },
|
|
117
|
+
{ url = "https://files.pythonhosted.org/packages/2c/4a/e2e7b4d8dbf233d4eace59c75bc3435fa6d8bd3bae82d351d4e4300c0fd1/ruff-0.15.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ebab6013ec887d439d8b7593737a0a4ffb06d45d209d4e4bf2e92813082d3f", size = 11400794, upload-time = "2026-05-21T14:34:39.773Z" },
|
|
118
|
+
{ url = "https://files.pythonhosted.org/packages/97/c7/83c0539fe34c3e09136204d1e75d6052492364e0b3cb05e9465423f567d7/ruff-0.15.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:49072d36abdbe97a8dd7f480afe9c675699c0c495d4c84076e2c1203c4550581", size = 10804759, upload-time = "2026-05-21T14:34:31.045Z" },
|
|
119
|
+
{ url = "https://files.pythonhosted.org/packages/86/a6/18f2bfc095a2ab4a78745644e428205532ce6653a5d0fa8501572891534d/ruff-0.15.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:958522aee105068640c2c2ceae08f413ae44d922f52a1374ac13d6a96032fc93", size = 10539517, upload-time = "2026-05-21T14:34:53.064Z" },
|
|
120
|
+
{ url = "https://files.pythonhosted.org/packages/54/3a/5a8b3b69c654d4e4bf1d246ac5b49cbcdac6eaab6905925f8915f31e3b80/ruff-0.15.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f3707da619a143a2e8830e2abab8224478d69ace2d28cb6c20543ae97c36bf61", size = 11065169, upload-time = "2026-05-21T14:34:24.484Z" },
|
|
121
|
+
{ url = "https://files.pythonhosted.org/packages/ed/c5/8864e4e7925b836ea354b31d57641ec03830564e281a8b6f061f8c3e0ec1/ruff-0.15.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bb01d645694e3ec0102105d07ef2d53703970407d59c04e59d3ba0b7a1d53553", size = 11560214, upload-time = "2026-05-21T14:34:50.975Z" },
|
|
122
|
+
{ url = "https://files.pythonhosted.org/packages/36/38/012bf76752e1f89ed50b77b99532d90f3a3e287bc7918e1fc0948ac866ac/ruff-0.15.14-py3-none-win32.whl", hash = "sha256:6d0c1ad2a0ab718d39b6d8fd2217981ce4d625cd96a720095f798fb47d8b13e6", size = 10805548, upload-time = "2026-05-21T14:34:33.453Z" },
|
|
123
|
+
{ url = "https://files.pythonhosted.org/packages/d1/b7/4ea2c170f10ad760fff2a5250beb18897719dc8b52b53a24cddbb9dd3f19/ruff-0.15.14-py3-none-win_amd64.whl", hash = "sha256:802342981e056db3851a7836e5b070f8f15f67d4a685ae2a6160939d364b2902", size = 11939523, upload-time = "2026-05-21T14:34:18.077Z" },
|
|
124
|
+
{ url = "https://files.pythonhosted.org/packages/62/d5/bc97ff895ec35cf3925d4bd60f3b39d822f377a446906ec9bcc87405e59b/ruff-0.15.14-py3-none-win_arm64.whl", hash = "sha256:ff47b90a9ef6a40c9e2f3b479c1fb78531adf055b94c1eba0a7ba04b31951826", size = 11208607, upload-time = "2026-05-21T14:34:26.525Z" },
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
[[package]]
|
|
128
|
+
name = "shiboken6"
|
|
129
|
+
version = "6.11.1"
|
|
130
|
+
source = { registry = "https://pypi.org/simple" }
|
|
131
|
+
wheels = [
|
|
132
|
+
{ url = "https://files.pythonhosted.org/packages/17/f3/f2b63df0251e7cd3172ea28e32ede52739de9566bcefcd0178681538ac81/shiboken6-6.11.1-cp310-abi3-macosx_13_0_universal2.whl", hash = "sha256:1a16867f103ef1c662a5f09dfed03273a9f81688b174555162c58e83650a3f02", size = 476874, upload-time = "2026-05-13T09:47:01.091Z" },
|
|
133
|
+
{ url = "https://files.pythonhosted.org/packages/c7/9b/e0355d8897b5c150770f1d95718aad17d432fcc9c035c04f3f58427d4693/shiboken6-6.11.1-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9a8bccfafc8805254cabcfa1edfaf55cd52889f4998c91ad0d9a4433fb1bcdbe", size = 272222, upload-time = "2026-05-13T09:47:02.653Z" },
|
|
134
|
+
{ url = "https://files.pythonhosted.org/packages/57/d5/dd4f1defed400be03340f2ede34b61f846776650b4e7ed9ebaf4c71979a2/shiboken6-6.11.1-cp310-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:1bd2f4314414df2d122d9f646e03b731bc6d6b5f77a5f53f99a4fe4e97d84e6f", size = 270350, upload-time = "2026-05-13T09:47:04.02Z" },
|
|
135
|
+
{ url = "https://files.pythonhosted.org/packages/52/b5/3f6fb2ee65b534193fb4ef713dd619dc31dadff5d12c16979a7699ad58be/shiboken6-6.11.1-cp310-abi3-win_amd64.whl", hash = "sha256:c2c6863aa80ec18c0f82cea3417837b279cdc60024ac17123461dc9042577df7", size = 1223647, upload-time = "2026-05-13T09:47:05.924Z" },
|
|
136
|
+
{ url = "https://files.pythonhosted.org/packages/98/d1/f15ca0e1666faae02c945f48e745ea35f8fcd8243b176109b4e2c4251f47/shiboken6-6.11.1-cp310-abi3-win_arm64.whl", hash = "sha256:7c8d9af17db4495d4fa5b1c393f218311c4855546b9dfa6a0bd21bcd66b55e9d", size = 1784170, upload-time = "2026-05-13T09:47:07.617Z" },
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
[[package]]
|
|
140
|
+
name = "ty"
|
|
141
|
+
version = "0.0.39"
|
|
142
|
+
source = { registry = "https://pypi.org/simple" }
|
|
143
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d0/8d/7b5c74dc287fbcb37bae9853cec13bf44717c1735298500e4aeba31579a9/ty-0.0.39.tar.gz", hash = "sha256:f750277e76a01ecd86185960eca73823c26a53c51103568d56d4d904575159fd", size = 5702365, upload-time = "2026-05-22T21:09:56.403Z" }
|
|
144
|
+
wheels = [
|
|
145
|
+
{ url = "https://files.pythonhosted.org/packages/08/17/9b89802c26d12d0f7a27bc25d4066d941d42891e8898f9f26499f0067e32/ty-0.0.39-py3-none-linux_armv6l.whl", hash = "sha256:c1bb7ac70f1f7d70cc6655fd96558039e4562b10f489fa49c7ebfd5fcee73ad1", size = 11360431, upload-time = "2026-05-22T21:09:18.689Z" },
|
|
146
|
+
{ url = "https://files.pythonhosted.org/packages/9c/c6/663ded50e823dbf9fb9d002eca46b7cb1fb2c72b744b84f22ce732a0ee0b/ty-0.0.39-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3435b64c1e59c14c9aa39c20cc018823937cd38d55db853e74d95b8f420569b0", size = 11096281, upload-time = "2026-05-22T21:09:15.383Z" },
|
|
147
|
+
{ url = "https://files.pythonhosted.org/packages/8b/ae/5d38ba9a6456ff4c78d212cf464fd8b9a25d8118465197b0b2dc891c0b19/ty-0.0.39-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5f136377ce46c73677701a9e1ad730bf72f699bcec046e422eb79d0886cac3ab", size = 10529674, upload-time = "2026-05-22T21:09:46.471Z" },
|
|
148
|
+
{ url = "https://files.pythonhosted.org/packages/be/6f/43638cb8106445d3c8817256a0731cde9dd7b6a53ae2e881294bc1930ca3/ty-0.0.39-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36b65fb0cc17f03e851d40e210d420be94ab8bc52d041328ad1e45f616036a61", size = 11055561, upload-time = "2026-05-22T21:09:36.981Z" },
|
|
149
|
+
{ url = "https://files.pythonhosted.org/packages/91/17/95e62cf4458527ce78dc386eba18f8b10c3fb64cd8c9e7e59b262ff6029d/ty-0.0.39-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4967967bfadf3860ff84c3fccdbaec8edf8aa20d0d727521084733d853de6657", size = 11127185, upload-time = "2026-05-22T21:09:31.395Z" },
|
|
150
|
+
{ url = "https://files.pythonhosted.org/packages/4e/c0/93666c213db5c71ab1b1f1a0db5f66bf8c7c0e0b0bf59859f5da8f0b3c36/ty-0.0.39-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e10ecb1297099ddf9a1f054f8bd921d1863ce85fb819a3c96ed27865a1ba6ed", size = 11608459, upload-time = "2026-05-22T21:09:12.862Z" },
|
|
151
|
+
{ url = "https://files.pythonhosted.org/packages/79/85/3b26585afc8b50230d6464bb0642feef4fab3f847e38b1f0ffa971a81446/ty-0.0.39-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9b19cca70e465d71b0510656343883d62372bbe74b7845cae7c0e701d6d5264b", size = 12177101, upload-time = "2026-05-22T21:09:40.519Z" },
|
|
152
|
+
{ url = "https://files.pythonhosted.org/packages/49/4a/1039e4f6afc576dc1c3a4d22a6478904a1ad3766597cd0b93c077ab9dfce/ty-0.0.39-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:56c6704b01b9b3d80ff26b2918423b742516d1e469bef830e9254dcedc9185bf", size = 11827815, upload-time = "2026-05-22T21:09:49.89Z" },
|
|
153
|
+
{ url = "https://files.pythonhosted.org/packages/e2/c5/4688652870e350a76a8157f7ffb59ad54f37d5d10725aa7076f66ac94ec8/ty-0.0.39-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b7840ff46764b6a6757f4ade1cd0530fc3e8a0b435ca93e7602360e4cb90b6", size = 11694429, upload-time = "2026-05-22T21:09:21.568Z" },
|
|
154
|
+
{ url = "https://files.pythonhosted.org/packages/fc/72/8a1c4e823bb5bdc935a1c8140e100304e36a68a4139592f170aa9736fdb7/ty-0.0.39-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1c62a3a87ce26b50819f0dbf03bd95f23f19eeb87bbc7aa732ec64277c77f1aa", size = 11869846, upload-time = "2026-05-22T21:09:28.053Z" },
|
|
155
|
+
{ url = "https://files.pythonhosted.org/packages/17/9f/cf982457b861ae22d657c5dcdbc631199f7f90264279db1d17230dfbc3ff/ty-0.0.39-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f8c34bc81a9c3516e49904e9d8330aac385377cca98390193ea02b903a40fcf0", size = 11029763, upload-time = "2026-05-22T21:09:06.791Z" },
|
|
156
|
+
{ url = "https://files.pythonhosted.org/packages/46/c9/95b64f6d43ae6e8f0b7e13dacf9c196d35819af22b1924171fba31383156/ty-0.0.39-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:66f5ab11586a64e79cb692ad685ee5469325c31b5f30bd3554f52f36dbe28cc4", size = 11146761, upload-time = "2026-05-22T21:09:10.178Z" },
|
|
157
|
+
{ url = "https://files.pythonhosted.org/packages/52/69/0a89cfb06f7632a05bf56c78e0affb4a40f81759e275376cea75c9c5abe9/ty-0.0.39-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e8d89732bcbbcb091f439e556dfc4932f198b118b47d5b85212c60662099670e", size = 11281843, upload-time = "2026-05-22T21:09:34.234Z" },
|
|
158
|
+
{ url = "https://files.pythonhosted.org/packages/0e/53/64c4a27067a46643fea2b3fcf21a8a2f838d91a65ffdd14f2e82945b9538/ty-0.0.39-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:eceb6c91dcd05a231119f82abdd9aa337513de23ca6ac990bc44f88791dc1799", size = 11792477, upload-time = "2026-05-22T21:09:24.923Z" },
|
|
159
|
+
{ url = "https://files.pythonhosted.org/packages/1a/e8/02f4dd4a12bcdbda0006f9c7ff3b99a4be06bd0d257d3bd4a5b66de074e6/ty-0.0.39-py3-none-win32.whl", hash = "sha256:891c3262314dbc80bf3e872634d23dd216306945daa9a9fcc206ce5ed21ac4c9", size = 10615377, upload-time = "2026-05-22T21:09:43.167Z" },
|
|
160
|
+
{ url = "https://files.pythonhosted.org/packages/b5/5a/aaeb22faa8d4dae90a287d4c3636c671edcff3b99be5f4fc8b79ad71eef6/ty-0.0.39-py3-none-win_amd64.whl", hash = "sha256:ba7f2d54452535419e90f6f03ff39282999e87b43c21c00559f6d7ad711a36d5", size = 11710711, upload-time = "2026-05-22T21:09:53.179Z" },
|
|
161
|
+
{ url = "https://files.pythonhosted.org/packages/a3/17/ae7339651bfcaa5f54698c8c70eaf5031baa400ecb67baec31d03a56cbd4/ty-0.0.39-py3-none-win_arm64.whl", hash = "sha256:eb4cf0fefbbfedf9a352597bb2431ebdcb7eb3a595c0f825f228e897a0ec285d", size = 11081409, upload-time = "2026-05-22T21:09:03.741Z" },
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
[[package]]
|
|
165
|
+
name = "wcwidth"
|
|
166
|
+
version = "0.7.0"
|
|
167
|
+
source = { registry = "https://pypi.org/simple" }
|
|
168
|
+
sdist = { url = "https://files.pythonhosted.org/packages/2c/ee/afaf0f85a9a18fe47a67f1e4422ed6cf1fe642f0ae0a2f81166231303c52/wcwidth-0.7.0.tar.gz", hash = "sha256:90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0", size = 182132, upload-time = "2026-05-02T16:04:12.653Z" }
|
|
169
|
+
wheels = [
|
|
170
|
+
{ url = "https://files.pythonhosted.org/packages/41/52/e465037f5375f43533d1a80b6923955201596a99142ed524d77b571a1418/wcwidth-0.7.0-py3-none-any.whl", hash = "sha256:5d69154c429a82910e241c738cd0e2976fac8a2dd47a1a805f4afed1c0f136f2", size = 110825, upload-time = "2026-05-02T16:04:11.033Z" },
|
|
171
|
+
]
|