Source code for mythx_cli.formatter.tabular

"""This module contains a tabular data formatter class printing a subset of the
response data."""

from collections import defaultdict
from itertools import zip_longest
from os.path import basename
from typing import List, Optional, Tuple

from mythx_models.response import (
    AnalysisInputResponse,
    AnalysisListResponse,
    AnalysisStatusResponse,
    DetectedIssuesResponse,
    GroupListResponse,
    GroupStatusResponse,
    VersionResponse,
)
from tabulate import tabulate

from .base import BaseFormatter
from .util import generate_dashboard_link, get_source_location_by_offset


[docs]class TabularFormatter(BaseFormatter): """The tabular formatter. This formatter displays an ASCII table. It is enabled by default and requires the analysis input data to display each issue's line number in the source file. It might break on very large field sizes as cell-internal line breaks are not supported by the tabulate library yet. """ report_requires_input = True
[docs] @staticmethod def format_analysis_list(resp: AnalysisListResponse) -> str: """Format an analysis list response to a tabular representation.""" data = [ (a.uuid, a.status, a.client_tool_name, a.submitted_at) for a in resp.analyses ] return tabulate(data, tablefmt="fancy_grid")
[docs] @staticmethod def format_group_list(resp: GroupListResponse) -> str: """Format an analysis group response to a tabular representation.""" data = [ ( group.identifier, group.status, ",".join([basename(x) for x in group.main_source_files]), group.created_at.strftime("%Y-%m-%d %H:%M:%S%z"), ) for group in resp.groups ] return tabulate(data, tablefmt="fancy_grid")
[docs] @staticmethod def format_group_status(resp: GroupStatusResponse) -> str: """Format a group status response to a tabular representation.""" data = ( ( ("ID", resp.group.identifier), ("Name", resp.group.name or "<unnamed>"), ( "Creation Date", resp.group.created_at.strftime("%Y-%m-%d %H:%M:%S%z"), ), ("Created By", resp.group.created_by), ("Progress", "{}/100".format(resp.group.progress)), ) + tuple( zip_longest( ("Main Sources",), resp.group.main_source_files, fillvalue="" ) ) + ( ("Status", resp.group.status.title()), ("Queued Analyses", resp.group.analysis_statistics.queued or 0), ("Running Analyses", resp.group.analysis_statistics.running or 0), ("Failed Analyses", resp.group.analysis_statistics.failed or 0), ("Finished Analyses", resp.group.analysis_statistics.finished or 0), ("Total Analyses", resp.group.analysis_statistics.total or 0), ( "High Severity Vulnerabilities", resp.group.vulnerability_statistics.high or 0, ), ( "Medium Severity Vulnerabilities", resp.group.vulnerability_statistics.medium or 0, ), ( "Low Severity Vulnerabilities", resp.group.vulnerability_statistics.low or 0, ), ( "Unknown Severity Vulnerabilities", resp.group.vulnerability_statistics.none or 0, ), ) ) return tabulate(data, tablefmt="fancy_grid")
[docs] @staticmethod def format_analysis_status(resp: AnalysisStatusResponse) -> str: """Format an analysis status response to a tabular representation.""" data = ((k, v) for k, v in resp.analysis.to_dict().items()) return tabulate(data, tablefmt="fancy_grid")
[docs] @staticmethod def format_detected_issues( issues_list: List[ Tuple[DetectedIssuesResponse, Optional[AnalysisInputResponse]] ] ) -> str: """Format an issue report to a tabular representation.""" res = [] for resp, inp in issues_list: file_to_issue = defaultdict(list) for report in resp.issue_reports: for issue in report.issues: if ( issue.swc_id == "" and issue.swc_title == "" and not issue.locations ): res.extend((issue.description_long, "")) source_formats = [loc.source_format for loc in issue.locations] for loc in issue.locations: if loc.source_format != "text" and "text" in source_formats: continue for c in loc.source_map.components: # This is so nested, a barn swallow might be hidden somewhere. source_list = loc.source_list or report.source_list if not (source_list and 0 <= c.file_id < len(source_list)): continue filename = source_list[c.file_id] if not inp.sources or filename not in inp.sources: line = "bytecode offset {}".format(c.offset) else: line = get_source_location_by_offset( inp.sources[filename]["source"], c.offset ) file_to_issue[filename].append( ( resp.uuid, line, issue.swc_title, issue.severity, issue.description_short, ) ) for filename, data in file_to_issue.items(): res.append("Report for {}".format(filename)) res.extend( ( generate_dashboard_link(data[0][0]), tabulate( [d[1:] for d in data], tablefmt="fancy_grid", headers=( "Line", "SWC Title", "Severity", "Short Description", ), ), ) ) return "\n".join(res)
[docs] @staticmethod def format_version(resp: VersionResponse) -> str: """Format a version response to a tabular representation.""" data = ((k.title(), v) for k, v in resp.to_dict().items()) return tabulate(data, tablefmt="fancy_grid")