97 lines
2.9 KiB
Elixir
97 lines
2.9 KiB
Elixir
|
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, 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
|