Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ defmodule Person do
field :name, String.t(), enforce: true
field :age, non_neg_integer()
field :happy?, boolean(), default: true
field :phone, String.t()
field :phone, String.t(), doc: "strictly the home number"
end
end
```
Expand Down Expand Up @@ -126,6 +126,9 @@ defmodule MyStruct do

# You can enforce a field.
field :enforced_field, integer(), enforce: true

# Define each field with the field macro.
field :a_field_thats_purpose_is_not_obvious, String.t(), doc: "some explanation about this field"
end
end
```
Expand Down Expand Up @@ -196,6 +199,38 @@ typedstruct do
end
```

You can also add documentation to individual fields:

```elixir
typedstruct do
@typedoc "A typed struct"

field :a_string, String.t(), doc: "this is just a series of letters"
field :an_int, integer(), doc: "some explanation"
end
```

As an added benefit, the above will generate a typedoc as below:

```elixir
@typedoc """
A typed struct

- `a_string`: this is just a series of letters
- `an_int`: some explanation
"""
```

This works without the `@typedoc ...` declaration,
with the following result:

```elixir
@typedoc """
- `a_string`: this is just a series of letters
- `an_int`: some explanation
"""
```

You can also document submodules this way:

```elixir
Expand Down
37 changes: 36 additions & 1 deletion lib/typed_struct.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ defmodule TypedStruct do
:ts_plugin_fields,
:ts_fields,
:ts_types,
:ts_enforce_keys
:ts_enforce_keys,
:ts_comments
]

@attrs_to_delete [:ts_enforce? | @accumulating_attrs]
Expand Down Expand Up @@ -110,6 +111,11 @@ defmodule TypedStruct do
@enforce_keys @ts_enforce_keys
defstruct @ts_fields

td =
if Module.has_attribute?(__MODULE__, :typedoc), do: @typedoc, else: nil

TypedStruct.__typedoc_generator__(td, @ts_comments)

TypedStruct.__type__(@ts_types, unquote(opts))
end
end
Expand All @@ -127,6 +133,28 @@ defmodule TypedStruct do
end
end

@doc false
defmacro __typedoc_generator__(typedoc, comments) do
quote bind_quoted: [typedoc: typedoc, comments: comments] do
# If there are no comments we just leave it as it is
if comments != [] do
comments =
comments
|> Enum.reverse()
|> Enum.map(fn {x, c} -> "- `#{x}`: #{c}" end)

comments =
if typedoc == nil, do: comments, else: [typedoc, "" | comments]

Module.delete_attribute(__MODULE__, :typedoc)

@typedoc """
#{Enum.join(comments, "\n")}
"""
end
end
end

@doc """
Registers a plugin for the currently defined struct.

Expand Down Expand Up @@ -168,6 +196,8 @@ defmodule TypedStruct do
* `default` - sets the default value for the field
* `enforce` - if set to true, enforces the field and makes its type
non-nullable
* `doc` - short description for the field,
that will be appended to the typedoc as well
"""
defmacro field(name, type, opts \\ []) do
quote bind_quoted: [name: name, type: Macro.escape(type), opts: opts] do
Expand All @@ -192,6 +222,11 @@ defmodule TypedStruct do

nullable? = !has_default? && !enforce?

comment = opts[:doc]

if comment != nil,
do: Module.put_attribute(mod, :ts_comments, {name, comment})

Module.put_attribute(mod, :ts_fields, {name, opts[:default]})
Module.put_attribute(mod, :ts_plugin_fields, {name, type, opts, env})
Module.put_attribute(mod, :ts_types, {name, type_for(type, nullable?)})
Expand Down