woolly 0.3.0__py3-none-any.whl → 0.5.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.
- woolly/cache.py +21 -6
- woolly/commands/check.py +264 -82
- woolly/http.py +22 -3
- woolly/languages/base.py +187 -10
- woolly/languages/python.py +221 -8
- woolly/languages/rust.py +59 -1
- woolly/reporters/__init__.py +15 -1
- woolly/reporters/base.py +25 -0
- woolly/reporters/json.py +102 -3
- woolly/reporters/markdown.py +83 -9
- woolly/reporters/stdout.py +171 -38
- woolly/reporters/template.py +245 -0
- {woolly-0.3.0.dist-info → woolly-0.5.0.dist-info}/METADATA +3 -1
- woolly-0.5.0.dist-info/RECORD +26 -0
- woolly-0.3.0.dist-info/RECORD +0 -25
- {woolly-0.3.0.dist-info → woolly-0.5.0.dist-info}/WHEEL +0 -0
- {woolly-0.3.0.dist-info → woolly-0.5.0.dist-info}/entry_points.txt +0 -0
- {woolly-0.3.0.dist-info → woolly-0.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Template-based report generator using Jinja2.
|
|
3
|
+
|
|
4
|
+
Allows users to provide a custom markdown template file for report generation.
|
|
5
|
+
Only a limited set of variables are exposed for security and simplicity.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from woolly.reporters.base import ReportData, Reporter, strip_markup
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TemplateReporter(Reporter):
|
|
15
|
+
"""Reporter that generates reports from user-provided Jinja2 templates.
|
|
16
|
+
|
|
17
|
+
This reporter allows users to customize the output format by providing
|
|
18
|
+
a markdown template file. Only a limited, safe set of variables are
|
|
19
|
+
exposed to the template.
|
|
20
|
+
|
|
21
|
+
Available template variables:
|
|
22
|
+
Metadata:
|
|
23
|
+
- root_package: Name of the analyzed package
|
|
24
|
+
- language: Language/ecosystem name (e.g., "Rust", "Python")
|
|
25
|
+
- registry: Registry name (e.g., "crates.io", "PyPI")
|
|
26
|
+
- root_license: License of the root package (or None)
|
|
27
|
+
- version: Package version (if specified)
|
|
28
|
+
- timestamp: Formatted timestamp string (YYYY-MM-DD HH:MM:SS)
|
|
29
|
+
- max_depth: Maximum recursion depth used
|
|
30
|
+
|
|
31
|
+
Statistics:
|
|
32
|
+
- total_dependencies: Total number of dependencies analyzed
|
|
33
|
+
- packaged_count: Number of packages available in Fedora
|
|
34
|
+
- missing_count: Number of packages missing from Fedora
|
|
35
|
+
|
|
36
|
+
Optional dependency statistics:
|
|
37
|
+
- include_optional: Whether optional deps were included
|
|
38
|
+
- optional_total: Total optional dependencies
|
|
39
|
+
- optional_packaged: Optional deps available in Fedora
|
|
40
|
+
- optional_missing: Optional deps missing from Fedora
|
|
41
|
+
|
|
42
|
+
Dev/Build dependency statistics:
|
|
43
|
+
- dev_total, dev_packaged, dev_missing: Dev dep counts
|
|
44
|
+
- build_total, build_packaged, build_missing: Build dep counts
|
|
45
|
+
- dev_dependencies: List of dev dep dicts with Fedora status
|
|
46
|
+
- build_dependencies: List of build dep dicts with Fedora status
|
|
47
|
+
|
|
48
|
+
Features / Extras:
|
|
49
|
+
- features: List of feature/extra objects with name and dependencies
|
|
50
|
+
|
|
51
|
+
Package lists (sorted):
|
|
52
|
+
- missing_packages: List of missing required package names
|
|
53
|
+
- packaged_packages: List of packaged package names
|
|
54
|
+
- optional_missing_packages: List of missing optional package names
|
|
55
|
+
|
|
56
|
+
Flags:
|
|
57
|
+
- missing_only: Whether missing-only mode was enabled
|
|
58
|
+
|
|
59
|
+
Example template:
|
|
60
|
+
# Report for {{ root_package }}
|
|
61
|
+
|
|
62
|
+
Generated: {{ timestamp }}
|
|
63
|
+
Language: {{ language }}
|
|
64
|
+
|
|
65
|
+
## Summary
|
|
66
|
+
- Total: {{ total_dependencies }}
|
|
67
|
+
- Packaged: {{ packaged_count }}
|
|
68
|
+
- Missing: {{ missing_count }}
|
|
69
|
+
|
|
70
|
+
{% if missing_packages %}
|
|
71
|
+
## Missing Packages
|
|
72
|
+
{% for pkg in missing_packages %}
|
|
73
|
+
- {{ pkg }}
|
|
74
|
+
{% endfor %}
|
|
75
|
+
{% endif %}
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
name = "template"
|
|
79
|
+
description = "Custom template-based report (requires --template)"
|
|
80
|
+
file_extension = "md"
|
|
81
|
+
writes_to_file = True
|
|
82
|
+
|
|
83
|
+
def __init__(self, template_path: Optional[Path] = None):
|
|
84
|
+
"""Initialize the template reporter.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
template_path: Path to the Jinja2 template file.
|
|
88
|
+
"""
|
|
89
|
+
self._template_path = template_path
|
|
90
|
+
self._jinja2_available: Optional[bool] = None
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def template_path(self) -> Optional[Path]:
|
|
94
|
+
"""Get the template path."""
|
|
95
|
+
return self._template_path
|
|
96
|
+
|
|
97
|
+
@template_path.setter
|
|
98
|
+
def template_path(self, value: Optional[Path]) -> None:
|
|
99
|
+
"""Set the template path."""
|
|
100
|
+
self._template_path = value
|
|
101
|
+
|
|
102
|
+
def _check_jinja2(self) -> bool:
|
|
103
|
+
"""Check if Jinja2 is available.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
True if Jinja2 is available, False otherwise.
|
|
107
|
+
"""
|
|
108
|
+
if self._jinja2_available is None:
|
|
109
|
+
try:
|
|
110
|
+
import jinja2 # type: ignore[import-not-found] # noqa: F401
|
|
111
|
+
|
|
112
|
+
self._jinja2_available = True
|
|
113
|
+
except ImportError:
|
|
114
|
+
self._jinja2_available = False
|
|
115
|
+
return self._jinja2_available
|
|
116
|
+
|
|
117
|
+
def _get_template_context(self, data: ReportData) -> dict:
|
|
118
|
+
"""Build the template context with allowed variables only.
|
|
119
|
+
|
|
120
|
+
This method creates a safe, limited context for template rendering.
|
|
121
|
+
Only specific variables from ReportData are exposed.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
data: Report data containing all information.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Dictionary of template variables.
|
|
128
|
+
"""
|
|
129
|
+
# Convert features to plain dicts for template use
|
|
130
|
+
features_list = []
|
|
131
|
+
for f in data.features:
|
|
132
|
+
if hasattr(f, "name"):
|
|
133
|
+
features_list.append({"name": f.name, "dependencies": f.dependencies})
|
|
134
|
+
else:
|
|
135
|
+
features_list.append(f)
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
# Metadata
|
|
139
|
+
"root_package": data.root_package,
|
|
140
|
+
"language": data.language,
|
|
141
|
+
"registry": data.registry,
|
|
142
|
+
"root_license": data.root_license,
|
|
143
|
+
"version": data.version,
|
|
144
|
+
"timestamp": data.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
|
|
145
|
+
"max_depth": data.max_depth,
|
|
146
|
+
# Statistics
|
|
147
|
+
"total_dependencies": data.total_dependencies,
|
|
148
|
+
"packaged_count": data.packaged_count,
|
|
149
|
+
"missing_count": data.missing_count,
|
|
150
|
+
# Optional dependency statistics
|
|
151
|
+
"include_optional": data.include_optional,
|
|
152
|
+
"optional_total": data.optional_total,
|
|
153
|
+
"optional_packaged": data.optional_packaged,
|
|
154
|
+
"optional_missing": data.optional_missing,
|
|
155
|
+
# Dev/Build dependency statistics
|
|
156
|
+
"dev_total": data.dev_total,
|
|
157
|
+
"dev_packaged": data.dev_packaged,
|
|
158
|
+
"dev_missing": data.dev_missing,
|
|
159
|
+
"build_total": data.build_total,
|
|
160
|
+
"build_packaged": data.build_packaged,
|
|
161
|
+
"build_missing": data.build_missing,
|
|
162
|
+
"dev_dependencies": data.dev_dependencies,
|
|
163
|
+
"build_dependencies": data.build_dependencies,
|
|
164
|
+
# Features / Extras
|
|
165
|
+
"features": features_list,
|
|
166
|
+
# Package lists (sorted for consistent output)
|
|
167
|
+
"missing_packages": sorted(data.required_missing_packages),
|
|
168
|
+
"packaged_packages": sorted(data.unique_packaged_packages),
|
|
169
|
+
"optional_missing_packages": sorted(data.optional_missing_set),
|
|
170
|
+
# Flags
|
|
171
|
+
"missing_only": data.missing_only,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
def generate(self, data: ReportData) -> str:
|
|
175
|
+
"""Generate report content from the template.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
data: Report data containing all information.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Rendered template content as a string.
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
RuntimeError: If Jinja2 is not installed or template is not set.
|
|
185
|
+
FileNotFoundError: If the template file doesn't exist.
|
|
186
|
+
jinja2.TemplateError: If there's a template syntax error.
|
|
187
|
+
"""
|
|
188
|
+
if not self._check_jinja2():
|
|
189
|
+
raise RuntimeError(
|
|
190
|
+
"Jinja2 is required for template reports. "
|
|
191
|
+
"Install it with: pip install jinja2"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if self._template_path is None:
|
|
195
|
+
raise RuntimeError(
|
|
196
|
+
"No template path specified. Use --template to provide a template file."
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
if not self._template_path.exists():
|
|
200
|
+
raise FileNotFoundError(f"Template file not found: {self._template_path}")
|
|
201
|
+
|
|
202
|
+
# Import Jinja2 here (we've already checked it's available)
|
|
203
|
+
from jinja2 import ( # type: ignore[import-not-found]
|
|
204
|
+
Environment,
|
|
205
|
+
FileSystemLoader,
|
|
206
|
+
StrictUndefined,
|
|
207
|
+
select_autoescape,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Create Jinja2 environment with security settings
|
|
211
|
+
env = Environment(
|
|
212
|
+
loader=FileSystemLoader(self._template_path.parent),
|
|
213
|
+
autoescape=select_autoescape(default=False),
|
|
214
|
+
undefined=StrictUndefined, # Raise error on undefined variables
|
|
215
|
+
# Disable dangerous features
|
|
216
|
+
extensions=[],
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Add custom filter to strip Rich markup
|
|
220
|
+
env.filters["strip_markup"] = strip_markup
|
|
221
|
+
|
|
222
|
+
# Load and render template
|
|
223
|
+
template = env.get_template(self._template_path.name)
|
|
224
|
+
context = self._get_template_context(data)
|
|
225
|
+
|
|
226
|
+
return template.render(**context)
|
|
227
|
+
|
|
228
|
+
def get_output_filename(self, data: ReportData) -> str:
|
|
229
|
+
"""Get the output filename for the template report.
|
|
230
|
+
|
|
231
|
+
Uses the template filename as a base if available, otherwise
|
|
232
|
+
falls back to the default naming convention.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
data: Report data.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Filename string.
|
|
239
|
+
"""
|
|
240
|
+
timestamp = data.timestamp.strftime("%Y%m%d_%H%M%S")
|
|
241
|
+
if self._template_path:
|
|
242
|
+
# Use template name as base (without extension)
|
|
243
|
+
base_name = self._template_path.stem
|
|
244
|
+
return f"woolly_{data.root_package}_{base_name}_{timestamp}.md"
|
|
245
|
+
return f"woolly_{data.root_package}_template_{timestamp}.md"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: woolly
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Check if package dependencies are available in Fedora. Supports Rust, Python, and more.
|
|
5
5
|
Author-email: Rodolfo Olivieri <rodolfo.olivieri3@gmail.com>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -26,6 +26,8 @@ Requires-Dist: cyclopts>=4.3.0
|
|
|
26
26
|
Requires-Dist: httpx>=0.28.0
|
|
27
27
|
Requires-Dist: pydantic>=2.10.0
|
|
28
28
|
Requires-Dist: rich>=14.2.0
|
|
29
|
+
Provides-Extra: template
|
|
30
|
+
Requires-Dist: jinja2>=3.1.0; extra == 'template'
|
|
29
31
|
Description-Content-Type: text/markdown
|
|
30
32
|
|
|
31
33
|
# 🐑 Woolly
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
woolly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
woolly/__main__.py,sha256=em9-CiwyaP3N5FKr_Srv6Eh8Vm6TOC3VWSwXR1BjzV8,304
|
|
3
|
+
woolly/cache.py,sha256=lUoTUd_Ui8052QoOHIc3CcNLQc8ErDwwRK_uP8Qzxjo,2933
|
|
4
|
+
woolly/debug.py,sha256=85_0_lsqccT8TAkyWFinOIqkPVPpcmrKuNfIYQOEqZ4,5294
|
|
5
|
+
woolly/http.py,sha256=bG1AjyF5Umjej-R2ywEPivGarTBiW9Q7bMwy2JsULJU,1521
|
|
6
|
+
woolly/progress.py,sha256=Ij-K3-6-qeVLFKD_WdSfuclZh6uiso9XSSVRlpbB4jA,2040
|
|
7
|
+
woolly/commands/__init__.py,sha256=3xzeIPYD0dmVWwRXHkdPmsF2IIRU6KyvDwgemKdyghI,840
|
|
8
|
+
woolly/commands/check.py,sha256=w3bc8eAdhfrUJeFTGOb7nCsFwBY0B6hQtbOPfInWgRk,20520
|
|
9
|
+
woolly/commands/clear_cache.py,sha256=nCS52v_hNvKTwj62CKrQRfjX53fGpZnWpfB0GMmi8e4,1039
|
|
10
|
+
woolly/commands/list_formats.py,sha256=f4UAWjoBbDu8YhobQyccErwpmOqElrCxBg0xcnJVDUQ,695
|
|
11
|
+
woolly/commands/list_languages.py,sha256=VFjvMgXnvaW3O-MBEpzf5nd89yQ9TqLqcULPDCDUthg,857
|
|
12
|
+
woolly/languages/__init__.py,sha256=CwcPDIMvFpCK1tFbx5rew_5mLrrMy6s4HT6C-ZZz_7M,2527
|
|
13
|
+
woolly/languages/base.py,sha256=OmpMuVSZZo8xjAp6JAZAyr98fH6FQhsPL-BI6YnvOPw,18288
|
|
14
|
+
woolly/languages/python.py,sha256=4EhHKlcDxjyk6_rKaLNmCfLReavJfm1DKyV0SdoJMqY,15303
|
|
15
|
+
woolly/languages/rust.py,sha256=gMCcFRGcNCyEbLruRHk9oxWbW9oYXmVIWXbDVGjnfeg,6674
|
|
16
|
+
woolly/reporters/__init__.py,sha256=-85BHuDAQwyrgWtJqME76FT4jyrvS4cuas7CxaWDODU,3301
|
|
17
|
+
woolly/reporters/base.py,sha256=M8A3vZLPGR7btR10P9Ac56UYDFpH2xC2zCvwL7_xAR8,6911
|
|
18
|
+
woolly/reporters/json.py,sha256=B_QSUs-ldVFhx4ijcRGCLmF0umxQHk2M76E40k-8PUU,9265
|
|
19
|
+
woolly/reporters/markdown.py,sha256=CfYD9t31IgRTU4GfYytWLv-jVTsv5KpZ6J6yEFrv_nE,7454
|
|
20
|
+
woolly/reporters/stdout.py,sha256=1O7jz2u_6Onr5dLnWqcHamxID-fHBaIAfqnE5ZEC0i0,8126
|
|
21
|
+
woolly/reporters/template.py,sha256=C9amNJ3kJg6Cv8DCcLM3QiOIV_Sa8I1qAWIoAODv0D8,9066
|
|
22
|
+
woolly-0.5.0.dist-info/METADATA,sha256=BZ0GnVZv7N1lyQrTZB35jwMtEmK0yotyoKpEepE4qbA,10477
|
|
23
|
+
woolly-0.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
24
|
+
woolly-0.5.0.dist-info/entry_points.txt,sha256=tMSCR0PXsaIWiiY5fwQ5eowwNqPSrH0j9QriPDoE75k,48
|
|
25
|
+
woolly-0.5.0.dist-info/licenses/LICENSE,sha256=TnfrdmTYydTTenujKk2LdAuDuLSMwjpXhW7EU2VR0e8,1064
|
|
26
|
+
woolly-0.5.0.dist-info/RECORD,,
|
woolly-0.3.0.dist-info/RECORD
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
woolly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
woolly/__main__.py,sha256=em9-CiwyaP3N5FKr_Srv6Eh8Vm6TOC3VWSwXR1BjzV8,304
|
|
3
|
-
woolly/cache.py,sha256=tzZE0FrymWQZSpJ5ZFgv2G17CVYq7SzsxpVWsF7ByXI,2405
|
|
4
|
-
woolly/debug.py,sha256=85_0_lsqccT8TAkyWFinOIqkPVPpcmrKuNfIYQOEqZ4,5294
|
|
5
|
-
woolly/http.py,sha256=xPFbHPWEWTN4mjEONyJ9hERmb62mMWun0jRjTAaaNE8,872
|
|
6
|
-
woolly/progress.py,sha256=Ij-K3-6-qeVLFKD_WdSfuclZh6uiso9XSSVRlpbB4jA,2040
|
|
7
|
-
woolly/commands/__init__.py,sha256=3xzeIPYD0dmVWwRXHkdPmsF2IIRU6KyvDwgemKdyghI,840
|
|
8
|
-
woolly/commands/check.py,sha256=kCFQClTOiBAP-DRpAQFbDGgIKiPv8SlMYKoQzx6Fq4E,13312
|
|
9
|
-
woolly/commands/clear_cache.py,sha256=nCS52v_hNvKTwj62CKrQRfjX53fGpZnWpfB0GMmi8e4,1039
|
|
10
|
-
woolly/commands/list_formats.py,sha256=f4UAWjoBbDu8YhobQyccErwpmOqElrCxBg0xcnJVDUQ,695
|
|
11
|
-
woolly/commands/list_languages.py,sha256=VFjvMgXnvaW3O-MBEpzf5nd89yQ9TqLqcULPDCDUthg,857
|
|
12
|
-
woolly/languages/__init__.py,sha256=CwcPDIMvFpCK1tFbx5rew_5mLrrMy6s4HT6C-ZZz_7M,2527
|
|
13
|
-
woolly/languages/base.py,sha256=Sjm9wXWeM36w6eYx_YyHm0jM6TqAUshlCiSPczW1_IA,12365
|
|
14
|
-
woolly/languages/python.py,sha256=aA1fmHO0VWV_sHjZF92G30frv916Veh6cqjIj354UgM,6723
|
|
15
|
-
woolly/languages/rust.py,sha256=DdhflO0PDvpgGy41lst9vUW9vF1Xrf94RJIMAsWSRDc,4508
|
|
16
|
-
woolly/reporters/__init__.py,sha256=dS1XZGPTjvCkDetiWikkmBn7OnpvnmMXGZDYz2LVQr4,2833
|
|
17
|
-
woolly/reporters/base.py,sha256=xXwrNb2NVVktQHjYCBT4nCzdqpvQsVCVsSs5-hB0D8I,6264
|
|
18
|
-
woolly/reporters/json.py,sha256=mJb3tZDxvzEZ2zeOKELKJaU1E3b4TG86_DtsEJ8JHGE,5707
|
|
19
|
-
woolly/reporters/markdown.py,sha256=fh--BH1rQP46qhTXiWcTHX3X9kjQJsAAXFFiToqK-RA,4300
|
|
20
|
-
woolly/reporters/stdout.py,sha256=Xzku45BElUFyHh_pCWqAW4efb2mhC54CEja0xN6Xoe0,2784
|
|
21
|
-
woolly-0.3.0.dist-info/METADATA,sha256=SMLGfdfa3_Rk6S36nGkJfiagX2bvmYMpb4llzvE6NCc,10402
|
|
22
|
-
woolly-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
23
|
-
woolly-0.3.0.dist-info/entry_points.txt,sha256=tMSCR0PXsaIWiiY5fwQ5eowwNqPSrH0j9QriPDoE75k,48
|
|
24
|
-
woolly-0.3.0.dist-info/licenses/LICENSE,sha256=TnfrdmTYydTTenujKk2LdAuDuLSMwjpXhW7EU2VR0e8,1064
|
|
25
|
-
woolly-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|