<commit_before>%% -------------------------------------------------------------------
%%
%% Copyright (c) 2017 <NAME>.  All Rights Reserved.
%%
%% This file is provided to you 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.
%%
%% -------------------------------------------------------------------

%% @doc NkLIB Config Server.
-module(nklib_config).
-author('<NAME> <<EMAIL>>').
-behaviour(gen_server).

-export([get/2, get/3, put/3, del/2, increment/3]).
-export([get_domain/3, get_domain/4, put_domain/4, del_domain/3, increment_domain/4]).
-export([load_env/2, load_env/3]).
-export([make_cache/5]).
-export([parse_config/2, parse_config/3]).

-export([start_link/0, init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, 
         handle_info/2]).

-compile({no_auto_import, [get/1, put/2]}).


-type syntax() :: nklib_syntax:syntax().


%% ===================================================================
%% Public
%% ===================================================================


%% @doc Equivalent to `get(Key, undefined)'.
-spec get(term(), term()) -> 
    Value :: term().

get(Mod, Key) ->
    get(Mod, Key, undefined).


%% @doc Gets an config value.
-spec get(term(), term(), term()) -> 
    Value :: term().

get(Mod, Key, Default) -> 
    case ets:lookup(?MODULE, {Mod, none, Key}) of
        [] -> Default;
        [{_, Value}] -> Value
    end.


%% @doc Sets a config value.
-spec put(term(), term(), term()) -> 
    ok.

put(Mod, Key, Val) -> 
    put_domain(Mod, none, Key, Val).


%% @doc Deletes a config value.
-spec del(term(), term()) -> 
    ok.

del(Mod, Key) -> 
    del_domain(Mod, none, Key).


%% @doc Atomically increments or decrements a counter
-spec increment(term(), term(), integer()) ->
    integer().

increment(Mod, Key, Count) ->
    increment_domain(Mod, none, Key, Count).


%% @private
-spec get_domain(term(), nklib:domain(), term()) -> 
    Value :: term().

get_domain(Mod, Domain, Key) ->
    get_domain(Mod, Domain, Key, undefined).


%% @private
-spec get_domain(term(), nklib:domain(), term(), term()) -> 
    Value :: term().

get_domain(Mod, Domain, Key, Default) ->
    case ets:lookup(?MODULE, {Mod, Domain, Key}) of
        [] -> get(Mod, Key, Default);
        [{_, Value}] -> Value
    end.


%% @doc Sets a config value.
-spec put_domain(term(), nklib:domain(), term(), term()) -> 
    ok.

put_domain(Mod, Domain, Key, Val) -> 
    true = ets:insert(?MODULE, {{Mod, Domain, Key}, Val}),
    ok.


%% @doc Deletes a config value.
-spec del_domain(term(), nklib:domain(), term()) -> 
    ok.

del_domain(Mod, Domain, Key) -> 
    true = ets:delete(?MODULE, {Mod, Domain, Key}),
    ok.


%% @doc Atomically increments or decrements a counter
-spec increment_domain(term(), nklib:domain(), term(), integer()) ->
    integer().

increment_domain(Mod, Domain, Key, Count) ->
    ets:update_counter(?MODULE, {Mod, Domain, Key}, Count).


%% @doc Loads parsed application environment
-spec load_env(atom(), syntax()) ->
    {ok, map()} | {error, term()}.

load_env(App, Syntax) ->
    load_env(App, Syntax, #{}).


%% @doc Loads parsed application environment
-spec load_env(atom(), syntax(), map()) ->
    {ok, map()} | {error, term()}.

load_env(App, Syntax, Defaults) ->
    AppEnv = application:get_all_env(App),
    case nklib_syntax:parse(AppEnv, Syntax, #{defaults=>Defaults}) of
        {ok, Opts, _, _} ->
            lists:foreach(fun({K,V}) -> put(App, K, V) end, maps:to_list(Opts)),
            {ok, Opts};
        {error, Error} ->
            {error, Error}
    end.


% %% @doc Loads a domain configuration
% -spec load_domain(term(), nklib:domain(), map(), map(), syntax()) ->
%     ok | {error, term()}.

% load_domain(Mod, Domain, Opts, Defaults, Syntax) ->
%     do_load_domain(Mod, Domain, nklib_util:to_list(Opts), 
%                     nklib_util:to_list(Defaults), Syntax).

% %% @private
% do_load_domain(Mod, Domain, Opts, Defaults, Syntax) ->
%     ValidDomainKeys = proplists:get_keys(Defaults),
%     DomainKeys = proplists:get_keys(Opts),
%     case DomainKeys -- ValidDomainKeys of
%         [] ->
%             ok;
%         Rest ->
%             lager:warning("Ignoring config keys ~p starting domain", [Rest])
%     end,
%     ValidOpts = nklib_util:extract(Opts, ValidDomainKeys),
%     DefaultDomainOpts = [{K, get(Mod, K)} || K <- ValidDomainKeys],
%     Opts2 = nklib_util:defaults(ValidOpts, DefaultDomainOpts),
%     case parse_config(Opts2, Syntax) of
%         {ok, Opts3, _} ->
%             lists:foreach(fun({K,V}) -> put_domain(Mod, Domain, K, V) end, Opts3),
%             ok;
%         {error, Error} ->
%             {error, Error}
%     end.


%% Generates on the fly a 'cache' module for the indicated keys
-spec make_cache([atom()], module(), nklib:domain(), module(), string()|binary()|none) ->
    ok.

make_cache(KeyList, Mod, Domain, Module, Path) ->
    Syntax = lists:foldl(
        fun(Key, Acc) ->
            Val = get_domain(Mod, Domain, Key),
            [nklib_code:getter(Key, Val)|Acc] 
        end,
        [],
        KeyList),
    {ok, Tree} = nklib_code:compile(Module, Syntax),
    case Path of
        none -> ok;
        _ -> ok = nklib_code:write(Module, Tree, Path)
    end.


%% @doc Temporary old version
parse_config(Terms, Spec) ->
    parse_config(Terms, Spec, #{}).


%% @doc
parse_config(Terms, Syntax, Opts) -><commit_msg>Added hot_compiled to compiled modules<commit_after>%% -------------------------------------------------------------------
%%
%% Copyright (c) 2017 <NAME>.  All Rights Reserved.
%%
%% This file is provided to you 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.
%%
%% -------------------------------------------------------------------

%% @doc NkLIB Config Server.
-module(nklib_config).
-author('<NAME> <<EMAIL>>').
-behaviour(gen_server).

-export([get/2, get/3, put/3, del/2, increment/3]).
-export([get_domain/3, get_domain/4, put_domain/4, del_domain/3, increment_domain/4]).
-export([load_env/2, load_env/3]).
-export([make_cache/5]).
-export([parse_config/2, parse_config/3]).

-export([start_link/0, init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, 
         handle_info/2]).

-compile({no_auto_import, [get/1, put/2]}).


-type syntax() :: nklib_syntax:syntax().


%% ===================================================================
%% Public
%% ===================================================================


%% @doc Equivalent to `get(Key, undefined)'.
-spec get(term(), term()) -> 
    Value :: term().

get(Mod, Key) ->
    get(Mod, Key, undefined).


%% @doc Gets an config value.
-spec get(term(), term(), term()) -> 
    Value :: term().

get(Mod, Key, Default) -> 
    case ets:lookup(?MODULE, {Mod, none, Key}) of
        [] -> Default;
        [{_, Value}] -> Value
    end.


%% @doc Sets a config value.
-spec put(term(), term(), term()) -> 
    ok.

put(Mod, Key, Val) -> 
    put_domain(Mod, none, Key, Val).


%% @doc Deletes a config value.
-spec del(term(), term()) -> 
    ok.

del(Mod, Key) -> 
    del_domain(Mod, none, Key).


%% @doc Atomically increments or decrements a counter
-spec increment(term(), term(), integer()) ->
    integer().

increment(Mod, Key, Count) ->
    increment_domain(Mod, none, Key, Count).


%% @private
-spec get_domain(term(), nklib:domain(), term()) -> 
    Value :: term().

get_domain(Mod, Domain, Key) ->
    get_domain(Mod, Domain, Key, undefined).


%% @private
-spec get_domain(term(), nklib:domain(), term(), term()) -> 
    Value :: term().

get_domain(Mod, Domain, Key, Default) ->
    case ets:lookup(?MODULE, {Mod, Domain, Key}) of
        [] -> get(Mod, Key, Default);
        [{_, Value}] -> Value
    end.


%% @doc Sets a config value.
-spec put_domain(term(), nklib:domain(), term(), term()) -> 
    ok.

put_domain(Mod, Domain, Key, Val) -> 
    true = ets:insert(?MODULE, {{Mod, Domain, Key}, Val}),
    ok.


%% @doc Deletes a config value.
-spec del_domain(term(), nklib:domain(), term()) -> 
    ok.

del_domain(Mod, Domain, Key) -> 
    true = ets:delete(?MODULE, {Mod, Domain, Key}),
    ok.


%% @doc Atomically increments or decrements a counter
-spec increment_domain(term(), nklib:domain(), term(), integer()) ->
    integer().

increment_domain(Mod, Domain, Key, Count) ->
    ets:update_counter(?MODULE, {Mod, Domain, Key}, Count).


%% @doc Loads parsed application environment
-spec load_env(atom(), syntax()) ->
    {ok, map()} | {error, term()}.

load_env(App, Syntax) ->
    load_env(App, Syntax, #{}).


%% @doc Loads parsed application environment
-spec load_env(atom(), syntax(), map()) ->
    {ok, map()} | {error, term()}.

load_env(App, Syntax, Defaults) ->
    AppEnv = application:get_all_env(App),
    case nklib_syntax:parse(AppEnv, Syntax, #{defaults=>Defaults}) of
        {ok, Opts, _, _} ->
            lists:foreach(fun({K,V}) -> put(App, K, V) end, maps:to_list(Opts)),
            {ok, Opts};
        {error, Error} ->
            {error, Error}
    end.


% %% @doc Loads a domain configuration
% -spec load_domain(term(), nklib:domain(), map(), map(), syntax()) ->
%     ok | {error, term()}.

% load_domain(Mod, Domain, Opts, Defaults, Syntax) ->
%     do_load_domain(Mod, Domain, nklib_util:to_list(Opts), 
%                     nklib_util:to_list(Defaults), Syntax).

% %% @private
% do_load_domain(Mod, Domain, Opts, Defaults, Syntax) ->
%     ValidDomainKeys = proplists:get_keys(Defaults),
%     DomainKeys = proplists:get_keys(Opts),
%     case DomainKeys -- ValidDomainKeys of
%         [] ->
%             ok;
%         Rest ->
%             lager:warning("Ignoring config keys ~p starting domain", [Rest])
%     end,
%     ValidOpts = nklib_util:extract(Opts, ValidDomainKeys),
%     DefaultDomainOpts = [{K, get(Mod, K)} || K <- ValidDomainKeys],
%     Opts2 = nklib_util:defaults(ValidOpts, DefaultDomainOpts),
%     case parse_config(Opts2, Syntax) of
%         {ok, Opts3, _} ->
%             lists:foreach(fun({K,V}) -> put_domain(Mod, Domain, K, V) end, Opts3),
%             ok;
%         {error, Error} ->
%             {error, Error}
%     end.


%% Generates on the fly a 'cache' module for the indicated keys
-spec make_cache([atom()], module(), nklib:domain(), module(), string()|binary()|none) ->
    ok.

make_cache(KeyList, Mod, Domain, Module, Path) ->
    Syntax1 = lists:foldl(
        fun(Key, Acc) ->
            Val = get_domain(Mod, Domain, Key),
            [nklib_code:getter(Key, Val)|Acc]
        end,
        [],
        KeyList),
    Syntax2 = [nklib_code:getter(hot_compiled, true) | Syntax1],
    {ok, Tree} = nklib_code:compile(Module, Syntax2),
    case Path of
        none -> ok;
        _ -> ok = nklib_code:write(Module, Tree, Path)
    end.


%% @doc Temporary old version
parse_config(Terms, Spec) ->
    parse_config(Terms, Spec, #{}).


%% @doc
parse_config(Terms, Syntax, Opts) ->
{'fraction_non_alphanumeric': 0.21420737366067663, 'fraction_numerical': 0.00641187884923648, 'mean_word_length': 2.9434464404524285, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 105, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '879098', 'n_tokens_mistral': 3977, 'n_tokens_neox': 3819, 'n_words': 1032}