src\wily\commands\report.py
-- -- --- --- --- --- --- --- ------- ------- ------- | """
-- -- --- --- --- --- --- --- ------- ------- ------- | 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 | )
-- -- --- --- --- --- --- --- ------- ------- ------- | )