Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
otp: ['25', '26']
otp: ['26', '28']
steps:
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{matrix.otp}}
rebar3-version: '3.16.1'
rebar3-version: '3.25.1'
- name: Compile and run tests
run: |
make
9 changes: 2 additions & 7 deletions rebar.config
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
%% -*- mode:erlang -*-
{erl_opts, [debug_info, warnings_as_errors]}.

{escript_name, example_model2}.
{escript_emu_args, "%%! -escript main example_model2\n"}.

{deps, [ {snabbkaffe, "1.0.10"}
, {typerefl, {git, "https://github.com/ieQu1/typerefl.git", {tag, "0.9.5"}}}
, {typerefl, {git, "https://github.com/ieQu1/typerefl.git", {tag, "0.9.7"}}}
]}.

{cover_enabled, true}.
Expand All @@ -16,10 +13,8 @@

{eunit_opts, [verbose, {print_depth, 100}]}.

%{dialyzer, [{warnings, [overspecs, underspecs]}]}.

{profiles, [ {test,
[{deps, [{proper, "1.3.0"}]}]}
[{deps, [{proper, "1.5.0"}]}]}
, {dev,
[ {plugins, [rebar3_hex]}
]}
Expand Down
4 changes: 2 additions & 2 deletions rebar.lock
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{"1.2.0",
[{<<"erlang_qq">>,
{git,"https://github.com/k32/erlang_qq.git",
{ref,"beab75239f799240397aee0aec30685b9bbf33bb"}},
{ref,"21fe46d8c03ce2f65733aadf3ad97b7bb7edea28"}},
1},
{<<"snabbkaffe">>,{pkg,<<"snabbkaffe">>,<<"1.0.10">>},0},
{<<"typerefl">>,
{git,"https://github.com/ieQu1/typerefl.git",
{ref,"90947e191ecc284ff12a193f9c6fe0265863c77e"}},
{ref,"a202b2a483745373057cc28e04f259a28c4a5c27"}},
0}]}.
[
{pkg_hash,[
Expand Down
114 changes: 75 additions & 39 deletions src/framework/lee_lib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,22 @@

-include("lee.hrl").

-export([ term_to_string/1
, format/2
, patch_key/1
, make_nested_patch/3
, splitl/2
, splitr/2
-export([ term_to_string/1, format/2
, patch_key/1, make_nested_patch/3
, tree_to_patch/3, tree_get/2
, splitl/2, splitr/2
, inject_error_location/2
, compose_checks/1
, perform_checks/3
, run_cmd/2
, validate_optional_meta_attr/4
, validate_optional_meta_attr/3
, validate_meta_attr/3
, format_typerefl_error/1
, report_error/2
, report_warning/2
, collect_errors/1
, compose_checks/1, perform_checks/3
, validate_optional_meta_attr/4, validate_optional_meta_attr/3, validate_meta_attr/3
, format_typerefl_error/1, report_error/2, report_warning/2, collect_errors/1
]).

-export_type([check_result/0]).
-export_type([check_result/0, conf_tree/0]).

-type check_result() :: {[string()], [string()]}.

-type conf_tree() :: #{atom() | [atom()] => term()}.

-spec format(string(), [term()]) -> string().
format(Fmt, Attrs) ->
lists:flatten(io_lib:format(Fmt, Attrs)).
Expand Down Expand Up @@ -63,6 +56,71 @@ make_nested_patch(Model, Parent, Children) ->
[{set, Parent ++ [ChildKey], ?lee_map_placeholder}
|[{set, Parent ++ [ChildKey|K], V} || {K, V} <- maps:to_list(Children)]].

-spec tree_to_patch(lee:model(), conf_tree(), [lee:model_key()]) -> {ok, lee:patch()}.
tree_to_patch(Model, Tree, MKeys) ->
try
{ok, tree_to_patch(Model, [], Tree, [lee_model:key_parts(K) || K <- MKeys])}
catch
%% TODO: better error handling
EC:Err:Stack ->
{error, {EC, Err, Stack}}
end.

tree_to_patch(Model, Parent, Tree, MKeys) ->
lists:foldl(
fun([Key | Rest], Acc) ->
case tree_get(Key, Tree) of
undefined ->
Acc;
{ok, Value} when Rest =:= [] ->
[{set, Parent ++ Key, Value} | Acc];
{ok, SubTrees} when is_list(SubTrees) ->
lists:foldl(
fun(SubTree, Acc1) ->
InstKey = make_inst_key(Model, Parent, Key, SubTree),
NewParent = Parent ++ Key ++ [InstKey],
tree_to_patch(Model, NewParent, SubTree, [Rest]) ++ Acc1
end,
Acc,
SubTrees)
end
end,
[],
MKeys).

make_inst_key(Model, Parent, MapKey, Conf) ->
ModelKey = lee_model:get_model_key(Parent ++ MapKey),
#mnode{metaparams = MAttrs} = lee_model:get(ModelKey, Model),
KeyElems = [case tree_get(I, Conf) of
{ok, Val} ->
Val;
undefined ->
%% TODO: handle defaults
error({ModelKey, MapKey, I})
end
|| I <- ?m_attr(map, ?key_elements, MAttrs, [])],
list_to_tuple(KeyElems).

-spec tree_get(lee:key(), conf_tree()) -> {ok, term()} | undefined.
tree_get(Key, Conf) when is_list(Key), is_map(Conf) ->
case Conf of
#{Key := Val} ->
{ok, Val};
_ ->
case Key of
[Tail] ->
case Conf of
#{Tail := Val} -> {ok, Val};
_ -> undefined
end;
[Head | Tail] ->
case Conf of
#{Head := Children} -> tree_get(Tail, Children);
_ -> undefined
end
end
end.

-spec splitl(fun((A) -> boolean()), [A]) -> [[A]].
splitl(_, []) ->
[];
Expand Down Expand Up @@ -114,28 +172,6 @@ inject_error_location(Location, {Err, Warn}) ->
end,
{[Fun(I) || I <- Err], [Fun(I) || I <- Warn]}.

-spec run_cmd(string(), [string()]) -> {integer(), binary()} | {error, term()}.
run_cmd(Cmd, Args) ->
case os:find_executable(Cmd) of
false ->
{error, enoent};
Executable ->
Port = erlang:open_port( {spawn_executable, Executable}
, [ exit_status
, stderr_to_stdout
, {args, Args}
]),
collect_port_output(Port, [])
end.

collect_port_output(Port, Acc) ->
receive
{Port, {exit_status, Status}} ->
{Status, iolist_to_binary(Acc)};
{Port, {data, Data}} ->
[Acc|Data]
end.

-spec compose_checks([check_result()]) -> check_result().
compose_checks(L) ->
{Err, Warn} = lists:unzip(L),
Expand Down
41 changes: 23 additions & 18 deletions src/framework/lee_model.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,15 @@
-module(lee_model).

%% API exports
-export([ compile/2
-export([ compile/2, merge/1, merge/2, clone/4
, map_vals/2

, fold/3
, fold/4
, fold_metatypes/3
, fold_mt_instances/3

, get/2
, get_meta/3
, get_meta/2
, patch_meta/2
, all_metatypes/1
, get_metatype_index/2
, fold/3, fold/4, fold_metatypes/3, fold_mt_instances/3

, get/2, get_meta/3, get_meta/2, patch_meta/2
, all_metatypes/1, get_metatype_index/2
, match/2
, get_model_key/1
, merge/1
, merge/2
, split_key/1
, full_split_key/1
, clone/4
, get_model_key/1, split_key/1, key_parts/1, full_split_key/1
]).

-export_type([ metatype_index/0
Expand Down Expand Up @@ -175,6 +163,8 @@ fold(Fun, Acc, Scope, Model) -> %% Fold over cooked module
lee_storage:fold(Fun, Acc, Scope, Model).

%% @doc Transform instance key to model key
%%
%% WARNING: trailing ?children element is dropped.
-spec get_model_key(lee:key()) -> lee:model_key().
get_model_key([]) ->
[];
Expand Down Expand Up @@ -206,6 +196,14 @@ split_key(K) ->
),
{lists:reverse(Base0), lists:reverse(Req0)}.

%% @doc
%% ```
%% [foo, ?children, bar, ?children, baz] -> [[foo], [bar], [baz]]
%% '''
key_parts(L) ->
key_parts(L, []).


%% @doc Split a key into a list of child keys. Example:
%% ```full_split_key([foo, ?children, bar, baz, {[1]}, quux]) -->
%% [[foo, ?children], [bar, baz, {[1]}], [quux]]'''
Expand Down Expand Up @@ -320,3 +318,10 @@ mk_metatype_index_(Key, #mnode{metatypes = MetaTypes}, Acc0) ->
end
, Acc0
, MetaTypes).

key_parts([], Acc) ->
[lists:reverse(Acc)];
key_parts([?children | Rest], Acc) ->
[lists:reverse(Acc) | key_parts(Rest)];
key_parts([A|Rest], Acc) ->
key_parts(Rest, [A | Acc]).
13 changes: 3 additions & 10 deletions src/metatypes/lee_config_file.erl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2022-2024 k32 All Rights Reserved.
%% Copyright (c) 2022-2025 k32 All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -34,7 +34,8 @@
read(Model, Filename) ->
case file:consult(Filename) of
{ok, [Term]} ->
{ok, make_patch([], Model, Term, [])};
Keys = lee_model:get_metatype_index(value, Model),
lee_lib:tree_to_patch(Model, Term, Keys);
{error, enoent} ->
{ok, []};
{error, Err} ->
Expand Down Expand Up @@ -77,11 +78,3 @@ read_patch(Tag, Model) ->
%%================================================================================
%% Internal functions
%%================================================================================

make_patch(Path, Model, Term, Acc0) when is_map(Term) ->
Fun = fun(K, V, Acc) ->
make_patch(Path ++ [K], Model, V, Acc)
end,
maps:fold(Fun, Acc0, Term);
make_patch(Path, _Model, Term, Acc) ->
[{set, Path, Term}|Acc].
10 changes: 7 additions & 3 deletions test/data/conf-file-correct.eterm
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
%% -*- mode:erlang -*-
#{ foo => "I am foo"
, bar => {"I am bar"}
, baz =>
#{ quux => 42
}
, [baz, quux] => 42
, map =>
[ #{ foo => 2
, bar => bar
, baz => [#{foo => 22}]
}
]
}.
40 changes: 27 additions & 13 deletions test/lee_conf_file_tests.erl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2022 k32 All Rights Reserved.
%% Copyright (c) 2022, 2025 k32 All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
Expand All @@ -22,18 +22,26 @@


model() ->
Model0 = #{ foo => {[value],
#{ type => string()
}}
, bar => {[value],
#{ type => {string()}
}}
, baz =>
#{ quux =>
{[value],
#{ type => integer()
}}
}
Model0 = #{ foo => {[value], #{type => string()}}
, bar => {[value], #{type => {string()}}}
, baz => #{quux => {[value], #{type => integer()}}}
, map =>
{[map],
#{ key_elements => [[foo], [bar]]
},
#{ foo =>
{[value],
#{ type => integer()
, default => 1
}}
, bar =>
{[value], #{type => atom()}}
, baz =>
{[map],
#{},
#{ foo => {[value], #{type => integer()}}
}}
}}
},
File = "test/data/conf-file-correct.eterm",
Meta = [ lee:base_metamodel()
Expand All @@ -54,4 +62,10 @@ read_test() ->
),
?assertMatch( 42
, lee:get(Model, Data, [baz, quux])
),
?assertMatch( 2
, lee:get(Model, Data, [map, {2, bar}, foo])
),
?assertMatch( 22
, lee:get(Model, Data, [map, {2, bar}, baz, {}, foo])
).