src\wily\commands\diff.py
-- -- --- --- --- --- --- --- ------- ------- ------- | """
-- -- --- --- --- --- --- --- ------- ------- ------- | Diff command.
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- | Compares metrics between uncommitted files and indexed files.
-- -- --- --- --- --- --- --- ------- ------- ------- | """
-- -- --- --- --- --- --- --- ------- ------- ------- | import multiprocessing
-- -- --- --- --- --- --- --- ------- ------- ------- | import os
-- -- --- --- --- --- --- --- ------- ------- ------- | from pathlib import Path
-- -- --- --- --- --- --- --- ------- ------- ------- | from sys import exit
-- -- --- --- --- --- --- --- ------- ------- ------- | from typing import List, Optional
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- | import radon.cli.harvest
-- -- --- --- --- --- --- --- ------- ------- ------- | import tabulate
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- | from wily import format_date, format_revision, logger
-- -- --- --- --- --- --- --- ------- ------- ------- | from wily.archivers import resolve_archiver
-- -- --- --- --- --- --- --- ------- ------- ------- | from wily.commands.build import run_operator
-- -- --- --- --- --- --- --- ------- ------- ------- | from wily.config import DEFAULT_PATH
-- -- --- --- --- --- --- --- ------- ------- ------- | from wily.config.types import WilyConfig
-- -- --- --- --- --- --- --- ------- ------- ------- | from wily.helper import get_maxcolwidth, get_style
-- -- --- --- --- --- --- --- ------- ------- ------- | from wily.operators import (
-- -- --- --- --- --- --- --- ------- ------- ------- | BAD_COLORS,
-- -- --- --- --- --- --- --- ------- ------- ------- | GOOD_COLORS,
-- -- --- --- --- --- --- --- ------- ------- ------- | OperatorLevel,
-- -- --- --- --- --- --- --- ------- ------- ------- | get_metric,
-- -- --- --- --- --- --- --- ------- ------- ------- | resolve_metric,
-- -- --- --- --- --- --- --- ------- ------- ------- | resolve_operator,
-- -- --- --- --- --- --- --- ------- ------- ------- | )
-- -- --- --- --- --- --- --- ------- ------- ------- | from wily.state import State
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- -- --- --- --- --- --- --- ------- ------- ------- |
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | def diff(
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | config: WilyConfig,
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | files: List[str],
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | metrics: List[str],
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | changes_only: bool = True,
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | detail: bool = True,
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | revision: Optional[str] = None,
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | wrap: bool = False,
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | ) -> None:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | """
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | Show the differences in metrics for each of the files.
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 |
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | :param config: The wily configuration
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | :param files: The files to compare.
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | :param metrics: The metrics to measure.
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | :param changes_only: Only include changes files in output.
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | :param detail: Show details (function-level)
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | :param revision: Compare with specific revision
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | :param wrap: Wrap output
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | """
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | config.targets = files
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | files = list(files)
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | state = State(config)
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 |
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | # Resolve target paths when the cli has specified --path
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | if config.path != DEFAULT_PATH:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | targets = [str(Path(config.path) / Path(file)) for file in files]
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | else:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | targets = files
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 |
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | # Expand directories to paths
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | files = [
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | os.path.relpath(fn, config.path)
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | for fn in radon.cli.harvest.iter_filenames(targets)
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | ]
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | logger.debug(f"Targeting - {files}")
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 |
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | if not revision:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | target_revision = state.index[state.default_archiver].last_revision
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | else:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | rev = (
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | resolve_archiver(state.default_archiver).archiver_cls(config).find(revision)
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | )
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | logger.debug(f"Resolved {revision} to {rev.key} ({rev.message})")
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | try:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | target_revision = state.index[state.default_archiver][rev.key]
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | except KeyError:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | logger.error(
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | f"Revision {revision} is not in the cache, make sure you have run wily build."
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | )
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | exit(1)
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 |
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | logger.info(
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | f"Comparing current with {format_revision(target_revision.revision.key)} by {target_revision.revision.author_name} on {format_date(target_revision.revision.date)}."
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | )
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 |
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | # Convert the list of metrics to a list of metric instances
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | operators = {resolve_operator(metric.split(".")[0]) for metric in metrics}
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | resolved_metrics = [
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | (metric.split(".")[0], resolve_metric(metric)) for metric in metrics
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | ]
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | results = []
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 |
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | # Build a set of operators
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | with multiprocessing.Pool(processes=len(operators)) as pool:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | operator_exec_out = pool.starmap(
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | run_operator, [(operator, None, config, targets) for operator in operators]
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | )
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | data = {}
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | for operator_name, result in operator_exec_out:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | data[operator_name] = result
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 |
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | # Write a summary table
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | extra = []
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | for operator, metric in resolved_metrics:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | if detail and resolve_operator(operator).level == OperatorLevel.Object:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | for file in files:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | try:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | extra.extend(
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | [
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | f"{file}:{k}"
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | for k in data[operator][file]["detailed"].keys()
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | if k != metric.name
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | and isinstance(data[operator][file]["detailed"][k], dict)
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | ]
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | )
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | except KeyError:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | logger.debug(f"File {file} not in cache")
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | logger.debug("Cache follows -- ")
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | logger.debug(data[operator])
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | files.extend(extra)
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | logger.debug(files)
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | for file in files:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | metrics_data = []
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | has_changes = False
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | for operator, metric in resolved_metrics:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | try:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | current = target_revision.get(
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | config, state.default_archiver, operator, file, metric.name
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | )
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | except KeyError:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | current = "-"
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | try:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | new = get_metric(data, operator, file, metric.name)
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | except KeyError:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | new = "-"
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | if new != current:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | has_changes = True
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | if metric.metric_type in (int, float) and new != "-" and current != "-":
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | if current > new: # type: ignore
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | metrics_data.append(
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | "{0:n} -> \u001b[{2}m{1:n}\u001b[0m".format(
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | current, new, BAD_COLORS[metric.measure]
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | )
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | )
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | elif current < new: # type: ignore
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | metrics_data.append(
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | "{0:n} -> \u001b[{2}m{1:n}\u001b[0m".format(
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | current, new, GOOD_COLORS[metric.measure]
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | )
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | )
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | else:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | metrics_data.append(f"{current:n} -> {new:n}")
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | else:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | if current == "-" and new == "-":
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | metrics_data.append("-")
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | else:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | metrics_data.append(f"{current} -> {new}")
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | if has_changes or not changes_only:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | results.append((file, *metrics_data))
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | else:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | logger.debug(metrics_data)
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 |
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | descriptions = [metric.description for _, metric in resolved_metrics]
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | headers = ("File", *descriptions)
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | if len(results) > 0:
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | maxcolwidth = get_maxcolwidth(headers, wrap)
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | style = get_style()
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | print(
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | # But it still makes more sense to show the newest at the top, so reverse again
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | tabulate.tabulate(
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | headers=headers,
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | tabular_data=results,
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | tablefmt=style,
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | maxcolwidths=maxcolwidth,
-- 34 009 028 020 039 037 059 0307.36 1926.47 0006.27 | maxheadercolwidths=maxcolwidth,
-- -- 009 028 020 039 037 059 0307.36 1926.47 0006.27 | )
-- -- --- --- --- --- --- --- ------- ------- ------- | )