debian-policy-checker 0.1.3__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.
- debian_policy_checker-0.1.3/LICENSE +17 -0
- debian_policy_checker-0.1.3/PKG-INFO +10 -0
- debian_policy_checker-0.1.3/README.md +20 -0
- debian_policy_checker-0.1.3/pyproject.toml +18 -0
- debian_policy_checker-0.1.3/setup.cfg +4 -0
- debian_policy_checker-0.1.3/src/debian_policy_checker/__init__.py +1 -0
- debian_policy_checker-0.1.3/src/debian_policy_checker/__main__.py +1 -0
- debian_policy_checker-0.1.3/src/debian_policy_checker/accessibility.py +66 -0
- debian_policy_checker-0.1.3/src/debian_policy_checker/main.py +385 -0
- debian_policy_checker-0.1.3/src/debian_policy_checker.egg-info/PKG-INFO +10 -0
- debian_policy_checker-0.1.3/src/debian_policy_checker.egg-info/SOURCES.txt +13 -0
- debian_policy_checker-0.1.3/src/debian_policy_checker.egg-info/dependency_links.txt +1 -0
- debian_policy_checker-0.1.3/src/debian_policy_checker.egg-info/entry_points.txt +2 -0
- debian_policy_checker-0.1.3/src/debian_policy_checker.egg-info/requires.txt +1 -0
- debian_policy_checker-0.1.3/src/debian_policy_checker.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
GNU GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 3, 29 June 2007
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2026 Daniel Nylander <daniel@danielnylander.se>
|
|
5
|
+
|
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU General Public License as published by
|
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
10
|
+
|
|
11
|
+
This program is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU General Public License for more details.
|
|
15
|
+
|
|
16
|
+
You should have received a copy of the GNU General Public License
|
|
17
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: debian-policy-checker
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: GTK4 Debian Policy Manual validator with fix suggestions
|
|
5
|
+
Author-email: Daniel Nylander <daniel@danielnylander.se>
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: PyGObject>=3.42
|
|
10
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Debian Policy Checker
|
|
2
|
+
|
|
3
|
+
Validate packages against the Debian Policy Manual with fix suggestions.
|
|
4
|
+
|
|
5
|
+
Built with GTK4/Adwaita. Part of the [Danne L10n Suite](https://github.com/yeager/debian-repo).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
### Debian/Ubuntu
|
|
10
|
+
```bash
|
|
11
|
+
sudo apt install debian-policy-checker
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## License
|
|
15
|
+
|
|
16
|
+
GPL-3.0
|
|
17
|
+
|
|
18
|
+
## Author
|
|
19
|
+
|
|
20
|
+
Daniel Nylander — [danielnylander.se](https://danielnylander.se)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "debian-policy-checker"
|
|
7
|
+
version = "0.1.3"
|
|
8
|
+
description = "GTK4 Debian Policy Manual validator with fix suggestions"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
license = "GPL-3.0-or-later"
|
|
11
|
+
authors = [{name = "Daniel Nylander", email = "daniel@danielnylander.se"}]
|
|
12
|
+
dependencies = ["PyGObject>=3.42"]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
debian-policy-checker = "debian_policy_checker.main:main"
|
|
16
|
+
|
|
17
|
+
[tool.setuptools.packages.find]
|
|
18
|
+
where = ["src"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.1"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .main import main
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Accessibility features: zoom, high contrast, ATK."""
|
|
2
|
+
import gi
|
|
3
|
+
gi.require_version('Gtk', '4.0')
|
|
4
|
+
from gi.repository import Gtk, Gdk, Gio
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AccessibilityManager:
|
|
8
|
+
"""Manages accessibility features for a GTK4 window."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, window, app=None):
|
|
11
|
+
self._window = window
|
|
12
|
+
self._app = app or window.get_application()
|
|
13
|
+
self._font_scale = 1.0
|
|
14
|
+
self._high_contrast = False
|
|
15
|
+
self._css = Gtk.CssProvider()
|
|
16
|
+
Gtk.StyleContext.add_provider_for_display(
|
|
17
|
+
Gdk.Display.get_default(), self._css,
|
|
18
|
+
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + 1
|
|
19
|
+
)
|
|
20
|
+
self._setup_actions()
|
|
21
|
+
|
|
22
|
+
def _setup_actions(self):
|
|
23
|
+
if self._app is None:
|
|
24
|
+
return
|
|
25
|
+
actions = [
|
|
26
|
+
('zoom-in', self._zoom_in, ['<Control>plus', '<Control>equal']),
|
|
27
|
+
('zoom-out', self._zoom_out, ['<Control>minus']),
|
|
28
|
+
('zoom-reset', self._zoom_reset, ['<Control>0']),
|
|
29
|
+
('toggle-high-contrast', self._toggle_hc, ['<Control><Shift>h']),
|
|
30
|
+
]
|
|
31
|
+
for name, cb, accels in actions:
|
|
32
|
+
if not self._app.lookup_action(name):
|
|
33
|
+
action = Gio.SimpleAction.new(name, None)
|
|
34
|
+
action.connect('activate', lambda a, p, c=cb: c())
|
|
35
|
+
self._app.add_action(action)
|
|
36
|
+
self._app.set_accels_for_action(f'app.{name}', accels)
|
|
37
|
+
|
|
38
|
+
def _apply_css(self):
|
|
39
|
+
css = f'window {{ font-size: {self._font_scale}em; }}'
|
|
40
|
+
if self._high_contrast:
|
|
41
|
+
css += """
|
|
42
|
+
window.high-contrast {
|
|
43
|
+
border: 2px solid @accent_color;
|
|
44
|
+
font-weight: bold;
|
|
45
|
+
}"""
|
|
46
|
+
self._css.load_from_string(css.encode())
|
|
47
|
+
|
|
48
|
+
def _zoom_in(self):
|
|
49
|
+
self._font_scale = min(self._font_scale + 0.1, 3.0)
|
|
50
|
+
self._apply_css()
|
|
51
|
+
|
|
52
|
+
def _zoom_out(self):
|
|
53
|
+
self._font_scale = max(self._font_scale - 0.1, 0.5)
|
|
54
|
+
self._apply_css()
|
|
55
|
+
|
|
56
|
+
def _zoom_reset(self):
|
|
57
|
+
self._font_scale = 1.0
|
|
58
|
+
self._apply_css()
|
|
59
|
+
|
|
60
|
+
def _toggle_hc(self):
|
|
61
|
+
self._high_contrast = not self._high_contrast
|
|
62
|
+
if self._high_contrast:
|
|
63
|
+
self._window.add_css_class('high-contrast')
|
|
64
|
+
else:
|
|
65
|
+
self._window.remove_css_class('high-contrast')
|
|
66
|
+
self._apply_css()
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"""Debian Policy Checker — Validate packages against Debian Policy Manual."""
|
|
2
|
+
import gi
|
|
3
|
+
gi.require_version("Gtk", "4.0")
|
|
4
|
+
gi.require_version("Adw", "1")
|
|
5
|
+
from gi.repository import Gtk, Adw, Gdk, Gio, GLib, Pango
|
|
6
|
+
|
|
7
|
+
import gettext
|
|
8
|
+
import locale
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import json
|
|
12
|
+
import datetime
|
|
13
|
+
import threading
|
|
14
|
+
import subprocess
|
|
15
|
+
import re
|
|
16
|
+
from debian_policy_checker.accessibility import AccessibilityManager
|
|
17
|
+
|
|
18
|
+
LOCALE_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "po")
|
|
19
|
+
if not os.path.isdir(LOCALE_DIR):
|
|
20
|
+
LOCALE_DIR = "/usr/share/locale"
|
|
21
|
+
locale.bindtextdomain("debian-policy-checker", LOCALE_DIR)
|
|
22
|
+
gettext.bindtextdomain("debian-policy-checker", LOCALE_DIR)
|
|
23
|
+
gettext.textdomain("debian-policy-checker")
|
|
24
|
+
_ = gettext.gettext
|
|
25
|
+
|
|
26
|
+
APP_ID = "se.danielnylander.debian.policy.checker"
|
|
27
|
+
SETTINGS_DIR = os.path.join(
|
|
28
|
+
os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")),
|
|
29
|
+
"debian-policy-checker"
|
|
30
|
+
)
|
|
31
|
+
SETTINGS_FILE = os.path.join(SETTINGS_DIR, "settings.json")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _load_settings():
|
|
35
|
+
if os.path.exists(SETTINGS_FILE):
|
|
36
|
+
with open(SETTINGS_FILE) as f:
|
|
37
|
+
return json.load(f)
|
|
38
|
+
return {"welcome_shown": False}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _save_settings(s):
|
|
42
|
+
os.makedirs(SETTINGS_DIR, exist_ok=True)
|
|
43
|
+
with open(SETTINGS_FILE, "w") as f:
|
|
44
|
+
json.dump(s, f, indent=2)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _run_lintian(path):
|
|
49
|
+
"""Run lintian on a .deb or source directory."""
|
|
50
|
+
results = []
|
|
51
|
+
try:
|
|
52
|
+
r = subprocess.run(["lintian", "--display-info", "--display-experimental",
|
|
53
|
+
"--pedantic", path],
|
|
54
|
+
capture_output=True, text=True, timeout=120)
|
|
55
|
+
for line in r.stdout.splitlines():
|
|
56
|
+
m = re.match(r'(\w):\s+(\S+):\s+(\S+)\s+(.*)', line)
|
|
57
|
+
if m:
|
|
58
|
+
results.append({
|
|
59
|
+
"severity": m.group(1),
|
|
60
|
+
"package": m.group(2),
|
|
61
|
+
"tag": m.group(3),
|
|
62
|
+
"detail": m.group(4),
|
|
63
|
+
})
|
|
64
|
+
else:
|
|
65
|
+
results.append({"severity": "I", "package": "", "tag": line, "detail": ""})
|
|
66
|
+
except FileNotFoundError:
|
|
67
|
+
results.append({"severity": "E", "tag": "lintian-not-installed",
|
|
68
|
+
"detail": _("Install with: sudo apt install lintian"), "package": ""})
|
|
69
|
+
except Exception as e:
|
|
70
|
+
results.append({"severity": "E", "tag": "error", "detail": str(e), "package": ""})
|
|
71
|
+
return results
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class DebianPolicyCheckerWindow(Adw.ApplicationWindow):
|
|
76
|
+
def __init__(self, app):
|
|
77
|
+
super().__init__(application=app, title=_("Debian Policy Checker"), default_width=1000, default_height=700)
|
|
78
|
+
self.settings = _load_settings()
|
|
79
|
+
self._results = []
|
|
80
|
+
|
|
81
|
+
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
82
|
+
|
|
83
|
+
# Header
|
|
84
|
+
headerbar = Adw.HeaderBar()
|
|
85
|
+
title_widget = Adw.WindowTitle(title=_("Debian Policy Checker"), subtitle="")
|
|
86
|
+
headerbar.set_title_widget(title_widget)
|
|
87
|
+
self._title_widget = title_widget
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
open_btn = Gtk.Button(icon_name="document-open-symbolic", tooltip_text=_("Open .deb or package directory"))
|
|
91
|
+
open_btn.connect("clicked", self._on_open)
|
|
92
|
+
headerbar.pack_start(open_btn)
|
|
93
|
+
|
|
94
|
+
# Menu
|
|
95
|
+
menu = Gio.Menu()
|
|
96
|
+
menu.append(_("Settings"), "app.settings")
|
|
97
|
+
menu.append(_("Copy Debug Info"), "app.copy-debug")
|
|
98
|
+
menu.append(_("Keyboard Shortcuts"), "app.shortcuts")
|
|
99
|
+
menu.append(_("About Debian Policy Checker"), "app.about")
|
|
100
|
+
menu_btn = Gtk.MenuButton(icon_name="open-menu-symbolic", menu_model=menu)
|
|
101
|
+
headerbar.pack_end(menu_btn)
|
|
102
|
+
|
|
103
|
+
main_box.append(headerbar)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
scroll = Gtk.ScrolledWindow(vexpand=True)
|
|
107
|
+
self._list = Gtk.ListBox()
|
|
108
|
+
self._list.set_selection_mode(Gtk.SelectionMode.NONE)
|
|
109
|
+
self._list.add_css_class("boxed-list")
|
|
110
|
+
self._list.set_margin_start(12)
|
|
111
|
+
self._list.set_margin_end(12)
|
|
112
|
+
self._list.set_margin_top(8)
|
|
113
|
+
self._list.set_margin_bottom(8)
|
|
114
|
+
scroll.set_child(self._list)
|
|
115
|
+
|
|
116
|
+
self._empty = Adw.StatusPage()
|
|
117
|
+
self._empty.set_icon_name("dialog-information-symbolic")
|
|
118
|
+
self._empty.set_title(_("No package checked"))
|
|
119
|
+
self._empty.set_description(_("Open a .deb file or package directory to check policy compliance."))
|
|
120
|
+
self._empty.set_vexpand(True)
|
|
121
|
+
|
|
122
|
+
self._stack = Gtk.Stack()
|
|
123
|
+
self._stack.add_named(self._empty, "empty")
|
|
124
|
+
self._stack.add_named(scroll, "list")
|
|
125
|
+
self._stack.set_vexpand(True)
|
|
126
|
+
main_box.append(self._stack)
|
|
127
|
+
|
|
128
|
+
# Status bar
|
|
129
|
+
self._status = Gtk.Label(label=_("Ready"), xalign=0)
|
|
130
|
+
self._status.set_margin_start(12)
|
|
131
|
+
self._status.set_margin_end(12)
|
|
132
|
+
self._status.set_margin_top(4)
|
|
133
|
+
self._status.set_margin_bottom(4)
|
|
134
|
+
self._status.add_css_class("dim-label")
|
|
135
|
+
main_box.append(self._status)
|
|
136
|
+
|
|
137
|
+
self.set_content(main_box)
|
|
138
|
+
|
|
139
|
+
if not self.settings.get("welcome_shown"):
|
|
140
|
+
GLib.idle_add(self._show_welcome)
|
|
141
|
+
|
|
142
|
+
def _show_welcome(self):
|
|
143
|
+
dialog = Adw.Dialog()
|
|
144
|
+
dialog.set_title(_("Welcome"))
|
|
145
|
+
dialog.set_content_width(420)
|
|
146
|
+
dialog.set_content_height(480)
|
|
147
|
+
|
|
148
|
+
page = Adw.StatusPage()
|
|
149
|
+
page.set_icon_name("dialog-information-symbolic")
|
|
150
|
+
page.set_title(_("Welcome to Debian Policy Checker"))
|
|
151
|
+
page.set_description(_("Check Debian Policy compliance.\n\n"
|
|
152
|
+
"✓ Validate packages against Debian Policy\n"
|
|
153
|
+
"✓ Clear error descriptions with fix suggestions\n"
|
|
154
|
+
"✓ Integrates with lintian output\n"
|
|
155
|
+
"✓ Policy section references\n"
|
|
156
|
+
"✓ Export validation reports"))
|
|
157
|
+
|
|
158
|
+
btn = Gtk.Button(label=_("Get Started"))
|
|
159
|
+
btn.add_css_class("suggested-action")
|
|
160
|
+
btn.add_css_class("pill")
|
|
161
|
+
btn.set_halign(Gtk.Align.CENTER)
|
|
162
|
+
btn.set_margin_top(12)
|
|
163
|
+
btn.connect("clicked", self._on_welcome_close, dialog)
|
|
164
|
+
page.set_child(btn)
|
|
165
|
+
|
|
166
|
+
box = Adw.ToolbarView()
|
|
167
|
+
hb = Adw.HeaderBar()
|
|
168
|
+
hb.set_show_title(False)
|
|
169
|
+
box.add_top_bar(hb)
|
|
170
|
+
box.set_content(page)
|
|
171
|
+
dialog.set_child(box)
|
|
172
|
+
dialog.present(self)
|
|
173
|
+
|
|
174
|
+
def _on_welcome_close(self, btn, dialog):
|
|
175
|
+
self.settings["welcome_shown"] = True
|
|
176
|
+
_save_settings(self.settings)
|
|
177
|
+
dialog.close()
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _on_open(self, btn):
|
|
181
|
+
dialog = Gtk.FileDialog()
|
|
182
|
+
dialog.set_title(_("Open package"))
|
|
183
|
+
dialog.open(self, None, self._on_file_opened)
|
|
184
|
+
|
|
185
|
+
def _on_file_opened(self, dialog, result):
|
|
186
|
+
try:
|
|
187
|
+
f = dialog.open_finish(result)
|
|
188
|
+
path = f.get_path()
|
|
189
|
+
self._status.set_text(_("Checking %s...") % path)
|
|
190
|
+
self._title_widget.set_subtitle(os.path.basename(path))
|
|
191
|
+
threading.Thread(target=self._do_check, args=(path,), daemon=True).start()
|
|
192
|
+
except:
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
def _do_check(self, path):
|
|
196
|
+
results = _run_lintian(path)
|
|
197
|
+
GLib.idle_add(self._show_results, results)
|
|
198
|
+
|
|
199
|
+
def _show_results(self, results):
|
|
200
|
+
self._results = results
|
|
201
|
+
while True:
|
|
202
|
+
row = self._list.get_row_at_index(0)
|
|
203
|
+
if row is None:
|
|
204
|
+
break
|
|
205
|
+
self._list.remove(row)
|
|
206
|
+
|
|
207
|
+
severity_icons = {"E": "❌", "W": "⚠️", "I": "ℹ️", "N": "📝", "P": "🔍"}
|
|
208
|
+
severity_names = {"E": _("Error"), "W": _("Warning"), "I": _("Info"), "N": _("Note"), "P": _("Pedantic")}
|
|
209
|
+
|
|
210
|
+
errors = sum(1 for r in results if r["severity"] == "E")
|
|
211
|
+
warnings = sum(1 for r in results if r["severity"] == "W")
|
|
212
|
+
|
|
213
|
+
for r in results:
|
|
214
|
+
icon = severity_icons.get(r["severity"], "❓")
|
|
215
|
+
sev = severity_names.get(r["severity"], r["severity"])
|
|
216
|
+
row = Adw.ActionRow()
|
|
217
|
+
row.set_title(f"{icon} {r['tag']}")
|
|
218
|
+
row.set_subtitle(r.get("detail", ""))
|
|
219
|
+
badge = Gtk.Label(label=sev)
|
|
220
|
+
badge.add_css_class("caption")
|
|
221
|
+
if r["severity"] == "E":
|
|
222
|
+
badge.add_css_class("error")
|
|
223
|
+
row.add_suffix(badge)
|
|
224
|
+
self._list.append(row)
|
|
225
|
+
|
|
226
|
+
self._stack.set_visible_child_name("list")
|
|
227
|
+
self._status.set_text(_("%(total)d issues: %(errors)d errors, %(warnings)d warnings") %
|
|
228
|
+
{"total": len(results), "errors": errors, "warnings": warnings})
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class DebianPolicyCheckerApp(Adw.Application):
|
|
232
|
+
def __init__(self):
|
|
233
|
+
super().__init__(application_id=APP_ID, flags=Gio.ApplicationFlags.FLAGS_NONE)
|
|
234
|
+
self.window = None
|
|
235
|
+
|
|
236
|
+
for name, callback in [
|
|
237
|
+
("settings", self._on_settings),
|
|
238
|
+
("copy-debug", self._on_copy_debug),
|
|
239
|
+
("shortcuts", self._on_shortcuts),
|
|
240
|
+
("about", self._on_about),
|
|
241
|
+
("quit", self._on_quit),
|
|
242
|
+
]:
|
|
243
|
+
action = Gio.SimpleAction.new(name, None)
|
|
244
|
+
action.connect("activate", callback)
|
|
245
|
+
self.add_action(action)
|
|
246
|
+
|
|
247
|
+
self.set_accels_for_action("app.quit", ["<Ctrl>q"])
|
|
248
|
+
self.set_accels_for_action("app.shortcuts", ["<Ctrl>slash"])
|
|
249
|
+
|
|
250
|
+
def do_activate(self):
|
|
251
|
+
if not self.window:
|
|
252
|
+
self.window = DebianPolicyCheckerWindow(self)
|
|
253
|
+
self.window.present()
|
|
254
|
+
|
|
255
|
+
def _on_settings(self, *_args):
|
|
256
|
+
if not self.window:
|
|
257
|
+
return
|
|
258
|
+
dialog = Adw.PreferencesDialog()
|
|
259
|
+
dialog.set_title(_("Settings"))
|
|
260
|
+
page = Adw.PreferencesPage()
|
|
261
|
+
|
|
262
|
+
group = Adw.PreferencesGroup(title=_("Lintian"))
|
|
263
|
+
row = Adw.SwitchRow(title=_("Show pedantic warnings"))
|
|
264
|
+
row.set_active(True)
|
|
265
|
+
group.add(row)
|
|
266
|
+
row2 = Adw.SwitchRow(title=_("Show experimental tags"))
|
|
267
|
+
row2.set_active(True)
|
|
268
|
+
group.add(row2)
|
|
269
|
+
page.add(group)
|
|
270
|
+
dialog.add(page)
|
|
271
|
+
dialog.present(self.window)
|
|
272
|
+
|
|
273
|
+
def _on_copy_debug(self, *_args):
|
|
274
|
+
if not self.window:
|
|
275
|
+
return
|
|
276
|
+
from . import __version__
|
|
277
|
+
info = (
|
|
278
|
+
f"Debian Policy Checker {__version__}\n"
|
|
279
|
+
f"Python {sys.version}\n"
|
|
280
|
+
f"GTK {Gtk.MAJOR_VERSION}.{Gtk.MINOR_VERSION}\n"
|
|
281
|
+
f"Adw {Adw.MAJOR_VERSION}.{Adw.MINOR_VERSION}\n"
|
|
282
|
+
f"OS: {os.uname().sysname} {os.uname().release}\n"
|
|
283
|
+
)
|
|
284
|
+
clipboard = Gdk.Display.get_default().get_clipboard()
|
|
285
|
+
clipboard.set(info)
|
|
286
|
+
self.window._status.set_text(_("Debug info copied"))
|
|
287
|
+
|
|
288
|
+
def _on_shortcuts(self, *_args):
|
|
289
|
+
if self.window:
|
|
290
|
+
dialog = Gtk.ShortcutsWindow(transient_for=self.window)
|
|
291
|
+
section = Gtk.ShortcutsSection(visible=True)
|
|
292
|
+
group = Gtk.ShortcutsGroup(title=_("General"), visible=True)
|
|
293
|
+
for accel, title in [
|
|
294
|
+
("<Ctrl>q", _("Quit")),
|
|
295
|
+
("<Ctrl>slash", _("Keyboard shortcuts")),
|
|
296
|
+
]:
|
|
297
|
+
group.append(Gtk.ShortcutsShortcut(accelerator=accel, title=title, visible=True))
|
|
298
|
+
section.append(group)
|
|
299
|
+
dialog.append(section)
|
|
300
|
+
dialog.present()
|
|
301
|
+
|
|
302
|
+
def _on_about(self, *_args):
|
|
303
|
+
from . import __version__
|
|
304
|
+
dialog = Adw.AboutDialog(
|
|
305
|
+
application_name=_("Debian Policy Checker"),
|
|
306
|
+
application_icon="dialog-information-symbolic",
|
|
307
|
+
version=__version__,
|
|
308
|
+
developer_name="Daniel Nylander",
|
|
309
|
+
website="https://github.com/yeager/debian-policy-checker",
|
|
310
|
+
license_type=Gtk.License.GPL_3_0,
|
|
311
|
+
issue_url="https://github.com/yeager/debian-policy-checker/issues",
|
|
312
|
+
comments=_("Validate packages against the Debian Policy Manual with clear error reports and fix suggestions."),
|
|
313
|
+
)
|
|
314
|
+
dialog.present(self.window)
|
|
315
|
+
|
|
316
|
+
def _on_quit(self, *_args):
|
|
317
|
+
self.quit()
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def main():
|
|
321
|
+
app = DebianPolicyCheckerApp()
|
|
322
|
+
app.run(sys.argv)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
# --- Session restore ---
|
|
326
|
+
import json as _json
|
|
327
|
+
import os as _os
|
|
328
|
+
|
|
329
|
+
def _save_session(window, app_name):
|
|
330
|
+
config_dir = _os.path.join(_os.path.expanduser('~'), '.config', app_name)
|
|
331
|
+
_os.makedirs(config_dir, exist_ok=True)
|
|
332
|
+
state = {'width': window.get_width(), 'height': window.get_height(),
|
|
333
|
+
'maximized': window.is_maximized()}
|
|
334
|
+
try:
|
|
335
|
+
with open(_os.path.join(config_dir, 'session.json'), 'w') as f:
|
|
336
|
+
_json.dump(state, f)
|
|
337
|
+
except OSError:
|
|
338
|
+
pass
|
|
339
|
+
|
|
340
|
+
def _restore_session(window, app_name):
|
|
341
|
+
path = _os.path.join(_os.path.expanduser('~'), '.config', app_name, 'session.json')
|
|
342
|
+
try:
|
|
343
|
+
with open(path) as f:
|
|
344
|
+
state = _json.load(f)
|
|
345
|
+
window.set_default_size(state.get('width', 800), state.get('height', 600))
|
|
346
|
+
if state.get('maximized'):
|
|
347
|
+
window.maximize()
|
|
348
|
+
except (FileNotFoundError, _json.JSONDecodeError, OSError):
|
|
349
|
+
pass
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
# --- Fullscreen toggle (F11) ---
|
|
353
|
+
def _setup_fullscreen(window, app):
|
|
354
|
+
"""Add F11 fullscreen toggle."""
|
|
355
|
+
from gi.repository import Gio
|
|
356
|
+
if not app.lookup_action('toggle-fullscreen'):
|
|
357
|
+
action = Gio.SimpleAction.new('toggle-fullscreen', None)
|
|
358
|
+
action.connect('activate', lambda a, p: (
|
|
359
|
+
window.unfullscreen() if window.is_fullscreen() else window.fullscreen()
|
|
360
|
+
))
|
|
361
|
+
app.add_action(action)
|
|
362
|
+
app.set_accels_for_action('app.toggle-fullscreen', ['F11'])
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
# --- Plugin system ---
|
|
366
|
+
import importlib.util
|
|
367
|
+
import os as _pos
|
|
368
|
+
|
|
369
|
+
def _load_plugins(app_name):
|
|
370
|
+
"""Load plugins from ~/.config/<app>/plugins/."""
|
|
371
|
+
plugin_dir = _pos.path.join(_pos.path.expanduser('~'), '.config', app_name, 'plugins')
|
|
372
|
+
plugins = []
|
|
373
|
+
if not _pos.path.isdir(plugin_dir):
|
|
374
|
+
return plugins
|
|
375
|
+
for fname in sorted(_pos.listdir(plugin_dir)):
|
|
376
|
+
if fname.endswith('.py') and not fname.startswith('_'):
|
|
377
|
+
path = _pos.path.join(plugin_dir, fname)
|
|
378
|
+
try:
|
|
379
|
+
spec = importlib.util.spec_from_file_location(fname[:-3], path)
|
|
380
|
+
mod = importlib.util.module_from_spec(spec)
|
|
381
|
+
spec.loader.exec_module(mod)
|
|
382
|
+
plugins.append(mod)
|
|
383
|
+
except Exception as e:
|
|
384
|
+
print(f"Plugin {fname}: {e}")
|
|
385
|
+
return plugins
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: debian-policy-checker
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: GTK4 Debian Policy Manual validator with fix suggestions
|
|
5
|
+
Author-email: Daniel Nylander <daniel@danielnylander.se>
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: PyGObject>=3.42
|
|
10
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/debian_policy_checker/__init__.py
|
|
5
|
+
src/debian_policy_checker/__main__.py
|
|
6
|
+
src/debian_policy_checker/accessibility.py
|
|
7
|
+
src/debian_policy_checker/main.py
|
|
8
|
+
src/debian_policy_checker.egg-info/PKG-INFO
|
|
9
|
+
src/debian_policy_checker.egg-info/SOURCES.txt
|
|
10
|
+
src/debian_policy_checker.egg-info/dependency_links.txt
|
|
11
|
+
src/debian_policy_checker.egg-info/entry_points.txt
|
|
12
|
+
src/debian_policy_checker.egg-info/requires.txt
|
|
13
|
+
src/debian_policy_checker.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
PyGObject>=3.42
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
debian_policy_checker
|