-module(glemplate@renderer). -compile([no_auto_import, nowarn_unused_vars]). -export([render/3]). -export_type([render_error/0, stringify_error/0, render_options/0]). -type render_error() :: {assign_not_found, glemplate@ast:var(), gleam@map:map_(binary(), glemplate@assigns:assign_data())} | {assign_not_iterable, glemplate@ast:var(), gleam@map:map_(binary(), glemplate@assigns:assign_data())} | {assign_not_stringifiable, glemplate@ast:var(), gleam@map:map_(binary(), glemplate@assigns:assign_data())} | {assign_field_not_found, glemplate@ast:var(), binary(), gleam@map:map_(binary(), glemplate@assigns:assign_data())} | {assign_not_field_accessible, glemplate@ast:var(), gleam@map:map_(binary(), glemplate@assigns:assign_data())} | {assign_index_out_of_bounds, glemplate@ast:var(), integer(), gleam@map:map_(binary(), glemplate@assigns:assign_data())} | {assign_not_indexable, glemplate@ast:var(), gleam@map:map_(binary(), glemplate@assigns:assign_data())} | {child_template_not_found, binary()}. -type stringify_error() :: stringify_error. -type render_options() :: {render_options, fun((binary()) -> gleam@string_builder:string_builder()), gleam@map:map_(binary(), glemplate@ast:template())}. -spec access_field( glemplate@assigns:assign_data(), binary(), glemplate@ast:var(), gleam@map:map_(binary(), glemplate@assigns:assign_data()) ) -> {ok, glemplate@assigns:assign_data()} | {error, render_error()}. access_field(Container, Field, Accessor, Assigns) -> case Container of {map, Map} -> case gleam@map:get(Map, Field) of {ok, Data} -> {ok, Data}; {error, _} -> {error, {assign_field_not_found, Accessor, Field, Assigns}} end; _ -> {error, {assign_not_field_accessible, Accessor, Assigns}} end. -spec access_index( glemplate@assigns:assign_data(), integer(), glemplate@ast:var(), gleam@map:map_(binary(), glemplate@assigns:assign_data()) ) -> {ok, glemplate@assigns:assign_data()} | {error, render_error()}. access_index(Container, Index, Accessor, Assigns) -> case Container of {list, L} -> case gleam@list:at(L, Index) of {ok, Data} -> {ok, Data}; {error, _} -> {error, {assign_index_out_of_bounds, Accessor, Index, Assigns}} end; _ -> {error, {assign_not_indexable, Accessor, Assigns}} end. -spec stringify(glemplate@assigns:assign_data()) -> {ok, binary()} | {error, stringify_error()}. stringify(Data) -> case Data of {string, S} -> {ok, S}; {int, I} -> {ok, gleam@int:to_string(I)}; _ -> {error, stringify_error} end. -spec render_output( glemplate@assigns:assign_data(), boolean(), glemplate@ast:var(), gleam@map:map_(binary(), glemplate@assigns:assign_data()), gleam@string_builder:string_builder(), render_options() ) -> {ok, gleam@string_builder:string_builder()} | {error, render_error()}. render_output(Data, Raw, Var, Assigns, Acc, Options) -> Str_result = stringify(Data), case Str_result of {ok, Str} -> Out = case Raw of true -> gleam@string_builder:append(Acc, Str); false -> gleam@string_builder:append_builder( Acc, (erlang:element(2, Options))(Str) ) end, {ok, Out}; {error, _} -> {error, {assign_not_stringifiable, Var, Assigns}} end. -spec get_assign( gleam@map:map_(binary(), glemplate@assigns:assign_data()), glemplate@ast:var() ) -> {ok, glemplate@assigns:assign_data()} | {error, render_error()}. get_assign(Assigns, Var) -> gleam@result:then(case Var of {assign, Name} -> Get_result = gleam@map:get(Assigns, Name), case Get_result of {ok, Data} -> {ok, Data}; {error, _} -> {error, {assign_not_found, Var, Assigns}} end; {field_access, Container, Field} -> gleam@result:then( get_assign(Assigns, Container), fun(Data@1) -> access_field(Data@1, Field, Var, Assigns) end ); {index_access, Container@1, Index} -> gleam@result:then( get_assign(Assigns, Container@1), fun(Data@2) -> access_index(Data@2, Index, Var, Assigns) end ) end, fun(Data@3) -> {ok, resolve_lazy(Data@3)} end). -spec resolve_lazy(glemplate@assigns:assign_data()) -> glemplate@assigns:assign_data(). resolve_lazy(Data) -> case Data of {lazy, Lazy_fn} -> resolve_lazy(Lazy_fn()); Other -> Other end. -spec render_node( glemplate@ast:node_(), gleam@map:map_(binary(), glemplate@assigns:assign_data()), gleam@string_builder:string_builder(), render_options() ) -> {ok, gleam@string_builder:string_builder()} | {error, render_error()}. render_node(Node, Assigns, Acc, Options) -> case Node of {text, Str} -> {ok, gleam@string_builder:append(Acc, Str)}; {dynamic, Dynamic} -> render_dynamic(Dynamic, Assigns, Acc, Options); {nodes, Nodes} -> render_nodes(Nodes, Assigns, Acc, Options) end. -spec render_nodes( list(glemplate@ast:node_()), gleam@map:map_(binary(), glemplate@assigns:assign_data()), gleam@string_builder:string_builder(), render_options() ) -> {ok, gleam@string_builder:string_builder()} | {error, render_error()}. render_nodes(Nodes, Assigns, Acc, Options) -> glemplate@result:fold( Nodes, Acc, fun(Acc@1, Node) -> render_node(Node, Assigns, Acc@1, Options) end ). -spec render( glemplate@ast:template(), gleam@map:map_(binary(), glemplate@assigns:assign_data()), render_options() ) -> {ok, gleam@string_builder:string_builder()} | {error, render_error()}. render(Template, Assigns, Opts) -> render_nodes( erlang:element(3, Template), Assigns, gleam@string_builder:new(), Opts ). -spec render_if( glemplate@assigns:assign_data(), list(glemplate@ast:node_()), list(glemplate@ast:node_()), gleam@map:map_(binary(), glemplate@assigns:assign_data()), gleam@string_builder:string_builder(), render_options() ) -> {ok, gleam@string_builder:string_builder()} | {error, render_error()}. render_if(Data, If_true, If_false, Assigns, Acc, Options) -> case Data of {bool, false} -> render_nodes(If_false, Assigns, Acc, Options); _ -> render_nodes(If_true, Assigns, Acc, Options) end. -spec render_iter( glemplate@assigns:assign_data(), glemplate@ast:var(), binary(), list(glemplate@ast:node_()), gleam@map:map_(binary(), glemplate@assigns:assign_data()), gleam@string_builder:string_builder(), render_options() ) -> {ok, gleam@string_builder:string_builder()} | {error, render_error()}. render_iter(Data, Over, Binding, Nodes, Assigns, Acc, Options) -> case Data of {list, Items} -> glemplate@result:fold( Items, Acc, fun(Output, Item) -> New_assigns = gleam@map:insert(Assigns, Binding, Item), render_nodes(Nodes, New_assigns, Output, Options) end ); _ -> {error, {assign_not_iterable, Over, Assigns}} end. -spec render_child( binary(), gleam@map:map_(binary(), glemplate@assigns:assign_data()), list({glemplate@ast:var(), binary()}), gleam@string_builder:string_builder(), render_options() ) -> {ok, gleam@string_builder:string_builder()} | {error, render_error()}. render_child(Template_name, Assigns, Assigns_map, Acc, Options) -> gleam@result:then( begin _pipe = gleam@map:get(erlang:element(3, Options), Template_name), gleam@result:replace_error( _pipe, {child_template_not_found, Template_name} ) end, fun(Template) -> gleam@result:then( glemplate@result:fold( Assigns_map, gleam@map:new(), fun(Acc@1, Mapping) -> {From, To} = Mapping, gleam@result:then( get_assign(Assigns, From), fun(Assign) -> {ok, gleam@map:insert(Acc@1, To, Assign)} end ) end ), fun(New_assigns) -> render_nodes( erlang:element(3, Template), New_assigns, Acc, Options ) end ) end ). -spec render_dynamic( glemplate@ast:dynamic_node(), gleam@map:map_(binary(), glemplate@assigns:assign_data()), gleam@string_builder:string_builder(), render_options() ) -> {ok, gleam@string_builder:string_builder()} | {error, render_error()}. render_dynamic(Node, Assigns, Acc, Options) -> case Node of {output, Var} -> _pipe = get_assign(Assigns, Var), gleam@result:then( _pipe, fun(Data) -> render_output(Data, false, Var, Assigns, Acc, Options) end ); {raw_output, Var@1} -> _pipe@1 = get_assign(Assigns, Var@1), gleam@result:then( _pipe@1, fun(Data@1) -> render_output(Data@1, true, Var@1, Assigns, Acc, Options) end ); {'if', Var@2, If_true, If_false} -> _pipe@2 = get_assign(Assigns, Var@2), gleam@result:then( _pipe@2, fun(Data@2) -> render_if(Data@2, If_true, If_false, Assigns, Acc, Options) end ); {iter, Over, Binding, Nodes} -> _pipe@3 = get_assign(Assigns, Over), gleam@result:then( _pipe@3, fun(Data@3) -> render_iter( Data@3, Over, Binding, Nodes, Assigns, Acc, Options ) end ); {render, Tpl, Assigns_map} -> render_child(Tpl, Assigns, Assigns_map, Acc, Options) end.