Packages

BACstack is a low-level Elixir implementation for the ASHRAE standard 135, BACnet - Building Automation and Controller network. In its current published state, v0.0.x, it can (only) act as a BACnet client for other BACnet devices.

Current section

Files

Jump to
bacstack lib bacnet macro.ex
Raw

lib/bacnet/macro.ex

defmodule BACnet.Macro do
@module_doc """
This module also provides typed structs, that will generate a struct with type `t`, based on the given arguments.
Such as this module
```elixir
defmodule Report do
@type report_type :: :refactoring | :warning | :consistency
@type t :: %__MODULE__{
type: report_type,
description: String.t,
message: String.t | nil
}
@enforce_keys [:type, :description]
defstruct [:description, :message, type: :refactoring]
end
```
can be rewritten to
```elixir
defmodule Report do
@type report_type :: :refactoring | :warning | :consistency
# required? is by default true
typedstruct do
field :type, report_type, default: :refactoring
field :description, String.t
field :message, String.t, required: false
end
end
```
"""
# Do not generate module docs meanwhile (and do not generate a warning)
@moduledoc !@module_doc
# The typedstruct macro has been copied and slightly adjusted
# Original author: https://dorgan.ar/posts/2021/04/the_elixir_ast_typedstruct/
@doc """
Generate a typed struct. It defines `defstruct`, `@enforce_keys` and `@type t`.
Example:
```elixir
defmodule Report do
@type report_type :: :refactoring | :warning | :consistency
# required? is by default true
typedstruct do
field :type, report_type, default: :refactoring
field :description, String.t
field :message, String.t, required: false
end
end
```
"""
defmacro typedstruct(do_block)
defmacro typedstruct(do: ast) do
fields_ast =
case ast do
{:__block__, _meta, fields} -> fields
field -> [field]
end
fields_data = Enum.map(fields_ast, &get_field_data/1)
enforced_fields =
for field <- fields_data, field.required do
field.name
end
typespecs =
Enum.map(fields_data, fn
%{name: name, typespec: typespec, required: true} ->
{name, typespec}
%{name: name, typespec: typespec} ->
{
name,
{:|, [], [typespec, nil]}
}
end)
fields =
for %{name: name, default: default} <- fields_data do
{name, default}
end
quote generated: true, location: :keep do
@type t :: %__MODULE__{unquote_splicing(typespecs)}
@enforce_keys unquote(enforced_fields)
defstruct unquote(fields)
end
end
@doc """
Same as `typedstruct/1`, but `@opaque` gets generated.
"""
defmacro opaquedstruct(do_block)
defmacro opaquedstruct(do: ast) do
fields_ast =
case ast do
{:__block__, _meta, fields} -> fields
field -> [field]
end
fields_data = Enum.map(fields_ast, &get_field_data/1)
enforced_fields =
for field <- fields_data, field.required do
field.name
end
typespecs =
Enum.map(fields_data, fn
%{name: name, typespec: typespec, required: true} ->
{name, typespec}
%{name: name, typespec: typespec} ->
{
name,
{:|, [], [typespec, nil]}
}
end)
fields =
for %{name: name, default: default} <- fields_data do
{name, default}
end
quote generated: true, location: :keep do
@opaque t :: %__MODULE__{unquote_splicing(typespecs)}
@enforce_keys unquote(enforced_fields)
defstruct unquote(fields)
end
end
defp get_field_data({:field, _meta, [name, typespec]}) do
get_field_data({:field, [], [name, typespec, []]})
end
defp get_field_data({:field, _meta, [name, typespec, opts]}) do
default = Keyword.get(opts, :default)
required = Keyword.get(opts, :required, false)
%{
name: name,
typespec: typespec,
default: default,
required: required
}
end
end