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.
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
+ ![GitHub release (latest by date)](https://img.shields.io/github/v/release/geociencio/qgis-plugin-analyzer?color=blue&logo=github)
31
+ ![Python Version](https://img.shields.io/badge/python-3.8%2B-blue?logo=python)
32
+ ![License](https://img.shields.io/badge/License-GPLv3-blue.svg)
33
+ ![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)
34
+ ![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg?logo=git)
35
+ ![Quality Score](https://img.shields.io/badge/Module%20Stability-55.6%2F100-yellow)
36
+ ![Maintainability](https://img.shields.io/badge/Maintainability-100.0%2F100-brightgreen)
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,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ qgis-analyzer = analyzer.cli:main