Skip to content

Commit b27423a

Browse files
authored
Merge pull request #270 from emqx/do-not-split-dots-in-quoted-keys
fix: do not split quoted key as path
2 parents de7428d + f4b7a5f commit b27423a

14 files changed

+76
-37
lines changed

.github/workflows/run_elvis.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77
runs-on: ubuntu-latest
88

99
container:
10-
image: erlang:23.2
10+
image: erlang:25.3
1111

1212
steps:
1313
- uses: actions/checkout@v1

.github/workflows/run_test_case.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
runs-on: ubuntu-latest
1010

1111
container:
12-
image: erlang:23.2
12+
image: erlang:25.3
1313

1414
steps:
1515
- uses: actions/checkout@v1

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ REBAR := $(CURDIR)/rebar3
44
all: es
55

66
$(REBAR):
7-
@curl -k -f -L "https://github.com/emqx/rebar3/releases/download/3.14.3-emqx-7/rebar3" -o ./rebar3
7+
@curl -k -f -L "https://github.com/emqx/rebar3/releases/download/3.19.0-emqx-6/rebar3" -o ./rebar3
88
@chmod +x ./rebar3
99

1010
.PHONY: compile

rebar.config

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@
3333
{test, [
3434
{deps, [
3535
{proper, "1.4.0"},
36-
{cuttlefish, {git, "https://github.com/emqx/cuttlefish.git", {tag, "v3.3.5"}}},
36+
{cuttlefish, {git, "https://github.com/emqx/cuttlefish.git", {tag, "v3.3.8"}}},
3737
{erlymatch, {git, "https://github.com/zmstone/erlymatch.git", {tag, "1.1.0"}}}
3838
]},
3939
{extra_src_dirs, ["sample-configs", "sample-schemas", "_build/test/lib/cuttlefish/test"]},
4040
{erl_opts, [{i, "_build/test/lib/cuttlefish/include"}]}
4141
]},
4242
{cuttlefish, [
43-
{deps, [{cuttlefish, {git, "https://github.com/emqx/cuttlefish.git", {tag, "v3.3.4"}}}]},
43+
{deps, [{cuttlefish, {git, "https://github.com/emqx/cuttlefish.git", {tag, "v3.3.8"}}}]},
4444
{erl_opts, [{d, 'CUTTLEFISH_CONVERTER', true}]}
4545
]},
4646
{es, [

scripts/elvis-check.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
set -euo pipefail
44

5-
ELVIS_VERSION='1.0.0-emqx-2'
5+
ELVIS_VERSION='1.1.0-emqx-2'
66

77
elvis_version="${2:-$ELVIS_VERSION}"
88

src/hocon.erl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,12 @@ do_expand([Other | More], Acc) ->
192192
do_expand(More, [Other | Acc]).
193193

194194
create_nested(#{?HOCON_T := key} = Key, Value) ->
195-
do_create_nested(paths(value_of(Key)), Value, Key).
195+
case value_of(Key) of
196+
{keypath, Path} ->
197+
do_create_nested(Path, Value, Key);
198+
Path ->
199+
do_create_nested([Path], Value, Key)
200+
end.
196201

197202
do_create_nested([], Value, _OriginalKey) ->
198203
Value;
@@ -398,7 +403,7 @@ transform(#{?HOCON_T := object, ?HOCON_V := V}, Opts) ->
398403
do_transform([], Map, _Opts) ->
399404
Map;
400405
do_transform([{Key, Value} | More], Map, Opts) ->
401-
[KeyReal] = paths(value_of(Key)),
406+
KeyReal = unicode_bin(value_of(Key)),
402407
ValueReal = unpack(Value, Opts),
403408
do_transform(More, merge(KeyReal, ValueReal, Map), Opts).
404409

src/hocon_maps.erl

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,8 @@ flatten_l([H | T], Opts, Meta, Stack, Acc, [Tag | Tags]) ->
320320
bin(B) when is_binary(B) -> B;
321321
bin(I) when is_integer(I) -> integer_to_binary(I).
322322

323-
infix([], _) -> [];
324-
infix([X], _) -> [X];
325-
infix([H | T], I) -> [H, I | infix(T, I)].
323+
infix(List, Sep) ->
324+
lists:join(Sep, List).
326325

327326
ensure_plain(M) ->
328327
case is_richmap(M) of

src/hocon_md.erl

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
-module(hocon_md).
1818

1919
-export([h/2, link/2, local_link/2, th/1, td/1, ul/1, code/1]).
20-
-export([join/1, indent/2]).
20+
-export([indent/2]).
2121

2222
h(1, Text) -> format("# ~s~n", [Text]);
2323
h(2, Text) -> format("## ~s~n", [Text]);
@@ -49,9 +49,6 @@ escape_bar(Str) ->
4949

5050
code(Text) -> ["<code>", Text, "</code>"].
5151

52-
join(Mds) ->
53-
lists:join("\n", [Mds]).
54-
5552
indent(N, Lines) when is_list(Lines) ->
5653
indent(N, unicode:characters_to_binary(infix(Lines, "\n"), utf8));
5754
indent(N, Lines0) ->
@@ -62,9 +59,8 @@ indent(N, Lines0) ->
6259
pad(_Pad, <<>>) -> <<>>;
6360
pad(Pad, Line) -> [Pad, Line].
6461

65-
infix([], _) -> [];
66-
infix([X], _) -> [X];
67-
infix([H | T], In) -> [H, In | infix(T, In)].
62+
infix(List, Sep) ->
63+
lists:join(Sep, List).
6864

6965
%% ref: https://gist.github.com/asabaylus/3071099
7066
%% GitHub flavored markdown likes ':' being removed

src/hocon_parser.yrl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Terminals
1212
'{' '}' '[' ']' ','
1313
bool integer float null
1414
percent bytesize duration
15-
string variable
15+
unqstr string variable
1616
endstr endvar endarr endobj
1717
include key required.
1818

@@ -29,6 +29,7 @@ partials -> '[' elements endarr : [make_array(line_of('$1'), '$2')].
2929
partials -> '{' endobj : [make_object(line_of('$1'), [])].
3030
partials -> '[' endarr : [make_array(line_of('$1'), [])].
3131

32+
partial -> unqstr : str_to_bin(make_primitive_value('$1')).
3233
partial -> string : str_to_bin(make_primitive_value('$1')).
3334
partial -> variable : make_variable('$1').
3435
partial -> '{' fields '}' : make_object(line_of('$1'), '$2').
@@ -47,8 +48,10 @@ elements -> value ',' elements : ['$1' | '$3'].
4748
elements -> value elements : ['$1' | '$2'].
4849
elements -> value : ['$1'].
4950

51+
directive -> include unqstr : make_include('$2', false).
5052
directive -> include string : make_include('$2', false).
5153
directive -> include endstr : make_include('$2', false).
54+
directive -> include required unqstr : make_include('$3', true).
5255
directive -> include required string : make_include('$3', true).
5356
directive -> include required endstr : make_include('$3', true).
5457

@@ -88,7 +91,7 @@ make_include(String, false) -> #{'$hcTyp' => include,
8891

8992
make_concat(S) -> #{'$hcTyp' => concat, '$hcVal' => S}.
9093

91-
str_to_bin(#{'$hcTyp' := T, '$hcVal' := V} = M) when T =:= string -> M#{'$hcVal' => bin(V)}.
94+
str_to_bin(#{'$hcTyp' := T, '$hcVal' := V} = M) when T =:= string orelse T =:= unqstr -> M#{'$hcTyp' := string, '$hcVal' => bin(V)}.
9295

9396
line_of(Token) -> element(2, Token).
9497
value_of(Token) -> element(3, Token).

src/hocon_pp.erl

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,16 +176,16 @@ is_quote_key(K) ->
176176
true
177177
end.
178178

179+
%% Return 'true' if a string is to be quoted when formatted as HOCON.
179180
%% A sequence of characters outside of a quoted string is a string value if:
180181
%% it does not contain "forbidden characters":
181182
%% '$', '"', '{', '}', '[', ']', ':', '=', ',', '+', '#', '`', '^', '?', '!', '@', '*',
182183
%% '&', '' (backslash), or whitespace.
183184
%% '$"{}[]:=,+#`^?!@*& \\'
184-
185-
is_quote_str(S) ->
185+
is_to_quote_str(S) ->
186186
case hocon_scanner:string(S) of
187-
{ok, [{string, 1, S}], 1} ->
188-
%% contain $"{}[]:=,+#`^?!@*& \\ should be quote
187+
{ok, [{Tag, 1, S}], 1} when Tag =:= string orelse Tag =:= unqstr ->
188+
%% contain $"{}[]:=,+#`^?!@*& \\ should be quoted
189189
case re:run(S, "^[^$\"{}\\[\\]:=,+#`\\^?!@*&\\ \\\\]*$") of
190190
nomatch -> true;
191191
_ -> false
@@ -195,7 +195,7 @@ is_quote_str(S) ->
195195
end.
196196

197197
maybe_quote_latin1_str(S) ->
198-
case is_quote_str(S) of
198+
case is_to_quote_str(S) of
199199
true -> bin(io_lib:format("~0p", [S]));
200200
false -> S
201201
end.
@@ -219,9 +219,8 @@ fmt({indent, Block}) ->
219219
split(Bin) ->
220220
[Line || Line <- binary:split(Bin, ?NL, [global]), Line =/= <<>>].
221221

222-
infix([], _) -> [];
223-
infix([One], _) -> [One];
224-
infix([H | T], Infix) -> [[H, Infix] | infix(T, Infix)].
222+
infix(List, Sep) ->
223+
lists:join(Sep, List).
225224

226225
format_escape_sequences(Str) ->
227226
bin(lists:map(fun esc/1, Str)).

src/hocon_scanner.xrl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ Rules.
9393
Erlang code.
9494
9595
maybe_include("include", TokenLine) -> {include, TokenLine};
96-
maybe_include(TokenChars, TokenLine) -> {string, TokenLine, TokenChars}.
96+
maybe_include(TokenChars, TokenLine) -> {unqstr, TokenLine, TokenChars}.
9797
9898
get_filename_from_required("required(" ++ Filename) ->
9999
[$) | FilenameRev] = lists:reverse(Filename),

src/hocon_token.erl

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ trans_key([{'{', Line} | Tokens], Acc) ->
109109
trans_key([T | Tokens], Acc) ->
110110
trans_key(Tokens, [T | Acc]).
111111

112+
trans_key_lb([{unqstr, Line, Value} | TokensRev]) ->
113+
[{key, Line, {keypath, paths(Value)}} | TokensRev];
112114
trans_key_lb([{string, Line, Value} | TokensRev]) ->
113115
[{key, Line, Value} | TokensRev];
114116
trans_key_lb(Otherwise) ->
@@ -145,6 +147,7 @@ trans_splice_end([], Seq, Acc) ->
145147
lists:reverse(NewAcc).
146148

147149
do_trans_splice_end([]) -> [];
150+
do_trans_splice_end([{unqstr, Line, Value} | T]) -> [{endstr, Line, Value} | T];
148151
do_trans_splice_end([{string, Line, Value} | T]) -> [{endstr, Line, Value} | T];
149152
do_trans_splice_end([{variable, Line, Value} | T]) -> [{endvar, Line, Value} | T];
150153
do_trans_splice_end([{'}', Line} | T]) -> [{endobj, Line} | T];
@@ -225,7 +228,12 @@ abspath(Var, PathStack) ->
225228
do_abspath(Var, ['$root']) ->
226229
Var;
227230
do_abspath(Var, [#{?HOCON_T := key} = K | More]) ->
228-
do_abspath(unicode_bin([value_of(K), <<".">>, Var]), More).
231+
do_abspath(unicode_bin([maybe_join(value_of(K)), <<".">>, Var]), More).
232+
233+
maybe_join({keypath, Path}) ->
234+
infix(Path, ".");
235+
maybe_join(Path) ->
236+
Path.
229237

230238
-spec load_include(boxed(), hocon:ctx()) -> boxed() | nothing.
231239

@@ -308,3 +316,11 @@ format_error(Line, ErrorInfo, Ctx) ->
308316

309317
unicode_bin(L) -> unicode:characters_to_binary(L, utf8).
310318
unicode_list(B) -> unicode:characters_to_list(B, utf8).
319+
320+
paths(Key) when is_binary(Key) ->
321+
paths(unicode:characters_to_list(Key, utf8));
322+
paths(Key) when is_list(Key) ->
323+
lists:map(fun unicode_bin/1, string:tokens(Key, ".")).
324+
325+
infix(List, Sep) ->
326+
lists:join(Sep, List).

test/hocon_tconf_tests.erl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ union_with_default(_) ->
8282
undefined.
8383

8484
default_value_test() ->
85-
Conf = "{\"bar.field1\": \"foo\"}",
85+
Conf = "{bar.field1: \"foo\"}",
8686
Res = check(Conf, #{format => richmap}),
8787
?assertEqual(Res, check_plain(Conf)),
8888
?assertEqual(
@@ -107,7 +107,7 @@ default_value_test() ->
107107
).
108108

109109
obfuscate_sensitive_values_test() ->
110-
Conf = "{\"bar.field1\": \"foo\"}",
110+
Conf = "{bar.field1: \"foo\"}",
111111
Res = check(Conf, #{format => richmap}),
112112
Res1 = check_plain(Conf, #{obfuscate_sensitive_values => true}),
113113
?assertNotEqual(Res, Res1),
@@ -195,7 +195,7 @@ nest_ref_fill_default_test() ->
195195
env_override_test() ->
196196
with_envs(
197197
fun() ->
198-
Conf = "{\"bar.field1\": \"foo\", bar.host: \"127.0.0.1\"}",
198+
Conf = "{bar.field1: \"foo\", bar.host: \"127.0.0.1\"}",
199199
Opts = #{format => richmap},
200200
Res = check(Conf, Opts#{apply_override_envs => true}),
201201
?assertEqual(
@@ -244,7 +244,7 @@ env_override_test() ->
244244
no_env_override_test() ->
245245
with_envs(
246246
fun() ->
247-
Conf = "{\"bar.field1\": \"foo\"}",
247+
Conf = "{bar.field1: \"foo\"}",
248248
Res = check(Conf, #{format => richmap}),
249249
PlainRes = check_plain(Conf, #{logger => fun(_, _) -> ok end}),
250250
?assertEqual(Res, PlainRes),
@@ -270,7 +270,7 @@ unknown_env_test() ->
270270
Ref = make_ref(),
271271
with_envs(
272272
fun() ->
273-
Conf = "{\"bar.field1\": \"foo\"}",
273+
Conf = "{bar.field1: \"foo\"}",
274274
Opts = #{
275275
logger => fun(Level, Msg) ->
276276
Tester ! {Ref, Level, Msg},

test/hocon_tests.erl

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -820,7 +820,7 @@ files_unicode_path_test() ->
820820
"a=1\n"
821821
"b=2\n"
822822
"unicode = \"测试unicode文件路径\"\n"
823-
"\"语言.英文\" = english\n"/utf8
823+
"\"语言英文\" = english\n"/utf8
824824
>>,
825825
Filename =
826826
case file:native_name_encoding() of
@@ -834,7 +834,7 @@ files_unicode_path_test() ->
834834
#{format => richmap}
835835
),
836836
?assertEqual(<<"测试unicode文件路径"/utf8>>, deep_get("unicode", Conf, ?HOCON_V)),
837-
?assertEqual(<<"english">>, deep_get("语言.英文", Conf, ?HOCON_V))
837+
?assertEqual(<<"english">>, deep_get("语言英文", Conf, ?HOCON_V))
838838
after
839839
file:delete(Filename)
840840
end.
@@ -1002,3 +1002,24 @@ adjacent_maps_test_() ->
10021002
hocon:binary(<<"x = [{a = 1}\n {b = 2}]">>)
10031003
)}
10041004
].
1005+
1006+
map_with_placeholders_test() ->
1007+
RawConf =
1008+
#{
1009+
<<"headers">> =>
1010+
#{
1011+
<<"fixed_key">> => <<"fixed_value">>,
1012+
<<"${.payload.key}">> => <<"fixed_value">>,
1013+
<<"${.payload.key}2">> => <<"${.payload.value}">>,
1014+
<<"fixed_key2">> => <<"${.payload.value}">>
1015+
}
1016+
},
1017+
TmpFile = "/tmp/" ++ atom_to_list(?FUNCTION_NAME) ++ ".conf",
1018+
try
1019+
ok = file:write_file(TmpFile, hocon_pp:do(RawConf, #{})),
1020+
{ok, LoadedConf} = hocon:load(TmpFile, #{format => map}),
1021+
?assertEqual(RawConf, LoadedConf),
1022+
ok
1023+
after
1024+
file:delete(TmpFile)
1025+
end.

0 commit comments

Comments
 (0)