Current section

Files

Jump to
hecate_sdk src hecate_plugin_codegen_integration.erl
Raw

src/hecate_plugin_codegen_integration.erl

%%% @doc Integration templates for code generation.
%%%
%%% Templates for: emitter, listener, process manager, requester, responder.
%%% Each includes a worker supervisor.
%%% @end
-module(hecate_plugin_codegen_integration).
-export([generate/1]).
-import(hecate_plugin_codegen, [fmt/2, s/1, write_files/1]).
%% ===================================================================
%% generate/1 -- Dispatch by integration type
%% ===================================================================
-spec generate(map()) -> {ok, [string()]}.
generate(#{type := emitter} = Opts) -> emitter(Opts);
generate(#{type := listener} = Opts) -> listener(Opts);
generate(#{type := process_manager} = Opts) -> process_manager(Opts);
generate(#{type := requester} = Opts) -> requester(Opts);
generate(#{type := responder} = Opts) -> responder(Opts).
%% ===================================================================
%% Entry Points
%% ===================================================================
emitter(#{event := Event, target := Target} = Opts) ->
OutDir = maps:get(output_dir, Opts, "src"),
TargetStr = atom_to_list(Target),
ModName = s(Event) ++ "_v1_to_" ++ TargetStr,
DeskDir = filename:join(OutDir, ModName),
Files = [
{filename:join(DeskDir, ModName ++ ".erl"),
tpl_emitter(ModName, Event, Target)}
],
write_files(Files).
listener(#{fact := Fact} = Opts) ->
OutDir = maps:get(output_dir, Opts, "src"),
ModName = s(Fact) ++ "_listener",
DeskDir = filename:join(OutDir, ModName),
Files = [
{filename:join(DeskDir, ModName ++ ".erl"),
tpl_listener(ModName, Fact)},
{filename:join(DeskDir, ModName ++ "_sup.erl"),
tpl_worker_sup(ModName)}
],
write_files(Files).
process_manager(#{source_event := SourceEvent, verb := Verb, subject := Subject} = Opts) ->
OutDir = maps:get(output_dir, Opts, "src"),
ModName = "on_" ++ s(SourceEvent) ++ "_" ++ s(Verb) ++ "_" ++ s(Subject),
DeskDir = filename:join(OutDir, ModName),
Files = [
{filename:join(DeskDir, ModName ++ ".erl"),
tpl_process_manager(ModName, SourceEvent, Verb, Subject)},
{filename:join(DeskDir, ModName ++ "_sup.erl"),
tpl_worker_sup(ModName)}
],
write_files(Files).
requester(#{hope := Hope} = Opts) ->
OutDir = maps:get(output_dir, Opts, "src"),
ModName = s(Hope) ++ "_requester",
DeskDir = filename:join(OutDir, ModName),
Files = [
{filename:join(DeskDir, ModName ++ ".erl"),
tpl_requester(ModName, Hope)},
{filename:join(DeskDir, ModName ++ "_sup.erl"),
tpl_worker_sup(ModName)}
],
write_files(Files).
responder(#{hope := Hope} = Opts) ->
OutDir = maps:get(output_dir, Opts, "src"),
ModName = s(Hope) ++ "_responder",
DeskDir = filename:join(OutDir, ModName),
Files = [
{filename:join(DeskDir, ModName ++ ".erl"),
tpl_responder(ModName, Hope)},
{filename:join(DeskDir, ModName ++ "_sup.erl"),
tpl_worker_sup(ModName)}
],
write_files(Files).
%% ===================================================================
%% Templates
%% ===================================================================
tpl_emitter(ModName, Event, Target) ->
TargetMod = case Target of
pg -> "pg";
mesh -> "hecate_mesh"
end,
fmt("%%% @doc Emitter: ~s_v1 -> ~p\n"
"-module(~s).\n"
"-behaviour(evoq_emitter).\n"
"\n"
"-export([start_link/0, init/1, handle_event/3]).\n"
"\n"
"start_link() ->\n"
" evoq_emitter:start_link(?MODULE, []).\n"
"\n"
"init(_Args) ->\n"
" {ok, #{store_id => undefined, events => [<<\"~s_v1\">>]}}.\n"
"\n"
"handle_event(Event, _Metadata, State) ->\n"
" %% TODO: Publish to ~s\n"
" logger:info(\"[~s] Emitting event\"),\n"
" {ok, State}.\n",
[s(Event), Target, ModName, s(Event), TargetMod, ModName]).
tpl_listener(ModName, Fact) ->
fmt("%%% @doc Listener: ~s facts from mesh\n"
"-module(~s).\n"
"-behaviour(evoq_listener).\n"
"\n"
"-export([start_link/0, init/1, handle_fact/3]).\n"
"\n"
"start_link() ->\n"
" evoq_listener:start_link(?MODULE, []).\n"
"\n"
"init(_Args) ->\n"
" {ok, #{topics => [<<\"~s\">>]}}.\n"
"\n"
"handle_fact(Fact, _Metadata, State) ->\n"
" %% TODO: Convert fact to command, dispatch\n"
" logger:info(\"[~s] Received fact: ~~p\", [Fact]),\n"
" {ok, State}.\n",
[s(Fact), ModName, s(Fact), ModName]).
tpl_process_manager(ModName, SourceEvent, Verb, Subject) ->
fmt("%%% @doc Process manager: on ~s -> ~s ~s\n"
"-module(~s).\n"
"-behaviour(evoq_process_manager).\n"
"\n"
"-export([start_link/0, init/1, interested_in/0, handle_event/3]).\n"
"\n"
"start_link() ->\n"
" evoq_process_manager:start_link(?MODULE, []).\n"
"\n"
"init(_Args) ->\n"
" {ok, #{}}.\n"
"\n"
"interested_in() ->\n"
" [<<\"~s_v1\">>].\n"
"\n"
"handle_event(Event, _Metadata, State) ->\n"
" %% TODO: Extract data from source event,\n"
" %% construct and dispatch target command\n"
" logger:info(\"[~s] Handling ~s\", []),\n"
" {ok, State}.\n",
[s(SourceEvent), s(Verb), s(Subject), ModName,
s(SourceEvent), ModName, s(SourceEvent)]).
tpl_requester(ModName, Hope) ->
fmt("%%% @doc Requester: sends ~s hopes via RPC\n"
"-module(~s).\n"
"-behaviour(evoq_requester).\n"
"\n"
"-export([start_link/0, init/1, request/2, handle_feedback/3]).\n"
"\n"
"start_link() ->\n"
" evoq_requester:start_link(?MODULE, []).\n"
"\n"
"init(_Args) ->\n"
" {ok, #{}}.\n"
"\n"
"request(Params, State) ->\n"
" Hope = #{type => <<\"~s\">>, payload => Params},\n"
" {ok, Hope, State}.\n"
"\n"
"handle_feedback(Feedback, _Metadata, State) ->\n"
" logger:info(\"[~s] Got feedback: ~~p\", [Feedback]),\n"
" {ok, State}.\n",
[s(Hope), ModName, s(Hope), ModName]).
tpl_responder(ModName, Hope) ->
fmt("%%% @doc Responder: handles ~s hopes\n"
"-module(~s).\n"
"-behaviour(evoq_responder).\n"
"\n"
"-export([start_link/0, init/1, handle_hope/3]).\n"
"\n"
"start_link() ->\n"
" evoq_responder:start_link(?MODULE, []).\n"
"\n"
"init(_Args) ->\n"
" {ok, #{hopes => [<<\"~s\">>]}}.\n"
"\n"
"handle_hope(Hope, _Metadata, State) ->\n"
" %% TODO: Dispatch command, return feedback\n"
" logger:info(\"[~s] Handling hope: ~~p\", [Hope]),\n"
" {ok, #{status => <<\"ok\">>}, State}.\n",
[s(Hope), ModName, s(Hope), ModName]).
%% Shared worker supervisor template
tpl_worker_sup(WorkerMod) ->
SupMod = WorkerMod ++ "_sup",
fmt("%%% @doc Supervisor for ~s.\n"
"-module(~s).\n"
"-behaviour(supervisor).\n"
"\n"
"-export([start_link/0, init/1]).\n"
"\n"
"start_link() ->\n"
" supervisor:start_link({local, ?MODULE}, ?MODULE, []).\n"
"\n"
"init([]) ->\n"
" Child = #{id => ~s, start => {~s, start_link, []},\n"
" restart => permanent, type => worker},\n"
" {ok, {#{strategy => one_for_one, intensity => 5, period => 60}, [Child]}}.\n",
[WorkerMod, SupMod, WorkerMod, WorkerMod]).