task.ex (4584B)
1 defmodule Credo.Execution.Task do 2 @moduledoc """ 3 A Task is a step in a pipeline, which is given an `Credo.Execution` struct and must return one as well. 4 5 Tasks in a pipeline are only called if they are not "halted" (see `Credo.Execution.halt/2`). 6 7 It implements a `call/1` or `call/2` callback, which is called with the `Credo.Execution` struct 8 as first parameter (and the Task's options as the second in case of `call/2`). 9 """ 10 11 @typedoc false 12 @type t :: module 13 14 @doc """ 15 Is called by the pipeline and contains the Task's actual code. 16 17 defmodule FooTask do 18 use Credo.Execution.Task 19 20 def call(exec) do 21 IO.inspect(exec) 22 end 23 end 24 25 The `call/1` functions receives an `exec` struct and must return a (modified) `Credo.Execution`. 26 """ 27 @callback call(exec :: Credo.Execution.t()) :: Credo.Execution.t() 28 29 @doc """ 30 Works like `call/1`, but receives the options, which are optional when registering the Task, as second argument. 31 32 defmodule FooTask do 33 use Credo.Execution.Task 34 35 def call(exec, opts) do 36 IO.inspect(opts) 37 38 exec 39 end 40 end 41 42 """ 43 @callback call(exec :: Credo.Execution.t(), opts :: Keyword.t()) :: Credo.Execution.t() 44 45 @doc """ 46 Gets called if `call` holds the execution via `Credo.Execution.halt/1` or `Credo.Execution.halt/2`. 47 """ 48 @callback error(exec :: Credo.Execution.t()) :: Credo.Execution.t() 49 50 @doc """ 51 Works like `error/1`, but receives the options, which were given during pipeline registration, as second argument. 52 """ 53 @callback error(exec :: Credo.Execution.t(), opts :: Keyword.t()) :: Credo.Execution.t() 54 55 require Logger 56 57 alias Credo.Execution 58 alias Credo.Execution.ExecutionTiming 59 60 defmacro __using__(_opts \\ []) do 61 quote do 62 @behaviour Credo.Execution.Task 63 64 import Credo.Execution 65 66 alias Credo.CLI.Output.UI 67 alias Credo.Execution 68 69 @impl true 70 def call(%Execution{halted: false} = exec) do 71 exec 72 end 73 74 @impl true 75 def call(%Execution{halted: false} = exec, opts) do 76 call(exec) 77 end 78 79 @impl true 80 def error(exec) do 81 case Execution.get_halt_message(exec) do 82 "" <> halt_message -> 83 command_name = Execution.get_command_name(exec) || "credo" 84 85 UI.warn([:red, "** (#{command_name}) ", halt_message]) 86 87 _ -> 88 IO.warn("Execution halted during #{__MODULE__}!") 89 end 90 91 exec 92 end 93 94 @impl true 95 def error(exec, _opts) do 96 error(exec) 97 end 98 99 defoverridable call: 1 100 defoverridable call: 2 101 defoverridable error: 1 102 defoverridable error: 2 103 end 104 end 105 106 @doc false 107 def run(task, exec, opts \\ []) 108 109 def run(task, %Credo.Execution{debug: true} = exec, opts) do 110 run_with_timing(task, exec, opts) 111 end 112 113 def run(task, %Execution{} = exec, opts) do 114 do_run(task, exec, opts) 115 end 116 117 def run(_task, exec, _opts) do 118 IO.warn( 119 "Expected second parameter of Task.run/3 to match %Credo.Execution{}, " <> 120 "got: #{inspect(exec)}" 121 ) 122 123 exec 124 end 125 126 defp do_run(task, %Credo.Execution{halted: false} = exec, opts) do 127 old_parent_task = exec.parent_task 128 old_current_task = exec.current_task 129 130 exec = 131 exec 132 |> Execution.set_parent_and_current_task(exec.current_task, task) 133 |> task.call(opts) 134 |> Execution.ensure_execution_struct("#{task}.call/2") 135 136 if exec.halted do 137 exec 138 |> task.error(opts) 139 |> Execution.set_parent_and_current_task(old_parent_task, old_current_task) 140 else 141 Execution.set_parent_and_current_task(exec, old_parent_task, old_current_task) 142 end 143 end 144 145 defp do_run(_task, exec, _opts) do 146 exec 147 end 148 149 # 150 151 defp run_with_timing(task, exec, opts) do 152 context_tuple = {:task, exec, task, opts} 153 log(:call_start, context_tuple) 154 155 {started_at, time, exec} = ExecutionTiming.run(&do_run/3, [task, exec, opts]) 156 157 log(:call_end, context_tuple, time) 158 159 ExecutionTiming.append(exec, [task: task, parent_task: exec.parent_task], started_at, time) 160 161 exec 162 end 163 164 defp log(:call_start, {:task, _exec, task, _opts}) do 165 Logger.info("Calling #{task} ...") 166 end 167 168 defp log(:call_end, {:task, _exec, task, _opts}, time) do 169 Logger.info("Finished #{task} in #{format_time(time)} ...") 170 end 171 172 defp format_time(time) do 173 cond do 174 time > 1_000_000 -> 175 "#{div(time, 1_000_000)}s" 176 177 time > 1_000 -> 178 "#{div(time, 1_000)}ms" 179 180 true -> 181 "#{time}μs" 182 end 183 end 184 end