Current section
Files
Jump to
Current section
Files
lib/codi/pattern/doc/doc.ex
defmodule Plymio.Codi.Pattern.Doc do
@moduledoc ~S"""
The *doc* pattern builds an `@doc` module attribute.
See `Plymio.Codi` for an overview and documentation terms.
## Pattern: *doc*
Valid keys in the *cpo* are:
| Key | Aliases |
| :--- | :--- |
| `:fun_name` | *:name, :spec_name, :fun_name, :function_name* |
| `:fun_args` | *:args, :spec_args, :fun_args, :function_args* |
| `:fun_arity` | *:arity, :spec_arity, :fun_arity, :function_arity* |
| `:fun_doc` | *:doc, :function_doc* |
## Examples
If the `:fun_doc` is `false`, documentation is turned off as expected:
iex> {:ok, {forms, _}} = [
...> doc: [doc: false]
...> ] |> produce_codi
...> forms |> harnais_helper_show_forms!
["@doc(false)"]
The simplest `:fun_doc` is a string:
iex> {:ok, {forms, _}} = [
...> doc: [doc: "This is the docstring for fun1"]
...> ] |> produce_codi
...> forms |> harnais_helper_show_forms!
["@doc(\"This is the docstring for fun1\")"]
For convenience, the `:fun_doc` can be `:bang` to generate a
suitable docstring for a bang function. For this, the *cpo* must include the
`:fun_name`, `:fun_args` or `:fun_arity`, and (optionally)
`:fun_module`.
iex> {:ok, {forms, _}} = [
...> doc: [name: :fun_one, arity: 1, doc: :bang]
...> ] |> produce_codi
...> forms |> harnais_helper_show_forms!
["@doc(\"Bang function for `fun_one/1`\")"]
iex> {:ok, {forms, _}} = [
...> doc: [name: :fun_due, arity: 2, module: ModuleA, doc: :bang]
...> ] |> produce_codi
...> forms |> harnais_helper_show_forms!
["@doc(\"Bang function for `ModuleA.fun_due/2`\")"]
Similarly, `:fun_doc` can be `:delegate` to generate a suitable
docstring for a delegation.
iex> {:ok, {forms, _}} = [
...> doc: [name: :fun_due, arity: 2, doc: :delegate]
...> ] |> produce_codi
...> forms |> harnais_helper_show_forms!
["@doc(\"Delegated to `fun_due/2`\")"]
iex> {:ok, {forms, _}} = [
...> doc: [name: :fun_due, arity: 2, module: ModuleA, doc: :delegate]
...> ] |> produce_codi
...> forms |> harnais_helper_show_forms!
["@doc(\"Delegated to `ModuleA.fun_due/2`\")"]
"""
alias Plymio.Codi, as: CODI
use Plymio.Fontais.Attribute
use Plymio.Codi.Attribute
import Plymio.Codi.Error,
only: [
new_error_result: 1
]
import Plymio.Fontais.Guard,
only: [
is_value_unset: 1,
is_value_unset_or_nil: 1
]
import Plymio.Codi.Error,
only: [
new_error_result: 1
]
import Plymio.Fontais.Option,
only: [
opts_create_aliases_dict: 1,
# opts_maybe_canonical_keys: 2,
opts_take_canonical_keys: 2
]
import Plymio.Codi.Utility,
only: [
cpo_resolve_fun_module: 1,
cpo_resolve_fun_name: 1,
cpo_resolve_fun_arity: 1,
cpo_resolve_guard_fun_fields: 1
]
import Plymio.Codi.CPO
@pattern_doc_kvs_alias [
@plymio_codi_key_alias_pattern,
@plymio_codi_key_alias_status,
@plymio_codi_key_alias_form,
@plymio_codi_key_alias_fun_doc,
@plymio_codi_key_alias_fun_module,
@plymio_codi_key_alias_fun_name,
@plymio_codi_key_alias_fun_args,
@plymio_codi_key_alias_fun_arity,
@plymio_codi_key_alias_fun_key,
@plymio_codi_key_alias_fun_default,
@plymio_codi_key_alias_delegate_name,
@plymio_codi_key_alias_forms_edit
]
@pattern_doc_dict_alias @pattern_doc_kvs_alias
|> opts_create_aliases_dict
@doc false
def cpo_pattern_doc_normalise(opts, dict \\ nil) do
opts |> opts_take_canonical_keys(dict || @pattern_doc_dict_alias)
end
defp express_doc_pattern(codi, pattern, opts)
defp express_doc_pattern(%CODI{} = state, pattern, _opts)
when is_value_unset_or_nil(pattern) do
{:ok, {@plymio_fontais_the_unset_value, state}}
end
defp express_doc_pattern(%CODI{} = state, pattern, _opts)
when is_binary(pattern) do
{:ok, {quote(do: @doc(unquote(pattern))), state}}
end
defp express_doc_pattern(%CODI{} = state, pattern, _opts)
when pattern == false do
{:ok, {quote(do: @doc(false)), state}}
end
defp express_doc_pattern(%CODI{} = state, pattern, opts)
when pattern == @plymio_codi_doc_type_bang do
with {:ok, fun_name} <- opts |> cpo_resolve_fun_name,
{:ok, fun_module} <- opts |> cpo_resolve_fun_module,
{:ok, fun_arity} <- opts |> cpo_resolve_fun_arity do
docstring =
fun_module
|> case do
x when is_value_unset_or_nil(x) ->
"Bang function for `#{to_string(fun_name)}/#{fun_arity}`"
x ->
"Bang function for `#{inspect(x)}.#{to_string(fun_name)}/#{fun_arity}`"
end
form =
quote do
@doc unquote(docstring)
end
{:ok, {form, state}}
else
{:error, %{__exception__: true}} = result -> result
end
end
defp express_doc_pattern(%CODI{} = state, pattern, opts)
when pattern == @plymio_codi_doc_type_query do
with {:ok, fun_name} <- opts |> cpo_resolve_fun_name,
{:ok, fun_module} <- opts |> cpo_resolve_fun_module,
{:ok, fun_arity} <- opts |> cpo_resolve_fun_arity do
docstring =
fun_module
|> case do
x when is_value_unset_or_nil(x) ->
"Query function for `#{to_string(fun_name)}/#{fun_arity}`"
x ->
"Query function for `#{inspect(x)}.#{to_string(fun_name)}/#{fun_arity}`"
end
form =
quote do
@doc unquote(docstring)
end
{:ok, {form, state}}
else
{:error, %{__exception__: true}} = result -> result
end
end
defp express_doc_pattern(%CODI{} = state, pattern, opts)
when pattern == @plymio_codi_doc_type_delegate do
with {:ok, fun_module} <- opts |> cpo_resolve_fun_module,
{:ok, fun_name} <- opts |> cpo_resolve_fun_name,
{:ok, fun_arity} <- opts |> cpo_resolve_fun_arity do
docstring =
fun_module
|> case do
x when is_value_unset_or_nil(x) ->
"Delegated to `#{fun_name}/#{fun_arity}`"
x ->
"Delegated to `#{inspect(x)}.#{fun_name}/#{fun_arity}`"
end
form =
quote do
@doc unquote(docstring)
end
{:ok, {form, state}}
else
{:error, %{__exception__: true}} = result -> result
end
end
defp express_doc_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_doc_type_struct_get1 do
with {:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_arity} <- cpo |> cpo_resolve_fun_arity,
{:ok, fun_default} <- cpo |> cpo_get_fun_default,
{:ok, fun_fields} <- cpo |> cpo_resolve_guard_fun_fields,
{:ok, fun_args} <- cpo |> cpo_fetch_fun_args,
true <- true do
fun_field = fun_fields |> hd |> elem(0)
fun_default =
fun_default
|> is_value_unset
|> case do
true -> "TheUnsetValue"
_ -> fun_default |> inspect
end
edits = [
{~r/proxy_fun_name/, fun_name |> to_string},
{~r/proxy_fun_arity/, fun_arity |> to_string},
{~r/proxy_fun_default/, fun_default},
{~r/proxy_field_name/, fun_field |> to_string},
{~r/proxy_struct_name/, fun_args |> Enum.at(0) |> elem(0) |> to_string}
]
docstring =
~S"""
`proxy_fun_name/proxy_fun_arity` takes `proxy_struct_name`
and, if the `proxy_field_name` field's `value`
is set, returns `{:ok, value}`, else `{:ok, proxy_fun_default}`.
"""
|> apply_doctsring_edits(edits)
form =
quote do
@doc unquote(docstring)
end
{:ok, {form, state}}
else
{:error, %{__exception__: true}} = result -> result
end
end
defp express_doc_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_doc_type_struct_get2 do
with {:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_arity} <- cpo |> cpo_resolve_fun_arity,
{:ok, fun_fields} <- cpo |> cpo_resolve_guard_fun_fields,
{:ok, fun_args} <- cpo |> cpo_fetch_fun_args,
true <- true do
fun_field = fun_fields |> hd |> elem(0)
edits = [
{~r/proxy_fun_name/, fun_name |> to_string},
{~r/proxy_fun_arity/, fun_arity |> to_string},
{~r/proxy_field_name/, fun_field |> to_string},
{~r/proxy_struct_name/, fun_args |> Enum.at(0) |> elem(0) |> to_string},
{~r/proxy_default_name/, fun_args |> Enum.at(1) |> elem(0) |> to_string}
]
docstring =
~S"""
`proxy_fun_name/proxy_fun_arity` takes `proxy_struct_name`
and the `proxy_default_name` and, if the `proxy_field_name` field's `value`
is set, returns `{:ok, value}`, else `{:ok, proxy_default_name}`.
"""
|> apply_doctsring_edits(edits)
form =
quote do
@doc unquote(docstring)
end
{:ok, {form, state}}
else
{:error, %{__exception__: true}} = result -> result
end
end
defp express_doc_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_doc_type_struct_fetch do
with {:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_arity} <- cpo |> cpo_resolve_fun_arity,
{:ok, fun_fields} <- cpo |> cpo_resolve_guard_fun_fields,
{:ok, fun_args} <- cpo |> cpo_fetch_fun_args,
true <- true do
fun_field = fun_fields |> hd |> elem(0)
edits = [
{~r/proxy_fun_name/, fun_name |> to_string},
{~r/proxy_fun_arity/, fun_arity |> to_string},
{~r/proxy_field_name/, fun_field |> to_string},
{~r/proxy_struct_name/, fun_args |> Enum.at(0) |> elem(0) |> to_string}
]
docstring =
~S"""
`proxy_fun_name/proxy_fun_arity` takes `proxy_struct_name`
and fetches field `proxy_field_name`'s `value`,
and, if `value` is set, returns `{:ok, value}`, else `{:error, error}`
"""
|> apply_doctsring_edits(edits)
form =
quote do
@doc unquote(docstring)
end
{:ok, {form, state}}
else
{:error, %{__exception__: true}} = result -> result
end
end
defp express_doc_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_doc_type_struct_put do
with {:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_arity} <- cpo |> cpo_resolve_fun_arity,
{:ok, fun_fields} <- cpo |> cpo_resolve_guard_fun_fields,
{:ok, fun_args} <- cpo |> cpo_fetch_fun_args,
true <- true do
fun_field = fun_fields |> hd |> elem(0)
edits = [
{~r/proxy_fun_name/, fun_name |> to_string},
{~r/proxy_fun_arity/, fun_arity |> to_string},
{~r/proxy_field_name/, fun_field |> to_string},
{~r/proxy_struct_name/, fun_args |> Enum.at(0) |> elem(0) |> to_string},
{~r/proxy_value_name/, fun_args |> Enum.at(1) |> elem(0) |> to_string}
]
docstring =
~S"""
`proxy_fun_name/proxy_fun_arity` takes `proxy_struct_name`
and `proxy_value_name`, and puts
`proxy_value_name` in `proxy_struct_name`'s field `proxy_field_name`,
returning `{:ok, proxy_struct_name}`.
"""
|> apply_doctsring_edits(edits)
form =
quote do
@doc unquote(docstring)
end
{:ok, {form, state}}
else
{:error, %{__exception__: true}} = result -> result
end
end
defp express_doc_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_doc_type_struct_maybe_put do
with {:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_arity} <- cpo |> cpo_resolve_fun_arity,
{:ok, fun_fields} <- cpo |> cpo_resolve_guard_fun_fields,
{:ok, fun_args} <- cpo |> cpo_fetch_fun_args,
true <- true do
fun_field = fun_fields |> hd |> elem(0)
edits = [
{~r/proxy_fun_name/, fun_name |> to_string},
{~r/proxy_fun_arity/, fun_arity |> to_string},
{~r/proxy_field_name/, fun_field |> to_string},
{~r/proxy_struct_name/, fun_args |> Enum.at(0) |> elem(0) |> to_string},
{~r/proxy_value_name/, fun_args |> Enum.at(1) |> elem(0) |> to_string}
]
docstring =
~S"""
`proxy_fun_name/proxy_fun_arity` takes `proxy_struct_name` and
`proxy_value_name`, and, if `proxy_value_name` is set, and the
value of the `proxy_field_name` field is unset,
puts `proxy_value_name` in the `proxy_field_name` field,
returning `{:ok, proxy_struct_name}`.
"""
|> apply_doctsring_edits(edits)
form =
quote do
@doc unquote(docstring)
end
{:ok, {form, state}}
else
{:error, %{__exception__: true}} = result -> result
end
end
defp express_doc_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_doc_type_struct_has? do
with {:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_arity} <- cpo |> cpo_resolve_fun_arity,
{:ok, fun_fields} <- cpo |> cpo_resolve_guard_fun_fields,
{:ok, fun_args} <- cpo |> cpo_fetch_fun_args,
true <- true do
fun_field = fun_fields |> hd |> elem(0)
edits = [
{~r/proxy_fun_name/, fun_name |> to_string},
{~r/proxy_fun_arity/, fun_arity |> to_string},
{~r/proxy_field_name/, fun_field |> to_string},
{~r/proxy_struct_name/, fun_args |> Enum.at(0) |> elem(0) |> to_string}
]
docstring =
~S"""
`proxy_fun_name/proxy_fun_arity` takes `proxy_struct_name`
and, if its `proxy_field_name` field is
set, returns `true`, else `false`.
"""
|> apply_doctsring_edits(edits)
form =
quote do
@doc unquote(docstring)
end
{:ok, {form, state}}
else
{:error, %{__exception__: true}} = result -> result
end
end
defp express_doc_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_doc_type_struct_set do
with {:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_arity} <- cpo |> cpo_resolve_fun_arity,
{:ok, fun_fields} <- cpo |> cpo_resolve_guard_fun_fields,
{:ok, fun_args} <- cpo |> cpo_fetch_fun_args,
true <- true do
fun_fields =
fun_fields
|> Enum.map(fn
{k, v} when is_value_unset(v) -> {k, "TheUnsetValue"}
x -> x
end)
edits = [
{~r/proxy_fun_name/, fun_name |> to_string},
{~r/proxy_fun_arity/, fun_arity |> to_string},
{~r/proxy_fun_default/, fun_fields |> inspect},
{~r/proxy_struct_name/, fun_args |> Enum.at(0) |> elem(0) |> to_string}
]
docstring =
~S"""
`proxy_fun_name/proxy_fun_arity` takes `proxy_struct_name` and
calls `Kernel.struct/1` with it and
proxy_fun_default
returning `{:ok, proxy_struct_name}`.
"""
|> apply_doctsring_edits(edits)
form =
quote do
@doc unquote(docstring)
end
{:ok, {form, state}}
else
{:error, %{__exception__: true}} = result -> result
end
end
defp express_doc_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_doc_type_struct_update do
with {:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_arity} <- cpo |> cpo_resolve_fun_arity,
{:ok, fun_fields} <- cpo |> cpo_resolve_guard_fun_fields,
{:ok, fun_args} <- cpo |> cpo_fetch_fun_args,
true <- true do
fun_field = fun_fields |> hd |> elem(0)
edits = [
{~r/proxy_fun_name/, fun_name |> to_string},
{~r/proxy_fun_arity/, fun_arity |> to_string},
{~r/proxy_field_name/, fun_field |> to_string},
{~r/proxy_struct_name/, fun_args |> Enum.at(0) |> elem(0) |> to_string},
{~r/proxy_value_name/, fun_args |> Enum.at(1) |> elem(0) |> to_string}
]
docstring =
~S"""
`proxy_fun_name/proxy_fun_arity` takes `proxy_struct_name` and
the `proxy_value_name` and calls `update/2` with
[{`proxy_field_name`, `proxy_value_name`}], returning `{:ok, struct}`.
"""
|> apply_doctsring_edits(edits)
form =
quote do
@doc unquote(docstring)
end
{:ok, {form, state}}
else
{:error, %{__exception__: true}} = result -> result
end
end
defp express_doc_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_doc_type_struct_export do
with {:ok, fun_name} <- cpo |> cpo_resolve_fun_name,
{:ok, fun_arity} <- cpo |> cpo_resolve_fun_arity,
{:ok, fun_fields} <- cpo |> cpo_resolve_guard_fun_fields,
true <- true do
edits = [
{~r/proxy_fun_name/, fun_name |> to_string},
{~r/proxy_fun_arity/, fun_arity |> to_string},
{~r/proxy_fun_fields/, fun_fields |> Keyword.keys() |> inspect}
]
docstring =
~S"""
`proxy_fun_name/proxy_fun_arity` takes an instance of the
module's *struct* and creates an *opts* (`Keyword`) from fields
`proxy_fun_fields` whose values are set, returning `{:ok, opts}`.
"""
|> apply_doctsring_edits(edits)
form =
quote do
@doc unquote(docstring)
end
{:ok, {form, state}}
else
{:error, %{__exception__: true}} = result -> result
end
end
defp express_doc_pattern(%CODI{} = state, pattern, opts)
when is_function(pattern, 2) do
pattern.(state, opts)
|> case do
{:error, %{__exception__: true}} = result -> result
{:ok, _} = result -> result
value -> {:ok, value}
end
|> case do
{:error, %{__exception__: true}} = result ->
result
{:ok, value} ->
value
|> case do
x when is_binary(x) -> {:ok, x}
x -> {:ok, x |> inspect}
end
end
end
defp express_doc_pattern(_codi, pattern, opts) do
new_error_result(m: "doc pattern #{inspect(pattern)} invalid", v: opts)
end
@doc false
def express_pattern(codi, pattern, opts \\ [])
def express_pattern(%CODI{} = state, pattern, cpo)
when pattern == @plymio_codi_pattern_doc do
with {:ok, cpo} <- cpo |> cpo_pattern_doc_normalise,
{:ok, fun_doc} <- cpo |> cpo_get_fun_doc,
{:ok, {form, %CODI{} = state}} <- state |> express_doc_pattern(fun_doc, cpo) do
with {:ok, cpo} <- cpo |> cpo_done_with_edited_form(form) do
{:ok, {cpo, state}}
else
{:error, %{__exception__: true}} = result -> result
end
else
{:error, %{__exception__: true}} = result -> result
end
end
defp apply_doctsring_edits(docstring, edits)
when is_list(edits) and is_binary(docstring) do
edits
|> Enum.reduce(docstring, fn {r, v}, s ->
Regex.replace(r, s, v)
end)
end
end