qgis-plugin-analyzer 1.3.0__py3-none-any.whl
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.
- __init__.py +19 -0
- analyzer/__init__.py +19 -0
- analyzer/cli.py +311 -0
- analyzer/engine.py +586 -0
- analyzer/fixer.py +314 -0
- analyzer/models/__init__.py +5 -0
- analyzer/models/analysis_models.py +62 -0
- analyzer/reporters/__init__.py +10 -0
- analyzer/reporters/html_reporter.py +388 -0
- analyzer/reporters/markdown_reporter.py +212 -0
- analyzer/reporters/summary_reporter.py +222 -0
- analyzer/rules/__init__.py +10 -0
- analyzer/rules/modernization_rules.py +33 -0
- analyzer/rules/qgis_rules.py +74 -0
- analyzer/scanner.py +794 -0
- analyzer/semantic.py +213 -0
- analyzer/transformers.py +190 -0
- analyzer/utils/__init__.py +39 -0
- analyzer/utils/ast_utils.py +133 -0
- analyzer/utils/config_utils.py +145 -0
- analyzer/utils/logging_utils.py +46 -0
- analyzer/utils/path_utils.py +135 -0
- analyzer/utils/performance_utils.py +150 -0
- analyzer/validators.py +263 -0
- qgis_plugin_analyzer-1.3.0.dist-info/METADATA +239 -0
- qgis_plugin_analyzer-1.3.0.dist-info/RECORD +30 -0
- qgis_plugin_analyzer-1.3.0.dist-info/WHEEL +5 -0
- qgis_plugin_analyzer-1.3.0.dist-info/entry_points.txt +2 -0
- qgis_plugin_analyzer-1.3.0.dist-info/licenses/LICENSE +677 -0
- qgis_plugin_analyzer-1.3.0.dist-info/top_level.txt +2 -0
analyzer/validators.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# /***************************************************************************
|
|
2
|
+
# QGIS Plugin Analyzer
|
|
3
|
+
#
|
|
4
|
+
# Repository compliance validators for QGIS.org policies.
|
|
5
|
+
# ***************************************************************************/
|
|
6
|
+
|
|
7
|
+
import ipaddress
|
|
8
|
+
import pathlib
|
|
9
|
+
import socket
|
|
10
|
+
import urllib.error
|
|
11
|
+
import urllib.parse
|
|
12
|
+
import urllib.request
|
|
13
|
+
from typing import Any, Dict, List
|
|
14
|
+
|
|
15
|
+
# Prohibited binary extensions per QGIS repository policy
|
|
16
|
+
BINARY_EXTENSIONS = {".exe", ".dll", ".so", ".dylib", ".pyd", ".bin", ".a", ".lib"}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def scan_for_binaries(project_path: pathlib.Path, ignore_matcher: Any = None) -> List[str]:
|
|
20
|
+
"""Scans the project for prohibited binary files per QGIS policies.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
project_path: Root path of the project.
|
|
24
|
+
ignore_matcher: Optional object to determine if a path should be ignored.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
A list of relative paths to any binary files found.
|
|
28
|
+
"""
|
|
29
|
+
binaries = []
|
|
30
|
+
|
|
31
|
+
for file_path in project_path.rglob("*"):
|
|
32
|
+
if file_path.is_file():
|
|
33
|
+
# Skip if matches ignore pattern
|
|
34
|
+
if ignore_matcher and ignore_matcher.is_ignored(file_path):
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
if file_path.suffix.lower() in BINARY_EXTENSIONS:
|
|
38
|
+
rel_path = str(file_path.relative_to(project_path))
|
|
39
|
+
binaries.append(rel_path)
|
|
40
|
+
|
|
41
|
+
return binaries
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def calculate_package_size(project_path: pathlib.Path, ignore_matcher: Any = None) -> float:
|
|
45
|
+
"""Calculates the total package size in Megabytes (MB).
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
project_path: Root path of the project.
|
|
49
|
+
ignore_matcher: Optional object to determine if a path should be ignored.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The total size of the plugin package in MB.
|
|
53
|
+
"""
|
|
54
|
+
total_size = 0
|
|
55
|
+
|
|
56
|
+
for file_path in project_path.rglob("*"):
|
|
57
|
+
if file_path.is_file():
|
|
58
|
+
# Skip if matches ignore pattern
|
|
59
|
+
if ignore_matcher:
|
|
60
|
+
str(file_path.relative_to(project_path))
|
|
61
|
+
if ignore_matcher.is_ignored(file_path):
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
total_size += file_path.stat().st_size
|
|
65
|
+
|
|
66
|
+
# Convert bytes to MB
|
|
67
|
+
return total_size / (1024 * 1024)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def is_ssrf_safe(url: str) -> bool:
|
|
71
|
+
"""Checks if a URL is safe from Server-Side Request Forgery (SSRF).
|
|
72
|
+
|
|
73
|
+
Validates that the hostname does not resolve to private, loopback, or
|
|
74
|
+
local IP ranges.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
url: The URL string to validate.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
True if the URL is considered safe for outbound requests, False otherwise.
|
|
81
|
+
"""
|
|
82
|
+
try:
|
|
83
|
+
parsed = urllib.parse.urlparse(url)
|
|
84
|
+
hostname = parsed.hostname
|
|
85
|
+
# We also block URLs without a hostname or non-standard ports if needed,
|
|
86
|
+
# but here we focus on IP resolving.
|
|
87
|
+
if not hostname:
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
# Basic name check for common local addresses
|
|
91
|
+
if hostname.lower() in ("localhost", "127.0.0.1", "::1"):
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
# Resolve host to IP
|
|
95
|
+
# We use socket.getaddrinfo to handle both IPv4 and IPv6
|
|
96
|
+
# This is more robust than gethostbyname
|
|
97
|
+
try:
|
|
98
|
+
addresses = socket.getaddrinfo(hostname, None)
|
|
99
|
+
except socket.gaierror:
|
|
100
|
+
# If we can't resolve it, it's either invalid or an internal name
|
|
101
|
+
# we shouldn't trust for public URL validation.
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
for addr in addresses:
|
|
105
|
+
ip_str = addr[4][0]
|
|
106
|
+
ip = ipaddress.ip_address(ip_str)
|
|
107
|
+
# Check for private ranges:
|
|
108
|
+
# 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 (Private)
|
|
109
|
+
# 127.0.0.0/8 (Loopback)
|
|
110
|
+
# 169.254.0.0/16 (Link Local)
|
|
111
|
+
# and IPv6 equivalents
|
|
112
|
+
if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_unspecified:
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
return True
|
|
116
|
+
except Exception:
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def validate_metadata_urls(metadata: Dict[str, str]) -> Dict[str, str]:
|
|
121
|
+
"""Validates the accessibility of URLs defined in the plugin metadata.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
metadata: A dictionary containing metadata fields and their values.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
A dictionary mapping each URL to its validation status (e.g., 'ok', 'error').
|
|
128
|
+
"""
|
|
129
|
+
url_fields = ["homepage", "tracker", "repository"]
|
|
130
|
+
results = {}
|
|
131
|
+
|
|
132
|
+
for field in url_fields:
|
|
133
|
+
url = metadata.get(field, "").strip()
|
|
134
|
+
if not url:
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
# Skip if not a valid URL
|
|
138
|
+
if not url.startswith(("http://", "https://")):
|
|
139
|
+
results[url] = "invalid"
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
# SSRF Protection: check if URL is safe before making the request
|
|
143
|
+
if not is_ssrf_safe(url):
|
|
144
|
+
results[url] = "error_ssrf_blocked"
|
|
145
|
+
continue
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
# HEAD request to check availability
|
|
149
|
+
req = urllib.request.Request(url, method="HEAD")
|
|
150
|
+
req.add_header("User-Agent", "QGIS-Plugin-Analyzer/1.0")
|
|
151
|
+
|
|
152
|
+
with urllib.request.urlopen(req, timeout=5) as response:
|
|
153
|
+
if response.status == 200:
|
|
154
|
+
results[url] = "ok"
|
|
155
|
+
else:
|
|
156
|
+
results[url] = f"error_{response.status}"
|
|
157
|
+
|
|
158
|
+
except urllib.error.HTTPError as e:
|
|
159
|
+
results[url] = f"error_{e.code}"
|
|
160
|
+
except urllib.error.URLError:
|
|
161
|
+
results[url] = "error"
|
|
162
|
+
except TimeoutError:
|
|
163
|
+
results[url] = "timeout"
|
|
164
|
+
except Exception:
|
|
165
|
+
results[url] = "error"
|
|
166
|
+
|
|
167
|
+
return results
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def validate_plugin_structure(project_path: pathlib.Path) -> Dict[str, Any]:
|
|
171
|
+
"""Validates that the plugin following the required QGIS file structure.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
project_path: Root path of the plugin project.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
A dictionary containing the validation results and overall status.
|
|
178
|
+
"""
|
|
179
|
+
mandatory = ["metadata.txt", "__init__.py", "LICENSE"]
|
|
180
|
+
found = {f: (project_path / f).exists() for f in mandatory}
|
|
181
|
+
|
|
182
|
+
# Check classFactory in __init__.py
|
|
183
|
+
init_file = project_path / "__init__.py"
|
|
184
|
+
has_factory = False
|
|
185
|
+
if init_file.exists():
|
|
186
|
+
try:
|
|
187
|
+
content = init_file.read_text(encoding="utf-8", errors="replace")
|
|
188
|
+
has_factory = "def classFactory" in content
|
|
189
|
+
except Exception:
|
|
190
|
+
has_factory = False
|
|
191
|
+
|
|
192
|
+
missing = [f for f, exists in found.items() if not exists]
|
|
193
|
+
py_files = list(project_path.glob("*.py"))
|
|
194
|
+
has_python = len(py_files) > 0
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
"files": found,
|
|
198
|
+
"missing_files": missing,
|
|
199
|
+
"has_class_factory": has_factory,
|
|
200
|
+
"has_python_files": has_python,
|
|
201
|
+
"is_valid": all(found.values()) and has_factory and has_python,
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def validate_metadata(metadata_path: pathlib.Path) -> Dict[str, Any]:
|
|
206
|
+
"""Validates the content of the metadata.txt file against QGIS requirements.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
metadata_path: Path to the metadata.txt file.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
A dictionary containing validation details and mandatory/recommended missing fields.
|
|
213
|
+
"""
|
|
214
|
+
required_fields = [
|
|
215
|
+
"name",
|
|
216
|
+
"description",
|
|
217
|
+
"version",
|
|
218
|
+
"qgisMinimumVersion",
|
|
219
|
+
"author",
|
|
220
|
+
"email",
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
recommended_fields = [
|
|
224
|
+
"homepage",
|
|
225
|
+
"tracker",
|
|
226
|
+
"repository",
|
|
227
|
+
"tags",
|
|
228
|
+
"category",
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
if not metadata_path.exists():
|
|
232
|
+
return {
|
|
233
|
+
"is_valid": False,
|
|
234
|
+
"missing": required_fields,
|
|
235
|
+
"recommended_missing": recommended_fields,
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
# Parse metadata.txt
|
|
239
|
+
metadata = {}
|
|
240
|
+
try:
|
|
241
|
+
with open(metadata_path, encoding="utf-8") as f:
|
|
242
|
+
for line in f:
|
|
243
|
+
line = line.strip()
|
|
244
|
+
if "=" in line and not line.startswith("#"):
|
|
245
|
+
key, value = line.split("=", 1)
|
|
246
|
+
metadata[key.strip()] = value.strip()
|
|
247
|
+
except Exception:
|
|
248
|
+
return {
|
|
249
|
+
"is_valid": False,
|
|
250
|
+
"missing": required_fields,
|
|
251
|
+
"recommended_missing": recommended_fields,
|
|
252
|
+
"error": "Failed to parse metadata.txt",
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
missing = [f for f in required_fields if f not in metadata or not metadata[f]]
|
|
256
|
+
recommended_missing = [f for f in recommended_fields if f not in metadata or not metadata[f]]
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
"is_valid": len(missing) == 0,
|
|
260
|
+
"missing": missing,
|
|
261
|
+
"recommended_missing": recommended_missing,
|
|
262
|
+
"metadata": metadata,
|
|
263
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qgis-plugin-analyzer
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: A professional static analysis tool for QGIS (PyQGIS) plugins
|
|
5
|
+
Author-email: geociencio <juanbernales@gmail.com>
|
|
6
|
+
License: GPL-3.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://github.com/geociencio/qgis-plugin-analyzer
|
|
8
|
+
Project-URL: Documentation, https://github.com/geociencio/qgis-plugin-analyzer/tree/main/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/geociencio/qgis-plugin-analyzer.git
|
|
10
|
+
Project-URL: Issues, https://github.com/geociencio/qgis-plugin-analyzer/issues
|
|
11
|
+
Project-URL: Chaneglog, https://github.com/geociencio/qgis-plugin-analyzer/blob/main/CHANGELOG.md
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
16
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# QGIS Plugin Analyzer 🛡️
|
|
30
|
+

|
|
31
|
+

|
|
32
|
+

|
|
33
|
+

|
|
34
|
+

|
|
35
|
+

|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
The **QGIS Plugin Analyzer** is a static analysis tool designed specifically for QGIS (PyQGIS) plugin developers. Its goal is to elevate plugin quality by ensuring they follow community best practices and are optimized for AI-assisted development.
|
|
39
|
+
|
|
40
|
+
## ✨ Main Features
|
|
41
|
+
|
|
42
|
+
- **High-Performance Engine**: Parallel analysis powered by `ProcessPoolExecutor` for ultra-fast execution on multi-core systems.
|
|
43
|
+
- **Project Auto-Detection**: Intelligently distinguishes between official QGIS Plugins and Generic Python Projects, tailoring validation logic accordingly.
|
|
44
|
+
- **Advanced Ignore Engine**: Robust `.analyzerignore` support with non-anchored patterns and smart default excludes (`.venv`, `build`, etc.).
|
|
45
|
+
- **Deep Semantic Analysis**: Cross-file dependency graphing, circular import detection, and module coupling metrics.
|
|
46
|
+
- **Interactive Auto-Fix Mode**: Automatically fix common QGIS issues (GDAL imports, PyQt bridge, logging, i18n) with safety checks.
|
|
47
|
+
- **Official Repository Compliance**: Proactive validation of binaries, package size, and metadata URLs.
|
|
48
|
+
- **Real-time Progress**: CLI feedback with a progress bar and ETA tracking.
|
|
49
|
+
- **Enhanced Configuration Profiles**: Rule-level severity control (`error`, `warning`, `info`, `ignore`) via `pyproject.toml`.
|
|
50
|
+
- **Integrated Ruff Analysis**: Combines custom QGIS rules with the fastest linter in the Python ecosystem.
|
|
51
|
+
- **Qt Resource Validation**: Detect missing or broken resource paths (`:/plugins/...`) in your code.
|
|
52
|
+
- **Signal/Slot Safety**: Detection of potentially missing slots or inherited slot warnings.
|
|
53
|
+
- **AI-Ready**: Generates structured summaries and optimized contexts for LLMs.
|
|
54
|
+
- **Zero Runtime Dependencies**: Works using only the Python standard library (Ruff as an external tool).
|
|
55
|
+
|
|
56
|
+
## ⚖️ Why use this Analyzer? (Comparison)
|
|
57
|
+
|
|
58
|
+
| Feature | **QGIS Plugin Analyzer** | flake8-qgis | Ruff (Standard) | Official Repo Bot |
|
|
59
|
+
| :--- | :---: | :---: | :---: | :---: |
|
|
60
|
+
| **Static Linting** | ✅ (Ruff + Custom) | ✅ (flake8) | ✅ (General) | ✅ (Limited) |
|
|
61
|
+
| **QGIS-Specific Rules**| ✅ (Precise AST) | ✅ (Regex/AST) | ❌ | ✅ |
|
|
62
|
+
| **Interactive Auto-Fix**| ✅ | ❌ | ❌ | ❌ |
|
|
63
|
+
| **Semantic Analysis** | ✅ | ❌ | ❌ | ❌ |
|
|
64
|
+
| **Compliance Checks** | ✅ | ❌ | ❌ | ✅ |
|
|
65
|
+
| **i18n / API Audit** | ✅ | ❌ | ❌ | ✅ |
|
|
66
|
+
| **Architecture Audit** | ✅ (UI/Core) | ❌ | ❌ | ❌ |
|
|
67
|
+
| **HTML/MD Reports** | ✅ | ❌ | ❌ | ❌ |
|
|
68
|
+
| **AI Context Gen** | ✅ (Project Brain) | ❌ | ❌ | ❌ |
|
|
69
|
+
|
|
70
|
+
### Key Differentiators
|
|
71
|
+
|
|
72
|
+
1. **High-Performance Hybrid Engine**: Combines multi-core AST processing with deep understanding of cross-file relationships and Qt-specific patterns.
|
|
73
|
+
2. **Safety-First Auto-Fixing**: AST-based transformations with Git status verification and interactive diff previews.
|
|
74
|
+
3. **Repository Compliance**: Local pre-checks to ensure your plugin passes the Official QGIS Repository policies.
|
|
75
|
+
4. **Zero Runtime Stack**: Minimal footprint, ultra-fast execution, and easy CI integration.
|
|
76
|
+
5. **AI-Centric Design**: Built to help developers and AI agents understand complex QGIS plugins instantly.
|
|
77
|
+
|
|
78
|
+
## 🚀 Installation and Usage
|
|
79
|
+
|
|
80
|
+
### Installation with `uv` (Recommended):
|
|
81
|
+
|
|
82
|
+
If you have [uv](https://github.com/astral-sh/uv) installed, you can install the analyzer quickly and in isolation:
|
|
83
|
+
|
|
84
|
+
**1. As a global tool (isolated):**
|
|
85
|
+
```bash
|
|
86
|
+
uv tool install git+https://github.com/geociencio/qgis-plugin-analyzer.git
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**2. Local installation for development:**
|
|
90
|
+
```bash
|
|
91
|
+
git clone https://github.com/geociencio/qgis-plugin-analyzer
|
|
92
|
+
cd qgis-plugin-analyzer
|
|
93
|
+
uv sync
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Installation with `pip`:
|
|
97
|
+
```bash
|
|
98
|
+
pip install .
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Main Commands:
|
|
102
|
+
|
|
103
|
+
**1. Analyze a Plugin:**
|
|
104
|
+
```bash
|
|
105
|
+
qgis-analyzer analyze /path/to/your/plugin -o ./quality_report
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**2. Auto-Fix issues (Dry Run):**
|
|
109
|
+
```bash
|
|
110
|
+
qgis-analyzer fix /path/to/your/plugin
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**3. Legacy Support:**
|
|
114
|
+
The default command remains analysis if no subcommand is specified:
|
|
115
|
+
```bash
|
|
116
|
+
qgis-analyzer /path/to/your/plugin
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## 🔄 Pre-commit Hook
|
|
120
|
+
|
|
121
|
+
You can run `qgis-plugin-analyzer` automatically before every commit to ensure quality. Add this to your `.pre-commit-config.yaml`:
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
- repo: https://github.com/geociencio/qgis-plugin-analyzer
|
|
125
|
+
rev: v1.1.0 # Use the latest tag
|
|
126
|
+
hooks:
|
|
127
|
+
- id: qgis-plugin-analyzer
|
|
128
|
+
- id: qgis-plugin-analyzer
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 🤖 GitHub Action
|
|
132
|
+
|
|
133
|
+
Use it directly in your CI/CD workflows:
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
steps:
|
|
137
|
+
- uses: actions/checkout@v4
|
|
138
|
+
- name: Run QGIS Quality Check
|
|
139
|
+
uses: geociencio/qgis-plugin-analyzer@main
|
|
140
|
+
with:
|
|
141
|
+
path: .
|
|
142
|
+
output: quality_report
|
|
143
|
+
args: --profile release
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## ⌨️ Full CLI Reference
|
|
147
|
+
|
|
148
|
+
### `qgis-analyzer analyze`
|
|
149
|
+
Audits an existing QGIS plugin repository.
|
|
150
|
+
|
|
151
|
+
| Argument | Description | Default |
|
|
152
|
+
| :--- | :--- | :--- |
|
|
153
|
+
| `project_path` | **(Required)** Path to the plugin directory to analyze. | N/A |
|
|
154
|
+
| `-o`, `--output` | Directory where HTML/Markdown reports will be saved. | `./analysis_results` |
|
|
155
|
+
| `-p`, `--profile`| Configuration profile from `pyproject.toml` (`default`, `release`). | `default` |
|
|
156
|
+
|
|
157
|
+
### `qgis-analyzer fix`
|
|
158
|
+
Automatically fix common QGIS issues identified during analysis.
|
|
159
|
+
|
|
160
|
+
| Argument | Description | Default |
|
|
161
|
+
| :--- | :--- | :--- |
|
|
162
|
+
| `path` | **(Required)** Path to the plugin directory. | N/A |
|
|
163
|
+
| `--dry-run` | Show proposed changes without applying them. | `True` |
|
|
164
|
+
| `--apply` | Apply fixes to the files (disables dry-run). | `False` |
|
|
165
|
+
| `--auto-approve`| Apply fixes without interactive confirmation. | `False` |
|
|
166
|
+
| `--rules` | Comma-separated list of rule IDs to fix. | Fix all |
|
|
167
|
+
| `-o`, `--output` | Directory to read previous analysis from. | `./analysis_results` |
|
|
168
|
+
|
|
169
|
+
### `qgis-analyzer summary`
|
|
170
|
+
Shows a professional, color-coded summary of findings directly in your terminal.
|
|
171
|
+
|
|
172
|
+
| Argument | Description | Default |
|
|
173
|
+
| :--- | :--- | :--- |
|
|
174
|
+
| `-b`, `--by` | Granularity of the summary: `total`, `modules`, `functions`, `classes`. | `total` |
|
|
175
|
+
| `-i`, `--input` | Path to the `project_context.json` file to summarize. | `analysis_results/project_context.json` |
|
|
176
|
+
|
|
177
|
+
**Example:**
|
|
178
|
+
```bash
|
|
179
|
+
# Executive summary
|
|
180
|
+
qgis-analyzer summary
|
|
181
|
+
|
|
182
|
+
# Identify high-complexity functions
|
|
183
|
+
qgis-analyzer summary --by functions
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### `qgis-analyzer list-rules`
|
|
187
|
+
Displays the full catalog of implemented QGIS audit rules with their severity and descriptions.
|
|
188
|
+
|
|
189
|
+
### `qgis-analyzer init`
|
|
190
|
+
Initializes a recommended `.analyzerignore` file in the current directory with common Python and QGIS development exclusions.
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
## 📊 Generated Reports
|
|
194
|
+
|
|
195
|
+
- `project_context.json`: Full structured data for external integrations.
|
|
196
|
+
|
|
197
|
+
## 📜 Audit Rules
|
|
198
|
+
|
|
199
|
+
For a complete list of all implemented checks, their severity, and recommendations, please refer to the:
|
|
200
|
+
|
|
201
|
+
👉 **[Detailed Rules Catalog (RULES.md)](RULES.md)**
|
|
202
|
+
|
|
203
|
+
## 📚 References and Standards
|
|
204
|
+
|
|
205
|
+
The development of this analyzer is based on official QGIS community guidelines, geospatial standards, and industry best practices:
|
|
206
|
+
|
|
207
|
+
### Official QGIS Documentation
|
|
208
|
+
- **[PyQGIS Developer Cookbook](https://docs.qgis.org/latest/en/docs/pyqgis_developer_cookbook/)**: The primary resource for PyQGIS API usage and standards.
|
|
209
|
+
- **[QGIS Plugin Repository Requirements](https://plugins.qgis.org/publish/)**: Mandatory criteria for plugin approval in the official repository.
|
|
210
|
+
- **[QGIS Coding Standards](https://docs.qgis.org/latest/en/docs/developer_guide/codingstandards.html)**: Core style and organization guidelines for the QGIS project.
|
|
211
|
+
- **[QGIS HIG (Human Interface Guidelines)](https://docs.qgis.org/latest/en/docs/developer_guide/hig.html)**: Standards for consistent and accessible user interface design.
|
|
212
|
+
|
|
213
|
+
### Industry & Community Standards
|
|
214
|
+
- **[flake8-qgis Rules](https://github.com/qgis/flake8-qgis)**: Community-driven linting rules for PyQGIS (QGS101-106).
|
|
215
|
+
- **[PEP 8 Style Guide](https://peps.python.org/pep-0008/)**: The fundamental style guide for Python code.
|
|
216
|
+
- **[PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)**: Standards for docstring structure and content.
|
|
217
|
+
- **[Maintainability Index (SEI)](https://learn.microsoft.com/en-us/visualstudio/code-quality/code-metrics-maintainability-index-range-and-meaning)**: Methodology for measuring software maintainability.
|
|
218
|
+
- **[Conventional Commits](https://www.conventionalcommits.org/)**: Standard for clear, machine-readable commit history.
|
|
219
|
+
- **[Keep a Changelog](https://keepachangelog.com/)**: Best practices for maintainable version history.
|
|
220
|
+
|
|
221
|
+
### Internal Resources
|
|
222
|
+
- **[Detailed Rules Catalog](RULES.md)**: Full documentation of all audit rules implemented in this analyzer.
|
|
223
|
+
- **[Standardized Scoring Metrics](docs/SCORING_STANDARDS.md)**: Mathematical logic and thresholds for project evaluation.
|
|
224
|
+
- **[Project Roadmap](docs/ROADMAP.md)**: Current status and future plans for the analyzer.
|
|
225
|
+
- **[Documentation Folder](docs/)**: Historical release notes, competitive analysis, and modernization guides.
|
|
226
|
+
|
|
227
|
+
## 🛠️ Contributing
|
|
228
|
+
|
|
229
|
+
Contributions are welcome! Please refer to our **[Contributing Guide](CONTRIBUTING.md)** to learn how to report bugs, propose rules, and submit code changes.
|
|
230
|
+
|
|
231
|
+
Audit rules are located in `src/analyzer/scanner.py`. Feel free to add new rules following the existing pattern!
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
## ⚖️ License
|
|
235
|
+
|
|
236
|
+
This project is licensed under the **GNU General Public License v3 (GPL v3)**. See the [LICENSE](LICENSE) file for details.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
*Developed for the SecInterp team and the QGIS community.*
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
__init__.py,sha256=m27nXDOFpStOzfg3Qqeaw7yj1wNmpnOH7xk5svlCprA,1185
|
|
2
|
+
analyzer/__init__.py,sha256=m27nXDOFpStOzfg3Qqeaw7yj1wNmpnOH7xk5svlCprA,1185
|
|
3
|
+
analyzer/cli.py,sha256=x6ZfhtTyJbLDSA8eNlKpXdeHc0nvvp0jdN40mYFHXPs,10488
|
|
4
|
+
analyzer/engine.py,sha256=rbJtEQj1_Qr69Z3Uzx3c97iCw1BkS8A4gQv9WnV0Egk,22241
|
|
5
|
+
analyzer/fixer.py,sha256=6CujODr4pJgdR2XuJoBOCrQxBHjJfvVsBmg4YZdKJAM,10653
|
|
6
|
+
analyzer/scanner.py,sha256=o4pvtUnYn2ZYo5OhEwswm7BEeIA61XrosmrhS5Al8SI,34260
|
|
7
|
+
analyzer/semantic.py,sha256=cVmSiTmI-b4DFZ5Q6kZUdqd6ZPH92pJmNAE60MIiZRI,8192
|
|
8
|
+
analyzer/transformers.py,sha256=tFmelReM8ILpFjMBSDIDMCXPSZlD9t8FD7lTXVgfOgA,7103
|
|
9
|
+
analyzer/validators.py,sha256=5JPwQYQ4KKThZchr34nV06-VToFhgBOtIp7lNhO5WTk,8344
|
|
10
|
+
analyzer/models/__init__.py,sha256=NlDgdV1i9CoWJBgiENoJQuHnYCOzO6U_ui3wHOeT7xI,160
|
|
11
|
+
analyzer/models/analysis_models.py,sha256=WXsYgbjD4ngBoyaY-YkwMMl2Jpbn96hue6zuSERZRb0,2362
|
|
12
|
+
analyzer/reporters/__init__.py,sha256=fVsu2gCuBdDd_STD97Jku8otcO3j7NZrxxd_pGzf6Sw,280
|
|
13
|
+
analyzer/reporters/html_reporter.py,sha256=j64KkjiRwwRBzIPZE7bBJGsbJEYIoSnZeho63tI4c5A,14919
|
|
14
|
+
analyzer/reporters/markdown_reporter.py,sha256=sc0zBM4_hJmLE8tZF8lV_v7VLRlNSEhWqSivFZMQKac,8074
|
|
15
|
+
analyzer/reporters/summary_reporter.py,sha256=v4XfmcE027fCIWmS5N0odCx-NdziWuCefBIacRhi8XA,7237
|
|
16
|
+
analyzer/rules/__init__.py,sha256=UNZoY89SFQoUKYGj_rjwvv8md6vXSSM2pGPV6MqC2Fg,261
|
|
17
|
+
analyzer/rules/modernization_rules.py,sha256=IJw8c6v7Q_5z3xF9Dh0eBrproRFZFtIOyWcArAsSv18,1071
|
|
18
|
+
analyzer/rules/qgis_rules.py,sha256=N_1RxWrckOZWzcAMPI-07vLQPazO6MM7-LF69_NFMy8,2548
|
|
19
|
+
analyzer/utils/__init__.py,sha256=7abAIymmrA8FlzqsM8JfKXnfx3HjX0r-8KdvmgKMRQA,1006
|
|
20
|
+
analyzer/utils/ast_utils.py,sha256=LZ9NLrbJlhFGM7LsX6YNexOi_avJ0Sxzf8GdZHvoXK4,3831
|
|
21
|
+
analyzer/utils/config_utils.py,sha256=reNyUonmik8Tpwx0RbaslsiienIqPDeiXBfh4Pv23_E,4690
|
|
22
|
+
analyzer/utils/logging_utils.py,sha256=V0z7XeF46tj2oI247j2fRRafClbtMDVscVmg2JFYHV4,1257
|
|
23
|
+
analyzer/utils/path_utils.py,sha256=BkffM1TRKAMPCzLx49rQzmYHYGbPwlI41LCK5QoN2oU,4097
|
|
24
|
+
analyzer/utils/performance_utils.py,sha256=M3mfMVrOvyfgQiCQB0R5IkkBnAx-L9er4fGR9UL5E-0,4253
|
|
25
|
+
qgis_plugin_analyzer-1.3.0.dist-info/licenses/LICENSE,sha256=bMxViCXCE9h3bzmw6oUvy4R_CvzOrV3aYhIvjBPx20A,35091
|
|
26
|
+
qgis_plugin_analyzer-1.3.0.dist-info/METADATA,sha256=H5proK4GbF0j0Dsalp_8H1yczJ_TF4iQ4oBsxPRcEvM,11400
|
|
27
|
+
qgis_plugin_analyzer-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
28
|
+
qgis_plugin_analyzer-1.3.0.dist-info/entry_points.txt,sha256=1dwiqkZC4hGYExrOgyhZkxv7CbClSZqJkVk42nlw1IY,52
|
|
29
|
+
qgis_plugin_analyzer-1.3.0.dist-info/top_level.txt,sha256=i9U1DboCuTuaYpS-5GcgUeKIlSI00PoxYtnrfQWD8wo,18
|
|
30
|
+
qgis_plugin_analyzer-1.3.0.dist-info/RECORD,,
|