Current section
Files
Jump to
Current section
Files
lib/codi/utility/utility.ex
defmodule Plymio.Codi.Utility do
@moduledoc false
use Plymio.Fontais.Attribute
use Plymio.Codi.Attribute
import Plymio.Fontais.Guard,
only: [
is_positive_integer: 1,
is_negative_integer: 1,
is_value_set: 1,
is_value_unset: 1,
is_value_unset_or_nil: 1
]
import Plymio.Codi.Error,
only: [
new_error_result: 1
]
import Plymio.Fontais.Error,
only: [
new_argument_error_result: 1
]
import Plymio.Fontais.Utility,
only: [
validate_key: 1
]
import Plymio.Fontais.Option,
only: [
opzioni_validate: 1,
opts_normalise: 1,
opts_validate: 1,
opts_get: 2,
opts_get: 3,
opts_put: 3,
opts_fetch: 2
]
import Plymio.Funcio.Enum.Map.Collate,
only: [
map_collate0_enum: 2
]
import Plymio.Fontais.Result,
only: [
normalise1_result: 1
]
import Plymio.Codi.CPO
@type error :: Plymio.Codi.error()
defp validate_vars(vars)
defp validate_vars([]) do
{:ok, []}
end
defp validate_vars(vars) when is_list(vars) do
vars
|> Macro.validate()
|> case do
:ok ->
{:ok, vars}
_ ->
new_error_result(m: "vars invalid", v: vars)
end
end
defp normalise_vars(vars, opts \\ [])
defp normalise_vars([], _) do
{:ok, []}
end
defp normalise_vars(vars, opts) do
vars
|> List.wrap()
|> case do
[] ->
[]
x when is_list(x) ->
x
|> Enum.map(fn
arg when is_atom(arg) ->
arg |> Macro.var(nil)
# +ve => generate e.g. arg1, arg2 etc
arg when is_positive_integer(arg) ->
opts
# prefix for generated var?
|> Keyword.get(@plymio_codi_key_prefix)
|> case do
x when is_nil(x) ->
arg |> Macro.generate_arguments(nil)
x when is_atom(x) ->
Range.new(0, arg - 1)
|> Enum.map(fn v ->
"#{to_string(x)}#{inspect(v)}"
|> String.to_atom()
|> Macro.var(nil)
end)
end
# -ve => generate same prefix var
# (use for type related argument hence any as default prefix)
arg when is_negative_integer(arg) ->
opts
# prefix for generated var?
|> Keyword.get(@plymio_codi_key_prefix, :any)
|> Macro.var(nil)
|> List.duplicate(arg |> abs())
# already a var? will be validated below
arg ->
arg
end)
|> List.flatten()
end
|> validate_vars
end
defp validate_fun_name(name)
defp validate_fun_name(name) when is_atom(name) do
{:ok, name}
end
defp validate_fun_name(name) do
new_error_result(m: "function name invalid", v: name)
end
def validate_fun_module(module)
def validate_fun_module(nil) do
new_error_result(m: "function module invalid", v: nil)
end
def validate_fun_module(module) when is_atom(module) do
{:ok, module}
end
def validate_fun_module(module) do
new_error_result(m: "function module invalid", v: module)
end
def validate_fun_modules(modules)
def validate_fun_modules(modules) when is_list(modules) do
modules
|> map_collate0_enum(&validate_fun_module/1)
end
def validate_fun_modules(modules) do
new_error_result(m: "functions modules invalid", v: modules)
end
defp validate_fun_arity(arity)
defp validate_fun_arity(arity) when is_positive_integer(arity) do
{:ok, arity}
end
defp validate_fun_arity(arity) do
new_error_result(m: "function arity invalid", v: arity)
end
# many are convenience mnemonics
defp normalise_type_result(result)
defp normalise_type_result(:ok_any_error_error) do
{:ok, quote(do: {:ok, any} | {:error, error})}
end
defp normalise_type_result(:any_no_return) do
{:ok, quote(do: any | no_return)}
end
defp normalise_type_result(:ok_opts_error_error) do
{:ok, quote(do: {:ok, opts} | {:error, error})}
end
defp normalise_type_result(:opts_no_return) do
{:ok, quote(do: opts | no_return)}
end
defp normalise_type_result(result) do
result |> normalise_vars
end
defp cpo_resolve(cpo, opts) do
with {:ok, cpo} <- cpo |> opts_validate,
{:ok, key} <- opts |> ctrl_fetch_key,
{:ok, key} <- key |> validate_key,
{:ok, fun} <- opts |> ctrl_get_validate_fun do
cpo
|> Keyword.has_key?(key)
|> case do
true ->
cpo
|> Keyword.get(key)
|> fun.()
|> normalise1_result
# any default?
_ ->
opts
|> Keyword.has_key?(@plymio_codi_key_default)
|> case do
true ->
opts
|> Keyword.get(@plymio_codi_key_default)
|> case do
# don't validate if unset
x when is_value_unset(x) ->
{:ok, x}
x ->
x
|> fun.()
|> normalise1_result
end
_ ->
new_argument_error_result("cpo #{to_string(key)} missing, got: #{inspect(cpo)}")
end
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_fun_module(cpo, opts \\ [])
# can return :ok with nil or unset
def cpo_resolve_fun_module(cpo, opts) do
with {:ok, cpo} <- cpo |> opts_validate,
{:ok, fun_module_key} <- opts |> ctrl_get_key_fun_module,
{:ok, opts} <- opts |> ctrl_put_key(fun_module_key),
{:ok, opts} <- opts |> ctrl_put_validate_fun(&validate_fun_module/1),
{:ok, opts} <- opts |> ctrl_put_default(@plymio_fontais_the_unset_value),
{:ok, _fun_module} = result <- cpo |> cpo_resolve(opts) do
result
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_fun_name(cpo, opts \\ [])
def cpo_resolve_fun_name(cpo, opts) do
with {:ok, cpo} <- cpo |> opts_validate,
{:ok, fun_name_key} <- opts |> ctrl_get_key_fun_name,
{:ok, fun_name_key} <- fun_name_key |> validate_key,
{:ok, opts} <- opts |> ctrl_put_validate_fun(&validate_fun_name/1),
{:ok, opts} <- opts |> ctrl_put_key(fun_name_key),
{:ok, _fun_name} = result <- cpo |> cpo_resolve(opts) do
result
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_fun_args(cpo, opts \\ [])
def cpo_resolve_fun_args(cpo, opts) do
with {:ok, cpo_args} <- cpo |> opts_validate,
{:ok, opts_args} <- opts |> opts_validate,
{:ok, fun_args_key} <- opts_args |> ctrl_get_key_fun_args,
{:ok, opts_args} <- opts |> ctrl_put_validate_fun(&resolve_fun_args/1),
{:ok, opts_args} <- opts_args |> ctrl_put_key(fun_args_key),
{:ok, opts_args} <- opts_args |> ctrl_put_default(@plymio_fontais_the_unset_value),
{:ok, fun_args} <- cpo_args |> cpo_resolve(opts_args) do
fun_args
|> is_value_set
|> case do
true ->
{:ok, fun_args}
_ ->
with {:ok, fun_arity} <- cpo |> cpo_resolve_fun_arity(opts),
{:ok, _} = result <- fun_arity |> resolve_fun_args do
result
else
{:error, %{__exception__: true}} = result -> result
end
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_fun_arity(cpo, opts \\ [])
def cpo_resolve_fun_arity(cpo, opts) do
with {:ok, cpo_arity} <- cpo |> opts_validate,
{:ok, opts_arity} <- opts |> opts_validate,
{:ok, fun_arity_key} <- opts_arity |> ctrl_get_key_fun_arity,
{:ok, opts_arity} <- opts |> ctrl_put_validate_fun(&validate_fun_arity/1),
{:ok, opts_arity} <- opts_arity |> ctrl_put_key(fun_arity_key),
{:ok, opts_arity} <- opts_arity |> ctrl_put_default(@plymio_fontais_the_unset_value),
{:ok, fun_arity} <- cpo_arity |> cpo_resolve(opts_arity) do
fun_arity
|> is_value_set
|> case do
true ->
{:ok, fun_arity}
_ ->
with {:ok, fun_args} <- cpo |> cpo_resolve_fun_args(opts) do
{:ok, fun_args |> length}
else
{:error, %{__exception__: true}} = result -> result
end
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_fun_doc(cpo, opts \\ [])
def cpo_resolve_fun_doc(cpo, opts) do
with {:ok, cpo} <- cpo |> opts_validate,
{:ok, fun_doc_key} <- opts |> ctrl_get_key_fun_doc,
{:ok, opts} <- opts |> ctrl_put_key(fun_doc_key),
{:ok, opts} <- opts |> ctrl_put_default(@plymio_fontais_the_unset_value),
{:ok, _fun_doc} = result <- cpo |> cpo_resolve(opts) do
result
else
{:error, %{__exception__: true}} = result -> result
end
end
def opts_resolve_opts_fun_args(opts) do
with {:ok, opts} <- opts |> opts_normalise do
cond do
# if fun_args explicilty give, must be correct!
Keyword.has_key?(opts, @plymio_codi_key_fun_args) ->
with {:ok, fun_args} <- opts |> cpo_resolve_fun_args do
{:ok, fun_args}
else
{:error, %{__exception__: true}} = result -> result
end
true ->
# this will look for arity
with {:ok, fun_args} <- opts |> cpo_resolve_fun_args do
{:ok, [Macro.var(:opts, nil) | fun_args |> Enum.slice(1..-1)]}
else
{:error, %{__exception__: true}} = result -> result
end
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_bang_module(cpo, opts \\ []) do
with {:ok, opts} <- opts |> opts_validate do
opts =
opts ++
[
{@plymio_codi_key_fun_module, @plymio_codi_key_bang_module}
]
cpo
|> cpo_resolve_fun_module(opts)
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_bang_name(cpo, opts \\ []) do
with {:ok, opts} <- opts |> opts_validate do
opts =
opts ++
[
{@plymio_codi_key_fun_name, @plymio_codi_key_bang_name}
]
cpo
|> cpo_resolve_fun_name(opts)
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_bang_args(cpo, opts \\ []) do
with {:ok, opts} <- opts |> opts_validate do
opts =
opts ++
[
{@plymio_codi_key_fun_args, @plymio_codi_key_bang_args},
{@plymio_codi_key_fun_arity, @plymio_codi_key_bang_arity}
]
cpo
|> cpo_resolve_fun_args(opts)
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_bang_doc(cpo, opts \\ []) do
with {:ok, opts} <- opts |> opts_validate do
opts =
opts ++
[
{@plymio_codi_key_fun_doc, @plymio_codi_key_bang_doc}
]
cpo
|> cpo_resolve_fun_doc(opts)
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_delegate_module(cpo, opts \\ []) do
with {:ok, opts} <- opts |> opts_validate do
opts =
opts ++
[
{@plymio_codi_key_fun_module, @plymio_codi_key_delegate_module}
]
cpo
|> cpo_resolve_fun_module(opts)
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_delegate_name(cpo, opts \\ []) do
with {:ok, opts} <- opts |> opts_validate do
opts =
opts ++
[
{@plymio_codi_key_fun_name, @plymio_codi_key_delegate_name}
]
cpo
|> cpo_resolve_fun_name(opts)
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_delegate_args(cpo, opts \\ []) do
with {:ok, opts} <- opts |> opts_validate do
opts =
opts ++
[
{@plymio_codi_key_fun_args, @plymio_codi_key_delegate_args},
{@plymio_codi_key_fun_arity, @plymio_codi_key_delegate_arity}
]
cpo
|> cpo_resolve_fun_args(opts)
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_delegate_doc(cpo, opts \\ []) do
with {:ok, opts} <- opts |> opts_validate do
opts =
opts ++
[
{@plymio_codi_key_fun_doc, @plymio_codi_key_delegate_doc}
]
cpo
|> cpo_resolve_fun_doc(opts)
else
{:error, %{__exception__: true}} = result -> result
end
end
def resolve_fun_args(args) do
args
|> case do
x when is_positive_integer(x) ->
# this will generate a list var1, var2, etc
x
x when is_negative_integer(x) ->
# this will generate a list var1, var2, etc
0 - x
x when is_value_unset_or_nil(x) ->
[]
x ->
x |> List.wrap()
end
|> normalise_vars
end
def resolve_type_args(args) do
args
|> case do
x when is_positive_integer(x) ->
# this will generate a list of vars all called any
0 - x
x when is_negative_integer(x) ->
# this will generate a list of vars all called any
x
x when is_value_unset_or_nil(x) ->
[]
x ->
x |> List.wrap()
end
|> normalise_vars
end
def cpo_resolve_type_name(cpo) do
with {:ok, cpo} <- cpo |> opts_validate do
cpo
|> Keyword.has_key?(@plymio_codi_key_typespec_spec_name)
|> case do
true ->
cpo |> cpo_get_type_name
_ ->
new_error_result(m: "cpo type name missing", v: cpo)
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_type_args(opts, default \\ @plymio_fontais_the_unset_value) do
with {:ok, opts} <- opts |> opts_validate do
cond do
Keyword.has_key?(opts, @plymio_codi_key_typespec_spec_args) ->
with {:ok, type_args} <- opts |> opts_get(@plymio_codi_key_typespec_spec_args),
{:ok, _} = result <- type_args |> resolve_type_args do
result
else
{:error, %{__exception__: true}} = result -> result
end
Keyword.has_key?(opts, @plymio_codi_key_typespec_spec_arity) ->
with {:ok, type_arity} <- opts |> opts_get(@plymio_codi_key_typespec_spec_arity),
{:ok, _type_arity} = result <- type_arity |> resolve_type_args do
result
else
{:error, %{__exception__: true}} = result -> result
end
Keyword.has_key?(opts, @plymio_codi_key_fun_arity) ->
with {:ok, fun_arity} <- opts |> opts_get(@plymio_codi_key_fun_arity) do
fun_arity
|> case do
x when is_positive_integer(x) ->
0 - x
x ->
0 - length(x |> List.wrap())
end
|> resolve_type_args
else
{:error, %{__exception__: true}} = result -> result
end
Keyword.has_key?(opts, @plymio_codi_key_fun_args) ->
with {:ok, fun_args} <- opts |> opts_get(@plymio_codi_key_fun_args),
{:ok, fun_args} <- fun_args |> resolve_fun_args,
{:ok, _type_args} = result <- fun_args |> length |> resolve_type_args do
result
else
{:error, %{__exception__: true}} = result -> result
end
true ->
default
|> resolve_type_args
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_type_result(opts) do
with {:ok, opts} <- opts |> opts_validate,
{:ok, type_result} <- opts |> opts_get(@plymio_codi_key_typespec_spec_result, []) do
type_result
|> case do
# only really useful if is 1 i.e. the result is any
x when is_positive_integer(x) ->
(0 - x) |> normalise_vars
x when is_list(x) ->
with {:ok, result_vars} <- x |> normalise_vars do
# how to build "properly"?
[{:|, [], result_vars}]
|> validate_vars
else
{:error, %{__exception__: true}} = result -> result
end
x when is_atom(x) ->
x |> normalise_type_result
x ->
x |> normalise_vars
end
else
{:error, %{__exception__: true}} = result -> result
end
end
defp option_getset_default_fun_namer(opts) do
with {:ok, opts} <- opts |> opts_validate do
opts
|> Keyword.has_key?(@plymio_codi_key_fun_name)
|> case do
true ->
opts |> opts_get(@plymio_codi_key_fun_name)
_ ->
with {:ok, pattern_name} <- opts |> opts_fetch(@plymio_codi_key_pattern),
{:ok, fun_key} <- opts |> opts_fetch(@plymio_codi_key_fun_key) do
{:ok, "#{to_string(pattern_name)}_#{to_string(fun_key)}" |> String.to_atom()}
else
{:error, %{__exception__: true}} = result -> result
end
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def cpo_resolve_fun_namer(opts) do
with {:ok, opts} <- opts |> opts_validate do
opts
|> Keyword.get(@plymio_codi_key_fun_namer, &option_getset_default_fun_namer/1)
|> case do
fun_namer when is_function(fun_namer, 1) ->
namer = fn opts ->
opts
|> fun_namer.()
|> case do
{:error, %{__exception__: true}} = result -> result
{:ok, _} = result -> result
fun_name -> {:ok, fun_name}
end
|> case do
{:error, %{__exception__: true}} = result ->
result
{:ok, fun_name} ->
fun_name
|> case do
x when is_atom(x) ->
opts |> opts_put(@plymio_codi_key_fun_name, x)
x when is_binary(x) ->
opts |> opts_put(@plymio_codi_key_fun_name, x |> String.to_atom())
x ->
new_error_result(m: "function namer result invalid", v: x)
end
end
end
{:ok, namer}
x ->
new_error_result(m: "function namer function invalid", v: x)
end
else
{:error, %{__exception__: true}} = result -> result
end
end
def opts_resolve_proxy_names(opts) do
with {:ok, opts} <- opts |> opts_validate do
opts
|> Keyword.fetch(@plymio_codi_key_proxy_name)
|> case do
{:ok, proxy_name} ->
proxy_name |> normalise_proxy_names
:error ->
new_error_result(m: "opts function proxy name missing", v: opts)
end
else
{:error, %{__exception__: true}} = result -> result
end
end
defp normalise_proxy_names(names) do
names
|> List.wrap()
|> validate_proxy_names
end
defp validate_proxy_names(names)
defp validate_proxy_names([]) do
{:ok, []}
end
defp validate_proxy_names(names) when is_list(names) do
names
|> Enum.split_with(&is_atom/1)
|> case do
{names, []} ->
{:ok, names}
{_, invalid_names} ->
new_error_result(m: "proxy names invalid", v: invalid_names)
end
end
defp resolve_function_sig(m, f, a)
defp resolve_function_sig(m, f, a)
when m in [
Plymio.Fontais.Option
] and a == 2 and
f in [
:opts_get,
:opts_fetch
] do
[opts: 0, key: 1]
end
defp resolve_function_sig(m, f, a)
when m in [
Plymio.Fontais.Option
] and f == :opts_get and a == 3 do
[opts: 0, key: 1, default: 2]
end
defp resolve_function_sig(_m, _f, a) do
a
|> Macro.generate_arguments(nil)
|> Enum.with_index()
end
def opts_create_fun_sig(opts) do
with {:ok, opts} <- opts |> opts_normalise,
{:ok, pattern} <- opts |> opts_fetch(@plymio_codi_key_pattern),
{:ok, fun_module} <- opts |> cpo_resolve_fun_module,
{:ok, fun_arity} <- opts |> opts_fetch(@plymio_codi_key_fun_arity) do
fun_sig_base =
0..(fun_arity - 1)
|> Enum.map(fn index -> {"var#{to_string(index)}" |> String.to_atom(), index} end)
fun_sig = resolve_function_sig(fun_module, pattern, fun_arity)
fun_sig =
(fun_sig_base ++ fun_sig)
|> Enum.group_by(fn {_k, v} -> v end)
|> Enum.map(fn {_v, kvs} -> kvs |> List.last() end)
|> Enum.into(%{})
opts |> opts_put(@plymio_codi_key_sig, fun_sig)
else
{:error, %{__exception__: true}} = result -> result
end
end
def validate_module_dict(module_dict)
def validate_module_dict(dict) when is_map(dict) do
with {:ok, _} <- dict |> Map.keys() |> validate_fun_modules,
{:ok, _} <- dict |> Map.values() |> opzioni_validate do
{:ok, dict}
else
{:error, %{__exception__: true} = error} ->
new_error_result(m: "module dictionary invalid", v: error)
end
end
def validate_module_dict(dict) do
new_error_result(m: "module dictionary invalid", v: dict)
end
# validate since is a semver spec
def validate_since(since)
def validate_since(since) when is_binary(since) do
since
|> Version.parse()
|> case do
{:ok, _} ->
{:ok, since}
_ ->
new_error_result(m: "since invalid", v: since)
end
end
def validate_since(since) do
new_error_result(m: "since invalid", v: since)
end
end