summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrea Leopardi <an.leopardi@gmail.com>2023-03-07 22:40:55 -0600
committerGitHub <noreply@github.com>2023-03-07 22:40:55 -0600
commitf0c88baf37206cac613a9e360bffe37467ab8a43 (patch)
treec9e159bdb5444671ddea4619b2d47866954ecdf1
parentfb1c0ab90d91d60894a2b17df71166ca91b93bcd (diff)
downloadelixir-f0c88baf37206cac613a9e360bffe37467ab8a43.tar.gz
Add --output to "mix xref" (#12452)
Closes #12343.
-rw-r--r--lib/mix/lib/mix/tasks/xref.ex33
-rw-r--r--lib/mix/lib/mix/utils.ex10
-rw-r--r--lib/mix/test/mix/tasks/xref_test.exs53
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", """