woolly 0.3.0__py3-none-any.whl → 0.4.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/commands/check.py +69 -1
- woolly/reporters/__init__.py +15 -1
- woolly/reporters/base.py +3 -0
- woolly/reporters/json.py +6 -1
- woolly/reporters/markdown.py +12 -9
- woolly/reporters/stdout.py +5 -4
- woolly/reporters/template.py +215 -0
- {woolly-0.3.0.dist-info → woolly-0.4.0.dist-info}/METADATA +3 -1
- {woolly-0.3.0.dist-info → woolly-0.4.0.dist-info}/RECORD +12 -11
- {woolly-0.3.0.dist-info → woolly-0.4.0.dist-info}/WHEEL +0 -0
- {woolly-0.3.0.dist-info → woolly-0.4.0.dist-info}/entry_points.txt +0 -0
- {woolly-0.3.0.dist-info → woolly-0.4.0.dist-info}/licenses/LICENSE +0 -0
woolly/commands/check.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Check command - analyze package dependencies for Fedora availability.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import fnmatch
|
|
6
|
+
from pathlib import Path
|
|
5
7
|
from typing import Annotated, Optional
|
|
6
8
|
|
|
7
9
|
import cyclopts
|
|
@@ -41,6 +43,7 @@ def build_tree(
|
|
|
41
43
|
tracker: Optional[ProgressTracker] = None,
|
|
42
44
|
include_optional: bool = False,
|
|
43
45
|
is_optional_dep: bool = False,
|
|
46
|
+
exclude_patterns: Optional[list[str]] = None,
|
|
44
47
|
):
|
|
45
48
|
"""
|
|
46
49
|
Recursively build a dependency tree for a package.
|
|
@@ -65,6 +68,8 @@ def build_tree(
|
|
|
65
68
|
If True, include optional dependencies in the analysis.
|
|
66
69
|
is_optional_dep
|
|
67
70
|
If True, this package is an optional dependency.
|
|
71
|
+
exclude_patterns
|
|
72
|
+
List of glob patterns to exclude from the dependency tree.
|
|
68
73
|
|
|
69
74
|
Returns
|
|
70
75
|
-------
|
|
@@ -156,6 +161,12 @@ def build_tree(
|
|
|
156
161
|
tracker.update(package_name, discovered=len(deps))
|
|
157
162
|
|
|
158
163
|
for dep_name, _dep_req, dep_is_optional in deps:
|
|
164
|
+
# Skip dependencies matching exclude patterns
|
|
165
|
+
if exclude_patterns:
|
|
166
|
+
if any(fnmatch.fnmatch(dep_name, pattern) for pattern in exclude_patterns):
|
|
167
|
+
log(f"Filtered out dependency: {dep_name}", level="info", depth=depth)
|
|
168
|
+
continue
|
|
169
|
+
|
|
159
170
|
child = build_tree(
|
|
160
171
|
provider,
|
|
161
172
|
dep_name,
|
|
@@ -166,6 +177,7 @@ def build_tree(
|
|
|
166
177
|
tracker,
|
|
167
178
|
include_optional=include_optional,
|
|
168
179
|
is_optional_dep=dep_is_optional,
|
|
180
|
+
exclude_patterns=exclude_patterns,
|
|
169
181
|
)
|
|
170
182
|
if isinstance(child, str):
|
|
171
183
|
node.add(child)
|
|
@@ -306,6 +318,28 @@ def check(
|
|
|
306
318
|
help="Report format: stdout, json, markdown. Use 'list-formats' for all options.",
|
|
307
319
|
),
|
|
308
320
|
] = "stdout",
|
|
321
|
+
missing_only: Annotated[
|
|
322
|
+
bool,
|
|
323
|
+
cyclopts.Parameter(
|
|
324
|
+
("--missing-only", "-m"),
|
|
325
|
+
negative=(),
|
|
326
|
+
help="Only display packages that are missing from Fedora.",
|
|
327
|
+
),
|
|
328
|
+
] = False,
|
|
329
|
+
exclude: Annotated[
|
|
330
|
+
tuple[str, ...],
|
|
331
|
+
cyclopts.Parameter(
|
|
332
|
+
("--exclude", "-e"),
|
|
333
|
+
help="Glob pattern(s) to exclude dependencies (e.g., '*-windows', 'win*'). Can be specified multiple times.",
|
|
334
|
+
),
|
|
335
|
+
] = (),
|
|
336
|
+
template: Annotated[
|
|
337
|
+
Optional[str],
|
|
338
|
+
cyclopts.Parameter(
|
|
339
|
+
("--template", "-t"),
|
|
340
|
+
help="Path to a Jinja2 template file for custom report format. Only used with --report=template.",
|
|
341
|
+
),
|
|
342
|
+
] = None,
|
|
309
343
|
):
|
|
310
344
|
"""Check if a package's dependencies are available in Fedora.
|
|
311
345
|
|
|
@@ -327,6 +361,12 @@ def check(
|
|
|
327
361
|
Enable verbose debug logging.
|
|
328
362
|
report
|
|
329
363
|
Output format for the report.
|
|
364
|
+
missing_only
|
|
365
|
+
Only display packages that are missing from Fedora.
|
|
366
|
+
exclude
|
|
367
|
+
Glob pattern(s) to exclude dependencies from the analysis.
|
|
368
|
+
template
|
|
369
|
+
Path to a Jinja2 template file for custom report format.
|
|
330
370
|
"""
|
|
331
371
|
# Get the language provider
|
|
332
372
|
provider = get_provider(lang)
|
|
@@ -336,12 +376,33 @@ def check(
|
|
|
336
376
|
raise SystemExit(1)
|
|
337
377
|
|
|
338
378
|
# Get the reporter
|
|
339
|
-
|
|
379
|
+
template_path = Path(template) if template else None
|
|
380
|
+
|
|
381
|
+
# Validate template reporter requirements
|
|
382
|
+
if report.lower() in ("template", "tpl", "jinja", "jinja2"):
|
|
383
|
+
if template_path is None:
|
|
384
|
+
console.print("[red]Template reporter requires --template parameter.[/red]")
|
|
385
|
+
console.print(
|
|
386
|
+
"Example: woolly check mypackage --report=template --template=my_template.md"
|
|
387
|
+
)
|
|
388
|
+
raise SystemExit(1)
|
|
389
|
+
if not template_path.exists():
|
|
390
|
+
console.print(f"[red]Template file not found: {template_path}[/red]")
|
|
391
|
+
raise SystemExit(1)
|
|
392
|
+
elif template_path is not None:
|
|
393
|
+
console.print(
|
|
394
|
+
"[yellow]Warning: --template is only used with --report=template[/yellow]"
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
reporter = get_reporter(report, console=console, template_path=template_path)
|
|
340
398
|
if reporter is None:
|
|
341
399
|
console.print(f"[red]Unknown report format: {report}[/red]")
|
|
342
400
|
console.print(f"Available formats: {', '.join(get_available_formats())}")
|
|
343
401
|
raise SystemExit(1)
|
|
344
402
|
|
|
403
|
+
# Convert exclude tuple to list for consistency
|
|
404
|
+
exclude_patterns = list(exclude) if exclude else None
|
|
405
|
+
|
|
345
406
|
# Initialize logging
|
|
346
407
|
setup_logger(debug=debug)
|
|
347
408
|
log(
|
|
@@ -352,6 +413,7 @@ def check(
|
|
|
352
413
|
include_optional=optional,
|
|
353
414
|
debug=debug,
|
|
354
415
|
report_format=report,
|
|
416
|
+
exclude_patterns=exclude_patterns,
|
|
355
417
|
)
|
|
356
418
|
|
|
357
419
|
console.print(
|
|
@@ -359,6 +421,10 @@ def check(
|
|
|
359
421
|
)
|
|
360
422
|
if optional:
|
|
361
423
|
console.print("[yellow]Including optional dependencies[/yellow]")
|
|
424
|
+
if exclude_patterns:
|
|
425
|
+
console.print(
|
|
426
|
+
f"[yellow]Excluding dependencies matching: {', '.join(exclude_patterns)}[/yellow]"
|
|
427
|
+
)
|
|
362
428
|
console.print(f"[dim]Registry: {provider.registry_name}[/dim]")
|
|
363
429
|
console.print(f"[dim]Cache directory: {CACHE_DIR}[/dim]")
|
|
364
430
|
console.print()
|
|
@@ -376,6 +442,7 @@ def check(
|
|
|
376
442
|
max_depth=max_depth,
|
|
377
443
|
tracker=tracker,
|
|
378
444
|
include_optional=optional,
|
|
445
|
+
exclude_patterns=exclude_patterns,
|
|
379
446
|
)
|
|
380
447
|
if tracker:
|
|
381
448
|
tracker.finish()
|
|
@@ -407,6 +474,7 @@ def check(
|
|
|
407
474
|
optional_packaged=stats.optional_packaged,
|
|
408
475
|
optional_missing=stats.optional_missing,
|
|
409
476
|
optional_missing_packages=stats.optional_missing_list,
|
|
477
|
+
missing_only=missing_only,
|
|
410
478
|
)
|
|
411
479
|
|
|
412
480
|
# Generate report
|
woolly/reporters/__init__.py
CHANGED
|
@@ -6,6 +6,7 @@ To add a new format, create a module in this directory that defines a class
|
|
|
6
6
|
inheriting from Reporter and add it to REPORTERS dict.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
from pathlib import Path
|
|
9
10
|
from typing import Optional
|
|
10
11
|
|
|
11
12
|
from pydantic import BaseModel, Field
|
|
@@ -15,6 +16,7 @@ from woolly.reporters.base import ReportData, Reporter, strip_markup
|
|
|
15
16
|
from woolly.reporters.json import JsonReporter
|
|
16
17
|
from woolly.reporters.markdown import MarkdownReporter
|
|
17
18
|
from woolly.reporters.stdout import StdoutReporter
|
|
19
|
+
from woolly.reporters.template import TemplateReporter
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class ReporterInfo(BaseModel):
|
|
@@ -32,6 +34,7 @@ REPORTERS: dict[str, type[Reporter]] = {
|
|
|
32
34
|
"stdout": StdoutReporter,
|
|
33
35
|
"markdown": MarkdownReporter,
|
|
34
36
|
"json": JsonReporter,
|
|
37
|
+
"template": TemplateReporter,
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
# Aliases for convenience
|
|
@@ -39,11 +42,16 @@ ALIASES: dict[str, str] = {
|
|
|
39
42
|
"md": "markdown",
|
|
40
43
|
"console": "stdout",
|
|
41
44
|
"terminal": "stdout",
|
|
45
|
+
"tpl": "template",
|
|
46
|
+
"jinja": "template",
|
|
47
|
+
"jinja2": "template",
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
|
|
45
51
|
def get_reporter(
|
|
46
|
-
format_name: str,
|
|
52
|
+
format_name: str,
|
|
53
|
+
console: Optional[Console] = None,
|
|
54
|
+
template_path: Optional[Path] = None,
|
|
47
55
|
) -> Optional[Reporter]:
|
|
48
56
|
"""
|
|
49
57
|
Get an instantiated reporter for the specified format.
|
|
@@ -51,6 +59,7 @@ def get_reporter(
|
|
|
51
59
|
Args:
|
|
52
60
|
format_name: Format identifier or alias (e.g., "json", "markdown", "md")
|
|
53
61
|
console: Console instance for stdout reporter.
|
|
62
|
+
template_path: Path to template file for template reporter.
|
|
54
63
|
|
|
55
64
|
Returns:
|
|
56
65
|
Instantiated Reporter, or None if not found.
|
|
@@ -68,6 +77,10 @@ def get_reporter(
|
|
|
68
77
|
if format_name == "stdout" and console:
|
|
69
78
|
return StdoutReporter(console=console)
|
|
70
79
|
|
|
80
|
+
# TemplateReporter needs a template path
|
|
81
|
+
if format_name == "template":
|
|
82
|
+
return TemplateReporter(template_path=template_path)
|
|
83
|
+
|
|
71
84
|
return reporter_class()
|
|
72
85
|
|
|
73
86
|
|
|
@@ -104,6 +117,7 @@ __all__ = [
|
|
|
104
117
|
"StdoutReporter",
|
|
105
118
|
"MarkdownReporter",
|
|
106
119
|
"JsonReporter",
|
|
120
|
+
"TemplateReporter",
|
|
107
121
|
"get_reporter",
|
|
108
122
|
"list_reporters",
|
|
109
123
|
"get_available_formats",
|
woolly/reporters/base.py
CHANGED
|
@@ -73,6 +73,9 @@ class ReportData(BaseModel):
|
|
|
73
73
|
max_depth: int = 50
|
|
74
74
|
version: Optional[str] = None
|
|
75
75
|
|
|
76
|
+
# Display options
|
|
77
|
+
missing_only: bool = False
|
|
78
|
+
|
|
76
79
|
@cached_property
|
|
77
80
|
def required_missing_packages(self) -> set[str]:
|
|
78
81
|
"""Get the set of required (non-optional) missing packages."""
|
woolly/reporters/json.py
CHANGED
|
@@ -37,6 +37,7 @@ class ReportMetadata(BaseModel):
|
|
|
37
37
|
version: Optional[str] = None
|
|
38
38
|
max_depth: int
|
|
39
39
|
include_optional: bool
|
|
40
|
+
missing_only: bool = False
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
class ReportSummary(BaseModel):
|
|
@@ -86,6 +87,7 @@ class JsonReporter(Reporter):
|
|
|
86
87
|
version=data.version,
|
|
87
88
|
max_depth=data.max_depth,
|
|
88
89
|
include_optional=data.include_optional,
|
|
90
|
+
missing_only=data.missing_only,
|
|
89
91
|
),
|
|
90
92
|
summary=ReportSummary(
|
|
91
93
|
total_dependencies=data.total_dependencies,
|
|
@@ -99,7 +101,10 @@ class JsonReporter(Reporter):
|
|
|
99
101
|
),
|
|
100
102
|
missing_packages=sorted(data.required_missing_packages),
|
|
101
103
|
missing_optional_packages=sorted(data.optional_missing_set),
|
|
102
|
-
|
|
104
|
+
# Skip packaged packages list when missing_only mode is enabled
|
|
105
|
+
packaged_packages=[]
|
|
106
|
+
if data.missing_only
|
|
107
|
+
else sorted(data.unique_packaged_packages),
|
|
103
108
|
dependency_tree=self._tree_to_model(data.tree),
|
|
104
109
|
)
|
|
105
110
|
|
woolly/reporters/markdown.py
CHANGED
|
@@ -29,6 +29,8 @@ class MarkdownReporter(Reporter):
|
|
|
29
29
|
lines.append(f"**Version:** {data.version}")
|
|
30
30
|
if data.include_optional:
|
|
31
31
|
lines.append("**Include optional:** Yes")
|
|
32
|
+
if data.missing_only:
|
|
33
|
+
lines.append("**Missing only:** Yes")
|
|
32
34
|
lines.append("")
|
|
33
35
|
|
|
34
36
|
# Summary
|
|
@@ -69,8 +71,8 @@ class MarkdownReporter(Reporter):
|
|
|
69
71
|
lines.append(f"- `{name}` *(optional)*")
|
|
70
72
|
lines.append("")
|
|
71
73
|
|
|
72
|
-
# Packaged packages - use computed property
|
|
73
|
-
if data.unique_packaged_packages:
|
|
74
|
+
# Packaged packages - use computed property (skip if missing_only mode)
|
|
75
|
+
if data.unique_packaged_packages and not data.missing_only:
|
|
74
76
|
lines.append("## Packaged Packages")
|
|
75
77
|
lines.append("")
|
|
76
78
|
lines.append("The following packages are already available in Fedora:")
|
|
@@ -79,13 +81,14 @@ class MarkdownReporter(Reporter):
|
|
|
79
81
|
lines.append(f"- `{name}`")
|
|
80
82
|
lines.append("")
|
|
81
83
|
|
|
82
|
-
# Dependency tree
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
# Dependency tree (skip if missing_only mode)
|
|
85
|
+
if not data.missing_only:
|
|
86
|
+
lines.append("## Dependency Tree")
|
|
87
|
+
lines.append("")
|
|
88
|
+
lines.append("```")
|
|
89
|
+
lines.append(self._tree_to_text(data.tree))
|
|
90
|
+
lines.append("```")
|
|
91
|
+
lines.append("")
|
|
89
92
|
|
|
90
93
|
return "\n".join(lines)
|
|
91
94
|
|
woolly/reporters/stdout.py
CHANGED
|
@@ -67,9 +67,10 @@ class StdoutReporter(Reporter):
|
|
|
67
67
|
self.console.print(f" • {name} [dim](optional)[/dim]")
|
|
68
68
|
self.console.print()
|
|
69
69
|
|
|
70
|
-
# Print dependency tree
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
# Print dependency tree (skip if missing_only mode is enabled)
|
|
71
|
+
if not data.missing_only:
|
|
72
|
+
self.console.print("[bold]Dependency Tree:[/bold]")
|
|
73
|
+
self.console.print(data.tree)
|
|
74
|
+
self.console.print()
|
|
74
75
|
|
|
75
76
|
return "" # Output is printed directly
|
|
@@ -0,0 +1,215 @@
|
|
|
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
|
+
- version: Package version (if specified)
|
|
27
|
+
- timestamp: Formatted timestamp string (YYYY-MM-DD HH:MM:SS)
|
|
28
|
+
- max_depth: Maximum recursion depth used
|
|
29
|
+
|
|
30
|
+
Statistics:
|
|
31
|
+
- total_dependencies: Total number of dependencies analyzed
|
|
32
|
+
- packaged_count: Number of packages available in Fedora
|
|
33
|
+
- missing_count: Number of packages missing from Fedora
|
|
34
|
+
|
|
35
|
+
Optional dependency statistics:
|
|
36
|
+
- include_optional: Whether optional deps were included
|
|
37
|
+
- optional_total: Total optional dependencies
|
|
38
|
+
- optional_packaged: Optional deps available in Fedora
|
|
39
|
+
- optional_missing: Optional deps missing from Fedora
|
|
40
|
+
|
|
41
|
+
Package lists (sorted):
|
|
42
|
+
- missing_packages: List of missing required package names
|
|
43
|
+
- packaged_packages: List of packaged package names
|
|
44
|
+
- optional_missing_packages: List of missing optional package names
|
|
45
|
+
|
|
46
|
+
Flags:
|
|
47
|
+
- missing_only: Whether missing-only mode was enabled
|
|
48
|
+
|
|
49
|
+
Example template:
|
|
50
|
+
# Report for {{ root_package }}
|
|
51
|
+
|
|
52
|
+
Generated: {{ timestamp }}
|
|
53
|
+
Language: {{ language }}
|
|
54
|
+
|
|
55
|
+
## Summary
|
|
56
|
+
- Total: {{ total_dependencies }}
|
|
57
|
+
- Packaged: {{ packaged_count }}
|
|
58
|
+
- Missing: {{ missing_count }}
|
|
59
|
+
|
|
60
|
+
{% if missing_packages %}
|
|
61
|
+
## Missing Packages
|
|
62
|
+
{% for pkg in missing_packages %}
|
|
63
|
+
- {{ pkg }}
|
|
64
|
+
{% endfor %}
|
|
65
|
+
{% endif %}
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
name = "template"
|
|
69
|
+
description = "Custom template-based report (requires --template)"
|
|
70
|
+
file_extension = "md"
|
|
71
|
+
writes_to_file = True
|
|
72
|
+
|
|
73
|
+
def __init__(self, template_path: Optional[Path] = None):
|
|
74
|
+
"""Initialize the template reporter.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
template_path: Path to the Jinja2 template file.
|
|
78
|
+
"""
|
|
79
|
+
self._template_path = template_path
|
|
80
|
+
self._jinja2_available: Optional[bool] = None
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def template_path(self) -> Optional[Path]:
|
|
84
|
+
"""Get the template path."""
|
|
85
|
+
return self._template_path
|
|
86
|
+
|
|
87
|
+
@template_path.setter
|
|
88
|
+
def template_path(self, value: Optional[Path]) -> None:
|
|
89
|
+
"""Set the template path."""
|
|
90
|
+
self._template_path = value
|
|
91
|
+
|
|
92
|
+
def _check_jinja2(self) -> bool:
|
|
93
|
+
"""Check if Jinja2 is available.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
True if Jinja2 is available, False otherwise.
|
|
97
|
+
"""
|
|
98
|
+
if self._jinja2_available is None:
|
|
99
|
+
try:
|
|
100
|
+
import jinja2 # type: ignore[import-not-found] # noqa: F401
|
|
101
|
+
|
|
102
|
+
self._jinja2_available = True
|
|
103
|
+
except ImportError:
|
|
104
|
+
self._jinja2_available = False
|
|
105
|
+
return self._jinja2_available
|
|
106
|
+
|
|
107
|
+
def _get_template_context(self, data: ReportData) -> dict:
|
|
108
|
+
"""Build the template context with allowed variables only.
|
|
109
|
+
|
|
110
|
+
This method creates a safe, limited context for template rendering.
|
|
111
|
+
Only specific variables from ReportData are exposed.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
data: Report data containing all information.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Dictionary of template variables.
|
|
118
|
+
"""
|
|
119
|
+
return {
|
|
120
|
+
# Metadata
|
|
121
|
+
"root_package": data.root_package,
|
|
122
|
+
"language": data.language,
|
|
123
|
+
"registry": data.registry,
|
|
124
|
+
"version": data.version,
|
|
125
|
+
"timestamp": data.timestamp.strftime("%Y-%m-%d %H:%M:%S"),
|
|
126
|
+
"max_depth": data.max_depth,
|
|
127
|
+
# Statistics
|
|
128
|
+
"total_dependencies": data.total_dependencies,
|
|
129
|
+
"packaged_count": data.packaged_count,
|
|
130
|
+
"missing_count": data.missing_count,
|
|
131
|
+
# Optional dependency statistics
|
|
132
|
+
"include_optional": data.include_optional,
|
|
133
|
+
"optional_total": data.optional_total,
|
|
134
|
+
"optional_packaged": data.optional_packaged,
|
|
135
|
+
"optional_missing": data.optional_missing,
|
|
136
|
+
# Package lists (sorted for consistent output)
|
|
137
|
+
"missing_packages": sorted(data.required_missing_packages),
|
|
138
|
+
"packaged_packages": sorted(data.unique_packaged_packages),
|
|
139
|
+
"optional_missing_packages": sorted(data.optional_missing_set),
|
|
140
|
+
# Flags
|
|
141
|
+
"missing_only": data.missing_only,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
def generate(self, data: ReportData) -> str:
|
|
145
|
+
"""Generate report content from the template.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
data: Report data containing all information.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Rendered template content as a string.
|
|
152
|
+
|
|
153
|
+
Raises:
|
|
154
|
+
RuntimeError: If Jinja2 is not installed or template is not set.
|
|
155
|
+
FileNotFoundError: If the template file doesn't exist.
|
|
156
|
+
jinja2.TemplateError: If there's a template syntax error.
|
|
157
|
+
"""
|
|
158
|
+
if not self._check_jinja2():
|
|
159
|
+
raise RuntimeError(
|
|
160
|
+
"Jinja2 is required for template reports. "
|
|
161
|
+
"Install it with: pip install jinja2"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if self._template_path is None:
|
|
165
|
+
raise RuntimeError(
|
|
166
|
+
"No template path specified. Use --template to provide a template file."
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
if not self._template_path.exists():
|
|
170
|
+
raise FileNotFoundError(f"Template file not found: {self._template_path}")
|
|
171
|
+
|
|
172
|
+
# Import Jinja2 here (we've already checked it's available)
|
|
173
|
+
from jinja2 import ( # type: ignore[import-not-found]
|
|
174
|
+
Environment,
|
|
175
|
+
FileSystemLoader,
|
|
176
|
+
StrictUndefined,
|
|
177
|
+
select_autoescape,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Create Jinja2 environment with security settings
|
|
181
|
+
env = Environment(
|
|
182
|
+
loader=FileSystemLoader(self._template_path.parent),
|
|
183
|
+
autoescape=select_autoescape(default=False),
|
|
184
|
+
undefined=StrictUndefined, # Raise error on undefined variables
|
|
185
|
+
# Disable dangerous features
|
|
186
|
+
extensions=[],
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Add custom filter to strip Rich markup
|
|
190
|
+
env.filters["strip_markup"] = strip_markup
|
|
191
|
+
|
|
192
|
+
# Load and render template
|
|
193
|
+
template = env.get_template(self._template_path.name)
|
|
194
|
+
context = self._get_template_context(data)
|
|
195
|
+
|
|
196
|
+
return template.render(**context)
|
|
197
|
+
|
|
198
|
+
def get_output_filename(self, data: ReportData) -> str:
|
|
199
|
+
"""Get the output filename for the template report.
|
|
200
|
+
|
|
201
|
+
Uses the template filename as a base if available, otherwise
|
|
202
|
+
falls back to the default naming convention.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
data: Report data.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Filename string.
|
|
209
|
+
"""
|
|
210
|
+
timestamp = data.timestamp.strftime("%Y%m%d_%H%M%S")
|
|
211
|
+
if self._template_path:
|
|
212
|
+
# Use template name as base (without extension)
|
|
213
|
+
base_name = self._template_path.stem
|
|
214
|
+
return f"woolly_{data.root_package}_{base_name}_{timestamp}.md"
|
|
215
|
+
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.4.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
|
|
@@ -5,7 +5,7 @@ woolly/debug.py,sha256=85_0_lsqccT8TAkyWFinOIqkPVPpcmrKuNfIYQOEqZ4,5294
|
|
|
5
5
|
woolly/http.py,sha256=xPFbHPWEWTN4mjEONyJ9hERmb62mMWun0jRjTAaaNE8,872
|
|
6
6
|
woolly/progress.py,sha256=Ij-K3-6-qeVLFKD_WdSfuclZh6uiso9XSSVRlpbB4jA,2040
|
|
7
7
|
woolly/commands/__init__.py,sha256=3xzeIPYD0dmVWwRXHkdPmsF2IIRU6KyvDwgemKdyghI,840
|
|
8
|
-
woolly/commands/check.py,sha256=
|
|
8
|
+
woolly/commands/check.py,sha256=6E61LJnVzkMbJDdk90eH9mFD34qemve4h15SButcthU,15975
|
|
9
9
|
woolly/commands/clear_cache.py,sha256=nCS52v_hNvKTwj62CKrQRfjX53fGpZnWpfB0GMmi8e4,1039
|
|
10
10
|
woolly/commands/list_formats.py,sha256=f4UAWjoBbDu8YhobQyccErwpmOqElrCxBg0xcnJVDUQ,695
|
|
11
11
|
woolly/commands/list_languages.py,sha256=VFjvMgXnvaW3O-MBEpzf5nd89yQ9TqLqcULPDCDUthg,857
|
|
@@ -13,13 +13,14 @@ woolly/languages/__init__.py,sha256=CwcPDIMvFpCK1tFbx5rew_5mLrrMy6s4HT6C-ZZz_7M,
|
|
|
13
13
|
woolly/languages/base.py,sha256=Sjm9wXWeM36w6eYx_YyHm0jM6TqAUshlCiSPczW1_IA,12365
|
|
14
14
|
woolly/languages/python.py,sha256=aA1fmHO0VWV_sHjZF92G30frv916Veh6cqjIj354UgM,6723
|
|
15
15
|
woolly/languages/rust.py,sha256=DdhflO0PDvpgGy41lst9vUW9vF1Xrf94RJIMAsWSRDc,4508
|
|
16
|
-
woolly/reporters/__init__.py,sha256
|
|
17
|
-
woolly/reporters/base.py,sha256=
|
|
18
|
-
woolly/reporters/json.py,sha256=
|
|
19
|
-
woolly/reporters/markdown.py,sha256=
|
|
20
|
-
woolly/reporters/stdout.py,sha256=
|
|
21
|
-
woolly
|
|
22
|
-
woolly-0.
|
|
23
|
-
woolly-0.
|
|
24
|
-
woolly-0.
|
|
25
|
-
woolly-0.
|
|
16
|
+
woolly/reporters/__init__.py,sha256=-85BHuDAQwyrgWtJqME76FT4jyrvS4cuas7CxaWDODU,3301
|
|
17
|
+
woolly/reporters/base.py,sha256=3j_tHH-epyYg-KGNW_ApsOCp_p_hJwAG_Azx1jUoDjs,6318
|
|
18
|
+
woolly/reporters/json.py,sha256=Zh0OpOIEKlJiUHTr_FZF_Ys-hJN0drLi1mZg5zZL6LM,5915
|
|
19
|
+
woolly/reporters/markdown.py,sha256=_lXzoFDkQXzBgpg7EX0Fl4U_Oj3NUtAMUOw8u6GuPec,4520
|
|
20
|
+
woolly/reporters/stdout.py,sha256=IOE8gk3U85gpDsZXRWCHBxxCYRieR6PqUhVhZplYGcM,2869
|
|
21
|
+
woolly/reporters/template.py,sha256=U48rP9DMwk4p-fk26jldfU_uXllHBT5N1LHTVomFTFI,7702
|
|
22
|
+
woolly-0.4.0.dist-info/METADATA,sha256=nVgVJAKQA1fsWXmfmyYUzDMY3f1zrVGs-3081Lo85a8,10477
|
|
23
|
+
woolly-0.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
24
|
+
woolly-0.4.0.dist-info/entry_points.txt,sha256=tMSCR0PXsaIWiiY5fwQ5eowwNqPSrH0j9QriPDoE75k,48
|
|
25
|
+
woolly-0.4.0.dist-info/licenses/LICENSE,sha256=TnfrdmTYydTTenujKk2LdAuDuLSMwjpXhW7EU2VR0e8,1064
|
|
26
|
+
woolly-0.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|