lib/mix/tasks/ex_unit_jumpstart.ex

defmodule Mix.Tasks.ExUnitJumpstart do
  @moduledoc false

  @app :ex_unit_jumpstart

  @doc "Generate cfg from mix.exs and app config"
  @spec parse_args(OptionParser.argv()) :: Keyword.t()
  def parse_args(argv) do
    opts = [strict: [version: :string]]
    {overrides, _} = OptionParser.parse!(argv, opts)

    user_config = Keyword.merge(Application.get_all_env(@app), overrides)
    mix_config = Mix.Project.config()

    create_config(mix_config, user_config)
  end

  @doc "Generate cfg based on params"
  @spec create_config(Keyword.t(), Keyword.t()) :: Keyword.t()
  def create_config(mix_config, user_config) do
    defaults = [
      test_dir: "test",
      code_dir: "lib",
      template_dir: "priv/templates"
    ]

    # Override values from user config
    cfg = Keyword.merge(defaults, user_config)

    # Calculate values from other things

    Keyword.merge(
      [
        test_dir: cfg[:test_dir],
        code_dir: cfg[:code_dir]
      ],
      cfg
    )
  end

  # Expand cfg vars in keys
  @doc false
  @spec expand_keys(Keyword.t(), list(atom)) :: Keyword.t()
  def expand_keys(cfg, keys) do
    Enum.reduce(Keyword.take(cfg, keys), cfg, fn {key, value}, acc ->
      Keyword.put(acc, key, expand_value(value, acc))
    end)
  end

  # Expand vars in value or list of values
  @doc false
  @spec expand_value(atom | binary | list, Keyword.t()) :: binary | list(binary)
  def expand_value(values, cfg) when is_list(values) do
    Enum.map(values, &expand_vars(&1, cfg))
  end

  def expand_value(value, cfg), do: expand_vars(value, cfg)

  # Expand references in values
  @doc false
  @spec expand_vars(binary | nil | atom | list, Keyword.t()) :: binary
  def expand_vars(value, _cfg) when is_binary(value), do: value
  def expand_vars(nil, _cfg), do: ""

  def expand_vars(key, cfg) when is_atom(key) do
    case Keyword.fetch(cfg, key) do
      {:ok, value} ->
        expand_vars(value, cfg)

      :error ->
        to_string(key)
    end
  end

  def expand_vars(terms, cfg) when is_list(terms) do
    terms
    |> Enum.map(&expand_vars(&1, cfg))
    |> Enum.join("")
  end

  def expand_vars(value, _cfg), do: to_string(value)
end

defmodule Mix.Tasks.ExUnitJumpstart.Init do
  @moduledoc """
  Initialize template files.

  ## Command line options

    * `--test_dir` - target directory

  ## Usage

      # Copy default templates into your project
      mix ex_unit_jumpstart.init
  """
  @shortdoc "Initialize template files"
  use Mix.Task

  @app :ex_unit_jumpstart

  @impl Mix.Task
  def run(args) do
    cfg = Mix.Tasks.ExUnitJumpstart.parse_args(args)

    template_dir = cfg[:template_dir]
    app_dir = Application.app_dir(@app, ["priv", "templates"])

    IO.puts("Copying templates to #{template_dir}")
    :ok = File.mkdir_p(template_dir)
    {:ok, _files} = File.cp_r(app_dir, template_dir)
  end
end

defmodule Mix.Tasks.ExUnitJumpstart.Generate do
  @moduledoc """
  Create ExUnitJumpstart test files and update existing ones.

  ## Usage

      mix ex_unit_jumpstart.generate
  """
  @shortdoc "Create ExUnitJumpstart scripts and files"
  use Mix.Task

  alias MixExUnitJumpstart.Templates

  @impl Mix.Task
  def run(args) do
    cfg = Mix.Tasks.ExUnitJumpstart.parse_args(args)

    code_files = ExUnitJumpstart.GetCodeFiles.get_code_files(cfg)
    test_files = ExUnitJumpstart.GetTestFiles.get_test_files(cfg)

    ExUnitJumpstart.MoveFiles.move_misplaced_test_files(cfg, code_files, test_files)
    ExUnitJumpstart.CreateFiles.create_missing_test_files(cfg, code_files, test_files)

    # refetch test files after moving and creating
    test_files = ExUnitJumpstart.GetTestFiles.get_test_files(cfg)

    ExUnitJumpstart.UnitTestGenerator.create_unit_tests(cfg, code_files, test_files)
  end

  # defp write_template(cfg, dest_dir, template),
  #   do: write_template(cfg, dest_dir, template, template)

  # defp write_template(cfg, dest_dir, template, file) do
  #   output_file = cfg[:target_prefix] <> file
  #   # target_file = Path.join(dest_dir, output_file)
  #   # Mix.shell.info "Generating #{target_file} from template #{template}"
  #   Templates.write_template(cfg, dest_dir, template, output_file)
  # end
end