diff options
author | Andrea Leopardi <an.leopardi@gmail.com> | 2023-03-07 22:40:55 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-07 22:40:55 -0600 |
commit | f0c88baf37206cac613a9e360bffe37467ab8a43 (patch) | |
tree | c9e159bdb5444671ddea4619b2d47866954ecdf1 | |
parent | fb1c0ab90d91d60894a2b17df71166ca91b93bcd (diff) | |
download | elixir-f0c88baf37206cac613a9e360bffe37467ab8a43.tar.gz |
Add --output to "mix xref" (#12452)
Closes #12343.
-rw-r--r-- | lib/mix/lib/mix/tasks/xref.ex | 33 | ||||
-rw-r--r-- | lib/mix/lib/mix/utils.ex | 10 | ||||
-rw-r--r-- | lib/mix/test/mix/tasks/xref_test.exs | 53 |
3 files changed, 86 insertions, 10 deletions
diff --git a/lib/mix/lib/mix/tasks/xref.ex b/lib/mix/lib/mix/tasks/xref.ex index 66f8b5749..8184c33b6 100644 --- a/lib/mix/lib/mix/tasks/xref.ex +++ b/lib/mix/lib/mix/tasks/xref.ex @@ -129,6 +129,14 @@ defmodule Mix.Tasks.Xref do * `dot` - produces a DOT graph description in `xref_graph.dot` in the current directory. Warning: this will override any previously generated file + * `--output` (since v1.15.0) - can be set to one of + + * `-` - prints the output to standard output; + + * a path - writes the output graph to the given path + + Defaults to `xref_graph.dot` in the current directory. + The `--source` and `--sink` options are particularly useful when trying to understand how the modules in a particular file interact with the whole system. You can combine those options with `--label` and `--only-nodes` to get all files that exhibit a certain @@ -275,7 +283,8 @@ defmodule Mix.Tasks.Xref do only_direct: :boolean, sink: :keep, source: :keep, - min_cycle_size: :integer + min_cycle_size: :integer, + output: :string ] @impl true @@ -816,23 +825,29 @@ defmodule Mix.Tasks.Xref do {found, count} = case opts[:format] do "dot" -> + path = Keyword.get(opts, :output, "xref_graph.dot") + Mix.Utils.write_dot_graph!( - "xref_graph.dot", + path, "xref graph", Enum.sort(roots), callback, opts ) - """ - Generated "xref_graph.dot" in the current directory. To generate a PNG: + if path != "-" do + png_path = (path |> Path.rootname() |> Path.basename()) <> ".png" + + """ + Generated #{inspect(path)} in the current directory. To generate a PNG: - dot -Tpng xref_graph.dot -o xref_graph.png + dot -Tpng #{inspect(path)} -o #{inspect(png_path)} - For more options see http://www.graphviz.org/. - """ - |> String.trim_trailing() - |> Mix.shell().info() + For more options see http://www.graphviz.org/. + """ + |> String.trim_trailing() + |> Mix.shell().info() + end {:references, count_references(file_references)} diff --git a/lib/mix/lib/mix/utils.ex b/lib/mix/lib/mix/utils.ex index ac96aef57..0d90df1d4 100644 --- a/lib/mix/lib/mix/utils.ex +++ b/lib/mix/lib/mix/utils.ex @@ -327,6 +327,8 @@ defmodule Mix.Utils do The callback will be invoked for each node and it must return a `{printed, children}` tuple. + + If `path` is `-`, prints the output to standard output. """ @spec write_dot_graph!( Path.t(), @@ -338,7 +340,13 @@ defmodule Mix.Utils do when node: term() def write_dot_graph!(path, title, nodes, callback, _opts \\ []) do {dot, _} = build_dot_graph(make_ref(), nodes, MapSet.new(), callback) - File.write!(path, ["digraph ", quoted(title), " {\n", dot, "}\n"]) + contents = ["digraph ", quoted(title), " {\n", dot, "}\n"] + + if path == "-" do + IO.write(contents) + else + File.write!(path, contents) + end end defp build_dot_graph(_parent, [], seen, _callback), do: {[], seen} diff --git a/lib/mix/test/mix/tasks/xref_test.exs b/lib/mix/test/mix/tasks/xref_test.exs index 73ecdf44c..c025ff142 100644 --- a/lib/mix/test/mix/tasks/xref_test.exs +++ b/lib/mix/test/mix/tasks/xref_test.exs @@ -822,6 +822,59 @@ defmodule Mix.Tasks.XrefTest do end) end + test "with export to a custom file" do + in_fixture("no_mixfile", fn -> + File.write!("lib/a.ex", """ + defmodule A do + def fun, do: :ok + end + """) + + File.write!("lib/b.ex", """ + defmodule B do + defstruct [] + end + """) + + assert Mix.Task.run("xref", ["graph", "--format", "dot", "--output", "custom.dot"]) == :ok + + assert File.read!("custom.dot") === """ + digraph "xref graph" { + "lib/a.ex" + "lib/b.ex" + } + """ + end) + end + + test "with export to stdout" do + in_fixture("no_mixfile", fn -> + File.write!("lib/a.ex", """ + defmodule A do + def fun, do: :ok + end + """) + + File.write!("lib/b.ex", """ + defmodule B do + defstruct [] + end + """) + + output = + capture_io(fn -> + assert Mix.Task.run("xref", ["graph", "--format", "dot", "--output", "-"]) == :ok + end) + + assert output === """ + digraph "xref graph" { + "lib/a.ex" + "lib/b.ex" + } + """ + end) + end + test "with mixed cyclic dependencies" do in_fixture("no_mixfile", fn -> File.write!("lib/a.ex", """ |