Packages
vix
0.18.0
0.40.0
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.1
0.33.0
0.32.0
0.31.1
0.31.0
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.1
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.1
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.1
0.10.0
0.9.0
0.8.0
0.7.0
0.6.1
0.6.0
0.5.0
0.4.0
0.3.0
0.2.1
0.2.0
retired
0.1.0
NIF based bindings for libvips
Current section
Files
Jump to
Current section
Files
lib/vix/vips/operation_helper.ex
defmodule Vix.Vips.OperationHelper do
@moduledoc false
alias Vix.GObject.GParamSpec
alias Vix.Nif
alias Vix.Type
def input_to_nif_terms(args, in_pspec) do
args
|> Enum.reduce([], fn {name, value}, terms ->
# skip unsupported additional arguments
case Map.fetch(in_pspec, name) do
:error ->
terms
{:ok, pspec} ->
term = Type.to_nif_term(pspec.type, value, pspec.data)
[{name, term} | terms]
end
end)
end
def vips_enum_list do
Nif.nif_vips_enum_list()
end
def vips_flag_list do
Nif.nif_vips_flag_list()
end
def vips_immutable_operation_list do
vips_operation_list()
|> Enum.reject(&mutable_operation?/1)
end
def vips_mutable_operation_list do
vips_operation_list()
|> Enum.filter(&mutable_operation?/1)
end
def vips_operation_list do
Nif.nif_vips_operation_list()
|> Enum.uniq()
|> Enum.reject(&unsupported_operation?/1)
end
def output_to_erl_terms(nif_out_args, required_out_pspec, optional_out_pspec) do
{required, optional} =
nif_out_args
|> Enum.reduce({[], []}, fn {param, value}, {required, optional} ->
cond do
Map.has_key?(required_out_pspec, param) ->
pspec = Map.get(required_out_pspec, param)
value = Type.to_erl_term(pspec.type, value)
{[{pspec.priority, value} | required], optional}
Map.has_key?(optional_out_pspec, param) ->
pspec = Map.get(optional_out_pspec, param)
value = Type.to_erl_term(pspec.type, value)
{required, [{String.to_atom(param), value} | optional]}
true ->
raise Vix.Vips.Operation.Error, message: "Invalid operation output field: #{param}"
end
end)
required =
required
|> Enum.sort_by(fn {priority, _} -> priority end)
|> Enum.map(fn {_, value} -> value end)
case {required, optional} do
{[], []} ->
:ok
# if it is single value then unwrap and return as single term
{[term], []} ->
{:ok, term}
{required, []} ->
{:ok, List.to_tuple(required)}
{required, optional} ->
{:ok, List.to_tuple(required ++ [Map.new(optional)])}
end
end
def prepare_doc(desc, required_in, optional_in, required_out, optional_out) do
"""
#{String.capitalize(to_string(desc))}
## Arguments
#{required_in_doc(required_in)}
#{optional_in_doc(optional_in)}
#{output_values_doc(required_out, optional_out)}
"""
end
def operation_args_spec(name) do
{desc, args} = vips_operation_arguments(name)
args =
Enum.reject(args, fn %{flags: flags} ->
# skip required deprecated arguments, but allow optional deprecated arguments.
# This is similar to ruby-vips.
:vips_argument_required in flags && :vips_argument_deprecated in flags
end)
{input, rest} =
Enum.split_with(args, fn %{flags: flags} ->
:vips_argument_input in flags
end)
{output, _rest} =
Enum.split_with(rest, fn %{flags: flags} ->
:vips_argument_output in flags
end)
{required_input, optional_input} =
Enum.split_with(input, fn %{flags: flags} ->
:vips_argument_required in flags
end)
{required_output, optional_output} =
Enum.split_with(output, fn %{flags: flags} ->
:vips_argument_required in flags
end)
optional_input =
Enum.filter(optional_input, fn pspec ->
Type.supported?(pspec.type)
end)
required_input = Enum.sort_by(required_input, & &1.priority)
required_output = Enum.sort_by(required_output, & &1.priority)
%{
desc: desc,
in_req_spec: required_input,
in_opt_spec: optional_input,
out_req_spec: required_output,
out_opt_spec: optional_output
}
end
def function_name(name), do: to_string(name) |> String.downcase() |> String.to_atom()
def atom_typespec_ast(list) do
Enum.reduce(list, &{:|, [], [&1, &2]})
end
def type_name(name) do
to_string(name)
|> Macro.underscore()
|> String.to_atom()
|> Macro.var(__MODULE__)
end
def output_typespec(required, optional) do
case {required, optional} do
{[], []} ->
quote do
:ok | {:error, term()}
end
{[pspec], []} ->
quote do
{:ok, unquote(typespec(pspec))} | {:error, term()}
end
{pspec_list, []} ->
quote do
{:ok, {unquote_splicing(required_args_typespec(pspec_list))}}
| {:error, term()}
end
{pspec_list, optional} ->
optional_out = optional_args_typespec(optional)
quote do
{:ok,
{unquote_splicing(required_args_typespec(pspec_list)),
%{unquote_splicing(optional_out)}}}
| {:error, term()}
end
end
end
def bang_output_typespec(required, optional) do
case {required, optional} do
{[], []} ->
quote do
:ok | no_return()
end
{[pspec], []} ->
quote do
unquote(typespec(pspec)) | no_return()
end
{pspec_list, []} ->
quote do
{unquote_splicing(required_args_typespec(pspec_list))}
| no_return()
end
{pspec_list, optional} ->
optional_out = optional_args_typespec(optional)
quote do
{unquote_splicing(required_args_typespec(pspec_list)),
%{unquote_splicing(optional_out)}}
| no_return()
end
end
end
def func_typespec(func_name, required_in, optional_in, required_out, optional_out) do
quote do
unquote(func_name)(
unquote_splicing(
if optional_in == [] do
required_args_typespec(required_in)
else
required_args_typespec(required_in) ++ [optional_args_typespec(optional_in)]
end
)
) ::
unquote(output_typespec(required_out, optional_out))
end
end
def bang_func_typespec(func_name, required_in, optional_in, required_out, optional_out) do
quote do
unquote(func_name)(
unquote_splicing(
if optional_in == [] do
required_args_typespec(required_in)
else
required_args_typespec(required_in) ++ [optional_args_typespec(optional_in)]
end
)
) ::
unquote(bang_output_typespec(required_out, optional_out))
end
end
def operation_call(name, args, opts) do
operation_call(name, args, opts, operation_args_spec(name))
end
def operation_call(name, args, opts, %{desc: _} = spec) do
nif_args = cast_arguments_to_nif_terms(args, opts, spec.in_req_spec, spec.in_opt_spec)
case Vix.Nif.nif_vips_operation_call(name, nif_args) do
{:ok, nif_out_args} ->
output_to_erl_terms(
nif_out_args,
Map.new(spec.out_req_spec, &{&1.param_name, &1}),
Map.new(spec.out_opt_spec, &{&1.param_name, &1})
)
{:error, {label, error}} ->
{:error, String.trim("#{label}: #{error}")}
{:error, term} ->
{:error, term}
end
end
def cast_arguments_to_nif_terms(args, _opts, args_spec, _opts_spec)
when length(args) != length(args_spec) do
{:error, "Expected #{length(args_spec)} required arguments, got #{length(args)}"}
end
def cast_arguments_to_nif_terms(args, opts, args_spec, opts_spec) do
args_values =
Enum.zip(args_spec, args)
|> Enum.map(fn {%{param_name: param_name}, value} ->
{param_name, value}
end)
opt_values =
Enum.map(opts, fn {name, value} ->
{Atom.to_string(name), value}
end)
all_args_values = args_values ++ opt_values
all_args_spec = Map.new(args_spec ++ opts_spec, &{&1.param_name, &1})
input_to_nif_terms(all_args_values, all_args_spec)
end
defp mutable_operation?(operation_name) do
{_desc, args} = vips_operation_arguments(operation_name)
Enum.any?(args, fn %{type: type} ->
type == "MutableVipsImage"
end)
end
defp unsupported_operation?(operation_name) do
{_desc, args} = vips_operation_arguments(operation_name)
Enum.any?(args, fn %{value_type: value_type} ->
value_type == "VipsSource" || value_type == "VipsTarget"
end)
end
defp vips_operation_arguments(name) do
{description, args} = Nif.nif_vips_operation_get_arguments(name)
args =
Enum.map(args, fn {name, spec_details, priority, flags} ->
{desc, spec_type, value_type, data} = spec_details
GParamSpec.new(%{
name: name,
desc: desc,
spec_type: spec_type,
value_type: value_type,
data: data,
priority: priority,
flags: flags
})
end)
{description, args}
end
defp default(pspec) do
type = pspec.type
data = pspec.data
if Type.default(type, data) == :unsupported do
""
else
"Default: `#{inspect(Type.default(type, data))}`"
end
end
defp optional_in_doc([]), do: ""
defp optional_in_doc(optional_in) do
optional_args =
Enum.map_join(optional_in, "\n", fn pspec ->
"* #{pspec.param_name} - #{pspec.desc}. #{default(pspec)}"
end)
"""
## Optional
#{optional_args}
"""
end
defp required_in_doc(required_in) do
Enum.map_join(required_in, "\n", fn pspec ->
" * #{pspec.param_name} - #{pspec.desc}"
end)
end
def output_values_doc([], []), do: ""
def output_values_doc([_], []), do: ""
def output_values_doc(required_out, optional_out) do
required_out_values =
Enum.map_join(required_out, "\n", fn pspec ->
"* #{pspec.param_name} - #{pspec.desc}. (`#{Macro.to_string(typespec(pspec))}`)"
end)
"""
## Returns
Operation returns a tuple
#{required_out_values}
#{optional_out_doc(optional_out)}
"""
end
defp optional_out_doc([]), do: ""
defp optional_out_doc(optional_out) do
optional_out_values =
Enum.map_join(optional_out, "\n", fn pspec ->
"* #{pspec.param_name} - #{pspec.desc}. (`#{Macro.to_string(typespec(pspec))}`)"
end)
"""
Last value of the tuple is a map of additional output values as key-value pair.
#{optional_out_values}
"""
end
defp typespec(%GParamSpec{} = pspec) do
case pspec.type do
{:enum, name} ->
type_name(name)
{:flags, name} ->
type_name(name)
type ->
Type.typespec(type)
end
end
defp required_args_typespec(psepc_list) do
Enum.map(psepc_list, &typespec/1)
end
defp optional_args_typespec(psepc_list) do
Enum.map(psepc_list, fn pspec ->
{String.to_atom(pspec.param_name), typespec(pspec)}
end)
end
end