Current section

Files

Jump to
grizzly lib mix tasks zwave.gen.xml_mappings.ex
Raw

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

if Code.ensure_loaded?(SweetXml) do
defmodule Mix.Tasks.Zwave.Gen.XmlMappings do
@moduledoc """
Generates a module that provides mappings based on the data in `ZWave_custom_cmd_classes.xml`.
mix zwave.gen.xml_mappings
### Flags
* `--input <path>` - reads the xml from the provided path
* `--dry-run` - print the generated module out to the console
* `--output <path>` - writes the generated module at the provided path
"""
use Mix.Task
import SweetXml
@shortdoc "Generates a module that provides mappings based on the data in `ZWave_custom_cmd_classes.xml`"
@switches [dry_run: :boolean]
@skip_command_classes [:zwave_cmd_class, :zwave_long_range]
@command_class_name_overrides %{
0x4F => :zip_6lowpan,
0x59 => :association_group_info,
0x96 => :av_content_renderer_status
}
def run(args) do
{opts, _, _} = OptionParser.parse(args, switches: @switches)
input_path = Keyword.get(opts, :input, "resources/ZWave_custom_cmd_classes.xml")
doc = input_path |> File.read!() |> parse()
command_class_mappings = build_macro(:command_class_mappings, command_class_mappings(doc))
basic_device_class_mappings =
build_macro(:basic_device_class_mappings, basic_device_class_mappings(doc))
generic_device_class_mappings =
build_macro(:generic_device_class_mappings, generic_device_class_mappings(doc))
specific_device_class_mappings =
build_macro(:specific_device_class_mappings, specific_device_class_mappings(doc))
module =
quote do
defmodule Grizzly.ZWave.GeneratedMappings do
@moduledoc false
unquote(command_class_mappings)
unquote(basic_device_class_mappings)
unquote(generic_device_class_mappings)
unquote(specific_device_class_mappings)
end
end
module_code = module |> Macro.to_string() |> Code.format_string!()
module_code =
"""
# !! THIS FILE IS GENERATED BY `mix zwave.gen.xml`
# !! DO NOT EDIT DIRECTLY
#{module_code}
"""
if opts[:dry_run] do
Mix.shell().info(module_code)
else
output_path = Keyword.get(opts, :output, "lib/grizzly/zwave/generated_mappings.ex")
Mix.Generator.create_file(output_path, module_code)
end
end
defp build_macro(name, value) do
quote do
def unquote(name)(), do: unquote(value)
end
end
defp command_class_mappings(doc) do
doc
|> xpath(~x"//zw_classes/cmd_class"l, key: ~x"@key"s, name: ~x"@name"s)
|> Enum.map(key_name_mapper("COMMAND_CLASS_", @command_class_name_overrides))
|> Enum.uniq_by(fn {key, _} -> key end)
|> Enum.reject(&(elem(&1, 1) in @skip_command_classes))
|> Enum.sort()
end
defp basic_device_class_mappings(doc) do
doc
|> xpath(~x"//zw_classes/bas_dev"l, key: ~x"@key"s, name: ~x"@name"s)
|> Enum.map(key_name_mapper("BASIC_TYPE_"))
|> Enum.sort()
end
defp generic_device_class_mappings(doc) do
doc
|> xpath(~x"//zw_classes/gen_dev"l, key: ~x"@key"s, name: ~x"@name"s)
|> Enum.map(key_name_mapper("GENERIC_TYPE_"))
|> Enum.sort()
end
defp specific_device_class_mappings(doc) do
doc
|> xpath(~x"//zw_classes/gen_dev"l,
generic_type: ~x"@name"s,
specific_types: [~x"./spec_dev"l, key: ~x"@key"s, name: ~x"@name"s]
)
|> Enum.flat_map(fn %{generic_type: generic_type, specific_types: specific_types} ->
Enum.map(specific_types, fn %{name: name, key: key} ->
generic_type = parse_name(generic_type, "GENERIC_TYPE_")
specific_type = parse_name(name, "SPECIFIC_TYPE_")
key = parse_hex_string(key)
Macro.escape({generic_type, key, specific_type})
end)
end)
|> Enum.sort()
end
defp key_name_mapper(name_prefix, name_overrides \\ %{}) do
fn %{name: name, key: key} ->
key = parse_hex_string(key)
name = Map.get(name_overrides, key, parse_name(name, name_prefix))
{key, name}
end
end
defp parse_hex_string(string) do
string |> String.replace_leading("0x", "") |> String.to_integer(16)
end
defp parse_name(name, prefix) do
name |> String.replace(prefix, "") |> Macro.underscore() |> String.to_atom()
end
end
end