diff options
Diffstat (limited to 'src/nvd/cli/formatters.py')
| -rw-r--r-- | src/nvd/cli/formatters.py | 137 |
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) |
