From c47ab8ead2407677843352d7a10a656f82b1689d Mon Sep 17 00:00:00 2001 From: zhongwencool Date: Wed, 13 Oct 2021 17:26:52 +0800 Subject: [PATCH] feat: support include-dirs opt to search include file (#147) * feat: support include-dirs opt to search include file * feat: throw error when required=false but reason =/= enoent --- etc/include-dir-enoent.conf | 2 ++ etc/include-dir.conf | 2 ++ src/hocon.erl | 8 ++++-- src/hocon_token.erl | 53 ++++++++++++++++++++++-------------- test/data/test-data-dir.conf | 1 + test/hocon_tests.erl | 23 +++++++++++++++- 6 files changed, 66 insertions(+), 23 deletions(-) create mode 100644 etc/include-dir-enoent.conf create mode 100644 etc/include-dir.conf create mode 100644 test/data/test-data-dir.conf diff --git a/etc/include-dir-enoent.conf b/etc/include-dir-enoent.conf new file mode 100644 index 0000000..5b0cdd5 --- /dev/null +++ b/etc/include-dir-enoent.conf @@ -0,0 +1,2 @@ +include required("node.conf") +include required("not-exist.conf") diff --git a/etc/include-dir.conf b/etc/include-dir.conf new file mode 100644 index 0000000..1e80e05 --- /dev/null +++ b/etc/include-dir.conf @@ -0,0 +1,2 @@ +include required("node.conf") +include required("test-data-dir.conf") diff --git a/src/hocon.erl b/src/hocon.erl index e4c4a33..0017f14 100644 --- a/src/hocon.erl +++ b/src/hocon.erl @@ -48,7 +48,9 @@ load(Filename0) -> -spec(load(file:filename(), opts()) -> {ok, config()} | {error, term()}). load(Filename0, Opts) -> Filename = hocon_util:real_file_name(filename:absname(Filename0)), - Ctx = hocon_util:stack_multiple_push([{path, '$root'}, {filename, Filename}], #{}), + IncludeDirs = [filename:dirname(Dir) || Dir <- maps:get(include_dirs, Opts, [])], + CtxList = [{path, '$root'}, {filename, Filename}, {include_dirs, IncludeDirs}], + Ctx = hocon_util:stack_multiple_push(CtxList, #{}), try Bytes = hocon_token:read(Filename), Conf = transform(do_binary(Bytes, Ctx), Opts), @@ -90,7 +92,9 @@ binary(Binary) -> binary(Binary, Opts) -> try - Ctx = hocon_util:stack_multiple_push([{path, '$root'}, {filename, undefined}], #{}), + IncludeDirs = [filename:dirname(Dir) || Dir <-maps:get(include_dirs, Opts, [])], + CtxList = [{path, '$root'}, {filename, undefined}, {include_dirs, IncludeDirs}], + Ctx = hocon_util:stack_multiple_push(CtxList, #{}), Map = transform(do_binary(Binary, Ctx), Opts), {ok, apply_opts(Map, Opts)} catch diff --git a/src/hocon_token.erl b/src/hocon_token.erl index ed9fe5c..cedfd0f 100644 --- a/src/hocon_token.erl +++ b/src/hocon_token.erl @@ -215,26 +215,39 @@ do_abspath(Var, [#{?HOCON_T := key}=K | More]) -> %% @end load_include(#{?HOCON_T := include, ?HOCON_V := Value, required := Required}, Ctx0) -> Cwd = filename:dirname(hd(hocon_util:get_stack(filename, Ctx0))), - Filename = binary_to_list(filename:join([Cwd, Value])), - case {file:read_file_info(Filename), Required} of - {{error, enoent}, true} -> - throw({enoent, Filename}); - {{error, enoent}, false} -> - nothing; - _ -> - case is_included(Filename, Ctx0) of - true -> - throw({cycle, hocon_util:get_stack(filename, Ctx0)}); - false -> - Ctx = hocon_util:stack_push({filename, Filename}, Ctx0), - hocon_util:pipeline(Filename, Ctx, - [ fun read/1 - , fun scan/2 - , fun trans_key/1 - , fun parse/2 - , fun include/2 - ]) - end + IncludeDirs = hd(hocon_util:get_stack(include_dirs, Ctx0)), + case search_file([Cwd | IncludeDirs], Value) of + {ok, Filename} -> + case is_included(Filename, Ctx0) of + true -> + throw({cycle, hocon_util:get_stack(filename, Ctx0)}); + false -> + Ctx = hocon_util:stack_push({filename, Filename}, Ctx0), + hocon_util:pipeline(Filename, Ctx, + [ fun read/1 + , fun scan/2 + , fun trans_key/1 + , fun parse/2 + , fun include/2 + ]) + end; + {error, enoent} when Required -> throw({enoent, Value}); + {error, enoent} -> nothing; + {error, Errors} -> throw(Errors) + end. + +search_file(Dirs, File) -> search_file(Dirs, File, []). + +search_file([], _File, []) -> {error, enoent}; +search_file([], _File, Reasons) -> {error, Reasons}; +search_file([Dir | Dirs], File, Reasons0) -> + Filename = binary_to_list(filename:join([Dir, File])), + case file:read_file_info(Filename) of + {ok, _} -> {ok, Filename}; + {error, enoent} -> search_file(Dirs, File, Reasons0); + {error, Reason} -> + Reasons = [{Reason, Filename} | Reasons0], + search_file(Dirs, File, Reasons) end. is_included(Filename, Ctx) -> diff --git a/test/data/test-data-dir.conf b/test/data/test-data-dir.conf new file mode 100644 index 0000000..5a4d3da --- /dev/null +++ b/test/data/test-data-dir.conf @@ -0,0 +1 @@ +include required("a_1.conf") diff --git a/test/hocon_tests.erl b/test/hocon_tests.erl index 2ab8819..a2caba3 100644 --- a/test/hocon_tests.erl +++ b/test/hocon_tests.erl @@ -314,7 +314,28 @@ delete_null_test() -> required_test() -> ?assertEqual({ok, #{}}, hocon:load("etc/optional-include.conf")), - ?assertMatch({error, {enoent, _}}, hocon:load("etc/required-include.conf")). + RequiredRes = hocon:load("etc/required-include.conf"), + ?assertMatch({error, {enoent, <<"no.conf">>}}, RequiredRes). + +include_dirs_test() -> + Expect = + #{<<"a">> => 1, + <<"cluster">> => + #{<<"autoclean">> => <<"5m">>, + <<"autoheal">> => <<"on">>, + <<"discovery">> => <<"manual">>, + <<"name">> => <<"emqxcl">>, + <<"proto_dist">> => <<"inet_tcp">>}, + <<"node">> => + #{<<"cookie">> => <<"emqxsecretcookie">>, + <<"data_dir">> => <<"platform_data_dir">>, + <<"name">> => <<"emqx@127.0.0.1">>}}, + Opts = #{include_dirs => ["test/data/", "sample-configs/"]}, + {ok, Map} = hocon:load("etc/include-dir.conf", Opts), + ?assertEqual(Expect, Map), + {error, Reason} = hocon:load("etc/include-dir-enoent.conf", Opts), + ?assertEqual({enoent, <<"not-exist.conf">>}, Reason), + ok. merge_when_resolve_test() -> ?assertEqual({ok, #{<<"a">> => #{<<"x">> => 1, <<"y">> => 2},