diff --git a/src/wily/__main__.py b/src/wily/__main__.py index ba14d4e6..8f3bd356 100644 --- a/src/wily/__main__.py +++ b/src/wily/__main__.py @@ -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", diff --git a/src/wily/commands/graph.py b/src/wily/commands/graph.py index 7c692f37..7e417574 100644 --- a/src/wily/commands/graph.py +++ b/src/wily/commands/graph.py @@ -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 @@ -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, @@ -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 = [] diff --git a/test/integration/test_graph.py b/test/integration/test_graph.py index d6743afa..7596f1b6 100644 --- a/test/integration/test_graph.py +++ b/test/integration/test_graph.py @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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", ], @@ -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 @@ -159,6 +172,7 @@ def test_graph_output(builddir): builddir, "graph", _path, + "-m", "raw.loc", "-o", "test.html", @@ -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 diff --git a/test/unit/test_cli.py b/test/unit/test_cli.py index 0300c671..40c1f8a0 100644 --- a/test/unit/test_cli.py +++ b/test/unit/test_cli.py @@ -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(): @@ -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(): @@ -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"