Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow passing multiple file names to graph, to get multiple lines in HTML #206

Merged
merged 9 commits into from
Aug 25, 2023
15 changes: 10 additions & 5 deletions src/wily/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,20 +386,25 @@ def diff(ctx, files, metrics, all, detail, revision, wrap):

Graph all .py files within src/ for the raw.loc metric

$ wily graph src/ raw.loc
$ wily graph src/ -m raw.loc

Graph test.py against raw.loc and cyclomatic.complexity metrics

$ wily graph src/test.py raw.loc cyclomatic.complexity
$ wily graph src/test.py -m raw.loc,cyclomatic.complexity

Graph test.py against raw.loc and raw.sloc on the x-axis

$ wily graph src/test.py raw.loc --x-axis raw.sloc
$ wily graph src/test.py -m raw.loc --x-axis raw.sloc
"""
)
)
@click.argument("path", type=click.Path(resolve_path=False))
@click.argument("metrics", nargs=-1, required=True)
@click.argument("path", nargs=-1, type=click.Path(resolve_path=False))
@click.option(
"-m",
"--metrics",
required=True,
help=_("Comma-separated list of metrics, see list-metrics for choices"),
)
@click.option(
"-o",
"--output",
Expand Down
49 changes: 32 additions & 17 deletions src/wily/commands/graph.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""
Draw graph in HTML for a specific metric.
Graph command.

TODO: Add multiple lines for multiple files
Draw graph in HTML for a specific metric.
"""

from pathlib import Path
from typing import Optional, Tuple, Union
from typing import Optional, Tuple

import plotly.graph_objs as go
import plotly.offline
Expand All @@ -22,10 +22,17 @@ def metric_parts(metric):
return operator.name, met.name


def path_startswith(filename: str, path: str) -> bool:
"""Check whether a filename starts with a given path in platform-agnostic way."""
filepath = Path(filename).resolve()
path_ = Path(path).resolve()
return str(filepath).startswith(str(path_))


def graph(
config: WilyConfig,
path: str,
metrics: Union[Tuple[str], Tuple[str, str]],
path: Tuple[str, ...],
metrics: str,
output: Optional[str] = None,
x_axis: Optional[str] = None,
changes: bool = True,
Expand Down Expand Up @@ -55,27 +62,35 @@ def graph(
else:
x_operator, x_key = metric_parts(x_axis)

y_metric = resolve_metric(metrics[0])
title = f"{x_axis.capitalize()} of {y_metric.description} for {path}{' aggregated' if aggregate else ''}"
metrics_list = metrics.split(",")

y_metric = resolve_metric(metrics_list[0])

if not aggregate:
tracked_files = set()
for rev in state.index[state.default_archiver].revisions:
tracked_files.update(rev.revision.tracked_files)
paths = {
tracked_file
for tracked_file in tracked_files
if tracked_file.startswith(path)
} or {path}
paths = (
tuple(
tracked_file
for tracked_file in tracked_files
if any(path_startswith(tracked_file, p) for p in path)
)
or path
)
else:
paths = {path}
paths = path

operator, key = metric_parts(metrics[0])
if len(metrics) == 1: # only y-axis
title = (
f"{x_axis.capitalize()} of {y_metric.description}"
f"{(' for ' + paths[0]) if len(paths) == 1 else ''}{' aggregated' if aggregate else ''}"
)
operator, key = metric_parts(metrics_list[0])
if len(metrics_list) == 1: # only y-axis
z_axis = z_operator = z_key = ""
else:
z_axis = resolve_metric(metrics[1])
z_operator, z_key = metric_parts(metrics[1])
z_axis = resolve_metric(metrics_list[1])
z_operator, z_key = metric_parts(metrics_list[1])
for path_ in paths:
current_path = str(Path(path_))
x = []
Expand Down
52 changes: 39 additions & 13 deletions test/integration/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,25 @@ def test_graph_no_cache(tmpdir, cache_path):
with patch.dict("os.environ", values=PATCHED_ENV, clear=True):
result = runner.invoke(
main.cli,
["--path", tmpdir, "--cache", cache_path, "graph", _path, "raw.loc"],
["--path", tmpdir, "--cache", cache_path, "graph", _path, "-m", "raw.loc"],
)
assert result.exit_code == 1, result.stdout


def test_graph_no_path(builddir):
"""Test the graph feature with no path given"""
runner = CliRunner()
with patch.dict("os.environ", values=PATCHED_ENV, clear=True):
result = runner.invoke(main.cli, ["--path", builddir, "graph", "-m", "raw.loc"])
assert result.exit_code == 1, result.stdout


def test_graph(builddir):
"""Test the graph feature"""
runner = CliRunner()
with patch.dict("os.environ", values=PATCHED_ENV, clear=True):
result = runner.invoke(
main.cli, ["--path", builddir, "graph", _path, "raw.loc"]
main.cli, ["--path", builddir, "graph", _path, "-m", "raw.loc"]
)
assert result.exit_code == 0, result.stdout

Expand All @@ -42,7 +50,7 @@ def test_graph_all(builddir):
runner = CliRunner()
with patch.dict("os.environ", values=PATCHED_ENV, clear=True):
result = runner.invoke(
main.cli, ["--path", builddir, "graph", _path, "raw.loc", "--all"]
main.cli, ["--path", builddir, "graph", _path, "-m", "raw.loc", "--all"]
)
assert result.exit_code == 0, result.stdout

Expand All @@ -52,7 +60,7 @@ def test_graph_all_with_shorthand_metric(builddir):
runner = CliRunner()
with patch.dict("os.environ", values=PATCHED_ENV, clear=True):
result = runner.invoke(
main.cli, ["--path", builddir, "graph", _path, "loc", "--all"]
main.cli, ["--path", builddir, "graph", _path, "-m", "loc", "--all"]
)
assert result.exit_code == 0, result.stdout

Expand All @@ -62,7 +70,7 @@ def test_graph_changes(builddir):
runner = CliRunner()
with patch.dict("os.environ", values=PATCHED_ENV, clear=True):
result = runner.invoke(
main.cli, ["--path", builddir, "graph", _path, "raw.loc", "--changes"]
main.cli, ["--path", builddir, "graph", _path, "-m", "raw.loc", "--changes"]
)
assert result.exit_code == 0, result.stdout

Expand All @@ -72,7 +80,8 @@ def test_graph_custom_x(builddir):
runner = CliRunner()
with patch.dict("os.environ", values=PATCHED_ENV, clear=True):
result = runner.invoke(
main.cli, ["--path", builddir, "graph", _path, "raw.loc", "-x", "raw.sloc"]
main.cli,
["--path", builddir, "graph", _path, "-m", "raw.loc", "-x", "raw.sloc"],
)
assert result.exit_code == 0, result.stdout

Expand All @@ -82,7 +91,8 @@ def test_graph_aggregate(builddir):
runner = CliRunner()
with patch.dict("os.environ", values=PATCHED_ENV, clear=True):
result = runner.invoke(
main.cli, ["--path", builddir, "graph", _path, "raw.loc", "--aggregate"]
main.cli,
["--path", builddir, "graph", _path, "-m", "raw.loc", "--aggregate"],
)
assert result.exit_code == 0, result.stdout

Expand All @@ -92,7 +102,8 @@ def test_graph_individual(builddir):
runner = CliRunner()
with patch.dict("os.environ", values=PATCHED_ENV, clear=True):
result = runner.invoke(
main.cli, ["--path", builddir, "graph", _path, "raw.loc", "--individual"]
main.cli,
["--path", builddir, "graph", _path, "-m", "raw.loc", "--individual"],
)
assert result.exit_code == 0, result.stdout

Expand All @@ -102,7 +113,7 @@ def test_graph_path(builddir):
runner = CliRunner()
with patch.dict("os.environ", values=PATCHED_ENV, clear=True):
result = runner.invoke(
main.cli, ["--path", builddir, "graph", "src/", "raw.loc"]
main.cli, ["--path", builddir, "graph", "src/", "-m", "raw.loc"]
)
assert result.exit_code == 0, result.stdout

Expand All @@ -112,7 +123,8 @@ def test_graph_multiple(builddir):
runner = CliRunner()
with patch.dict("os.environ", values=PATCHED_ENV, clear=True):
result = runner.invoke(
main.cli, ["--path", builddir, "graph", _path, "raw.loc", "raw.comments"]
main.cli,
["--path", builddir, "graph", _path, "-m", "raw.loc,raw.comments"],
)
assert result.exit_code == 0, result.stdout

Expand All @@ -128,8 +140,8 @@ def test_graph_multiple_custom_x(builddir):
builddir,
"graph",
_path,
"raw.loc",
"raw.comments",
"-m",
"raw.loc,raw.comments",
"-x",
"raw.sloc",
],
Expand All @@ -142,7 +154,8 @@ def test_graph_multiple_path(builddir):
runner = CliRunner()
with patch.dict("os.environ", values=PATCHED_ENV, clear=True):
result = runner.invoke(
main.cli, ["--path", builddir, "graph", "src/", "raw.loc", "raw.comments"]
main.cli,
["--path", builddir, "graph", "src/", "-m", "raw.loc,raw.comments"],
)
assert result.exit_code == 0, result.stdout

Expand All @@ -159,6 +172,7 @@ def test_graph_output(builddir):
builddir,
"graph",
_path,
"-m",
"raw.loc",
"-o",
"test.html",
Expand All @@ -180,9 +194,21 @@ def test_graph_output_granular(builddir):
builddir,
"graph",
"src/test.py:function1",
"-m",
"cyclomatic.complexity",
"-o",
"test_granular.html",
],
)
assert result.exit_code == 0, result.stdout


def test_graph_multiple_paths(builddir):
"""Test the graph feature with multiple paths"""
runner = CliRunner()
with patch.dict("os.environ", values=PATCHED_ENV, clear=True):
result = runner.invoke(
main.cli,
["--path", builddir, "graph", _path, "src/", "path3", "-m", "raw.loc"],
)
assert result.exit_code == 0, result.stdout
38 changes: 29 additions & 9 deletions test/unit/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,32 @@ def test_graph():
with patch("wily.__main__.exists", return_value=True) as check_cache:
with patch("wily.commands.graph.graph") as graph:
runner = CliRunner()
result = runner.invoke(main.cli, ["graph", "foo.py", "example_metric"])
result = runner.invoke(
main.cli, ["graph", "foo.py", "-m", "example_metric"]
)
assert result.exit_code == 0
assert graph.called_once
assert check_cache.called_once
assert graph.call_args[1]["path"] == ("foo.py",)
assert graph.call_args[1]["metrics"] == "example_metric"


def test_graph_multiple_paths():
"""
Test that graph calls the graph command with multiple paths
"""
with patch("wily.__main__.exists", return_value=True) as check_cache:
with patch("wily.commands.graph.graph") as graph:
runner = CliRunner()
result = runner.invoke(
main.cli,
["graph", "foo.py", "bar.py", "baz.py", "-m", "example_metric"],
)
assert result.exit_code == 0
assert graph.called_once
assert check_cache.called_once
assert graph.call_args[1]["path"] == "foo.py"
assert graph.call_args[1]["metrics"] == ("example_metric",)
assert graph.call_args[1]["path"] == ("foo.py", "bar.py", "baz.py")
assert graph.call_args[1]["metrics"] == "example_metric"


def test_graph_multiple_metrics():
Expand All @@ -303,13 +323,13 @@ def test_graph_multiple_metrics():
with patch("wily.commands.graph.graph") as graph:
runner = CliRunner()
result = runner.invoke(
main.cli, ["graph", "foo.py", "example_metric", "another_metric"]
main.cli, ["graph", "foo.py", "-m", "example_metric,another_metric"]
)
assert result.exit_code == 0
assert graph.called_once
assert check_cache.called_once
assert graph.call_args[1]["path"] == "foo.py"
assert graph.call_args[1]["metrics"] == ("example_metric", "another_metric")
assert graph.call_args[1]["path"] == ("foo.py",)
assert graph.call_args[1]["metrics"] == "example_metric,another_metric"


def test_graph_with_output():
Expand All @@ -320,13 +340,13 @@ def test_graph_with_output():
with patch("wily.commands.graph.graph") as graph:
runner = CliRunner()
result = runner.invoke(
main.cli, ["graph", "foo.py", "example_metric", "-o", "foo.html"]
main.cli, ["graph", "foo.py", "-m", "example_metric", "-o", "foo.html"]
)
assert result.exit_code == 0
assert graph.called_once
assert check_cache.called_once
assert graph.call_args[1]["path"] == "foo.py"
assert graph.call_args[1]["metrics"] == ("example_metric",)
assert graph.call_args[1]["path"] == ("foo.py",)
assert graph.call_args[1]["metrics"] == "example_metric"
assert graph.call_args[1]["output"] == "foo.html"


Expand Down
Loading