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 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
- reporter = get_reporter(report, console=console)
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
@@ -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, console: Optional[Console] = None
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
- packaged_packages=sorted(data.unique_packaged_packages),
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
 
@@ -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
- lines.append("## Dependency Tree")
84
- lines.append("")
85
- lines.append("```")
86
- lines.append(self._tree_to_text(data.tree))
87
- lines.append("```")
88
- lines.append("")
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
 
@@ -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
- self.console.print("[bold]Dependency Tree:[/bold]")
72
- self.console.print(data.tree)
73
- self.console.print()
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.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=kCFQClTOiBAP-DRpAQFbDGgIKiPv8SlMYKoQzx6Fq4E,13312
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=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,,
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