@@ -31,6 +31,9 @@ defmodule Mix.Task do
31
31
32
32
"""
33
33
34
+ @ type task_name :: String . t | atom
35
+ @ type task_module :: atom
36
+
34
37
@ doc """
35
38
A task needs to implement `run` which receives
36
39
a list of command line args.
@@ -42,58 +45,63 @@ defmodule Mix.Task do
42
45
quote do
43
46
Enum . each [ :shortdoc , :recursive ] ,
44
47
& Module . register_attribute ( __MODULE__ , & 1 , persist: true )
45
-
46
48
@ behaviour Mix.Task
47
49
end
48
50
end
49
51
50
52
@ doc """
51
53
Loads all tasks in all code paths.
52
54
"""
55
+ @ spec load_all ( ) :: [ task_module ]
53
56
def load_all , do: load_tasks ( :code . get_path )
54
57
55
58
@ doc """
56
59
Loads all tasks in the given `paths`.
57
60
"""
58
- def load_tasks ( paths ) do
59
- Enum . reduce ( paths , [ ] , fn ( path , matches ) ->
60
- { :ok , files } = :erl_prim_loader . list_dir ( path |> to_char_list )
61
- Enum . reduce ( files , matches , & match_tasks / 2 )
62
- end )
61
+ @ spec load_tasks ( [ List.Chars . t ] ) :: [ task_module ]
62
+ def load_tasks ( dirs ) do
63
+ for dir <- dirs ,
64
+ { :ok , files } = :erl_prim_loader . list_dir ( to_char_list ( dir ) ) ,
65
+ file <- files ,
66
+ mod = task_from_path ( file ) ,
67
+ do: mod
63
68
end
64
69
65
- @ re_pattern Regex . re_pattern ( ~r/ Elixir\. Mix\. Tasks\. .*\. beam$/ )
70
+ @ prefix_size byte_size ( "Elixir.Mix.Tasks." )
71
+ @ suffix_size byte_size ( ".beam" )
66
72
67
- defp match_tasks ( filename , modules ) do
68
- if :re . run ( filename , @ re_pattern , [ capture: :none ] ) == :match do
69
- mod = :filename . rootname ( filename , '.beam' ) |> List . to_atom
70
- if Code . ensure_loaded? ( mod ) , do: [ mod | modules ] , else: modules
71
- else
72
- modules
73
+ defp task_from_path ( filename ) do
74
+ base = Path . basename ( filename )
75
+ part = byte_size ( base ) - @ prefix_size - @ suffix_size
76
+
77
+ case base do
78
+ << "Elixir.Mix.Tasks." , rest :: binary - size ( part ) , ".beam" >> ->
79
+ mod = :"Elixir.Mix.Tasks.#{ rest } "
80
+ ensure_task? ( mod ) && mod
81
+ _ ->
82
+ nil
73
83
end
74
84
end
75
85
76
86
@ doc """
77
- Returns all loaded tasks .
87
+ Returns all loaded task modules .
78
88
79
89
Modules that are not yet loaded won't show up.
80
90
Check `load_all/0` if you want to preload all tasks.
81
91
"""
92
+ @ spec all_modules ( ) :: [ task_module ]
82
93
def all_modules do
83
- Enum . reduce :code . all_loaded , [ ] , fn ( { module , _ } , acc ) ->
84
- case Atom . to_char_list ( module ) do
85
- 'Elixir.Mix.Tasks.' ++ _ ->
86
- if is_task? ( module ) , do: [ module | acc ] , else: acc
87
- _ ->
88
- acc
89
- end
90
- end
94
+ for { module , _ } <- :code . all_loaded ,
95
+ task? ( module ) ,
96
+ do: module
91
97
end
92
98
93
99
@ doc """
94
100
Gets the moduledoc for the given task `module`.
101
+
95
102
Returns the moduledoc or `nil`.
96
103
"""
104
+ @ spec moduledoc ( task_module ) :: String . t | nil
97
105
def moduledoc ( module ) when is_atom ( module ) do
98
106
case Code . get_docs ( module , :moduledoc ) do
99
107
{ _line , moduledoc } -> moduledoc
@@ -103,8 +111,10 @@ defmodule Mix.Task do
103
111
104
112
@ doc """
105
113
Gets the shortdoc for the given task `module`.
114
+
106
115
Returns the shortdoc or `nil`.
107
116
"""
117
+ @ spec shortdoc ( task_module ) :: String . t | nil
108
118
def shortdoc ( module ) when is_atom ( module ) do
109
119
case List . keyfind module . __info__ ( :attributes ) , :shortdoc , 0 do
110
120
{ :shortdoc , [ shortdoc ] } -> shortdoc
@@ -114,8 +124,11 @@ defmodule Mix.Task do
114
124
115
125
@ doc """
116
126
Checks if the task should be run recursively for all sub-apps in
117
- umbrella projects. Returns `true`, `false` or `:both`.
127
+ umbrella projects.
128
+
129
+ Returns `true` or `false`.
118
130
"""
131
+ @ spec recursive ( task_module ) :: boolean
119
132
def recursive ( module ) when is_atom ( module ) do
120
133
case List . keyfind module . __info__ ( :attributes ) , :recursive , 0 do
121
134
{ :recursive , [ setting ] } -> setting
@@ -126,18 +139,25 @@ defmodule Mix.Task do
126
139
@ doc """
127
140
Returns the task name for the given `module`.
128
141
"""
129
- def task_name ( module ) do
142
+ @ spec task_name ( task_module ) :: task_name
143
+ def task_name ( module ) when is_atom ( module ) do
130
144
Mix.Utils . module_name_to_command ( module , 2 )
131
145
end
132
146
133
147
@ doc """
134
- Receives a task name and retrieves the task module.
135
- Returns nil if the task cannot be found.
148
+ Receives a task name and returns `{:ok, module}` if a task is found.
149
+
150
+ Otherwise returns `{:error, :invalid}` in case the module
151
+ exists but it isn't a task or `{:error, :not_found}`.
136
152
"""
137
- def get ( task ) do
138
- case Mix.Utils . command_to_module ( task , Mix.Tasks ) do
139
- { :module , module } -> module
140
- { :error , _ } -> nil
153
+ @ spec get ( task_name ) :: { :ok , task_module } | { :error , :invalid } | { :error , :not_found }
154
+
155
+ def get ( task ) when is_binary ( task ) or is_atom ( task ) do
156
+ case Mix.Utils . command_to_module ( to_string ( task ) , Mix.Tasks ) do
157
+ { :module , module } ->
158
+ if task? ( module ) , do: { :ok , module } , else: { :error , :invalid }
159
+ { :error , _ } ->
160
+ { :error , :not_found }
141
161
end
142
162
end
143
163
@@ -150,15 +170,15 @@ defmodule Mix.Task do
150
170
* `Mix.InvalidTaskError` - raised if the task is not a valid `Mix.Task`
151
171
152
172
"""
173
+ @ spec get! ( task_name ) :: task_module | no_return
153
174
def get! ( task ) do
154
- if module = get ( task ) do
155
- if is_task? ( module ) do
175
+ case get ( task ) do
176
+ { :ok , module } ->
156
177
module
157
- else
178
+ { :error , :invalid } ->
158
179
Mix . raise Mix.InvalidTaskError , task: task
159
- end
160
- else
161
- Mix . raise Mix.NoTaskError , task: task
180
+ { :error , :not_found } ->
181
+ Mix . raise Mix.NoTaskError , task: task
162
182
end
163
183
end
164
184
@@ -174,7 +194,8 @@ defmodule Mix.Task do
174
194
It may raise an exception if the task was not found
175
195
or it is invalid. Check `get!/1` for more information.
176
196
"""
177
- def run ( task , args \\ [ ] ) do
197
+ @ spec run ( task_name , [ any ] ) :: any
198
+ def run ( task , args \\ [ ] ) when is_binary ( task ) or is_atom ( task ) do
178
199
task = to_string ( task )
179
200
180
201
if Mix.TasksServer . run_task ( task , Mix.Project . get ) do
@@ -192,15 +213,18 @@ defmodule Mix.Task do
192
213
@ doc """
193
214
Clears all invoked tasks, allowing them to be reinvoked.
194
215
"""
216
+ @ spec clear :: :ok
195
217
def clear do
196
218
Mix.TasksServer . clear_tasks
197
219
end
198
220
199
221
@ doc """
200
- Reenables a given task so it can be executed again down the stack. If
201
- an umbrella project reenables a task it is reenabled for all sub projects.
222
+ Reenables a given task so it can be executed again down the stack.
223
+
224
+ If an umbrella project reenables a task it is reenabled for all sub projects.
202
225
"""
203
- def reenable ( task ) do
226
+ @ spec reenable ( task_name ) :: :ok
227
+ def reenable ( task ) when is_binary ( task ) or is_atom ( task ) do
204
228
task = to_string ( task )
205
229
module = get! ( task )
206
230
@@ -209,6 +233,8 @@ defmodule Mix.Task do
209
233
recur module , fn project ->
210
234
Mix.TasksServer . delete_task ( task , project )
211
235
end
236
+
237
+ :ok
212
238
end
213
239
214
240
defp recur ( module , fun ) do
@@ -233,7 +259,12 @@ defmodule Mix.Task do
233
259
@ doc """
234
260
Returns `true` if given module is a task.
235
261
"""
236
- def is_task? ( module ) do
237
- function_exported? ( module , :run , 1 )
262
+ @ spec task? ( task_module ) :: boolean ( )
263
+ def task? ( module ) when is_atom ( module ) do
264
+ match? ( 'Elixir.Mix.Tasks.' ++ _ , Atom . to_char_list ( module ) ) and ensure_task? ( module )
265
+ end
266
+
267
+ defp ensure_task? ( module ) do
268
+ Code . ensure_loaded? ( module ) and function_exported? ( module , :run , 1 )
238
269
end
239
270
end
0 commit comments