-
Notifications
You must be signed in to change notification settings - Fork 3.3k
/
do.ex
122 lines (87 loc) · 3.06 KB
/
do.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
defmodule Mix.Tasks.Do do
use Mix.Task
@shortdoc "Executes the tasks separated by plus"
@moduledoc """
Executes the tasks separated by `+`:
$ mix do compile --list + deps
The plus should be followed by at least one space before and after.
## Examples
The example below prints the available compilers and
then the list of dependencies.
$ mix do compile --list + deps
Note that the majority of Mix tasks are only executed once
per invocation. So for example, the following command will
only compile once:
$ mix do compile + some_other_command + compile
When `compile` is executed again, Mix will notice the task
has already ran, and skip it.
Inside umbrella projects, you can limit recursive tasks
(the ones that run inside every app) by selecting the
desired application via the `--app` flag after `do` and
before the first task:
$ mix do --app app1 --app app2 compile --list + deps
Elixir versions prior to v1.14 used the comma exclusively
to separate commands:
$ mix do compile --list, deps
Since then, the `+` operator has been introduced as a
separator for better support on Windows terminals.
## Command line options
* `--app` - limit recursive tasks to the given apps.
This option may be given multiple times and must come
before any of the tasks.
"""
# TODO: Deprecate using comma on Elixir v1.18
@impl true
def run(args) do
Mix.Task.reenable("do")
{apps, args} = extract_apps_from_args(args)
show_forgotten_apps_warning(apps)
Enum.each(gather_commands(args), fn [task | args] ->
if apps == [] do
Mix.Task.run(task, args)
else
Mix.Task.run_in_apps(task, apps, args)
end
end)
end
defp show_forgotten_apps_warning([]), do: nil
defp show_forgotten_apps_warning(apps) do
config = Mix.Project.config()
if Mix.Project.umbrella?(config) do
known_apps = Mix.Project.apps_paths(config)
for app <- apps, not Map.has_key?(known_apps, app) do
Mix.shell().info([:yellow, "warning: could not find application #{inspect(app)}"])
end
end
end
defp extract_apps_from_args(args) do
{opts, args} = OptionParser.parse_head!(args, strict: [app: :keep])
apps =
opts
|> Keyword.get_values(:app)
|> Enum.map(&String.to_atom/1)
{apps, args}
end
@doc false
def gather_commands(args) do
gather_commands(args, [], [])
end
defp gather_commands([head | rest], current, acc)
when binary_part(head, byte_size(head), -1) == "," do
current =
case binary_part(head, 0, byte_size(head) - 1) do
"" -> Enum.reverse(current)
part -> Enum.reverse([part | current])
end
gather_commands(rest, [], [current | acc])
end
defp gather_commands(["+" | rest], current, acc) do
gather_commands(rest, [], [Enum.reverse(current) | acc])
end
defp gather_commands([head | rest], current, acc) do
gather_commands(rest, [head | current], acc)
end
defp gather_commands([], current, acc) do
Enum.reverse([Enum.reverse(current) | acc])
end
end