defmodule GitHooks.Tasks.Cmd do @moduledoc """ Represents a command that will be executed as a git hook task. A command should be configured as `{:cmd, command, opts}`, being `opts` an optional configuration. For example: ```elixir config :git_hooks, hooks: [ pre_commit: [ {:cmd, "ls -lisa", include_hook_args: true} ] ] ``` """ @typedoc """ Represents a `command` to be executed. """ @type t :: %__MODULE__{ original_command: String.t(), command: String.t(), args: [any], env: [{String.t(), String.t()}], git_hook_type: atom, result: term } defstruct [:original_command, :command, :args, :env, :git_hook_type, result: nil] @doc """ Creates a new `cmd` struct that will execute a command. This function expects a tuple or triple with `:cmd`, the command to execute and the opts. ### Options * `include_hook_args`: Whether the git options will be passed as argument when executing the file. You will need to check [which arguments are being sent by each git hook](https://git-scm.com/docs/githooks). * `env`: The environment variables that will be set in the execution context of the file. ### Example iex> #{__MODULE__}.new({:cmd, "ls -l", env: [{"var", "test"}], include_hook_args: true}, :pre_commit, ["commit message"]) %#{__MODULE__}{command: "ls", original_command: "ls -l", args: ["-l", "commit message"], env: [{"var", "test"}], git_hook_type: :pre_commit} iex> #{__MODULE__}.new({:cmd, "ls", include_hook_args: false}, :pre_commit, ["commit message"]) %#{__MODULE__}{command: "ls", original_command: "ls", args: [], env: [], git_hook_type: :pre_commit} """ @spec new( {:cmd, command :: String.t(), [any]}, GitHooks.git_hook_type(), GitHooks.git_hook_args() ) :: __MODULE__.t() def new({:cmd, original_command, opts}, git_hook_type, git_hook_args) when is_list(opts) do [base_command | args] = String.split(original_command, " ") command_args = if Keyword.get(opts, :include_hook_args, false) do Enum.concat(args, git_hook_args) else args end %__MODULE__{ original_command: original_command, command: base_command, args: command_args, env: Keyword.get(opts, :env, []), git_hook_type: git_hook_type } end end defimpl GitHooks.Task, for: GitHooks.Tasks.Cmd do alias GitHooks.Config alias GitHooks.Tasks.Cmd alias GitHooks.Printer def run(%Cmd{command: command, args: args, env: env, git_hook_type: git_hook_type} = cmd, _opts) do result = System.cmd( command, args, into: Config.io_stream(git_hook_type), env: env ) Map.put(cmd, :result, result) rescue error -> Map.put(cmd, :result, {"Execution failed: #{inspect(error)}", 1}) end def success?(%Cmd{result: {_result, 0}}), do: true def success?(%Cmd{result: _}), do: false def print_result(%Cmd{original_command: original_command, result: {_result, 0}} = file) do Printer.success("`#{original_command}` was successful") file end def print_result( %Cmd{ git_hook_type: git_hook_type, original_command: original_command, result: {_result, _code} } = file ) do # if !Config.verbose?(git_hook_type), do: IO.puts(result) Printer.error("`#{git_hook_type}`: `#{original_command}` execution failed") file end end