Skip to content
This repository was archived by the owner on Nov 2, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions components/rvi_common/src/rvi_parse_ini.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
%% -*- erlang-indent-level: 4; indent-tabs-mode: nil -*-
%% Copyright (C) 2014, Jaguar Land Rover
%%
%% This program is licensed under the terms and conditions of the
% Mozilla Public License, version 2.0. The full text of the
%% Mozilla Public License is at https://www.mozilla.org/MPL/2.0/
-module(rvi_parse_ini).

-export([file/1]).


file(F) ->
case file:read_file(F) of
{ok, B} ->
parse(B, F);
{error, _} = Error ->
Error
end.

parse(B, F) ->
Lines = [L || L <- re:split(B, "\\n", [{return,binary}, notempty]),
L =/= <<>>], %% may still contain a trailing <<>>
compact(group(Lines)).

compact(Gs) ->
lists:foldl(
fun({G,Vs}, Acc) ->
case orddict:find(G, Acc) of
{ok, Vs0} -> orddict:store(G, Vs0 ++ Vs, Acc);
error -> orddict:store(G, Vs, Acc)
end
end, orddict:new(), Gs).

group([H|T] = Ls) ->
case heading(H) of
false ->
{G, T1} = collect(Ls, <<>>),
[G | group(T1)];
Head when is_binary(Head) ->
{G, T1} = collect(T, Head),
[G | group(T1)]
end;
group([]) ->
[].

heading(L) when is_binary(L) ->
case re:run(L, "\\[\\h*([^\\h]+)\\h*\\]", [{capture,[1],binary}]) of
{match, [H]} ->
H;
nomatch ->
false
end.

collect(Ls, G) ->
collect(Ls, G, []).

collect([H|T] = Ls, G, Acc) ->
case var(H) of
{K, V} -> collect(T, G, [{K,V}|Acc]);
false -> {{G, lists:reverse(Acc)}, Ls}
end;
collect([], G, Acc) ->
{{G, lists:reverse(Acc)}, []}.

var(L) ->
case re:run(L, "\\h*([^\\h]+)\\h*=\\h*(.*)", [{capture,[1,2],binary}]) of
{match, [K, V]} ->
{K, cmd(V)};
_ ->
false
end.

cmd(V) ->
Env = env(),
case os:cmd(binary_to_list(<<Env/binary, "echo ", V/binary>>)) of
"/bin/sh:" ++ _ ->
V;
Res ->
re:replace(Res, "^(.*)\\n$", "\\1", [{return, binary}])
end.

env() ->
case code:priv_dir(rvi_core) of
{error, _} ->
<<>>;
Priv ->
iolist_to_binary(["PRIV=", Priv, "; "])
end.
157 changes: 157 additions & 0 deletions priv/config/load_ini.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
%% -*- erlang-indent-level: 4; indent-tabs-mode: nil -*-
%% Copyright (C) 2016, Jaguar Land Rover
%%
%% This program is licensed under the terms and conditions of the
%% Mozilla Public License, version 2.0. The full text of the
%% Mozilla Public License is at https://www.mozilla.org/MPL/2.0/

%% Usage: From a setup .config file:
%% {ok, Instructions} = file:script("$PRIV/config/load_ini.config",
%% [{'FILE', YourINIfile}])
%% Then, either append or prepend Instructions to the other config
%% instructions.
%%

%% ==================================================
%% Mapping
%% ==================================================

Str = fun(B) -> binary_to_list(B) end.

ConfigMap =
[{<<"system">>, rvi_core,
[
{<<"device_key" >> , {device_key , Str}},
{<<"root_cert" >> , {root_cert , Str}},
{<<"device_cert">> , {device_cert , Str}},
{<<"cred_dir" >> , {cred_dir , Str}},
{<<"node_address">> , {node_address , Str}},
{<<"node_service_prefix">>, {node_service_prefix, Str}},
{<<"node_id">> , {node_id , Str}}
]}
].

%% This fun converts <Grp, K, V> to a 'set_env' list, or raise an error
%%
ApplyMap =
fun(Grp, K, V) ->
case lists:keyfind(Grp, 1, ConfigMap) of
{_, App, VarMap} = Found ->
case lists:keyfind(K, 1, VarMap) of
{_, {EVar, F}} ->
[{App, [{EVar, F(V)}]}];
false ->
error({not_found, {Grp, K, V}})
end;
false ->
error({not_found, {Grp, K, V}})
end
end.

%% ==================================================
%% Windows INI format parser
%% ==================================================

Compact =
fun(Gs) ->
lists:foldl(
fun({G,Vs}, Acc) ->
case orddict:find(G, Acc) of
{ok, Vs0} -> orddict:store(G, Vs0 ++ Vs, Acc);
error -> orddict:store(G, Vs, Acc)
end
end, orddict:new(), Gs)
end.

Heading =
fun(L) when is_binary(L) ->
case re:run(L, "\\[\\h*([^\\h]+)\\h*\\]", [{capture,[1],binary}]) of
{match, [H]} ->
H;
nomatch ->
false
end
end.

Cmd =
fun(V) ->
case os:cmd(binary_to_list(<<"echo ", V/binary>>)) of
"/bin/sh:" ++ _ ->
V;
Res ->
re:replace(Res, "^(.*)\\n$", "\\1", [{return, binary}])
end
end.

Var =
fun(L) ->
case re:run(L, "\\h*([^\\h]+)\\h*=\\h*(.*)",
[{capture,[1,2],binary}]) of
{match, [K, V]} ->
{K, Cmd(V)};
_ ->
false
end
end.

Collect =
fun C([H|T] = Ls, G, Acc) ->
case Var(H) of
{K, V} -> C(T, G, [{K,V}|Acc]);
false -> {{G, lists:reverse(Acc)}, Ls}
end;
C([], G, Acc) ->
{{G, lists:reverse(Acc)}, []}
end.

Group =
fun Grp([H|T] = Ls) ->
case Heading(H) of
false ->
{G, T1} = Collect(Ls, <<>>, []),
[G | Grp(T1)];
Head when is_binary(Head) ->
{G, T1} = Collect(T, Head, []),
[G | Grp(T1)]
end;
Grp([]) ->
[]
end.

Parse =
fun(B) ->
Lines =
[L ||
L <- re:split(B, "\\n", [{return,binary}, notempty]),
L =/= <<>>], %% may still contain a trailing <<>>
Compact(Group(Lines))
end.

%% ==================================================
%% End parser
%% ==================================================

%% For actual mapping rules, see the ConfigMap and ApplyMap functions
%% at the top of this script.
Map =
fun(Groups) ->
lists:flatmap(
fun({G, Vars}) ->
[{set_env,
Compact(
lists:flatmap(
fun({K, V}) ->
ApplyMap(G, K, V)
end, Vars))}]
end, Groups)
end.

%% This function reads the Windows INI file, parses it, then
%% transforms the parsed result into 'set_env' instructions.
case file:read_file(FILE) of
{ok, B} ->
ParseResult = Parse(B),
Map(ParseResult);
{error, _} = Error ->
Error
end.
141 changes: 141 additions & 0 deletions priv/config/load_ini.config.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
%% -*- erlang-indent-level: 4; indent-tabs-mode: nil -*-
%% Copyright (C) 2016, Jaguar Land Rover
%%
%% This program is licensed under the terms and conditions of the
%% Mozilla Public License, version 2.0. The full text of the
%% Mozilla Public License is at https://www.mozilla.org/MPL/2.0/

%% Usage: From a setup .config file:
%% {ok, Instructions} = file:script("$PRIV/config/load_ini.config",
%% [{'FILE', YourINIfile}])
%% Then, either append or prepend Instructions to the other config
%% instructions.
%%


%% ==================================================
%% Windows INI format parser
%% ==================================================

Compact =
fun(Gs) ->
lists:foldl(
fun({G,Vs}, Acc) ->
case orddict:find(G, Acc) of
{ok, Vs0} -> orddict:store(G, Vs0 ++ Vs, Acc);
error -> orddict:store(G, Vs, Acc)
end
end, orddict:new(), Gs)
end.

Heading =
fun(L) when is_binary(L) ->
case re:run(L, "\\[\\h*([^\\h]+)\\h*\\]", [{capture,[1],binary}]) of
{match, [H]} ->
H;
nomatch ->
false
end
end.

Cmd =
fun(V) ->
case os:cmd(binary_to_list(<<"echo ", V/binary>>)) of
"/bin/sh:" ++ _ ->
V;
Res ->
re:replace(Res, "^(.*)\\n$", "\\1", [{return, binary}])
end
end.

Var =
fun(L) ->
case re:run(L, "\\h*([^\\h]+)\\h*=\\h*(.*)",
[{capture,[1,2],binary}]) of
{match, [K, V]} ->
{K, Cmd(V)};
_ ->
false
end
end.

Collect =
fun C([H|T] = Ls, G, Acc) ->
case Var(H) of
{K, V} -> C(T, G, [{K,V}|Acc]);
false -> {{G, lists:reverse(Acc)}, Ls}
end;
C([], G, Acc) ->
{{G, lists:reverse(Acc)}, []}
end.

Group =
fun Grp([H|T] = Ls) ->
case Heading(H) of
false ->
{G, T1} = Collect(Ls, <<>>, []),
[G | Grp(T1)];
Head when is_binary(Head) ->
{G, T1} = Collect(T, Head, []),
[G | Grp(T1)]
end;
Grp([]) ->
[]
end.

Parse =
fun(B) ->
Lines =
[L ||
L <- re:split(B, "\\n", [{return,binary}, notempty]),
L =/= <<>>], %% may still contain a trailing <<>>
Compact(Group(Lines))
end.

%% ==================================================
%% End parser
%% ==================================================
Str = fun(B) -> binary_to_list(B) end.

SystemGrp =
fun({K, V}) ->
case lists:member(K, [<<"device_key">>, <<"root_cert">>,
<<"device_cert">>, <<"cred_dir">>,
<<"node_address">>, <<"node_service_prefix">>,
<<"node_id">>]) of
true ->
[{binary_to_atom(K, latin1), Str(V)}];
false ->
io:fwrite("Unknown config: ~p", [{K, V}])
end
end.

ServiceEdgeGrp =
fun(_) ->
[]
end.


Map =
fun(Groups) ->
lists:flatmap(
fun({G, Vars}) when G == <<>>; G == <<"system">> ->
[{set_env,
[{rvi_core,
lists:flatmap(SystemGrp, Vars)}]}];
({<<"service_edge">>, Vars}) ->
[{set_env,
[{rvi_core,
lists:map(ServiceEdgeGrp, Vars)}]}];
(Other) ->
io:fwrite("Unknown config: ~p", [Other]),
[]
end, Groups)
end.

case file:read_file(FILE) of
{ok, B} ->
Map(Parse(B));
{error, _} = Error ->
Error
end.
Loading