Skip to content

Commit 7f70574

Browse files
author
Lukas Geiger
committed
feat: add Windows Store screenshot generator
1 parent bc399a1 commit 7f70574

4 files changed

Lines changed: 348 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/).
88
### Portierung / Platforms
99
- `PORTIERUNGSPLAN.md` dokumentiert die Plattformstrategie: Windows Store zuerst, Web/PWA als gemeinsame Android-/iOS-/Web-Linie, macOS/Linux als Source-Smoke-Ziele.
1010
- `AUFGABEN.txt` enthält konkrete P0-P3-Aufgaben für CLI-Modus, JSON-Export, PWA-Companion und Cross-Platform-Smoke-Tests.
11+
- Windows-Store-P0 abgeschlossen: `_WARTUNG/generate_store_screenshots.py` erzeugt jetzt reproduzierbar `main.png`, `file-analysis.png`, `project-analysis.png`, `duplicate-detection.png` und `manifest.json` unter `releases/windowsstore/screenshots/`.
12+
- `releases/windowsstore/store_settings.json`, `BUILD.md` und die DE/EN-Store-Listings sind auf den realen Projektstand, aktuelle GitHub-URLs und den dokumentierten Pretest-Workflow synchronisiert.
1113

1214
### Hinzugefügt / Added
1315
- GitHub-Actions-Smoke-Matrix prüft den Quellstand jetzt auf Windows (Python 3.10-3.12) sowie zusätzlich auf Ubuntu und macOS (Python 3.11), inklusive Compile-, Tkinter-Import- und `unittest`-Smoke.
@@ -24,6 +26,7 @@ Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/).
2426
- JSON-Export über `--json-output` im Schema `methodenanalyser-report-v1.json`, inklusive Datei-, Projekt- und stdin-Snippet-Analyse.
2527
- `EXPORTFORMAT.md` dokumentiert Top-Level-Felder, `files[]`-Einträge und Stabilitätsregeln für Web/PWA-Companions.
2628
- `tests/test_cli.py` deckt CLI-Erfolg, Findings, Teilfehler und Fehlerpfade per `unittest` ab.
29+
- `tests/test_store_screenshots.py` deckt den Screenshot-Manifest-Pfad für die Store-Artefakte ab.
2730
- README dokumentiert jetzt den GitHub-/Privacy-Hygiene-Check vom 2026-05-16, den synchronen Branch-Stand und die lokalen Artefaktgrenzen.
2831
- README bindet jetzt den vorhandenen GUI-Screenshot aus `README/screenshots/main.png` direkt ein.
2932
- Das Hauptfenster verwendet das lokale `MethodenAnalyser.ico`, wenn es verfügbar ist.

PORTIERUNGSPLAN.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Portierungsplan - MethodenAnalyser
22

3-
Stand: 2026-05-26
4-
Status: CLI-, JSON-Export und lokaler Web/PWA-Companion inklusive ZIP-Upload, Report-Import, Install-Flow und Offline-Shell umgesetzt; Cross-Platform-Smoke-Automation für Windows/macOS/Linux eingerichtet
3+
Stand: 2026-05-27
4+
Status: Windows-Store-P0 mit reproduzierbarem Screenshot-Satz, synchronisierten Store-Settings und dokumentiertem Dogfooding-Pretest abgeschlossen; CLI-, JSON-Export und lokaler Web/PWA-Companion inklusive ZIP-Upload, Report-Import, Install-Flow und Offline-Shell umgesetzt; Cross-Platform-Smoke-Automation für Windows/macOS/Linux eingerichtet
55

66
## Ausgangslage
77

@@ -21,7 +21,7 @@ Die Nachfrage liegt vor allem bei Entwicklerinnen und Entwicklern, die kleine bi
2121

2222
| Plattform | Entscheidung | Begründung | Nächster Schritt |
2323
|---|---|---|---|
24-
| Windows Store | Priorität P0 | Beste Zielgruppe, vorhandene Store-Artefakte, keine externen Laufzeitabhängigkeiten | Store-Screenshots, Listing, Dogfooding-Build und Pre-Submission prüfen |
24+
| Windows Store | Priorität P0 | Beste Zielgruppe, vorhandene Store-Artefakte, keine externen Laufzeitabhängigkeiten | Screenshot-Satz, Listing und Dogfooding-Pretest sind fertig; als Nächstes MSIX-/WACK-Protokoll erneuern |
2525
| Webapp / PWA | Priorität P1 | Gute Demo- und Companion-Linie für Snippets, einzelne Dateien und kleine Uploads | Install-/Offline-Verhalten lokal dogfooden und danach Android-/iOS-Browsertests durchführen |
2626
| Android | P2 über PWA | Native App wäre Mehraufwand ohne klaren Mehrwert; PWA reicht für mobile Kurzchecks | Web Companion auf Android-Browser testen |
2727
| iOS | P2 über PWA | Gleiche Logik wie Android; nativer App-Store-Weg lohnt aktuell nicht | Web Companion auf iOS Safari testen |
@@ -67,12 +67,12 @@ Mindestfelder:
6767

6868
## Umsetzungsreihenfolge
6969

70-
1. P0: Windows-Store-Vorbereitung abschließen.
70+
1. P0: Windows-Store-Vorbereitung abschließen. (erledigt 2026-05-27)
7171
2. P0: CLI-Modus für Datei- und Projektanalyse ergänzen. (erledigt 2026-05-24)
7272
3. P1: JSON-Export `methodenanalyser-report-v1.json` aus Desktop-Kernlogik erzeugen. (erledigt 2026-05-24)
7373
4. P1: PWA-Companion für Snippet-, Einzeldatei- und kleine ZIP-Analyse umsetzen. (erledigt 2026-05-24)
7474
5. P1/P2: PWA-Installierbarkeit, Draft-Persistenz und Offline-Shell lokal absichern. (erledigt 2026-05-24)
75-
6. P2: Android-/iOS-Browsertests für die PWA durchführen. Vorbereitung über LAN-Startpfad, Laufzeit-Hinweise und kopierbare PWA-Testkarte im Companion erledigt 2026-05-26.
75+
6. P2: Android-/iOS-Browsertests für die PWA durchführen. Vorbereitung über LAN-Startpfad, Laufzeit-Hinweise und kopierbare PWA-Testkarte erledigt 2026-05-26.
7676
7. P3: macOS- und Linux-Smoke-Tests für Source-Start automatisieren und dokumentieren. (CI-Vorbereitung erledigt 2026-05-26)
7777

7878
## Nicht-Ziele
@@ -91,5 +91,6 @@ Mindestfelder:
9191
- Web Companion erklärt den LAN-/WLAN-Testpfad für Android und iOS direkt in der Oberfläche.
9292
- Web Companion zeigt eine kopierbare PWA-Testkarte für Install-, Speicher-, Viewport- und Service-Worker-Diagnose an.
9393
- Microsoft-Store-Paket bleibt ohne Netzwerkanforderung nutzbar.
94+
- `releases/windowsstore/screenshots/` enthält einen reproduzierbaren Screenshot-Satz samt Manifest.
9495
- PWA verarbeitet mindestens Snippets, einzelne Python-Dateien und kleine ZIP-Archive.
9596
- GitHub Actions prüft denselben Quellstand jetzt auf Windows (3.10-3.12) sowie zusätzlich auf Ubuntu und macOS (3.11) per Compile-, Tkinter-Import- und `unittest`-Smoke.
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
from __future__ import annotations
2+
3+
import json
4+
import os
5+
import sys
6+
import tempfile
7+
import textwrap
8+
import time
9+
import tkinter as tk
10+
from pathlib import Path
11+
from tkinter import scrolledtext
12+
from typing import Dict, List
13+
14+
from PIL import ImageGrab
15+
16+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
17+
if str(PROJECT_ROOT) not in sys.path:
18+
sys.path.insert(0, str(PROJECT_ROOT))
19+
20+
from MethodenAnalyser3 import (
21+
OUTPUT_FONT,
22+
OUTPUT_HEIGHT,
23+
OUTPUT_WIDTH,
24+
analyze_project,
25+
analyze_source,
26+
generate_project_report,
27+
generate_report,
28+
)
29+
30+
31+
README_SCREENSHOT = PROJECT_ROOT / "README" / "screenshots" / "main.png"
32+
SCREENSHOT_DIR = PROJECT_ROOT / "releases" / "windowsstore" / "screenshots"
33+
MANIFEST_PATH = SCREENSHOT_DIR / "manifest.json"
34+
WINDOW_GEOMETRY = "1440x920"
35+
36+
37+
def _write(path: Path, content: str) -> None:
38+
path.write_text(textwrap.dedent(content).strip() + "\n", encoding="utf-8")
39+
40+
41+
def _build_file_report() -> str:
42+
result = analyze_source(
43+
textwrap.dedent(
44+
"""
45+
import os
46+
import json
47+
48+
def load_settings():
49+
return {"theme": "light"}
50+
51+
def helper_debug():
52+
return os.getcwd()
53+
54+
print(load_settings())
55+
"""
56+
).strip()
57+
+ "\n",
58+
source_name="settings_panel.py",
59+
)
60+
return "Datei-Modus\n\n" + generate_report(result)
61+
62+
63+
def _build_duplicate_report() -> str:
64+
result = analyze_source(
65+
textwrap.dedent(
66+
"""
67+
def normalize_name(value):
68+
cleaned = value.strip().lower()
69+
return cleaned.replace("-", "_")
70+
71+
def normalize_slug(value):
72+
cleaned = value.strip().lower()
73+
return cleaned.replace("-", "_")
74+
75+
print(normalize_name("Demo"))
76+
"""
77+
).strip()
78+
+ "\n",
79+
source_name="duplicate_candidates.py",
80+
)
81+
return "Duplikat-Suche\n\n" + generate_report(result)
82+
83+
84+
def _build_project_report() -> str:
85+
with tempfile.TemporaryDirectory() as tmpdir:
86+
root = Path(tmpdir)
87+
pkg = root / "demo_project"
88+
pkg.mkdir()
89+
_write(
90+
pkg / "main.py",
91+
"""
92+
import math
93+
from helper import area
94+
95+
print(area(2), math.pi)
96+
""",
97+
)
98+
_write(
99+
pkg / "helper.py",
100+
"""
101+
import os
102+
103+
def area(radius):
104+
return radius * radius * 3.14159
105+
106+
def unused_helper():
107+
return os.getcwd()
108+
""",
109+
)
110+
_write(
111+
pkg / "broken.py",
112+
"""
113+
def broken(:
114+
return 1
115+
""",
116+
)
117+
result = analyze_project(str(pkg))
118+
return "Projekt-Modus\n\n" + generate_project_report(result)
119+
120+
121+
def build_scenarios() -> List[Dict[str, str]]:
122+
return [
123+
{
124+
"filename": "file-analysis.png",
125+
"title": "Einzeldateien schnell prüfen",
126+
"subtitle": "AST-basierte Analyse mit ungenutzten Imports und Definitionen",
127+
"report": _build_file_report(),
128+
},
129+
{
130+
"filename": "project-analysis.png",
131+
"title": "Projektüberblick mit Sammelreport",
132+
"subtitle": "Mehrere Python-Dateien inklusive Fehlerdateien gemeinsam auswerten",
133+
"report": _build_project_report(),
134+
},
135+
{
136+
"filename": "duplicate-detection.png",
137+
"title": "Ähnliche Code-Blöcke sichtbar machen",
138+
"subtitle": "Duplikat-Hinweise und Refactoring-Kandidaten im selben Report",
139+
"report": _build_duplicate_report(),
140+
},
141+
]
142+
143+
144+
def _create_window(report: str, title: str, subtitle: str) -> tk.Tk:
145+
root = tk.Tk()
146+
root.title("MethodenAnalyser - Windows Store Screenshots")
147+
root.geometry(WINDOW_GEOMETRY)
148+
root.configure(bg="#eef2f7")
149+
150+
outer = tk.Frame(root, bg="#eef2f7")
151+
outer.pack(fill=tk.BOTH, expand=True, padx=24, pady=24)
152+
153+
hero = tk.Frame(outer, bg="#102235")
154+
hero.pack(fill=tk.X, pady=(0, 18))
155+
tk.Label(
156+
hero,
157+
text="MethodenAnalyser",
158+
font=("Segoe UI", 22, "bold"),
159+
fg="white",
160+
bg="#102235",
161+
anchor="w",
162+
).pack(fill=tk.X, padx=22, pady=(20, 4))
163+
tk.Label(
164+
hero,
165+
text=title,
166+
font=("Segoe UI", 15, "bold"),
167+
fg="#d8e8ff",
168+
bg="#102235",
169+
anchor="w",
170+
).pack(fill=tk.X, padx=22)
171+
tk.Label(
172+
hero,
173+
text=subtitle,
174+
font=("Segoe UI", 11),
175+
fg="#c0d1e5",
176+
bg="#102235",
177+
anchor="w",
178+
).pack(fill=tk.X, padx=22, pady=(6, 18))
179+
180+
button_frame = tk.Frame(outer, bg="#eef2f7")
181+
button_frame.pack(fill=tk.X, pady=(0, 10))
182+
buttons = [
183+
("📂 Datei analysieren", "#2e7d32"),
184+
("ℹ️ Info", "#1565c0"),
185+
("🔧 Auto-Fix Imports", "#ef6c00"),
186+
("Projekt analysieren", "#7b1fa2"),
187+
]
188+
for text, color in buttons:
189+
tk.Button(
190+
button_frame,
191+
text=text,
192+
bg=color,
193+
fg="white",
194+
relief=tk.FLAT,
195+
font=("Segoe UI", 10, "bold"),
196+
padx=18,
197+
pady=10,
198+
).pack(side=tk.LEFT, padx=(0, 8))
199+
200+
output = scrolledtext.ScrolledText(
201+
outer,
202+
width=OUTPUT_WIDTH,
203+
height=OUTPUT_HEIGHT,
204+
font=OUTPUT_FONT,
205+
wrap=tk.WORD,
206+
bg="#f7f9fc",
207+
fg="#1f2933",
208+
bd=0,
209+
padx=16,
210+
pady=16,
211+
)
212+
output.pack(fill=tk.BOTH, expand=True)
213+
output.insert("1.0", report)
214+
output.configure(state=tk.DISABLED)
215+
return root
216+
217+
218+
def _capture(root: tk.Tk, destination: Path) -> None:
219+
root.update_idletasks()
220+
root.update()
221+
root.lift()
222+
root.attributes("-topmost", True)
223+
root.update()
224+
time.sleep(0.6)
225+
left = root.winfo_rootx()
226+
top = root.winfo_rooty()
227+
right = left + root.winfo_width()
228+
bottom = top + root.winfo_height()
229+
image = ImageGrab.grab(bbox=(left, top, right, bottom))
230+
image.save(destination, "PNG")
231+
root.destroy()
232+
233+
234+
def generate_store_screenshots() -> Dict[str, object]:
235+
SCREENSHOT_DIR.mkdir(parents=True, exist_ok=True)
236+
scenarios = build_scenarios()
237+
assets = []
238+
239+
for scenario in scenarios:
240+
target = SCREENSHOT_DIR / scenario["filename"]
241+
root = _create_window(
242+
report=scenario["report"],
243+
title=scenario["title"],
244+
subtitle=scenario["subtitle"],
245+
)
246+
_capture(root, target)
247+
assets.append(
248+
{
249+
"path": target.name,
250+
"title": scenario["title"],
251+
"subtitle": scenario["subtitle"],
252+
}
253+
)
254+
255+
if README_SCREENSHOT.exists():
256+
(SCREENSHOT_DIR / "main.png").write_bytes(README_SCREENSHOT.read_bytes())
257+
assets.insert(
258+
0,
259+
{
260+
"path": "main.png",
261+
"title": "Hauptfenster",
262+
"subtitle": "Die klassische Desktop-Oberfläche für lokale Python-Dateien",
263+
},
264+
)
265+
266+
manifest = {
267+
"generated_at": time.strftime("%Y-%m-%d %H:%M:%S"),
268+
"count": len(assets),
269+
"screenshots": assets,
270+
}
271+
MANIFEST_PATH.write_text(
272+
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
273+
encoding="utf-8",
274+
)
275+
return manifest
276+
277+
278+
def main() -> int:
279+
manifest = generate_store_screenshots()
280+
print(f"{manifest['count']} Screenshots aktualisiert: {SCREENSHOT_DIR}")
281+
return 0
282+
283+
284+
if __name__ == "__main__":
285+
raise SystemExit(main())

tests/test_store_screenshots.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import json
2+
import tempfile
3+
import unittest
4+
from pathlib import Path
5+
from unittest import mock
6+
7+
from _WARTUNG import generate_store_screenshots as shots
8+
9+
10+
class StoreScreenshotTests(unittest.TestCase):
11+
def test_build_scenarios_returns_expected_entries(self) -> None:
12+
scenarios = shots.build_scenarios()
13+
14+
self.assertEqual([entry["filename"] for entry in scenarios], [
15+
"file-analysis.png",
16+
"project-analysis.png",
17+
"duplicate-detection.png",
18+
])
19+
self.assertTrue(all(entry["title"] for entry in scenarios))
20+
self.assertTrue(all("report" in entry and entry["report"] for entry in scenarios))
21+
22+
def test_generate_store_screenshots_writes_manifest(self) -> None:
23+
with tempfile.TemporaryDirectory() as tmpdir:
24+
tmp_path = Path(tmpdir)
25+
screenshot_dir = tmp_path / "screenshots"
26+
manifest_path = screenshot_dir / "manifest.json"
27+
readme_shot = tmp_path / "main.png"
28+
readme_shot.write_bytes(b"png")
29+
touched = []
30+
31+
def fake_capture(_root, destination: Path) -> None:
32+
destination.write_bytes(b"png")
33+
touched.append(destination.name)
34+
35+
with mock.patch.object(shots, "SCREENSHOT_DIR", screenshot_dir), \
36+
mock.patch.object(shots, "MANIFEST_PATH", manifest_path), \
37+
mock.patch.object(shots, "README_SCREENSHOT", readme_shot), \
38+
mock.patch.object(shots, "_create_window", side_effect=lambda **_: object()), \
39+
mock.patch.object(shots, "_capture", side_effect=fake_capture):
40+
manifest = shots.generate_store_screenshots()
41+
payload = json.loads(manifest_path.read_text(encoding="utf-8"))
42+
43+
self.assertEqual(touched, [
44+
"file-analysis.png",
45+
"project-analysis.png",
46+
"duplicate-detection.png",
47+
])
48+
self.assertEqual(manifest["count"], 4)
49+
self.assertEqual(payload["screenshots"][0]["path"], "main.png")
50+
self.assertEqual(payload["screenshots"][1]["path"], "file-analysis.png")
51+
52+
53+
if __name__ == "__main__":
54+
unittest.main()

0 commit comments

Comments
 (0)