defmodule Mix.Tasks.Zwave.Gen.Command do @moduledoc """ Generates the scaffolding for a new Z-Wave Command mix zwave.gen.command ThermostatModeSet Thermostat mode The pattern is mix zwave.gen.command CommandModuleName CommandClassModule params.... Flags: * `--no-save` - will print the generated module out to the console * `--target-dir ` - will generate the new module at the provided path * `--no_test` - if set, will not generate a test file """ use Mix.Task @shortdoc "Generates the scaffolding for a new Z-Wave Command" @switches [no_save: :boolean, target_dir: :string, no_test: :boolean] def run(args) do {opts, args, _} = OptionParser.parse(args, switches: @switches) command_name = args |> Enum.at(0) |> maybe_prompt("Command:") command_id = args |> Enum.at(1) |> maybe_prompt_for_command_id() command_class = args |> Enum.at(2) |> maybe_prompt("Command Class:") params = args |> Enum.drop(3) |> Enum.reverse() |> Enum.map(&{&1, "any()"}) |> prompt_for_params(true) |> Enum.reverse() target_defs = build_target_defs(command_name, opts) copy_files(target_defs, command_name, command_id, command_class, params, opts) end defp maybe_prompt(value, prompt) do value = value || String.trim(Mix.shell().prompt(prompt)) if value == "" or not is_binary(value) do maybe_prompt("", prompt) else value end end defp maybe_prompt_for_command_id(id \\ nil) do id = id || String.trim(Mix.shell().prompt("Command ID (hex):")) case Regex.run(~r/^0?x?([0-9A-Fa-f]{2})$/, id) do [_, id] -> "0x" <> String.upcase(id) _ -> maybe_prompt_for_command_id() end end defp print_param_help() do Mix.shell().info("") Mix.shell().info("Enter one of the following commands to add command parameters:") Mix.shell().info("") Mix.shell().info(["* ", :bright, "a []", :reset, " - add a parameter"]) Mix.shell().info(["* ", :bright, "d ", :reset, " - delete a parameter by name"]) Mix.shell().info(["* ", :bright, "v", :reset, " - show parameter list"]) Mix.shell().info(["* ", :bright, "x", :reset, " - done entering parameters"]) Mix.shell().info(["* ", :bright, "?", :reset, " - display this message again"]) end defp print_params(params) do params |> Enum.reverse() |> Enum.each(fn {n, t} -> Mix.shell().info(["* ", :green, :bright, ":#{n}", :reset, " :: ", :yellow, "#{t}", :reset]) end) end defp prompt_for_params(params, print_help \\ false) do if print_help do print_param_help() end Mix.shell().info("") cmd = Mix.shell().prompt(">") |> String.trim() |> String.downcase() |> String.split(~r/\s+/) case cmd do ["a", name, type] -> type = if(String.ends_with?(type, "()"), do: type, else: type <> "()") params = [{name, type} | params] prompt_for_params(params) ["a", name] -> params = [{name, "any()"} | params] prompt_for_params(params) ["d", name] -> params = Enum.reject(params, fn {n, _} -> n == name end) prompt_for_params(params) ["v"] -> print_params(params) prompt_for_params(params) ["?"] -> prompt_for_params(params, true) ["x"] -> params _ -> prompt_for_params(params) end end defp copy_files(target_defs, command_name, command_id, command_class, params, opts) do command_module = Module.concat(Grizzly.ZWave.Commands, command_name) command_name_snakecase = String.to_atom(Macro.underscore(command_name)) command_class_module = Module.concat(Grizzly.ZWave.CommandClasses, command_class) bindings = [ params: params, command_name: command_name_snakecase, command_id: command_id, command_module: command_module, command_module_short: command_name, command_class_module: command_class_module, command_class_module_short: Module.concat([String.to_atom(command_class)]) ] Enum.each(target_defs, fn target_def -> to_out(target_def, bindings, opts) end) end defp to_out({:target, target_file, 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_file, evaled) end :ok end defp to_out({:target_test, test_file, test_template_file}, bindings, opts) do if Keyword.has_key?(opts, :no_test) || Keyword.has_key?(opts, :no_save) do :ok else Mix.Generator.create_file(test_file, EEx.eval_file(test_template_file, bindings)) end end defp get_target(file_name, opts) do case Keyword.get(opts, :target_dir) do nil -> Path.join([ File.cwd!(), "lib", "grizzly", "zwave", "commands", "#{file_name}.ex" ]) target_dir -> Path.join([File.cwd!(), "lib", target_dir, "#{file_name}.ex"]) end end defp get_test_target(file_name, opts) do case Keyword.get(opts, :target_dir) do nil -> Path.join([ File.cwd!(), "test", "grizzly", "zwave", "commands", "#{file_name}_test.exs" ]) target_dir -> Path.join([File.cwd!(), "test", target_dir, "#{file_name}_test.exs"]) end end defp build_target_defs(command_name, opts) do command_name_str = Macro.underscore(command_name) grizzly_path = Application.app_dir(:grizzly) target = get_target(command_name_str, opts) test_target = get_test_target(command_name_str, opts) target_template = Path.join(grizzly_path, "/priv/templates/zwave.gen/command.eex") test_template = Path.join(grizzly_path, "/priv/templates/zwave.gen/command_test.eex") [ {:target, target, target_template}, {:target_test, test_target, test_template} ] end end