/
deps.tree.ex
135 lines (102 loc) · 3.65 KB
/
deps.tree.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
defmodule Mix.Tasks.Deps.Tree do
use Mix.Task
@shortdoc "Prints the dependency tree"
@recursive true
@moduledoc """
Prints the dependency tree.
mix deps.tree
If no dependency is given, it uses the tree defined in the `mix.exs` file.
## Command line options
* `--only` - the environment to show dependencies for
* `--target` - the target to show dependencies for
* `--exclude` - exclude dependencies which you do not want to see printed.
* `--format` - Can be set to one of either:
* `pretty` - uses Unicode codepoints for formatting the tree.
This is the default except on Windows.
* `plain` - does not use Unicode codepoints for formatting the tree.
This is the default on Windows.
* `dot` - produces a DOT graph description of the dependency tree
in `deps_tree.dot` in the current directory.
Warning: this will override any previously generated file.
"""
@switches [only: :string, target: :string, exclude: :keep, format: :string]
@impl true
def run(args) do
Mix.Project.get!()
{opts, args, _} = OptionParser.parse(args, switches: @switches)
deps_opts =
for {switch, key} <- [only: :env, target: :target],
value = opts[switch],
do: {key, :"#{value}"}
deps = Mix.Dep.load_on_environment(deps_opts)
root =
case args do
[] ->
Mix.Project.config()[:app] ||
Mix.raise("no application given and none found in mix.exs file")
[app] ->
app = String.to_atom(app)
find_dep(deps, app) || Mix.raise("could not find dependency #{app}")
end
if opts[:format] == "dot" do
callback = callback(&format_dot/1, deps, opts)
Mix.Utils.write_dot_graph!("deps_tree.dot", "dependency tree", [root], callback, opts)
"""
Generated "deps_tree.dot" in the current directory. To generate a PNG:
dot -Tpng deps_tree.dot -o deps_tree.png
For more options see http://www.graphviz.org/.
"""
|> String.trim_trailing()
|> Mix.shell().info()
else
callback = callback(&format_tree/1, deps, opts)
Mix.Utils.print_tree([root], callback, opts)
end
end
defp callback(formatter, deps, opts) do
excluded = Keyword.get_values(opts, :exclude) |> Enum.map(&String.to_atom/1)
top_level = Enum.filter(deps, & &1.top_level)
fn
%Mix.Dep{app: app} = dep ->
# Do not show dependencies if they were
# already shown at the top level
deps =
if not dep.top_level && find_dep(top_level, app) do
[]
else
find_dep(deps, app).deps
end
{formatter.(dep), exclude(deps, excluded)}
app ->
{{Atom.to_string(app), nil}, exclude(top_level, excluded)}
end
end
defp exclude(deps, excluded) do
Enum.reject(deps, &(&1.app in excluded))
end
defp format_dot(%{app: app, requirement: requirement, opts: opts}) do
override =
if opts[:override] do
" *override*"
else
""
end
requirement = requirement && requirement(requirement)
{app, "#{requirement}#{override}"}
end
defp format_tree(%{app: app, scm: scm, requirement: requirement, opts: opts}) do
override =
if opts[:override] do
IO.ANSI.format([:bright, " *override*"])
else
""
end
requirement = requirement && "#{requirement(requirement)} "
{app, "#{requirement}(#{scm.format(opts)})#{override}"}
end
defp requirement(%Regex{} = regex), do: "#{inspect(regex)}"
defp requirement(binary) when is_binary(binary), do: binary
defp find_dep(deps, app) do
Enum.find(deps, &(&1.app == app))
end
end