execution.ex (23765B)
1 defmodule Credo.Execution do 2 @moduledoc """ 3 Every run of Credo is configured via an `Credo.Execution` struct, which is created and 4 manipulated via the `Credo.Execution` module. 5 """ 6 7 @doc """ 8 The `Credo.Execution` struct is created and manipulated via the `Credo.Execution` module. 9 """ 10 defstruct argv: [], 11 cli_options: nil, 12 # TODO: these initial switches should also be %Credo.CLI.Switch{} struct 13 cli_switches: [ 14 debug: :boolean, 15 color: :boolean, 16 config_name: :string, 17 config_file: :string, 18 working_dir: :string 19 ], 20 cli_aliases: [C: :config_name, D: :debug], 21 cli_switch_plugin_param_converters: [], 22 23 # config 24 files: nil, 25 color: true, 26 debug: false, 27 checks: nil, 28 requires: [], 29 plugins: [], 30 parse_timeout: 5000, 31 strict: false, 32 33 # options, set by the command line 34 format: nil, 35 help: false, 36 verbose: false, 37 version: false, 38 39 # options, that are kept here for legacy reasons 40 all: false, 41 crash_on_error: true, 42 enable_disabled_checks: nil, 43 ignore_checks_tags: [], 44 ignore_checks: nil, 45 max_concurrent_check_runs: nil, 46 min_priority: 0, 47 mute_exit_status: false, 48 only_checks_tags: [], 49 only_checks: nil, 50 read_from_stdin: false, 51 52 # state, which is accessed and changed over the course of Credo's execution 53 pipeline_map: %{}, 54 commands: %{}, 55 config_files: [], 56 current_task: nil, 57 parent_task: nil, 58 initializing_plugin: nil, 59 halted: false, 60 config_files_pid: nil, 61 source_files_pid: nil, 62 issues_pid: nil, 63 timing_pid: nil, 64 skipped_checks: nil, 65 assigns: %{}, 66 results: %{}, 67 config_comment_map: %{} 68 69 @typedoc false 70 @type t :: %__MODULE__{} 71 72 @execution_pipeline_key __MODULE__ 73 @execution_pipeline_key_backwards_compatibility_map %{ 74 Credo.CLI.Command.Diff.DiffCommand => "diff", 75 Credo.CLI.Command.List.ListCommand => "list", 76 Credo.CLI.Command.Suggest.SuggestCommand => "suggest", 77 Credo.CLI.Command.Info.InfoCommand => "info" 78 } 79 @execution_pipeline [ 80 __pre__: [ 81 Credo.Execution.Task.AppendDefaultConfig, 82 Credo.Execution.Task.AppendExtraConfig, 83 {Credo.Execution.Task.ParseOptions, parser_mode: :preliminary}, 84 Credo.Execution.Task.ConvertCLIOptionsToConfig, 85 Credo.Execution.Task.InitializePlugins 86 ], 87 parse_cli_options: [{Credo.Execution.Task.ParseOptions, parser_mode: :preliminary}], 88 initialize_plugins: [ 89 # This is where plugins can "put" their hooks using `Credo.Plugin.append_task/3` 90 # to initialize themselves based on the params given in the config as well as 91 # in their own command line switches. 92 # 93 # Example: 94 # 95 # defmodule CredoDemoPlugin do 96 # import Credo.Plugin 97 # 98 # def init(exec) do 99 # append_task(exec, :initialize_plugins, CredoDemoPlugin.SetDiffAsDefaultCommand) 100 # end 101 # end 102 ], 103 determine_command: [Credo.Execution.Task.DetermineCommand], 104 set_default_command: [Credo.Execution.Task.SetDefaultCommand], 105 initialize_command: [Credo.Execution.Task.InitializeCommand], 106 parse_cli_options_final: [{Credo.Execution.Task.ParseOptions, parser_mode: :strict}], 107 validate_cli_options: [Credo.Execution.Task.ValidateOptions], 108 convert_cli_options_to_config: [Credo.Execution.Task.ConvertCLIOptionsToConfig], 109 resolve_config: [Credo.Execution.Task.UseColors, Credo.Execution.Task.RequireRequires], 110 validate_config: [Credo.Execution.Task.ValidateConfig], 111 run_command: [Credo.Execution.Task.RunCommand], 112 halt_execution: [Credo.Execution.Task.AssignExitStatusForIssues] 113 ] 114 115 alias Credo.Execution.ExecutionConfigFiles 116 alias Credo.Execution.ExecutionIssues 117 alias Credo.Execution.ExecutionSourceFiles 118 alias Credo.Execution.ExecutionTiming 119 120 @doc "Builds an Execution struct for the the given `argv`." 121 def build(argv \\ []) when is_list(argv) do 122 max_concurrent_check_runs = System.schedulers_online() 123 124 %__MODULE__{argv: argv, max_concurrent_check_runs: max_concurrent_check_runs} 125 |> put_pipeline(@execution_pipeline_key, @execution_pipeline) 126 |> put_builtin_command("categories", Credo.CLI.Command.Categories.CategoriesCommand) 127 |> put_builtin_command("diff", Credo.CLI.Command.Diff.DiffCommand) 128 |> put_builtin_command("explain", Credo.CLI.Command.Explain.ExplainCommand) 129 |> put_builtin_command("gen.check", Credo.CLI.Command.GenCheck) 130 |> put_builtin_command("gen.config", Credo.CLI.Command.GenConfig) 131 |> put_builtin_command("help", Credo.CLI.Command.Help) 132 |> put_builtin_command("info", Credo.CLI.Command.Info.InfoCommand) 133 |> put_builtin_command("list", Credo.CLI.Command.List.ListCommand) 134 |> put_builtin_command("suggest", Credo.CLI.Command.Suggest.SuggestCommand) 135 |> put_builtin_command("version", Credo.CLI.Command.Version) 136 |> start_servers() 137 end 138 139 @doc false 140 def build(%__MODULE__{} = previous_exec, files_that_changed) when is_list(files_that_changed) do 141 previous_exec.argv 142 |> build() 143 |> put_rerun(previous_exec, files_that_changed) 144 end 145 146 def build(argv, files_that_changed) when is_list(files_that_changed) do 147 build(argv) 148 end 149 150 @doc false 151 defp start_servers(%__MODULE__{} = exec) do 152 exec 153 |> ExecutionConfigFiles.start_server() 154 |> ExecutionIssues.start_server() 155 |> ExecutionSourceFiles.start_server() 156 |> ExecutionTiming.start_server() 157 end 158 159 @doc """ 160 Returns the checks that should be run for a given `exec` struct. 161 162 Takes all checks from the `checks:` field of the exec, matches those against 163 any patterns to include or exclude certain checks given via the command line. 164 """ 165 def checks(exec) 166 167 def checks(%__MODULE__{checks: nil}) do 168 {[], [], []} 169 end 170 171 def checks(%__MODULE__{ 172 checks: %{enabled: checks}, 173 only_checks: only_checks, 174 only_checks_tags: only_checks_tags, 175 ignore_checks: ignore_checks, 176 ignore_checks_tags: ignore_checks_tags 177 }) do 178 only_matching = 179 checks |> filter_only_checks_by_tags(only_checks_tags) |> filter_only_checks(only_checks) 180 181 ignore_matching_by_name = filter_ignore_checks(checks, ignore_checks) 182 ignore_matching_by_tags = filter_ignore_checks_by_tags(checks, ignore_checks_tags) 183 ignore_matching = ignore_matching_by_name ++ ignore_matching_by_tags 184 185 result = only_matching -- ignore_matching 186 187 {result, only_matching, ignore_matching} 188 end 189 190 defp filter_only_checks(checks, nil), do: checks 191 defp filter_only_checks(checks, []), do: checks 192 defp filter_only_checks(checks, patterns), do: filter_checks(checks, patterns) 193 194 defp filter_ignore_checks(_checks, nil), do: [] 195 defp filter_ignore_checks(_checks, []), do: [] 196 defp filter_ignore_checks(checks, patterns), do: filter_checks(checks, patterns) 197 198 defp filter_checks(checks, patterns) do 199 regexes = 200 patterns 201 |> List.wrap() 202 |> to_match_regexes 203 204 Enum.filter(checks, &match_regex(&1, regexes, true)) 205 end 206 207 defp match_regex(_tuple, [], default_for_empty), do: default_for_empty 208 209 defp match_regex(tuple, regexes, _default_for_empty) do 210 check_name = 211 tuple 212 |> Tuple.to_list() 213 |> List.first() 214 |> to_string 215 216 Enum.any?(regexes, &Regex.run(&1, check_name)) 217 end 218 219 defp to_match_regexes(list) do 220 Enum.map(list, fn match_check -> 221 {:ok, match_pattern} = Regex.compile(match_check, "i") 222 match_pattern 223 end) 224 end 225 226 defp filter_only_checks_by_tags(checks, nil), do: checks 227 defp filter_only_checks_by_tags(checks, []), do: checks 228 defp filter_only_checks_by_tags(checks, tags), do: filter_checks_by_tags(checks, tags) 229 230 defp filter_ignore_checks_by_tags(_checks, nil), do: [] 231 defp filter_ignore_checks_by_tags(_checks, []), do: [] 232 defp filter_ignore_checks_by_tags(checks, tags), do: filter_checks_by_tags(checks, tags) 233 234 defp filter_checks_by_tags(_checks, nil), do: [] 235 defp filter_checks_by_tags(_checks, []), do: [] 236 237 defp filter_checks_by_tags(checks, tags) do 238 tags = Enum.map(tags, &String.to_atom/1) 239 240 Enum.filter(checks, &match_tags(&1, tags, true)) 241 end 242 243 defp match_tags(_tuple, [], default_for_empty), do: default_for_empty 244 245 defp match_tags({check, params}, tags, _default_for_empty) do 246 tags_for_check = tags_for_check(check, params) 247 248 Enum.any?(tags, &Enum.member?(tags_for_check, &1)) 249 end 250 251 @doc """ 252 Returns the tags for a given `check` and its `params`. 253 """ 254 def tags_for_check(check, params) 255 256 def tags_for_check(check, nil), do: check.tags 257 def tags_for_check(check, []), do: check.tags 258 259 def tags_for_check(check, params) when is_list(params) do 260 params 261 |> Credo.Check.Params.tags(check) 262 |> Enum.flat_map(fn 263 :__initial__ -> check.tags 264 tag -> [tag] 265 end) 266 end 267 268 @doc """ 269 Sets the exec values which `strict` implies (if applicable). 270 """ 271 def set_strict(exec) 272 273 def set_strict(%__MODULE__{strict: true} = exec) do 274 %__MODULE__{exec | all: true, min_priority: -99} 275 end 276 277 def set_strict(%__MODULE__{strict: false} = exec) do 278 %__MODULE__{exec | min_priority: 0} 279 end 280 281 def set_strict(exec), do: exec 282 283 @doc false 284 @deprecated "Use `Execution.working_dir/1` instead" 285 def get_path(exec) do 286 exec.cli_options.path 287 end 288 289 @doc false 290 def working_dir(exec) do 291 Path.expand(exec.cli_options.path) 292 end 293 294 # Commands 295 296 @doc """ 297 Returns the name of the command, which should be run by the given execution. 298 299 Credo.Execution.get_command_name(exec) 300 # => "suggest" 301 """ 302 def get_command_name(exec) do 303 exec.cli_options.command 304 end 305 306 @doc """ 307 Returns all valid command names. 308 309 Credo.Execution.get_valid_command_names(exec) 310 # => ["categories", "diff", "explain", "gen.check", "gen.config", "help", "info", 311 # "list", "suggest", "version"] 312 """ 313 def get_valid_command_names(exec) do 314 Map.keys(exec.commands) 315 end 316 317 @doc """ 318 Returns the `Credo.CLI.Command` module for the given `name`. 319 320 Credo.Execution.get_command(exec, "explain") 321 # => Credo.CLI.Command.Explain.ExplainCommand 322 """ 323 def get_command(exec, name) do 324 Map.get(exec.commands, name) || 325 raise ~s'Command not found: "#{inspect(name)}"\n\nRegistered commands: #{inspect(exec.commands, pretty: true)}' 326 end 327 328 @doc false 329 def put_command(exec, _plugin_mod, name, command_mod) do 330 commands = Map.put(exec.commands, name, command_mod) 331 332 %__MODULE__{exec | commands: commands} 333 |> command_mod.init() 334 end 335 336 @doc false 337 def set_initializing_plugin(%__MODULE__{initializing_plugin: nil} = exec, plugin_mod) do 338 %__MODULE__{exec | initializing_plugin: plugin_mod} 339 end 340 341 def set_initializing_plugin(exec, nil) do 342 %__MODULE__{exec | initializing_plugin: nil} 343 end 344 345 def set_initializing_plugin(%__MODULE__{initializing_plugin: mod1}, mod2) do 346 raise "Attempting to initialize plugin #{inspect(mod2)}, " <> 347 "while already initializing plugin #{mod1}" 348 end 349 350 # Plugin params 351 352 @doc """ 353 Returns the `Credo.Plugin` module's param value. 354 355 Credo.Execution.get_command(exec, CredoDemoPlugin, "foo") 356 # => nil 357 358 Credo.Execution.get_command(exec, CredoDemoPlugin, "foo", 42) 359 # => 42 360 """ 361 def get_plugin_param(exec, plugin_mod, param_name) do 362 exec.plugins[plugin_mod][param_name] 363 end 364 365 @doc false 366 def put_plugin_param(exec, plugin_mod, param_name, param_value) do 367 plugins = 368 Keyword.update(exec.plugins, plugin_mod, [], fn list -> 369 Keyword.update(list, param_name, param_value, fn _ -> param_value end) 370 end) 371 372 %__MODULE__{exec | plugins: plugins} 373 end 374 375 # CLI switches 376 377 @doc """ 378 Returns the value for the given `switch_name`. 379 380 Credo.Execution.get_given_cli_switch(exec, "foo") 381 # => "bar" 382 """ 383 def get_given_cli_switch(exec, switch_name) do 384 if Map.has_key?(exec.cli_options.switches, switch_name) do 385 {:ok, exec.cli_options.switches[switch_name]} 386 else 387 :error 388 end 389 end 390 391 @doc false 392 def put_cli_switch(exec, _plugin_mod, name, type) do 393 %__MODULE__{exec | cli_switches: exec.cli_switches ++ [{name, type}]} 394 end 395 396 @doc false 397 def put_cli_switch_alias(exec, _plugin_mod, _name, nil), do: exec 398 399 def put_cli_switch_alias(exec, _plugin_mod, name, alias_name) do 400 %__MODULE__{exec | cli_aliases: exec.cli_aliases ++ [{alias_name, name}]} 401 end 402 403 @doc false 404 def put_cli_switch_plugin_param_converter(exec, plugin_mod, cli_switch_name, plugin_param_name) do 405 converter_tuple = {cli_switch_name, plugin_mod, plugin_param_name} 406 407 %__MODULE__{ 408 exec 409 | cli_switch_plugin_param_converters: 410 exec.cli_switch_plugin_param_converters ++ [converter_tuple] 411 } 412 end 413 414 # Assigns 415 416 @doc """ 417 Returns the assign with the given `name` for the given `exec` struct (or return the given `default` value). 418 419 Credo.Execution.get_assign(exec, "foo") 420 # => nil 421 422 Credo.Execution.get_assign(exec, "foo", 42) 423 # => 42 424 """ 425 def get_assign(exec, name_or_list, default \\ nil) 426 427 def get_assign(exec, path, default) when is_list(path) do 428 case get_in(exec.assigns, path) do 429 nil -> default 430 value -> value 431 end 432 end 433 434 def get_assign(exec, name, default) do 435 Map.get(exec.assigns, name, default) 436 end 437 438 @doc """ 439 Puts the given `value` with the given `name` as assign into the given `exec` struct and returns the struct. 440 441 Credo.Execution.put_assign(exec, "foo", 42) 442 # => %Credo.Execution{...} 443 """ 444 def put_assign(exec, name_or_list, value) 445 446 def put_assign(exec, path, value) when is_list(path) do 447 %__MODULE__{exec | assigns: do_put_nested_assign(exec.assigns, path, value)} 448 end 449 450 def put_assign(exec, name, value) do 451 %__MODULE__{exec | assigns: Map.put(exec.assigns, name, value)} 452 end 453 454 defp do_put_nested_assign(map, [last_key], value) do 455 Map.put(map, last_key, value) 456 end 457 458 defp do_put_nested_assign(map, [next_key | rest], value) do 459 new_map = 460 map 461 |> Map.get(next_key, %{}) 462 |> do_put_nested_assign(rest, value) 463 464 Map.put(map, next_key, new_map) 465 end 466 467 # Config Files 468 469 @doc false 470 def get_config_files(exec) do 471 Credo.Execution.ExecutionConfigFiles.get(exec) 472 end 473 474 @doc false 475 def append_config_file(exec, {_, _, _} = config_file) do 476 config_files = get_config_files(exec) ++ [config_file] 477 478 ExecutionConfigFiles.put(exec, config_files) 479 480 exec 481 end 482 483 # Source Files 484 485 @doc """ 486 Returns all source files for the given `exec` struct. 487 488 Credo.Execution.get_source_files(exec) 489 # => [%SourceFile<lib/my_project.ex>, 490 # %SourceFile<lib/credo/my_project/foo.ex>] 491 """ 492 def get_source_files(exec) do 493 Credo.Execution.ExecutionSourceFiles.get(exec) 494 end 495 496 @doc false 497 def put_source_files(exec, source_files) do 498 ExecutionSourceFiles.put(exec, source_files) 499 500 exec 501 end 502 503 # Issues 504 505 @doc """ 506 Returns all issues for the given `exec` struct. 507 """ 508 def get_issues(exec) do 509 exec 510 |> ExecutionIssues.to_map() 511 |> Map.values() 512 |> List.flatten() 513 end 514 515 @doc """ 516 Returns all issues grouped by filename for the given `exec` struct. 517 """ 518 def get_issues_grouped_by_filename(exec) do 519 ExecutionIssues.to_map(exec) 520 end 521 522 @doc """ 523 Returns all issues for the given `exec` struct that relate to the given `filename`. 524 """ 525 def get_issues(exec, filename) do 526 exec 527 |> ExecutionIssues.to_map() 528 |> Map.get(filename, []) 529 end 530 531 @doc """ 532 Sets the issues for the given `exec` struct, overwriting any existing issues. 533 """ 534 def put_issues(exec, issues) do 535 ExecutionIssues.set(exec, issues) 536 537 exec 538 end 539 540 @doc false 541 @deprecated "Use put_issues/2 instead" 542 def set_issues(exec, issues) do 543 put_issues(exec, issues) 544 end 545 546 # Results 547 548 @doc """ 549 Returns the result with the given `name` for the given `exec` struct (or return the given `default` value). 550 551 Credo.Execution.get_result(exec, "foo") 552 # => nil 553 554 Credo.Execution.get_result(exec, "foo", 42) 555 # => 42 556 """ 557 def get_result(exec, name, default \\ nil) do 558 Map.get(exec.results, name, default) 559 end 560 561 @doc """ 562 Puts the given `value` with the given `name` as result into the given `exec` struct. 563 564 Credo.Execution.put_result(exec, "foo", 42) 565 # => %Credo.Execution{...} 566 """ 567 def put_result(exec, name, value) do 568 %__MODULE__{exec | results: Map.put(exec.results, name, value)} 569 end 570 571 @doc false 572 def put_exit_status(exec, exit_status) do 573 put_assign(exec, "credo.exit_status", exit_status) 574 end 575 576 @doc false 577 def get_exit_status(exec) do 578 get_assign(exec, "credo.exit_status", 0) 579 end 580 581 # Halt 582 583 @doc """ 584 Halts further execution of the pipeline meaning all subsequent steps are skipped. 585 586 The `error` callback is called for the current Task. 587 588 defmodule FooTask do 589 use Credo.Execution.Task 590 591 def call(exec) do 592 Execution.halt(exec) 593 end 594 595 def error(exec) do 596 IO.puts("Execution has been halted!") 597 598 exec 599 end 600 end 601 """ 602 def halt(exec) do 603 %__MODULE__{exec | halted: true} 604 end 605 606 @doc """ 607 Halts further execution of the pipeline using the given `halt_message`. 608 609 The `error` callback is called for the current Task. 610 If the callback is not implemented, Credo outputs the given `halt_message`. 611 612 defmodule FooTask do 613 use Credo.Execution.Task 614 615 def call(exec) do 616 Execution.halt(exec, "Execution has been halted!") 617 end 618 end 619 """ 620 def halt(exec, halt_message) do 621 %__MODULE__{exec | halted: true} 622 |> put_halt_message(halt_message) 623 end 624 625 @doc false 626 def get_halt_message(exec) do 627 get_assign(exec, "credo.halt_message") 628 end 629 630 @doc false 631 def put_halt_message(exec, halt_message) do 632 put_assign(exec, "credo.halt_message", halt_message) 633 end 634 635 # Task tracking 636 637 @doc false 638 def set_parent_and_current_task(exec, parent_task, current_task) do 639 %__MODULE__{exec | parent_task: parent_task, current_task: current_task} 640 end 641 642 # Running tasks 643 644 @doc false 645 def run(exec) do 646 run_pipeline(exec, __MODULE__) 647 end 648 649 # Pipelines 650 651 @doc false 652 defp get_pipeline(exec, pipeline_key) do 653 case exec.pipeline_map[get_pipeline_key(exec, pipeline_key)] do 654 nil -> raise "Could not find execution pipeline for '#{pipeline_key}'" 655 pipeline -> pipeline 656 end 657 end 658 659 @doc false 660 defp get_pipeline_key(exec, pipeline_key) do 661 case exec.pipeline_map[pipeline_key] do 662 nil -> @execution_pipeline_key_backwards_compatibility_map[pipeline_key] 663 _ -> pipeline_key 664 end 665 end 666 667 @doc """ 668 Puts a given `pipeline` in `exec` under `pipeline_key`. 669 670 A pipeline is a keyword list of named groups. Each named group is a list of `Credo.Execution.Task` modules: 671 672 Execution.put_pipeline(exec, :my_pipeline_key, 673 load_things: [ MyProject.LoadThings ], 674 run_analysis: [ MyProject.Run ], 675 print_results: [ MyProject.PrintResults ] 676 ) 677 678 A named group can also be a list of two-element tuples, consisting of a `Credo.Execution.Task` module and a 679 keyword list of options, which are passed to the Task module's `call/2` function: 680 681 Execution.put_pipeline(exec, :my_pipeline_key, 682 load_things: [ {MyProject.LoadThings, []} ], 683 run_analysis: [ {MyProject.Run, [foo: "bar"]} ], 684 print_results: [ {MyProject.PrintResults, []} ] 685 ) 686 """ 687 def put_pipeline(exec, pipeline_key, pipeline) do 688 new_pipelines = Map.put(exec.pipeline_map, pipeline_key, pipeline) 689 690 %__MODULE__{exec | pipeline_map: new_pipelines} 691 end 692 693 @doc """ 694 Runs the pipeline with the given `pipeline_key` and returns the result `Credo.Execution` struct. 695 696 Execution.run_pipeline(exec, :my_pipeline_key) 697 # => %Credo.Execution{...} 698 """ 699 def run_pipeline(%__MODULE__{} = initial_exec, pipeline_key) 700 when is_atom(pipeline_key) and not is_nil(pipeline_key) do 701 initial_pipeline = get_pipeline(initial_exec, pipeline_key) 702 703 Enum.reduce(initial_pipeline, initial_exec, fn {group_name, _list}, exec_inside_pipeline -> 704 outer_pipeline = get_pipeline(exec_inside_pipeline, pipeline_key) 705 706 task_group = outer_pipeline[group_name] 707 708 Enum.reduce(task_group, exec_inside_pipeline, fn 709 {task_mod, opts}, exec_inside_task_group -> 710 Credo.Execution.Task.run(task_mod, exec_inside_task_group, opts) 711 712 task_mod, exec_inside_task_group when is_atom(task_mod) -> 713 Credo.Execution.Task.run(task_mod, exec_inside_task_group, []) 714 end) 715 end) 716 end 717 718 @doc false 719 def prepend_task(exec, plugin_mod, pipeline_key, group_name, task_tuple) 720 721 def prepend_task(exec, plugin_mod, nil, group_name, task_tuple) do 722 prepend_task(exec, plugin_mod, @execution_pipeline_key, group_name, task_tuple) 723 end 724 725 def prepend_task(exec, plugin_mod, pipeline_key, group_name, task_mod) when is_atom(task_mod) do 726 prepend_task(exec, plugin_mod, pipeline_key, group_name, {task_mod, []}) 727 end 728 729 def prepend_task(exec, _plugin_mod, pipeline_key, group_name, task_tuple) do 730 pipeline = 731 exec 732 |> get_pipeline(pipeline_key) 733 |> Enum.map(fn 734 {^group_name, list} -> {group_name, [task_tuple] ++ list} 735 value -> value 736 end) 737 738 put_pipeline(exec, get_pipeline_key(exec, pipeline_key), pipeline) 739 end 740 741 @doc false 742 def append_task(exec, plugin_mod, pipeline_key, group_name, task_tuple) 743 744 def append_task(exec, plugin_mod, nil, group_name, task_tuple) do 745 append_task(exec, plugin_mod, __MODULE__, group_name, task_tuple) 746 end 747 748 def append_task(exec, plugin_mod, pipeline_key, group_name, task_mod) when is_atom(task_mod) do 749 append_task(exec, plugin_mod, pipeline_key, group_name, {task_mod, []}) 750 end 751 752 def append_task(exec, _plugin_mod, pipeline_key, group_name, task_tuple) do 753 pipeline = 754 exec 755 |> get_pipeline(pipeline_key) 756 |> Enum.map(fn 757 {^group_name, list} -> {group_name, list ++ [task_tuple]} 758 value -> value 759 end) 760 761 put_pipeline(exec, get_pipeline_key(exec, pipeline_key), pipeline) 762 end 763 764 @doc false 765 defp put_builtin_command(exec, name, command_mod) do 766 exec 767 |> command_mod.init() 768 |> put_command(Credo, name, command_mod) 769 end 770 771 @doc ~S""" 772 Ensures that the given `value` is a `%Credo.Execution{}` struct, raises an error otherwise. 773 774 Example: 775 776 exec 777 |> mod.init() 778 |> Credo.Execution.ensure_execution_struct("#{mod}.init/1") 779 """ 780 def ensure_execution_struct(value, fun_name) 781 782 def ensure_execution_struct(%__MODULE__{} = exec, _fun_name), do: exec 783 784 def ensure_execution_struct(value, fun_name) do 785 raise("Expected #{fun_name} to return %Credo.Execution{}, got: #{inspect(value)}") 786 end 787 788 @doc false 789 def get_rerun(exec) do 790 case get_assign(exec, "credo.rerun.previous_execution") do 791 nil -> :notfound 792 previous_exec -> {previous_exec, get_assign(exec, "credo.rerun.files_that_changed")} 793 end 794 end 795 796 defp put_rerun(exec, previous_exec, files_that_changed) do 797 exec 798 |> put_assign("credo.rerun.previous_execution", previous_exec) 799 |> put_assign( 800 "credo.rerun.files_that_changed", 801 Enum.map(files_that_changed, fn filename -> 802 filename 803 |> Path.expand() 804 |> Path.relative_to_cwd() 805 end) 806 ) 807 end 808 end