Current section
Files
Jump to
Current section
Files
lib/quick_struct.ex
defmodule QuickStruct do
@moduledoc """
Creates a struct with enforced keys, the type of the struct and
a make function to create the struct.
You have to "use" this module and give a list of fields or a
keyword list with fields and specs to create a struct.
As an alternative you can create a module and a struct together;
just require QuickStruct and call `define_module/2`.
## Examples
Assume you define the following structs:
```
defmodule QuickStructTest.User do
use QuickStruct, [firstname: String.t, name: String.t]
end
defmodule QuickStructTest.Pair do
use QuickStruct, [:first, :second]
end
```
Or equivalent:
```
require QuickStruct
QuickStruct.define_module QuickStructTest.User, [firstname: String.t, name: String.t]
QuickStruct.define_module QuickStructTest.Pair, [:first, :second]
```
To create a struct you can either use `make/1` with a keyword list to specify
the fields, or use `make`, where each argument is one field (order matters):
iex> alias QuickStructTest.User
iex> User.make("Jon", "Adams")
%User{firstname: "Jon", name: "Adams"}
iex> User.make([name: "Adams", firstname: "Jon"])
%User{firstname: "Jon", name: "Adams"}
iex> alias QuickStructTest.Pair
iex> Pair.make(1, 0)
%Pair{a: 1, b: 0}
iex> Pair.make([a: "My", b: "String"])
%Pair{a: "My", b: "String"}
"""
@doc """
Returns true if the given object is a struct of the given module, otherwise false.
## Examples
iex> alias QuickStructTest.User
iex> QuickStruct.is_struct_of(%User{firstname: "Jon", name: "Adams"}, QuickStructTest.User)
true
iex> QuickStruct.is_struct_of(%User{firstname: "Jon", name: "Adams"}, MyModule)
false
"""
@spec is_struct_of(any(), module()) :: boolean()
def is_struct_of(%{__struct__: struct_module}, module), do: struct_module == module
def is_struct_of(_, _), do: false
@doc false
defmacro struct(fields, args) do
quote do
@enforce_keys unquote(fields)
defstruct unquote(fields)
@doc "Creates a #{__MODULE__}-struct from a keyword list."
def make([_ | _] = fields) do
Kernel.struct!(__MODULE__, fields)
end
@doc "Creates a #{__MODULE__}-struct from the given ordered fields."
def make(unquote_splicing(args)) do
l =
Enum.zip(unquote(fields), unquote(args))
|> Enum.into([])
Kernel.struct!(__MODULE__, l)
end
@doc """
Returns true if the given object is a #{__MODULE__}-struct, otherwise false.
## Examples
Pair.is_struct(%User{firstname: "Jon", name: "Adams"}) # => false
Pair.is_struct(%Pair{first: 1, second: 2}) # => true
"""
@spec is_struct(any()) :: boolean()
def is_struct(object), do: QuickStruct.is_struct_of(object, __MODULE__)
end
end
defmacro __using__([{_, _} | _] = fieldspecs) do
fields = Keyword.keys(fieldspecs)
types = Keyword.values(fieldspecs)
args = Enum.map(fields, &{&1, [], __MODULE__})
# This does something similar to Macro.generate_arguments/2, but
# with the original field names as arguments (better for generated
# documentation of the function).
quote do
@type t :: %__MODULE__{unquote_splicing(fieldspecs)}
@spec make(unquote(fieldspecs)) :: __MODULE__.t()
@spec make(unquote_splicing(types)) :: __MODULE__.t()
QuickStruct.struct(unquote(fields), unquote(args))
end
end
defmacro __using__([]) do
quote do
defstruct []
@type t :: %__MODULE__{}
@doc "Creates a #{__MODULE__}-struct."
@spec make([]) :: __MODULE__.t()
def make([] \\ []) do
Kernel.struct!(__MODULE__, [])
end
@doc """
Returns true if the given object is a #{__MODULE__}-struct, otherwise false.
## Example
"""
@spec is_struct(any()) :: boolean()
def is_struct(object), do: QuickStruct.is_struct_of(object, __MODULE__)
end
end
defmacro __using__(fields) when is_list(fields) do
args = Enum.map(fields, &{&1, [], __MODULE__})
quote do
QuickStruct.struct(unquote(fields), unquote(args))
end
end
@doc """
Defines a module together with a struct with the given field list.
## Example
```
require QuickStruct
QuickStruct.define_module(MyDate, [day: integer(), month: integer(), year: integer()])
new_year = %MyDate{day: 1, month: 1, year: 2000}
```
This is equivalent to:
```
defmodule MyDate do
use QuickStruct, [day: integer(), month: integer(), year: integer()]
end
new_year = %MyDate{day: 1, month: 1, year: 2000}
```
"""
@spec define_module(module(), keyword()) :: {:defmodule, keyword(), keyword()}
defmacro define_module(modulename, fields) do
quote do
defmodule unquote(modulename) do
use QuickStruct, unquote(fields)
end
end
end
@doc """
Generates a function which will generate a struct with some given default values.
## Example
```
defmodule Triple do
use QuickStruct, [:first, :second, :third]
QuickStruct.constructor_with_defaults([third: 0])
end
pair = Triple.make_with_defaults([first: 24, second: 12])
# => %Triple{first: 24, second: 12, third: 0}
```
"""
@spec constructor_with_defaults(keyword()) :: {:__block__, [], [any()]}
defmacro constructor_with_defaults(defaults) do
quote do
@doc "Creates a #{__MODULE__}-struct from a keyword list with defaults: #{
unquote(inspect(defaults))
}."
@spec make_with_defaults(keyword()) :: __MODULE__.t()
def make_with_defaults(fields) do
make(Keyword.merge(unquote(defaults), fields))
end
end
end
end