Ejemplo n.º 1
0
def rank(config, path, metric, revision_index, limit, threshold, descending):
    """
    Rank command ordering files, methods or functions using metrics.

    :param config: The configuration
    :type config: :class:'wily.config.WilyConfig'

    :param path: The path to the file
    :type path ''str''

    :param metric: Name of the metric to report on
    :type metric: ''str''

    :param revision_index: Version of git repository to revert to.
    :type revision_index: ``str``

    :param limit: Limit the number of items in the table
    :type  limit: ``int``

    :param threshold: For total values beneath the threshold return a non-zero exit code
    :type  threshold: ``int``

    :return: Sorted table of all files in path, sorted in order of metric.
    """
    logger.debug("Running rank command")

    data = []

    operator, metric = resolve_metric_as_tuple(metric)
    operator = operator.name

    state = State(config)

    if not revision_index:
        target_revision = state.index[state.default_archiver].last_revision
    else:
        rev = resolve_archiver(
            state.default_archiver).cls(config).find(revision_index)
        logger.debug(f"Resolved {revision_index} to {rev.key} ({rev.message})")
        try:
            target_revision = state.index[state.default_archiver][rev.key]
        except KeyError:
            logger.error(
                f"Revision {revision_index} is not in the cache, make sure you have run wily build."
            )
            exit(1)

    logger.info(
        f"-----------Rank for {metric.description} for {format_revision(target_revision.revision.key)} by {target_revision.revision.author_name} on {format_date(target_revision.revision.date)}.------------"
    )

    if path is None:
        files = target_revision.get_paths(config, state.default_archiver,
                                          operator)
        logger.debug(f"Analysing {files}")
    else:
        # Resolve target paths when the cli has specified --path
        if config.path != DEFAULT_PATH:
            targets = [str(Path(config.path) / Path(path))]
        else:
            targets = [path]

        # Expand directories to paths
        files = [
            os.path.relpath(fn, config.path)
            for fn in radon.cli.harvest.iter_filenames(targets)
        ]
        logger.debug(f"Targeting - {files}")

    for item in files:
        for archiver in state.archivers:
            try:
                logger.debug(
                    f"Fetching metric {metric.name} for {operator} in {str(item)}"
                )
                val = target_revision.get(config, archiver, operator,
                                          str(item), metric.name)
                value = val
                data.append((item, value))
            except KeyError:
                logger.debug(f"Could not find file {item} in index")

    # Sort by ideal value
    data = sorted(data, key=op.itemgetter(1), reverse=descending)

    if limit:
        data = data[:limit]

    # Tack on the total row at the end
    total = metric.aggregate(rev[1] for rev in data)
    data.append(["Total", total])

    headers = ("File", metric.description)
    print(
        tabulate.tabulate(headers=headers,
                          tabular_data=data,
                          tablefmt=DEFAULT_GRID_STYLE))

    if threshold and total < threshold:
        logger.error(
            f"Total value below the specified threshold: {total} < {threshold}"
        )
        exit(1)
Ejemplo n.º 2
0
def metric_parts(metric):
    """Convert a metric name into the operator and metric names."""
    operator, met = resolve_metric_as_tuple(metric)
    return operator.name, met.name
Ejemplo n.º 3
0
def report(
    config,
    path,
    metrics,
    n,
    output,
    include_message=False,
    format=ReportFormat.CONSOLE,
    console_format=None,
):
    """
    Show information about the cache and runtime.

    :param config: The configuration
    :type  config: :class:`wily.config.WilyConfig`

    :param path: The path to the file
    :type  path: ``str``

    :param metrics: Name of the metric to report on
    :type  metrics: ``str``

    :param n: Number of items to list
    :type  n: ``int``

    :param output: Output path
    :type  output: ``Path``

    :param include_message: Include revision messages
    :type  include_message: ``bool``

    :param format: Output format
    :type  format: ``ReportFormat``

    :param console_format: Grid format style for tabulate
    :type  console_format: ``str``
    """
    logger.debug("Running report command")
    logger.info(f"-----------History for {metrics}------------")

    data = []
    metric_metas = []

    for metric in metrics:
        operator, metric = resolve_metric_as_tuple(metric)
        key = metric.name
        operator = operator.name
        # Set the delta colors depending on the metric type
        if metric.measure == MetricType.AimHigh:
            good_color = 32
            bad_color = 31
        elif metric.measure == MetricType.AimLow:
            good_color = 31
            bad_color = 32
        elif metric.measure == MetricType.Informational:
            good_color = 33
            bad_color = 33
        metric_meta = {
            "key": key,
            "operator": operator,
            "good_color": good_color,
            "bad_color": bad_color,
            "title": metric.description,
            "type": metric.type,
        }
        metric_metas.append(metric_meta)

    state = State(config)
    for archiver in state.archivers:
        # We have to do it backwards to get the deltas between releases
        history = state.index[archiver].revisions[:n][::-1]
        last = {}
        for rev in history:
            vals = []
            for meta in metric_metas:
                try:
                    logger.debug(
                        f"Fetching metric {meta['key']} for {meta['operator']} in {path}"
                    )
                    val = rev.get(config, archiver, meta["operator"], path,
                                  meta["key"])

                    last_val = last.get(meta["key"], None)
                    # Measure the difference between this value and the last
                    if meta["type"] in (int, float):
                        if last_val:
                            delta = val - last_val
                        else:
                            delta = 0
                        last[meta["key"]] = val
                    else:
                        # TODO : Measure ranking increases/decreases for str types?
                        delta = 0

                    if delta == 0:
                        delta_col = delta
                    elif delta < 0:
                        delta_col = f"\u001b[{meta['good_color']}m{delta:n}\u001b[0m"
                    else:
                        delta_col = f"\u001b[{meta['bad_color']}m+{delta:n}\u001b[0m"

                    if meta["type"] in (int, float):
                        k = f"{val:n} ({delta_col})"
                    else:
                        k = f"{val}"
                except KeyError as e:
                    k = f"Not found {e}"
                vals.append(k)
            if include_message:
                data.append((
                    format_revision(rev.revision.key),
                    rev.revision.message[:MAX_MESSAGE_WIDTH],
                    rev.revision.author_name,
                    format_date(rev.revision.date),
                    *vals,
                ))
            else:
                data.append((
                    format_revision(rev.revision.key),
                    rev.revision.author_name,
                    format_date(rev.revision.date),
                    *vals,
                ))
    descriptions = [meta["title"] for meta in metric_metas]
    if include_message:
        headers = ("Revision", "Message", "Author", "Date", *descriptions)
    else:
        headers = ("Revision", "Author", "Date", *descriptions)

    if format == ReportFormat.HTML:
        if output.is_file and output.suffix == ".html":
            report_path = output.parents[0]
            report_output = output
        else:
            report_path = output
            report_output = output.joinpath("index.html")

        report_path.mkdir(exist_ok=True, parents=True)

        templates_dir = (Path(__file__).parents[1] / "templates").resolve()
        report_template = Template(
            (templates_dir / "report_template.html").read_text())

        table_headers = "".join([f"<th>{header}</th>" for header in headers])
        table_content = ""
        for line in data[::-1]:
            table_content += "<tr>"
            for element in line:
                element = element.replace("[32m", "<span class='green-color'>")
                element = element.replace("[31m", "<span class='red-color'>")
                element = element.replace("[33m",
                                          "<span class='orange-color'>")
                element = element.replace("[0m", "</span>")
                table_content += f"<td>{element}</td>"
            table_content += "</tr>"

        report_template = report_template.safe_substitute(
            headers=table_headers, content=table_content)

        with report_output.open("w") as output:
            output.write(report_template)

        try:
            copytree(str(templates_dir / "css"), str(report_path / "css"))
        except FileExistsError:
            pass

        logger.info(f"wily report was saved to {report_path}")
    else:
        print(
            # But it still makes more sense to show the newest at the top, so reverse again
            tabulate.tabulate(headers=headers,
                              tabular_data=data[::-1],
                              tablefmt=console_format))
Ejemplo n.º 4
0
def report(
    config: WilyConfig,
    path: Path,
    metrics: str,
    n: int,
    output: Path,
    include_message: bool = False,
    format: ReportFormat = ReportFormat.CONSOLE,
    console_format: str = None,
) -> None:
    """
    Show information about the cache and runtime.

    :param config: The configuration
    :type  config: :class:`wily.config.WilyConfig`

    :param path: The path to the file
    :type  path: ``str``

    :param metrics: Name of the metric to report on
    :type  metrics: ``str``

    :param n: Number of items to list
    :type  n: ``int``

    :param output: Output path
    :type  output: ``Path``

    :param include_message: Include revision messages
    :type  include_message: ``bool``

    :param format: Output format
    :type  format: ``ReportFormat``

    :param console_format: Grid format style for tabulate
    :type  console_format: ``str``
    """
    logger.debug("Running report command")
    logger.info(f"-----------History for {metrics}------------")

    data = []
    metric_metas = []

    for metric in metrics:
        operator, metric = resolve_metric_as_tuple(metric)
        # Set the delta colors depending on the metric type
        metric_meta = {
            "key": metric.name,
            "operator": operator.name,
            "title": metric.description,
            "type": metric.type,
            "measure": metric.measure,
        }
        metric_metas.append(metric_meta)

    state = State(config)
    for archiver in state.archivers:
        history = state.index[archiver].revisions[:n][::-1]
        last = {}
        for rev in history:
            vals = []
            for meta in metric_metas:
                try:
                    logger.debug(
                        f"Fetching metric {meta['key']} for {meta['operator']} in {path}"
                    )
                    val = rev.get(config, archiver, meta["operator"], path,
                                  meta["key"])
                    last_val = last.get(meta["key"], None)
                    # Measure the difference between this value and the last
                    if meta["type"] in (int, float):
                        delta = val - last_val if last_val else 0
                        change = delta
                    elif last_val:
                        delta = ord(last_val) - ord(
                            val) if last_val != val else 1
                        change = last_val
                    else:
                        delta = 1
                        change = val

                    last[meta["key"]] = val
                    if delta == 0:
                        delta_col = delta
                    elif delta < 0:
                        delta_col = _plant_delta_color(
                            BAD_COLORS[meta["measure"]], change)
                    else:
                        delta_col = _plant_delta_color(
                            GOOD_COLORS[meta["measure"]], change)
                    k = _plant_delta(val, delta_col)
                except KeyError as e:
                    k = f"Not found {e}"
                vals.append(k)
            if include_message:
                data.append((
                    format_revision(rev.revision.key),
                    rev.revision.message[:MAX_MESSAGE_WIDTH],
                    rev.revision.author_name,
                    format_date(rev.revision.date),
                    *vals,
                ))
            else:
                data.append((
                    format_revision(rev.revision.key),
                    rev.revision.author_name,
                    format_date(rev.revision.date),
                    *vals,
                ))
    descriptions = [meta["title"] for meta in metric_metas]
    if include_message:
        headers = (_("Revision"), _("Message"), _("Author"), _("Date"),
                   *descriptions)
    else:
        headers = (_("Revision"), _("Author"), _("Date"), *descriptions)

    if format in FORMAT_MAP:
        FORMAT_MAP[format](path, output, data, headers)
        return

    print(
        tabulate.tabulate(headers=headers,
                          tabular_data=data[::-1],
                          tablefmt=console_format))