qgis-plugin-analyzer 1.5.0__tar.gz → 1.6.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.
Files changed (70) hide show
  1. {qgis_plugin_analyzer-1.5.0/src/qgis_plugin_analyzer.egg-info → qgis_plugin_analyzer-1.6.0}/PKG-INFO +16 -7
  2. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/README.md +15 -6
  3. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/pyproject.toml +6 -2
  4. qgis_plugin_analyzer-1.6.0/src/analyzer/cli/__init__.py +14 -0
  5. qgis_plugin_analyzer-1.6.0/src/analyzer/cli/app.py +147 -0
  6. qgis_plugin_analyzer-1.6.0/src/analyzer/cli/base.py +93 -0
  7. qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/__init__.py +19 -0
  8. qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/analyze.py +47 -0
  9. qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/fix.py +58 -0
  10. qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/init.py +41 -0
  11. qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/list_rules.py +41 -0
  12. qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/security.py +46 -0
  13. qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/summary.py +52 -0
  14. qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/version.py +41 -0
  15. qgis_plugin_analyzer-1.6.0/src/analyzer/cli.py +34 -0
  16. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/commands.py +7 -7
  17. qgis_plugin_analyzer-1.6.0/src/analyzer/engine.py +832 -0
  18. qgis_plugin_analyzer-1.6.0/src/analyzer/fixer.py +390 -0
  19. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/reporters/markdown_reporter.py +48 -15
  20. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/reporters/summary_reporter.py +193 -80
  21. qgis_plugin_analyzer-1.6.0/src/analyzer/scanner.py +302 -0
  22. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/transformers.py +29 -8
  23. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/__init__.py +2 -0
  24. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/path_utils.py +53 -1
  25. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/validators.py +90 -55
  26. qgis_plugin_analyzer-1.6.0/src/analyzer/visitors/__init__.py +19 -0
  27. qgis_plugin_analyzer-1.6.0/src/analyzer/visitors/base.py +75 -0
  28. qgis_plugin_analyzer-1.6.0/src/analyzer/visitors/composite_visitor.py +73 -0
  29. qgis_plugin_analyzer-1.6.0/src/analyzer/visitors/imports_visitor.py +85 -0
  30. qgis_plugin_analyzer-1.6.0/src/analyzer/visitors/metrics_visitor.py +158 -0
  31. qgis_plugin_analyzer-1.6.0/src/analyzer/visitors/security_visitor.py +52 -0
  32. qgis_plugin_analyzer-1.6.0/src/analyzer/visitors/standards_visitor.py +284 -0
  33. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0/src/qgis_plugin_analyzer.egg-info}/PKG-INFO +16 -7
  34. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/qgis_plugin_analyzer.egg-info/SOURCES.txt +18 -1
  35. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_scanner.py +1 -0
  36. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_validators.py +41 -0
  37. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_vulnerability.py +1 -0
  38. qgis_plugin_analyzer-1.5.0/src/analyzer/cli.py +0 -214
  39. qgis_plugin_analyzer-1.5.0/src/analyzer/engine.py +0 -649
  40. qgis_plugin_analyzer-1.5.0/src/analyzer/fixer.py +0 -314
  41. qgis_plugin_analyzer-1.5.0/src/analyzer/scanner.py +0 -222
  42. qgis_plugin_analyzer-1.5.0/src/analyzer/visitors.py +0 -455
  43. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/LICENSE +0 -0
  44. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/setup.cfg +0 -0
  45. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/__init__.py +0 -0
  46. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/__init__.py +0 -0
  47. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/models/__init__.py +0 -0
  48. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/models/analysis_models.py +0 -0
  49. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/reporters/__init__.py +0 -0
  50. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/reporters/html_reporter.py +0 -0
  51. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/rules/__init__.py +0 -0
  52. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/rules/modernization_rules.py +0 -0
  53. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/rules/qgis_rules.py +0 -0
  54. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/secrets.py +0 -0
  55. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/security_checker.py +0 -0
  56. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/security_rules.py +0 -0
  57. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/semantic.py +0 -0
  58. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/ast_utils.py +0 -0
  59. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/config_utils.py +0 -0
  60. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/logging_utils.py +0 -0
  61. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/performance_utils.py +0 -0
  62. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/qgis_plugin_analyzer.egg-info/dependency_links.txt +0 -0
  63. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/qgis_plugin_analyzer.egg-info/entry_points.txt +0 -0
  64. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/qgis_plugin_analyzer.egg-info/top_level.txt +0 -0
  65. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_analyzer.py +0 -0
  66. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_fixer.py +0 -0
  67. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_high_complexity.py +0 -0
  68. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_safety.py +0 -0
  69. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_security.py +0 -0
  70. {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_semantic.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qgis-plugin-analyzer
3
- Version: 1.5.0
3
+ Version: 1.6.0
4
4
  Summary: A professional static analysis tool for QGIS (PyQGIS) plugins
5
5
  Author-email: geociencio <juanbernales@gmail.com>
6
6
  License: GPL-3.0-or-later
@@ -62,20 +62,21 @@ The **QGIS Plugin Analyzer** is a static analysis tool designed specifically for
62
62
 
63
63
  | Feature | **QGIS Plugin Analyzer** | flake8-qgis | Ruff (Standard) | Official Repo Bot |
64
64
  | :--- | :---: | :---: | :---: | :---: |
65
+ | **Run Locally / Offline**| ✅ (Your Machine) | ✅ | ✅ | ❌ (Upload Only) |
65
66
  | **Static Linting** | ✅ (Ruff + Custom) | ✅ (flake8) | ✅ (General) | ✅ (Limited) |
66
67
  | **QGIS-Specific Rules**| ✅ (Precise AST) | ✅ (Regex/AST) | ❌ | ✅ |
67
68
  | **Interactive Auto-Fix**| ✅ | ❌ | ❌ | ❌ |
68
69
  | **Semantic Analysis** | ✅ | ❌ | ❌ | ❌ |
69
- | **Security Audit** | ✅ (Bandit-style) | ❌ | ❌ | |
70
- | **Secret Scanning** | ✅ (Entropy) | ❌ | ❌ | |
70
+ | **Security Audit** | ✅ (Bandit-style) | ❌ | ❌ | (Server-side) |
71
+ | **Secret Scanning** | ✅ (Entropy) | ❌ | ❌ | (Server-side) |
71
72
  | **HTML/MD Reports** | ✅ | ❌ | ❌ | ❌ |
72
73
  | **AI Context Gen** | ✅ (Project Brain) | ❌ | ❌ | ❌ |
73
74
 
74
75
  ### Key Differentiators
75
76
 
76
- 1. **High-Performance Hybrid Engine**: Combines multi-core AST processing with deep understanding of cross-file relationships and Qt-specific patterns.
77
- 2. **Safety-First Auto-Fixing**: AST-based transformations with Git status verification and interactive diff previews.
78
- 3. **Repository Compliance**: Local pre-checks to ensure your plugin passes the Official QGIS Repository policies.
77
+ 1. **Shift Left (Run Locally)**: The biggest advantage is being able to run the **same high-standard checks** as the Official Repository *before* you upload your plugin. No more "reject-fix-upload" loops.
78
+ 2. **High-Performance Hybrid Engine**: Combines multi-core AST processing with deep understanding of cross-file relationships and Qt-specific patterns.
79
+ 3. **Safety-First Auto-Fixing**: AST-based transformations with Git status verification and interactive diff previews.
79
80
  4. **Zero Runtime Stack**: Minimal footprint, ultra-fast execution, and easy CI integration.
80
81
  5. **AI-Centric Design**: Built to help developers and AI agents understand complex QGIS plugins instantly.
81
82
 
@@ -131,7 +132,7 @@ You can run `qgis-plugin-analyzer` automatically before every commit to ensure q
131
132
 
132
133
  ```yaml
133
134
  - repo: https://github.com/geociencio/qgis-plugin-analyzer
134
- rev: v1.5.0 # Use the latest tag
135
+ rev: main # Use 'main' for latest features or a specific tag like v1.5.0
135
136
  hooks:
136
137
  - id: qgis-plugin-analyzer
137
138
  ```
@@ -190,6 +191,7 @@ Audits an existing QGIS plugin repository.
190
191
  | :--- | :--- | :--- |
191
192
  | `project_path` | **(Required)** Path to the plugin directory to analyze. | N/A |
192
193
  | `-o`, `--output` | Directory where HTML/Markdown reports will be saved. | `./analysis_results` |
194
+ | `-r`, `--report` | Explicitly generate detailed HTML/Markdown reports. | `False` |
193
195
  | `-p`, `--profile`| Configuration profile from `pyproject.toml` (`default`, `release`). | `default` |
194
196
 
195
197
  ### `qgis-analyzer fix`
@@ -218,6 +220,7 @@ Performs a focused security scan on a file or directory.
218
220
  | Argument | Description | Default |
219
221
  | :--- | :--- | :--- |
220
222
  | `path` | **(Required)** Path to the file or directory to scan. | N/A |
223
+ | `--deep` | Run more intensive (but slower) security checks. | `False` |
221
224
  | `-p`, `--profile`| Configuration profile. | `default` |
222
225
 
223
226
  ### `qgis-analyzer version`
@@ -258,6 +261,7 @@ The development of this analyzer is based on official QGIS community guidelines,
258
261
  - **[QGIS Plugin Repository Requirements](https://plugins.qgis.org/publish/)**: Mandatory criteria for plugin approval in the official repository.
259
262
  - **[QGIS Coding Standards](https://docs.qgis.org/latest/en/docs/developer_guide/codingstandards.html)**: Core style and organization guidelines for the QGIS project.
260
263
  - **[QGIS HIG (Human Interface Guidelines)](https://docs.qgis.org/latest/en/docs/developer_guide/hig.html)**: Standards for consistent and accessible user interface design.
264
+ - **[QGIS Security Scanning Documentation](https://plugins.qgis.org/docs/security-scanning)**: Official guide on automated security analysis (Bandit, detect-secrets) for plugins.
261
265
 
262
266
  ### Industry & Community Standards
263
267
  - **[flake8-qgis Rules](https://github.com/qgis/flake8-qgis)**: Community-driven linting rules for PyQGIS (QGS101-106).
@@ -267,6 +271,11 @@ The development of this analyzer is based on official QGIS community guidelines,
267
271
  - **[Conventional Commits](https://www.conventionalcommits.org/)**: Standard for clear, machine-readable commit history.
268
272
  - **[Keep a Changelog](https://keepachangelog.com/)**: Best practices for maintainable version history.
269
273
 
274
+ ### Security Standards
275
+ - **[Bandit (PyCQA)](https://bandit.readthedocs.io/)**: The security rules implemented (B1xx - B6xx) are directly derived from the Bandit project's rule set for identifying common security issues in Python code.
276
+ - **[CWE (Common Weakness Enumeration)](https://cwe.mitre.org/)**: Security findings are mapped to standard CWE IDs (e.g., CWE-78 Command Injection, CWE-89 SQL Injection) for industry-standard classification.
277
+ - **[OWASP Top 10](https://owasp.org/www-project-top-ten/)**: The "Hardcoded Secret" and "Injection" checks align with critical OWASP vulnerabilities.
278
+
270
279
  ### Internal Resources
271
280
  - **[Detailed Rules Catalog](RULES.md)**: Full documentation of all audit rules implemented in this analyzer.
272
281
  - **[Standardized Scoring Metrics](docs/SCORING_STANDARDS.md)**: Mathematical logic and thresholds for project evaluation.
@@ -34,20 +34,21 @@ The **QGIS Plugin Analyzer** is a static analysis tool designed specifically for
34
34
 
35
35
  | Feature | **QGIS Plugin Analyzer** | flake8-qgis | Ruff (Standard) | Official Repo Bot |
36
36
  | :--- | :---: | :---: | :---: | :---: |
37
+ | **Run Locally / Offline**| ✅ (Your Machine) | ✅ | ✅ | ❌ (Upload Only) |
37
38
  | **Static Linting** | ✅ (Ruff + Custom) | ✅ (flake8) | ✅ (General) | ✅ (Limited) |
38
39
  | **QGIS-Specific Rules**| ✅ (Precise AST) | ✅ (Regex/AST) | ❌ | ✅ |
39
40
  | **Interactive Auto-Fix**| ✅ | ❌ | ❌ | ❌ |
40
41
  | **Semantic Analysis** | ✅ | ❌ | ❌ | ❌ |
41
- | **Security Audit** | ✅ (Bandit-style) | ❌ | ❌ | |
42
- | **Secret Scanning** | ✅ (Entropy) | ❌ | ❌ | |
42
+ | **Security Audit** | ✅ (Bandit-style) | ❌ | ❌ | (Server-side) |
43
+ | **Secret Scanning** | ✅ (Entropy) | ❌ | ❌ | (Server-side) |
43
44
  | **HTML/MD Reports** | ✅ | ❌ | ❌ | ❌ |
44
45
  | **AI Context Gen** | ✅ (Project Brain) | ❌ | ❌ | ❌ |
45
46
 
46
47
  ### Key Differentiators
47
48
 
48
- 1. **High-Performance Hybrid Engine**: Combines multi-core AST processing with deep understanding of cross-file relationships and Qt-specific patterns.
49
- 2. **Safety-First Auto-Fixing**: AST-based transformations with Git status verification and interactive diff previews.
50
- 3. **Repository Compliance**: Local pre-checks to ensure your plugin passes the Official QGIS Repository policies.
49
+ 1. **Shift Left (Run Locally)**: The biggest advantage is being able to run the **same high-standard checks** as the Official Repository *before* you upload your plugin. No more "reject-fix-upload" loops.
50
+ 2. **High-Performance Hybrid Engine**: Combines multi-core AST processing with deep understanding of cross-file relationships and Qt-specific patterns.
51
+ 3. **Safety-First Auto-Fixing**: AST-based transformations with Git status verification and interactive diff previews.
51
52
  4. **Zero Runtime Stack**: Minimal footprint, ultra-fast execution, and easy CI integration.
52
53
  5. **AI-Centric Design**: Built to help developers and AI agents understand complex QGIS plugins instantly.
53
54
 
@@ -103,7 +104,7 @@ You can run `qgis-plugin-analyzer` automatically before every commit to ensure q
103
104
 
104
105
  ```yaml
105
106
  - repo: https://github.com/geociencio/qgis-plugin-analyzer
106
- rev: v1.5.0 # Use the latest tag
107
+ rev: main # Use 'main' for latest features or a specific tag like v1.5.0
107
108
  hooks:
108
109
  - id: qgis-plugin-analyzer
109
110
  ```
@@ -162,6 +163,7 @@ Audits an existing QGIS plugin repository.
162
163
  | :--- | :--- | :--- |
163
164
  | `project_path` | **(Required)** Path to the plugin directory to analyze. | N/A |
164
165
  | `-o`, `--output` | Directory where HTML/Markdown reports will be saved. | `./analysis_results` |
166
+ | `-r`, `--report` | Explicitly generate detailed HTML/Markdown reports. | `False` |
165
167
  | `-p`, `--profile`| Configuration profile from `pyproject.toml` (`default`, `release`). | `default` |
166
168
 
167
169
  ### `qgis-analyzer fix`
@@ -190,6 +192,7 @@ Performs a focused security scan on a file or directory.
190
192
  | Argument | Description | Default |
191
193
  | :--- | :--- | :--- |
192
194
  | `path` | **(Required)** Path to the file or directory to scan. | N/A |
195
+ | `--deep` | Run more intensive (but slower) security checks. | `False` |
193
196
  | `-p`, `--profile`| Configuration profile. | `default` |
194
197
 
195
198
  ### `qgis-analyzer version`
@@ -230,6 +233,7 @@ The development of this analyzer is based on official QGIS community guidelines,
230
233
  - **[QGIS Plugin Repository Requirements](https://plugins.qgis.org/publish/)**: Mandatory criteria for plugin approval in the official repository.
231
234
  - **[QGIS Coding Standards](https://docs.qgis.org/latest/en/docs/developer_guide/codingstandards.html)**: Core style and organization guidelines for the QGIS project.
232
235
  - **[QGIS HIG (Human Interface Guidelines)](https://docs.qgis.org/latest/en/docs/developer_guide/hig.html)**: Standards for consistent and accessible user interface design.
236
+ - **[QGIS Security Scanning Documentation](https://plugins.qgis.org/docs/security-scanning)**: Official guide on automated security analysis (Bandit, detect-secrets) for plugins.
233
237
 
234
238
  ### Industry & Community Standards
235
239
  - **[flake8-qgis Rules](https://github.com/qgis/flake8-qgis)**: Community-driven linting rules for PyQGIS (QGS101-106).
@@ -239,6 +243,11 @@ The development of this analyzer is based on official QGIS community guidelines,
239
243
  - **[Conventional Commits](https://www.conventionalcommits.org/)**: Standard for clear, machine-readable commit history.
240
244
  - **[Keep a Changelog](https://keepachangelog.com/)**: Best practices for maintainable version history.
241
245
 
246
+ ### Security Standards
247
+ - **[Bandit (PyCQA)](https://bandit.readthedocs.io/)**: The security rules implemented (B1xx - B6xx) are directly derived from the Bandit project's rule set for identifying common security issues in Python code.
248
+ - **[CWE (Common Weakness Enumeration)](https://cwe.mitre.org/)**: Security findings are mapped to standard CWE IDs (e.g., CWE-78 Command Injection, CWE-89 SQL Injection) for industry-standard classification.
249
+ - **[OWASP Top 10](https://owasp.org/www-project-top-ten/)**: The "Hardcoded Secret" and "Injection" checks align with critical OWASP vulnerabilities.
250
+
242
251
  ### Internal Resources
243
252
  - **[Detailed Rules Catalog](RULES.md)**: Full documentation of all audit rules implemented in this analyzer.
244
253
  - **[Standardized Scoring Metrics](docs/SCORING_STANDARDS.md)**: Mathematical logic and thresholds for project evaluation.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "qgis-plugin-analyzer"
3
- version = "1.5.0"
3
+ version = "1.6.0"
4
4
  description = "A professional static analysis tool for QGIS (PyQGIS) plugins"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.8"
@@ -44,6 +44,9 @@ dev = [
44
44
  "mypy>=1.0.0",
45
45
  "qgis-stubs>=0.1",
46
46
  "pytest>=8.3.5",
47
+ "PyYAML>=6.0",
48
+ "types-pyyaml>=6.0.12.20241230",
49
+ "build>=1.2.2.post1",
47
50
  ]
48
51
 
49
52
  [tool.mypy]
@@ -57,7 +60,7 @@ disallow_untyped_defs = false # Gradual adoption
57
60
  [tool.ruff]
58
61
  line-length = 100
59
62
  target-version = "py38"
60
- exclude = ["src/analyzer/templates"]
63
+ exclude = ["src/analyzer/templates", ".ai-context"]
61
64
 
62
65
  [tool.ruff.lint]
63
66
  select = ["E", "F", "W", "I", "N", "UP", "B"]
@@ -65,6 +68,7 @@ ignore = ["E701", "E501"] # Permite if cond: statement y líneas largas
65
68
 
66
69
  [tool.ruff.lint.per-file-ignores]
67
70
  "src/analyzer/reporters.py" = ["F401", "F403", "F405"] # Dominate usa mucho import *
71
+ "src/analyzer/visitors/*.py" = ["N802"] # visit_* methods are part of ast.NodeVisitor API
68
72
 
69
73
  [tool.qgis-analyzer.profiles.default]
70
74
  strict = false
@@ -0,0 +1,14 @@
1
+ """CLI package for QGIS Plugin Analyzer."""
2
+
3
+ import sys
4
+
5
+ from .app import CLIApp
6
+
7
+
8
+ def main() -> None:
9
+ """Main entry point for the QGIS Plugin Analyzer CLI."""
10
+ app = CLIApp()
11
+ sys.exit(app.run())
12
+
13
+
14
+ __all__ = ["CLIApp", "main"]
@@ -0,0 +1,147 @@
1
+ """CLI Application orchestrator."""
2
+
3
+ import argparse
4
+ import pathlib
5
+ import sys
6
+ from typing import Dict, List, Optional
7
+
8
+ from ..utils import logger, setup_logger
9
+ from .base import BaseCommand
10
+ from .commands import (
11
+ AnalyzeCommand,
12
+ FixCommand,
13
+ InitCommand,
14
+ ListRulesCommand,
15
+ SecurityCommand,
16
+ SummaryCommand,
17
+ VersionCommand,
18
+ )
19
+
20
+
21
+ class CLIApp:
22
+ """Main CLI application orchestrator.
23
+
24
+ Manages command registration, argument parsing, and execution.
25
+ """
26
+
27
+ def __init__(self):
28
+ """Initialize the CLI application with all available commands."""
29
+ self.commands: Dict[str, BaseCommand] = self._discover_commands()
30
+
31
+ def _discover_commands(self) -> Dict[str, BaseCommand]:
32
+ """Auto-discover and instantiate all command classes.
33
+
34
+ Returns:
35
+ Dictionary mapping command names to command instances.
36
+ """
37
+ command_classes: List[type[BaseCommand]] = [
38
+ AnalyzeCommand,
39
+ SecurityCommand,
40
+ FixCommand,
41
+ ListRulesCommand,
42
+ InitCommand,
43
+ SummaryCommand,
44
+ VersionCommand,
45
+ ]
46
+ return {cmd().name: cmd() for cmd in command_classes}
47
+
48
+ def _build_parser(self) -> argparse.ArgumentParser:
49
+ """Build the argument parser with all commands.
50
+
51
+ Returns:
52
+ Configured ArgumentParser instance.
53
+ """
54
+ from .. import __version__
55
+
56
+ parser = argparse.ArgumentParser(
57
+ description="QGIS Plugin Analyzer - A guardian for your PyQGIS code"
58
+ )
59
+ parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {__version__}")
60
+ subparsers = parser.add_subparsers(dest="command", help="Command to execute")
61
+
62
+ # Register all commands
63
+ for cmd in self.commands.values():
64
+ cmd_parser = subparsers.add_parser(cmd.name, help=cmd.help)
65
+ cmd.configure_parser(cmd_parser)
66
+
67
+ return parser
68
+
69
+ def _parse_args(
70
+ self, parser: argparse.ArgumentParser, argv: Optional[List[str]] = None
71
+ ) -> argparse.Namespace:
72
+ """Parse command-line arguments with legacy support.
73
+
74
+ Args:
75
+ parser: The argument parser.
76
+ argv: Optional argument list (defaults to sys.argv).
77
+
78
+ Returns:
79
+ Parsed arguments namespace.
80
+ """
81
+ if argv is None:
82
+ argv = sys.argv[1:]
83
+
84
+ # Legacy support: default to 'analyze' if first arg is a path
85
+ if argv and argv[0] not in self.commands and not argv[0].startswith("-"):
86
+ argv.insert(0, "analyze")
87
+
88
+ return parser.parse_args(argv)
89
+
90
+ def _setup_logging(self, args: argparse.Namespace) -> None:
91
+ """Setup logging based on command arguments.
92
+
93
+ Args:
94
+ args: Parsed command-line arguments.
95
+ """
96
+ output_dir = pathlib.Path(getattr(args, "output", "./analysis_results")).resolve()
97
+ output_dir.mkdir(parents=True, exist_ok=True)
98
+ setup_logger(output_dir)
99
+
100
+ def _execute_command(self, args: argparse.Namespace) -> int:
101
+ """Execute the selected command.
102
+
103
+ Args:
104
+ args: Parsed command-line arguments.
105
+
106
+ Returns:
107
+ Exit code from command execution.
108
+ """
109
+ if not args.command or args.command not in self.commands:
110
+ return 1
111
+
112
+ command = self.commands[args.command]
113
+ return command.execute(args)
114
+
115
+ def run(self, argv: Optional[List[str]] = None) -> int:
116
+ """Run the CLI application.
117
+
118
+ Args:
119
+ argv: Optional argument list (defaults to sys.argv).
120
+
121
+ Returns:
122
+ Exit code (0 for success, non-zero for failure).
123
+ """
124
+ parser = self._build_parser()
125
+
126
+ try:
127
+ args = self._parse_args(parser, argv)
128
+
129
+ if not args.command:
130
+ parser.print_help()
131
+ return 0
132
+
133
+ self._setup_logging(args)
134
+ return self._execute_command(args)
135
+
136
+ except KeyboardInterrupt:
137
+ logger.info("\n⏹️ Analysis interrupted.")
138
+ return 1
139
+ except FileNotFoundError as e:
140
+ logger.error(f"Error: File not found: {e}")
141
+ return 1
142
+ except ValueError as e:
143
+ logger.error(f"Error: {e}")
144
+ return 1
145
+ except Exception as e:
146
+ logger.critical(f"Critical Error: {e}", exc_info=True)
147
+ return 1
@@ -0,0 +1,93 @@
1
+ """Base command class for CLI commands."""
2
+
3
+ import argparse
4
+ import pathlib
5
+ from abc import ABC, abstractmethod
6
+ from typing import Optional
7
+
8
+
9
+ class BaseCommand(ABC):
10
+ """Abstract base class for CLI commands.
11
+
12
+ Each command encapsulates its own argument configuration and execution logic.
13
+ """
14
+
15
+ @property
16
+ @abstractmethod
17
+ def name(self) -> str:
18
+ """Command name as it appears in the CLI.
19
+
20
+ Returns:
21
+ The command name string.
22
+ """
23
+
24
+ @property
25
+ @abstractmethod
26
+ def help(self) -> str:
27
+ """Help text for the command.
28
+
29
+ Returns:
30
+ A brief description of what the command does.
31
+ """
32
+
33
+ @abstractmethod
34
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None:
35
+ """Configure command-specific arguments.
36
+
37
+ Args:
38
+ parser: The argument parser for this command.
39
+ """
40
+
41
+ @abstractmethod
42
+ def execute(self, args: argparse.Namespace) -> int:
43
+ """Execute the command.
44
+
45
+ Args:
46
+ args: Parsed command-line arguments.
47
+
48
+ Returns:
49
+ Exit code (0 for success, non-zero for failure).
50
+ """
51
+
52
+ def add_common_args(
53
+ self,
54
+ parser: argparse.ArgumentParser,
55
+ include_output: bool = True,
56
+ include_profile: bool = True,
57
+ ) -> None:
58
+ """Add common arguments shared across multiple commands.
59
+
60
+ Args:
61
+ parser: The argument parser to add arguments to.
62
+ include_output: Whether to include the --output argument.
63
+ include_profile: Whether to include the --profile argument.
64
+ """
65
+ if include_output:
66
+ parser.add_argument(
67
+ "-o",
68
+ "--output",
69
+ help="Output directory for reports",
70
+ default="./analysis_results",
71
+ )
72
+ if include_profile:
73
+ parser.add_argument(
74
+ "-p",
75
+ "--profile",
76
+ help="Configuration profile from pyproject.toml",
77
+ default="default",
78
+ )
79
+
80
+ def setup_output_dir(self, args: argparse.Namespace) -> Optional[pathlib.Path]:
81
+ """Setup and return the output directory if present in args.
82
+
83
+ Args:
84
+ args: Parsed command-line arguments.
85
+
86
+ Returns:
87
+ The resolved output directory path, or None if not applicable.
88
+ """
89
+ if hasattr(args, "output"):
90
+ output_dir = pathlib.Path(args.output).resolve()
91
+ output_dir.mkdir(parents=True, exist_ok=True)
92
+ return output_dir
93
+ return None
@@ -0,0 +1,19 @@
1
+ """CLI commands package."""
2
+
3
+ from .analyze import AnalyzeCommand
4
+ from .fix import FixCommand
5
+ from .init import InitCommand
6
+ from .list_rules import ListRulesCommand
7
+ from .security import SecurityCommand
8
+ from .summary import SummaryCommand
9
+ from .version import VersionCommand
10
+
11
+ __all__ = [
12
+ "AnalyzeCommand",
13
+ "SecurityCommand",
14
+ "FixCommand",
15
+ "ListRulesCommand",
16
+ "InitCommand",
17
+ "SummaryCommand",
18
+ "VersionCommand",
19
+ ]
@@ -0,0 +1,47 @@
1
+ """Analyze command implementation."""
2
+
3
+ import argparse
4
+
5
+ from ...commands import handle_analyze
6
+ from ..base import BaseCommand
7
+
8
+
9
+ class AnalyzeCommand(BaseCommand):
10
+ """Command to analyze an existing QGIS plugin."""
11
+
12
+ @property
13
+ def name(self) -> str:
14
+ """Command name."""
15
+ return "analyze"
16
+
17
+ @property
18
+ def help(self) -> str:
19
+ """Command help text."""
20
+ return "Analyze an existing QGIS plugin"
21
+
22
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None:
23
+ """Configure analyze command arguments.
24
+
25
+ Args:
26
+ parser: The argument parser for this command.
27
+ """
28
+ parser.add_argument("project_path", help="Path to the QGIS project to analyze")
29
+ self.add_common_args(parser)
30
+ parser.add_argument(
31
+ "-r",
32
+ "--report",
33
+ action="store_true",
34
+ help="Generate detailed HTML/Markdown reports",
35
+ )
36
+
37
+ def execute(self, args: argparse.Namespace) -> int:
38
+ """Execute the analyze command.
39
+
40
+ Args:
41
+ args: Parsed command-line arguments.
42
+
43
+ Returns:
44
+ Exit code (0 for success).
45
+ """
46
+ handle_analyze(args)
47
+ return 0
@@ -0,0 +1,58 @@
1
+ """Fix command implementation."""
2
+
3
+ import argparse
4
+
5
+ from ...commands import handle_fix
6
+ from ..base import BaseCommand
7
+
8
+
9
+ class FixCommand(BaseCommand):
10
+ """Command to auto-fix common QGIS plugin issues."""
11
+
12
+ @property
13
+ def name(self) -> str:
14
+ """Command name."""
15
+ return "fix"
16
+
17
+ @property
18
+ def help(self) -> str:
19
+ """Command help text."""
20
+ return "Auto-fix common QGIS plugin issues"
21
+
22
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None:
23
+ """Configure fix command arguments.
24
+
25
+ Args:
26
+ parser: The argument parser for this command.
27
+ """
28
+ parser.add_argument("path", type=str, help="Path to the QGIS plugin directory")
29
+ parser.add_argument(
30
+ "--dry-run",
31
+ action="store_true",
32
+ default=True,
33
+ help="Show proposed changes without applying (default: True)",
34
+ )
35
+ parser.add_argument("--apply", action="store_true", help="Apply fixes (disables dry-run)")
36
+ parser.add_argument(
37
+ "--auto-approve",
38
+ action="store_true",
39
+ help="Apply all fixes without confirmation",
40
+ )
41
+ self.add_common_args(parser, include_output=False)
42
+ parser.add_argument(
43
+ "--rules",
44
+ type=str,
45
+ help="Comma-separated list of rule IDs to fix",
46
+ )
47
+
48
+ def execute(self, args: argparse.Namespace) -> int:
49
+ """Execute the fix command.
50
+
51
+ Args:
52
+ args: Parsed command-line arguments.
53
+
54
+ Returns:
55
+ Exit code (0 for success).
56
+ """
57
+ handle_fix(args)
58
+ return 0
@@ -0,0 +1,41 @@
1
+ """Init command implementation."""
2
+
3
+ import argparse
4
+
5
+ from ...commands import handle_init
6
+ from ..base import BaseCommand
7
+
8
+
9
+ class InitCommand(BaseCommand):
10
+ """Command to initialize a new .analyzerignore with defaults."""
11
+
12
+ @property
13
+ def name(self) -> str:
14
+ """Command name."""
15
+ return "init"
16
+
17
+ @property
18
+ def help(self) -> str:
19
+ """Command help text."""
20
+ return "Initialize a new .analyzerignore with defaults"
21
+
22
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None:
23
+ """Configure init command arguments.
24
+
25
+ Args:
26
+ parser: The argument parser for this command.
27
+ """
28
+ # No additional arguments needed
29
+ pass
30
+
31
+ def execute(self, args: argparse.Namespace) -> int:
32
+ """Execute the init command.
33
+
34
+ Args:
35
+ args: Parsed command-line arguments.
36
+
37
+ Returns:
38
+ Exit code (0 for success).
39
+ """
40
+ handle_init()
41
+ return 0
@@ -0,0 +1,41 @@
1
+ """List rules command implementation."""
2
+
3
+ import argparse
4
+
5
+ from ...commands import handle_list_rules
6
+ from ..base import BaseCommand
7
+
8
+
9
+ class ListRulesCommand(BaseCommand):
10
+ """Command to list all available QGIS audit rules."""
11
+
12
+ @property
13
+ def name(self) -> str:
14
+ """Command name."""
15
+ return "list-rules"
16
+
17
+ @property
18
+ def help(self) -> str:
19
+ """Command help text."""
20
+ return "List all available QGIS audit rules"
21
+
22
+ def configure_parser(self, parser: argparse.ArgumentParser) -> None:
23
+ """Configure list-rules command arguments.
24
+
25
+ Args:
26
+ parser: The argument parser for this command.
27
+ """
28
+ # No additional arguments needed
29
+ pass
30
+
31
+ def execute(self, args: argparse.Namespace) -> int:
32
+ """Execute the list-rules command.
33
+
34
+ Args:
35
+ args: Parsed command-line arguments.
36
+
37
+ Returns:
38
+ Exit code (0 for success).
39
+ """
40
+ handle_list_rules()
41
+ return 0