@@ -31,6 +31,9 @@ defmodule Mix.Task do
3131
3232 """
3333
34+ @ type task_name :: String . t | atom
35+ @ type task_module :: atom
36+
3437 @ doc """
3538 A task needs to implement `run` which receives
3639 a list of command line args.
@@ -42,58 +45,63 @@ defmodule Mix.Task do
4245 quote do
4346 Enum . each [ :shortdoc , :recursive ] ,
4447 & Module . register_attribute ( __MODULE__ , & 1 , persist: true )
45-
4648 @ behaviour Mix.Task
4749 end
4850 end
4951
5052 @ doc """
5153 Loads all tasks in all code paths.
5254 """
55+ @ spec load_all ( ) :: [ task_module ]
5356 def load_all , do: load_tasks ( :code . get_path )
5457
5558 @ doc """
5659 Loads all tasks in the given `paths`.
5760 """
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
6368 end
6469
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" )
6672
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
7383 end
7484 end
7585
7686 @ doc """
77- Returns all loaded tasks .
87+ Returns all loaded task modules .
7888
7989 Modules that are not yet loaded won't show up.
8090 Check `load_all/0` if you want to preload all tasks.
8191 """
92+ @ spec all_modules ( ) :: [ task_module ]
8293 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
9197 end
9298
9399 @ doc """
94100 Gets the moduledoc for the given task `module`.
101+
95102 Returns the moduledoc or `nil`.
96103 """
104+ @ spec moduledoc ( task_module ) :: String . t | nil
97105 def moduledoc ( module ) when is_atom ( module ) do
98106 case Code . get_docs ( module , :moduledoc ) do
99107 { _line , moduledoc } -> moduledoc
@@ -103,8 +111,10 @@ defmodule Mix.Task do
103111
104112 @ doc """
105113 Gets the shortdoc for the given task `module`.
114+
106115 Returns the shortdoc or `nil`.
107116 """
117+ @ spec shortdoc ( task_module ) :: String . t | nil
108118 def shortdoc ( module ) when is_atom ( module ) do
109119 case List . keyfind module . __info__ ( :attributes ) , :shortdoc , 0 do
110120 { :shortdoc , [ shortdoc ] } -> shortdoc
@@ -114,8 +124,11 @@ defmodule Mix.Task do
114124
115125 @ doc """
116126 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`.
118130 """
131+ @ spec recursive ( task_module ) :: boolean
119132 def recursive ( module ) when is_atom ( module ) do
120133 case List . keyfind module . __info__ ( :attributes ) , :recursive , 0 do
121134 { :recursive , [ setting ] } -> setting
@@ -126,18 +139,25 @@ defmodule Mix.Task do
126139 @ doc """
127140 Returns the task name for the given `module`.
128141 """
129- def task_name ( module ) do
142+ @ spec task_name ( task_module ) :: task_name
143+ def task_name ( module ) when is_atom ( module ) do
130144 Mix.Utils . module_name_to_command ( module , 2 )
131145 end
132146
133147 @ 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}`.
136152 """
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 }
141161 end
142162 end
143163
@@ -150,15 +170,15 @@ defmodule Mix.Task do
150170 * `Mix.InvalidTaskError` - raised if the task is not a valid `Mix.Task`
151171
152172 """
173+ @ spec get! ( task_name ) :: task_module | no_return
153174 def get! ( task ) do
154- if module = get ( task ) do
155- if is_task? ( module ) do
175+ case get ( task ) do
176+ { :ok , module } ->
156177 module
157- else
178+ { :error , :invalid } ->
158179 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
162182 end
163183 end
164184
@@ -174,7 +194,8 @@ defmodule Mix.Task do
174194 It may raise an exception if the task was not found
175195 or it is invalid. Check `get!/1` for more information.
176196 """
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
178199 task = to_string ( task )
179200
180201 if Mix.TasksServer . run_task ( task , Mix.Project . get ) do
@@ -192,15 +213,18 @@ defmodule Mix.Task do
192213 @ doc """
193214 Clears all invoked tasks, allowing them to be reinvoked.
194215 """
216+ @ spec clear :: :ok
195217 def clear do
196218 Mix.TasksServer . clear_tasks
197219 end
198220
199221 @ 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.
202225 """
203- def reenable ( task ) do
226+ @ spec reenable ( task_name ) :: :ok
227+ def reenable ( task ) when is_binary ( task ) or is_atom ( task ) do
204228 task = to_string ( task )
205229 module = get! ( task )
206230
@@ -209,6 +233,8 @@ defmodule Mix.Task do
209233 recur module , fn project ->
210234 Mix.TasksServer . delete_task ( task , project )
211235 end
236+
237+ :ok
212238 end
213239
214240 defp recur ( module , fun ) do
@@ -233,7 +259,12 @@ defmodule Mix.Task do
233259 @ doc """
234260 Returns `true` if given module is a task.
235261 """
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 )
238269 end
239270end
0 commit comments