meta/lib/recycledcloud/opennebula/schema.ex

100 lines
3 KiB
Elixir
Raw Normal View History

defmodule RecycledCloud.OpenNebula.Schema do
@moduledoc """
Helpers used to process OpenNebula's XSD schema.
,.
/,,;';;. ,;;;.. ,,;. '
.','' `::;:' ``;;;;' `..'
` ,,/' ,,//
Here be dragons! We import the records from Erlang header files (.hrl)
generated by erlsom as macros in the Records module. The only way I found to
programatically call a macro (apply/3 only works with functions) is by
playing around with the AST to template said call...
I'm not fond of this as it makes things more complex than I would like them
to be, but likely the easiest way to user erlsom's data binder from elixir
(and it's not *that* bad). The ideal would be to generate elixir structs from
source XSD files, but it would take much more work.
Heavily inspired from Gary Poster's:
http://codesinger.blogspot.com/2015/12/elixir-erlang-records-and-erlsom-xml.html
I stole the dragon art from ASCII.co.uk.
"""
require Logger
alias RecycledCloud.OpenNebula.Schema
alias RecycledCloud.OpenNebula.Schema.Records
@opennebula_release "5.12"
##
# Data-binding: access and extract XSD models.
defp get_raw(type, object) do
priv_dir = :code.priv_dir(:recycledcloud) |> to_string
{data_dir, extension} = case type do
:xsd -> {"xsd-src", ".xsd"}
:hrl -> {"xsd-records", ".hrl"}
end
raw_path = [priv_dir, "opennebula", @opennebula_release, data_dir, object <> extension]
raw_path |> Path.join()
end
def get_xsd_for(object), do: get_raw(:xsd, object)
def get_hrl_for(object), do: get_raw(:hrl, object)
def generate_model_for(object) do
{:ok, model} = object
|> get_xsd_for()
|> :erlsom.compile_xsd_file()
model
end
##
# Data-binding: transform erlsom records to Elixir maps.
def scan(raw, object_type) do
{:ok, data, _rest} = :erlsom.scan(raw, Records.get_model_for(object_type))
data
end
defp transform_value({k, l}) when is_list(l) do
{k, Enum.map(l, fn v -> map_record(v) end)}
end
defp transform_value({k, v}), do: {k, map_record(v)}
# Here be dragons! See moduledoc.
def map_record(data) when is_tuple(data) do
schema = elem(data, 0)
if schema in Keyword.keys(Schema.Records.__info__(:macros)) do
call = quote do
require RecycledCloud.OpenNebula.Schema.Records
RecycledCloud.OpenNebula.Schema.Records."XSD"(:data)
end
replace = fn
:XSD, {model, data} -> {model, {model, data}}
:data, {model, data} -> {data, {model, data}}
node, stubs -> {node, stubs}
end
{{inserted, _raw}, []} = call
|> Macro.prewalk({schema, Macro.escape(data)}, replace)
|> Code.eval_quoted()
Enum.into(inserted, Map.new, &transform_value/1)
else
Logger.debug("Ignoring unknown OpenNebula Model #{schema}.")
data
end
end
def map_record(data = [first | _rest]) when is_integer(first), do:
List.to_string(data)
def map_record(:undefined), do: nil
def map_record(data), do: data
end