blob: 16a5986eb14919c1ecf2f918708648d08228575a [file] [log] [blame]
% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.
-module(couch_epi_data_gen).
%% @doc
%% We generate and compile module with name constructed as:
%% "couch_epi_data_" + Service + "_" + Key
%% To get an idea about he code of the generated module see preamble()
-export([get_handle/1]).
-export([get/1, get/2, get/3]).
-export([generate/2]).
-export([by_key/1, by_key/2]).
-export([by_source/1, by_source/2]).
-export([keys/1, subscribers/1]).
-export([get_current_definitions/1]).
get(Handle) ->
Handle:all().
get(Handle, Key) ->
Handle:all(Key).
get(Handle, Source, Key) ->
Handle:get(Source, Key).
by_key(Handle) ->
Handle:by_key().
by_key(Handle, Key) ->
Handle:by_key(Key).
by_source(Handle) ->
Handle:by_source().
by_source(Handle, Source) ->
Handle:by_source(Source).
keys(Handle) ->
Handle:keys().
subscribers(Handle) ->
Handle:subscribers().
get_handle({Service, Key}) ->
module_name({atom_to_list(Service), atom_to_list(Key)}).
%% ------------------------------------------------------------------
%% Codegeneration routines
%% ------------------------------------------------------------------
preamble() ->
"
-export([by_key/0, by_key/1]).
-export([by_source/0, by_source/1]).
-export([all/0, all/1, get/2]).
-export([version/0, version/1]).
-export([keys/0, subscribers/0]).
-compile({no_auto_import,[get/0, get/1]}).
all() ->
lists:foldl(fun({Key, Defs}, Acc) ->
[D || {_Subscriber, D} <- Defs ] ++ Acc
end, [], by_key()).
all(Key) ->
lists:foldl(fun({Subscriber, Data}, Acc) ->
[Data | Acc]
end, [], by_key(Key)).
by_key() ->
[{Key, by_key(Key)} || Key <- keys()].
by_key(Key) ->
lists:foldl(
fun(Source, Acc) -> append_if_defined(Source, get(Source, Key), Acc)
end, [], subscribers()).
by_source() ->
[{Source, by_source(Source)} || Source <- subscribers()].
by_source(Source) ->
lists:foldl(
fun(Key, Acc) -> append_if_defined(Key, get(Source, Key), Acc)
end, [], keys()).
version() ->
[{Subscriber, version(Subscriber)} || Subscriber <- subscribers()].
%% Helper functions
append_if_defined(Type, undefined, Acc) -> Acc;
append_if_defined(Type, Value, Acc) -> [{Type, Value} | Acc].
"
%% In addition to preamble we also generate following methods
%% get(Source1, Key1) -> Data;
%% get(Source, Key) -> undefined.
%% version(Source1) -> "HASH";
%% version(Source) -> {error, {unknown, Source}}.
%% keys() -> [].
%% subscribers() -> [].
.
generate(Handle, Defs) ->
GetFunForms = couch_epi_codegen:function(getters(Defs)),
VersionFunForms = couch_epi_codegen:function(version_method(Defs)),
KeysForms = keys_method(Defs),
SubscribersForms = subscribers_method(Defs),
Forms = couch_epi_codegen:scan(preamble())
++ GetFunForms ++ VersionFunForms
++ KeysForms ++ SubscribersForms,
couch_epi_codegen:generate(Handle, Forms).
keys_method(Defs) ->
Keys = couch_epi_codegen:format_term(defined_keys(Defs)),
couch_epi_codegen:scan("keys() -> " ++ Keys ++ ".").
subscribers_method(Defs) ->
Subscribers = couch_epi_codegen:format_term(defined_subscribers(Defs)),
couch_epi_codegen:scan("subscribers() -> " ++ Subscribers ++ ".").
getters(Defs) ->
DefaultClause = "get(_S, _K) -> undefined.",
fold_defs(Defs, [couch_epi_codegen:scan(DefaultClause)],
fun({Source, Key, Data}, Acc) ->
getter(Source, Key, Data) ++ Acc
end).
version_method(Defs) ->
DefaultClause = "version(S) -> {error, {unknown, S}}.",
lists:foldl(fun({Source, Data}, Clauses) ->
version(Source, Data) ++ Clauses
end, [couch_epi_codegen:scan(DefaultClause)], Defs).
getter(Source, Key, Data) ->
D = couch_epi_codegen:format_term(Data),
Src = atom_to_list(Source),
K = couch_epi_codegen:format_term(Key),
couch_epi_codegen:scan(
"get(" ++ Src ++ ", " ++ K ++ ") ->" ++ D ++ ";").
version(Source, Data) ->
Src = atom_to_list(Source),
VSN = couch_epi_util:hash(Data),
couch_epi_codegen:scan("version(" ++ Src ++ ") ->" ++ VSN ++ ";").
%% ------------------------------------------------------------------
%% Helper functions
%% ------------------------------------------------------------------
module_name({Service, Key}) when is_list(Service) andalso is_list(Key) ->
list_to_atom(string:join([atom_to_list(?MODULE), Service, Key], "_")).
get_current_definitions(Handle) ->
if_exists(Handle, by_source, 0, [], fun() ->
Handle:by_source()
end).
if_exists(Handle, Func, Arity, Default, Fun) ->
case erlang:function_exported(Handle, Func, Arity) of
true -> Fun();
false -> Default
end.
defined_keys(Defs) ->
Keys = fold_defs(Defs, [], fun({_Source, Key, _Data}, Acc) ->
[Key | Acc]
end),
lists:usort(Keys).
defined_subscribers(Defs) ->
[Source || {Source, _} <- Defs].
fold_defs(Defs, Acc, Fun) ->
lists:foldl(fun({Source, SourceData}, Clauses) ->
lists:foldl(fun({Key, Data}, InAcc) ->
Fun({Source, Key, Data}, InAcc)
end, [], SourceData) ++ Clauses
end, Acc, Defs).
%% ------------------------------------------------------------------
%% Tests
%% ------------------------------------------------------------------
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
basic_test() ->
Module = foo_bar_baz_bugz,
Data1 = [some_nice_data],
Data2 = "other data",
Data3 = {"even more data"},
Defs1 = [{foo, Data1}],
Defs2 = lists:usort([{foo, Data2}, {bar, Data3}]),
Defs = [{app1, Defs1}, {app2, Defs2}],
generate(Module, Defs),
?assertEqual([bar, foo], lists:usort(Module:keys())),
?assertEqual([app1, app2], lists:usort(Module:subscribers())),
?assertEqual(Data1, Module:get(app1, foo)),
?assertEqual(Data2, Module:get(app2, foo)),
?assertEqual(Data3, Module:get(app2, bar)),
?assertEqual(undefined, Module:get(bad, key)),
?assertEqual(undefined, Module:get(source, bad)),
?assertEqual("3KZ4EG4WBF4J683W8GSDDPYR3", Module:version(app1)),
?assertEqual("4EFUU47W9XDNMV9RMZSSJQU3Y", Module:version(app2)),
?assertEqual({error,{unknown,bad}}, Module:version(bad)),
?assertEqual(
[{app1,"3KZ4EG4WBF4J683W8GSDDPYR3"},
{app2,"4EFUU47W9XDNMV9RMZSSJQU3Y"}], lists:usort(Module:version())),
?assertEqual(
[{app1,[some_nice_data]},{app2,"other data"}],
lists:usort(Module:by_key(foo))),
?assertEqual([], lists:usort(Module:by_key(bad))),
?assertEqual(
[
{bar, [{app2, {"even more data"}}]},
{foo, [{app2, "other data"}, {app1, [some_nice_data]}]}
],
lists:usort(Module:by_key())),
?assertEqual(Defs1, lists:usort(Module:by_source(app1))),
?assertEqual(Defs2, lists:usort(Module:by_source(app2))),
?assertEqual([], lists:usort(Module:by_source(bad))),
?assertEqual(
[
{app1, [{foo, [some_nice_data]}]},
{app2, [{foo, "other data"}, {bar, {"even more data"}}]}
],
lists:usort(Module:by_source())),
?assertEqual(
lists:usort([Data1, Data2, Data3]), lists:usort(Module:all())),
?assertEqual(lists:usort([Data1, Data2]), lists:usort(Module:all(foo))),
?assertEqual([], lists:usort(Module:all(bad))),
ok.
-endif.