aboutsummaryrefslogtreecommitdiffstats
path: root/src/nvd/cli/formatters.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvd/cli/formatters.py')
-rw-r--r--src/nvd/cli/formatters.py137
1 files changed, 137 insertions, 0 deletions
diff --git a/src/nvd/cli/formatters.py b/src/nvd/cli/formatters.py
new file mode 100644
index 0000000..fa95e9e
--- /dev/null
+++ b/src/nvd/cli/formatters.py
@@ -0,0 +1,137 @@
+"""Output formatters for CLI."""
+
+import json
+import sys
+from typing import Any, List
+
+import yaml
+from rich.console import Console
+from rich.table import Table
+
+from ..models import CPEData, CPEMatchString, CVEData, SourceData
+
+# Console for data output - explicitly use stdout
+console = Console(file=sys.stdout, stderr=False)
+
+
+def format_json(data: Any) -> None:
+ """Format output as JSON."""
+ if hasattr(data, "model_dump"):
+ output = data.model_dump(mode="json", exclude_none=True)
+ else:
+ output = data
+ print(json.dumps(output, indent=2, default=str, ensure_ascii=False))
+
+
+def format_json_lines(data: List[Any]) -> None:
+ """Format multiple items as JSON lines (one per line)."""
+ for item in data:
+ if hasattr(item, "model_dump"):
+ output = item.model_dump(mode="json", exclude_none=True)
+ else:
+ output = item
+ print(json.dumps(output, default=str))
+
+
+def format_yaml(data: Any) -> None:
+ """Format output as YAML."""
+ if hasattr(data, "model_dump"):
+ output = data.model_dump(mode="json", exclude_none=True)
+ else:
+ output = data
+ print(yaml.dump(output, default_flow_style=False, sort_keys=False, allow_unicode=True), end="")
+
+
+def format_cve_table(cves: List[CVEData]) -> None:
+ """Format CVEs as a table."""
+ table = Table(title="CVE Results", show_header=True, header_style="bold magenta")
+ table.add_column("CVE ID", style="cyan", no_wrap=True)
+ table.add_column("Published", style="green")
+ table.add_column("CVSS v3", justify="right", style="yellow")
+ table.add_column("Status", style="blue")
+ table.add_column("Description", style="white", max_width=60)
+
+ for cve in cves:
+ score = cve.cvss_v3_score or cve.cvss_v2_score
+ score_str = f"{score:.1f}" if score else "N/A"
+
+ description = cve.description[:100] + "..." if len(cve.description) > 100 else cve.description
+
+ table.add_row(
+ cve.id,
+ cve.published.strftime("%Y-%m-%d"),
+ score_str,
+ cve.vulnStatus,
+ description,
+ )
+
+ console.print(table)
+
+
+def format_cpe_table(cpes: List[CPEData]) -> None:
+ """Format CPEs as a table."""
+ table = Table(title="CPE Results", show_header=True, header_style="bold magenta")
+ table.add_column("CPE Name", style="cyan", no_wrap=True, max_width=50)
+ table.add_column("Title", style="green", max_width=40)
+ table.add_column("Deprecated", style="yellow")
+ table.add_column("Last Modified", style="blue")
+
+ for cpe in cpes:
+ table.add_row(
+ cpe.cpeName,
+ cpe.title,
+ "Yes" if cpe.deprecated else "No",
+ cpe.lastModified.strftime("%Y-%m-%d"),
+ )
+
+ console.print(table)
+
+
+def format_match_criteria_table(matches: List[CPEMatchString]) -> None:
+ """Format CPE match criteria as a table."""
+ table = Table(title="CPE Match Criteria", show_header=True, header_style="bold magenta")
+ table.add_column("Criteria", style="cyan", max_width=50)
+ table.add_column("Status", style="green")
+ table.add_column("Version Range", style="yellow", max_width=30)
+
+ for match in matches:
+ version_range = ""
+ if match.versionStartIncluding:
+ version_range += f">={match.versionStartIncluding} "
+ if match.versionStartExcluding:
+ version_range += f">{match.versionStartExcluding} "
+ if match.versionEndIncluding:
+ version_range += f"<={match.versionEndIncluding} "
+ if match.versionEndExcluding:
+ version_range += f"<{match.versionEndExcluding} "
+
+ table.add_row(
+ match.criteria,
+ match.status,
+ version_range.strip() or "All versions",
+ )
+
+ console.print(table)
+
+
+def format_source_table(sources: List[SourceData]) -> None:
+ """Format sources as a table."""
+ table = Table(title="Data Sources", show_header=True, header_style="bold magenta")
+ table.add_column("Name", style="cyan")
+ table.add_column("Contact Email", style="green")
+ table.add_column("Identifiers", style="yellow", max_width=40)
+ table.add_column("Created", style="blue")
+
+ for source in sources:
+ identifiers = ", ".join(source.sourceIdentifiers[:3])
+ if len(source.sourceIdentifiers) > 3:
+ identifiers += f" (+{len(source.sourceIdentifiers) - 3} more)"
+
+ table.add_row(
+ source.name,
+ source.contactEmail,
+ identifiers,
+ source.created.strftime("%Y-%m-%d"),
+ )
+
+ console.print(table)