lib/mix/tasks/zwave.gen.command_class.ex

defmodule Mix.Tasks.Zwave.Gen.CommandClass do
  @moduledoc """
  Generates the scaffolding for a new Z-Wave Command

    mix zwave.gen.command_class ThermostatMode 40

  The pattern is

    mix zwave.gen.command_class CommandClassModule CommandClassID

  Flags:

    * `--no-save` - will print the generated module out to the console
    * `--target-dir <path>` - will generate the new module at the provided path
  """

  use Mix.Task

  @shortdoc "Generates the scaffolding for a new Z-Wave Command Class"

  @switches [no_save: :boolean, target_dir: :string]

  def run(args) do
    {opts, args, _} = OptionParser.parse(args, switches: @switches)

    command_class = args |> Enum.at(0) |> maybe_prompt_for_command_class_name()
    cc_id = args |> Enum.at(1) |> maybe_prompt_for_command_class_id()

    command_class_name_str_snake_case = Macro.underscore(command_class)

    target_path = Path.join(get_target(opts), "#{command_class_name_str_snake_case}.ex")

    copy_files(target_path, command_class, cc_id, opts)
  end

  defp maybe_prompt_for_command_class_name(name \\ nil) do
    name = name || String.trim(Mix.shell().prompt("Command Class:"))

    if name == "" do
      maybe_prompt_for_command_class_name()
    else
      name
    end
  end

  defp maybe_prompt_for_command_class_id(id \\ nil) do
    id = id || String.trim(Mix.shell().prompt("Command Class ID (hex):"))

    case Regex.run(~r/^0?x?([0-9A-Fa-f]{2})$/, id) do
      [_, id] -> "0x" <> String.upcase(id)
      _ -> maybe_prompt_for_command_class_id()
    end
  end

  defp copy_files(target_path, command_class, cc_id, opts) do
    template_file =
      Path.join(Application.app_dir(:grizzly), "/priv/templates/zwave.gen/command_class.eex")

    command_class_module = Module.concat(Grizzly.ZWave.CommandClasses, command_class)
    command_class_name = String.to_atom(Macro.underscore(command_class))

    bindings = [
      command_class_name: command_class_name,
      command_class_module: command_class_module,
      command_class: command_class,
      command_class_id: cc_id
    ]

    to_out(target_path, template_file, bindings, opts)
  end

  defp to_out(target_path, template_file, bindings, opts) do
    evaled = EEx.eval_file(template_file, bindings, trim: true)

    evaled =
      try do
        Code.format_string!(evaled)
      rescue
        _ -> evaled
      end

    if Keyword.has_key?(opts, :no_save) do
      IO.puts(evaled)
    else
      Mix.Generator.create_file(target_path, EEx.eval_file(template_file, bindings))
    end
  end

  def get_target(opts) do
    case Keyword.get(opts, :target_dir) do
      nil ->
        Path.join([
          File.cwd!(),
          "lib",
          "grizzly",
          "zwave",
          "command_classes"
        ])

      target_dir ->
        target_dir
    end
  end
end