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):
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):
"""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):
"""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")