-- -- --- --- --- --- --- --- ------- ------- ------- |
"""
-- -- --- --- --- --- --- --- ------- ------- ------- |
Report command.
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
The report command gives a table of metrics for a specified list of files.
-- -- --- --- --- --- --- --- ------- ------- ------- |
Will compare the values between revisions and highlight changes in green/red.
-- -- --- --- --- --- --- --- ------- ------- ------- |
"""
-- -- --- --- --- --- --- --- ------- ------- ------- |
from pathlib import Path
-- -- --- --- --- --- --- --- ------- ------- ------- |
from shutil import copytree
-- -- --- --- --- --- --- --- ------- ------- ------- |
from string import Template
-- -- --- --- --- --- --- --- ------- ------- ------- |
from typing import Iterable
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
import tabulate
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
from wily import MAX_MESSAGE_WIDTH, format_date, format_revision, logger
-- -- --- --- --- --- --- --- ------- ------- ------- |
from wily.config.types import WilyConfig
-- -- --- --- --- --- --- --- ------- ------- ------- |
from wily.helper import get_maxcolwidth
-- -- --- --- --- --- --- --- ------- ------- ------- |
from wily.helper.custom_enums import ReportFormat
-- -- --- --- --- --- --- --- ------- ------- ------- |
from wily.lang import _
-- -- --- --- --- --- --- --- ------- ------- ------- |
from wily.operators import MetricType, resolve_metric_as_tuple
-- -- --- --- --- --- --- --- ------- ------- ------- |
from wily.state import State
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
ANSI_RED = 31
-- -- --- --- --- --- --- --- ------- ------- ------- |
ANSI_GREEN = 32
-- -- --- --- --- --- --- --- ------- ------- ------- |
ANSI_YELLOW = 33
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
def report(
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
config: WilyConfig,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
path: str,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
metrics: Iterable[str],
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
n: int,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
output: Path,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
console_format: str,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
include_message: bool = False,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
format: ReportFormat = ReportFormat.CONSOLE,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
changes_only: bool = False,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
wrap: bool = False,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
) -> None:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
"""
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
Show metrics for a given file.
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
:param config: The configuration
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
:type config: :class:`wily.config.WilyConfig`
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
:param path: The path to the file
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
:param metrics: List of metrics to report on
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
:param n: Number of items to list
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
:param output: Output path
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
:param include_message: Include revision messages
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
:param format: Output format
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
:param console_format: Grid format style for tabulate
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
:param changes_only: Only report revisions where delta != 0
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
:param wrap: Wrap output
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
"""
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
metrics = sorted(metrics)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
logger.debug("Running report command")
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
logger.info(f"-----------History for {metrics}------------")
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
data = []
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
metric_metas = []
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
for metric_name in metrics:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
operator, metric = resolve_metric_as_tuple(metric_name)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
key = metric.name
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
operator = operator.name
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
# Set the delta colors depending on the metric type
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
if metric.measure == MetricType.AimHigh:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
increase_color = ANSI_GREEN
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
decrease_color = ANSI_RED
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
elif metric.measure == MetricType.AimLow:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
increase_color = ANSI_RED
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
decrease_color = ANSI_GREEN
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
elif metric.measure == MetricType.Informational:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
increase_color = ANSI_YELLOW
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
decrease_color = ANSI_YELLOW
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
else:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
increase_color = ANSI_YELLOW
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
decrease_color = ANSI_YELLOW
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
metric_meta = {
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
"key": key,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
"operator": operator,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
"increase_color": increase_color,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
"decrease_color": decrease_color,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
"title": metric.description,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
"type": metric.metric_type,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
}
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
metric_metas.append(metric_meta)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
state = State(config)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
for archiver in state.archivers:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
history = state.index[archiver].revisions[:n][::-1]
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
last = {}
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
for rev in history:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
deltas = []
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
vals = []
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
for meta in metric_metas:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
try:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
logger.debug(
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
f"Fetching metric {meta['key']} for {meta['operator']} in {path}"
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
val = rev.get(config, archiver, meta["operator"], path, meta["key"])
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
last_val = last.get(meta["key"], None)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
# Measure the difference between this value and the last
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
if meta["type"] in (int, float):
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
if last_val:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
delta = val - last_val
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
else:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
delta = 0
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
last[meta["key"]] = val
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
else:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
# TODO : Measure ranking increases/decreases for str types?
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
delta = 0
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
if delta == 0:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
delta_col = delta
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
elif delta < 0:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
delta_col = (
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
f"\u001b[{meta['decrease_color']}m{delta:n}\u001b[0m"
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
else:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
delta_col = (
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
f"\u001b[{meta['increase_color']}m+{delta:n}\u001b[0m"
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
if meta["type"] in (int, float):
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
k = f"{val:n} ({delta_col})"
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
else:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
k = f"{val}"
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
except KeyError as e:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
k = f"Not found {e}"
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
delta = 0
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
deltas.append(delta)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
vals.append(k)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
if not changes_only or any(deltas):
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
if include_message:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
data.append(
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
(
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
format_revision(rev.revision.key),
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
rev.revision.message[:MAX_MESSAGE_WIDTH],
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
rev.revision.author_name,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
format_date(rev.revision.date),
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
*vals,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
else:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
data.append(
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
(
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
format_revision(rev.revision.key),
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
rev.revision.author_name,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
format_date(rev.revision.date),
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
*vals,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
if not data:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
logger.error(f"No data found for {path} with changes={changes_only}.")
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
return
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
descriptions = [meta["title"] for meta in metric_metas]
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
if include_message:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
headers = (_("Revision"), _("Message"), _("Author"), _("Date"), *descriptions)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
else:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
headers = (_("Revision"), _("Author"), _("Date"), *descriptions)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
if format == ReportFormat.HTML:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
if output.is_file and output.suffix == ".html":
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
report_path = output.parents[0]
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
report_output = output
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
else:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
report_path = output
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
report_output = output.joinpath("index.html")
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
report_path.mkdir(exist_ok=True, parents=True)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
templates_dir = (Path(__file__).parents[1] / "templates").resolve()
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
report_template = Template((templates_dir / "report_template.html").read_text())
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
table_headers = "".join([f"<th>{header}</th>" for header in headers])
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
table_content = ""
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
for line in data[::-1]:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
table_content += "<tr>"
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
for element in line:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
element = element.replace("\u001b[32m", "<span class='green-color'>")
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
element = element.replace("\u001b[31m", "<span class='red-color'>")
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
element = element.replace("\u001b[33m", "<span class='orange-color'>")
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
element = element.replace("\u001b[0m", "</span>")
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
table_content += f"<td>{element}</td>"
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
table_content += "</tr>"
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
report_template = report_template.safe_substitute(
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
headers=table_headers, content=table_content
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
with report_output.open("w", errors="xmlcharrefreplace") as output_f:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
output_f.write(report_template)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
try:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
copytree(str(templates_dir / "css"), str(report_path / "css"))
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
except FileExistsError:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
pass
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
logger.info(f"wily report was saved to {report_path}")
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
else:
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
maxcolwidth = get_maxcolwidth(headers, wrap)
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
print(
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
tabulate.tabulate(
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
headers=headers,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
tabular_data=data[::-1],
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
tablefmt=console_format,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
maxcolwidths=maxcolwidth,
-- 27 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
maxheadercolwidths=maxcolwidth,
-- -- 010 033 024 043 043 067 0363.56 2368.65 0006.52 |
)
-- -- --- --- --- --- --- --- ------- ------- ------- |
)