Skip to content

Commit

Permalink
Merge pull request #48 from Kyorai/mk-cuttlefish-37
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelklishin authored Aug 5, 2024
2 parents 656018e + 2f3d731 commit ea862b4
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 31 deletions.
65 changes: 41 additions & 24 deletions src/conf_parse.erl
Original file line number Diff line number Diff line change
Expand Up @@ -126,16 +126,6 @@ included_dir_test() ->
], Conf),
ok.

invalid_included_file_test() ->
Conf = conf_parse:file("test/invalid_include_file.conf"),
?assertMatch({[], _PathWithNewLineAndCarriage, {{line,_}, {column, _}}}, Conf),
ok.

invalid_included_dir_test() ->
Conf = conf_parse:file("test/invalid_include_dir.conf"),
?assertMatch({[], _PathWithNewLineAndCarriage, {{line, _},{column, _}}}, Conf),
ok.

escaped_dots_are_removed_test() ->
Conf = conf_parse:parse("#comment\nsetting\\.0 = thing0\n"),
?assertEqual([
Expand All @@ -156,6 +146,22 @@ invalid_utf8_test() ->
?assertMatch(Expected, Actual),
ok.

invalid_included_file_test() ->
Conf = conf_parse:file("test/invalid_include_file.conf"),
?assertMatch({[], _PathWithNewLineAndCarriage, {{line,_}, {column, _}}}, Conf),
ok.

invalid_included_dir_test() ->
Conf = conf_parse:file("test/invalid_include_dir.conf"),
?assertMatch({[], _PathWithNewLineAndCarriage, {{line, _},{column, _}}}, Conf),
ok.

escaped_string_test() ->
Expected = [{["setting"],"e9238-7_49%#sod7"}],
Actual = conf_parse:parse("setting = 'e9238-7_49%#sod7'" ++ "\n"),
?assertMatch(Expected, Actual),
ok.

gh_1_two_tab_test() ->
Conf = conf_parse:parse("setting0 = thing0\n\t\t\nsetting1 = thing1\n"),
?assertEqual([
Expand Down Expand Up @@ -183,16 +189,20 @@ file(Filename) ->
end.

-spec parse(binary() | list()) -> any().
parse(List) when is_list(List) -> parse(unicode:characters_to_binary(List));
parse(List) when is_list(List) ->
case unicode:characters_to_binary(List) of
Binary when is_binary(Binary) ->
parse(Binary);
ErrorOrIncomplete ->
ErrorOrIncomplete
end;
parse(Input) when is_binary(Input) ->
_ = setup_memo(),
Result = case 'config'(Input,{{line,1},{column,1}}) of
{AST, <<>>, _Index} -> AST;
Any -> Any
end,
release_memo(), Result;
parse(Error) ->
Error.
_ = setup_memo(),
Result = case 'config'(Input,{{line,1},{column,1}}) of
{AST, <<>>, _Index} -> AST;
Any -> Any
end,
release_memo(), Result.

-spec 'config'(input(), index()) -> parse_result().
'config'(Input, Index) ->
Expand All @@ -211,9 +221,9 @@ parse(Error) ->

-spec 'setting'(input(), index()) -> parse_result().
'setting'(Input, Index) ->
p(Input, Index, 'setting', fun(I,D) -> (p_seq([p_zero_or_more(fun 'ws'/2), fun 'key'/2, p_zero_or_more(fun 'ws'/2), p_string(<<"=">>), p_zero_or_more(fun 'ws'/2), fun 'value'/2, p_zero_or_more(fun 'ws'/2), p_optional(fun 'comment'/2)]))(I,D) end, fun(Node, _Idx) ->
p(Input, Index, 'setting', fun(I,D) -> (p_seq([p_zero_or_more(fun 'ws'/2), fun 'key'/2, p_zero_or_more(fun 'ws'/2), p_string(<<"=">>), p_zero_or_more(fun 'ws'/2), p_choose([fun 'escaped_value'/2, fun 'unescaped_value'/2]), p_zero_or_more(fun 'ws'/2), p_optional(fun 'comment'/2)]))(I,D) end, fun(Node, Idx) ->
[ _, Key, _, _Eq, _, Value, _, _ ] = Node,
{Key, Value}
{Key, try_unicode_characters_to_list(Value, Idx)}
end).

-spec 'key'(input(), index()) -> parse_result().
Expand All @@ -223,9 +233,16 @@ parse(Error) ->
[try_unicode_characters_to_list(H, Idx)| [try_unicode_characters_to_list(W, Idx) || [_, W] <- T]]
end).

-spec 'value'(input(), index()) -> parse_result().
'value'(Input, Index) ->
p(Input, Index, 'value', fun(I,D) -> (p_one_or_more(p_seq([p_not(p_choose([p_seq([p_zero_or_more(fun 'ws'/2), fun 'crlf'/2]), fun 'comment'/2])), p_anything()])))(I,D) end, fun(Node, Idx) ->
-spec 'escaped_value'(input(), index()) -> parse_result().
'escaped_value'(Input, Index) ->
p(Input, Index, 'escaped_value', fun(I,D) -> (p_seq([p_string(<<"\'">>), p_zero_or_more(p_seq([p_not(p_string(<<"\'">>)), p_anything()])), p_string(<<"\'">>)]))(I,D) end, fun(Node, Idx) ->
Stripped = string:trim(Node, both, [$']),
try_unicode_characters_to_list(Stripped, Idx)
end).

-spec 'unescaped_value'(input(), index()) -> parse_result().
'unescaped_value'(Input, Index) ->
p(Input, Index, 'unescaped_value', fun(I,D) -> (p_one_or_more(p_seq([p_not(p_choose([p_seq([p_zero_or_more(fun 'ws'/2), fun 'crlf'/2]), fun 'comment'/2])), p_anything()])))(I,D) end, fun(Node, Idx) ->
try_unicode_characters_to_list(Node, Idx)
end).

Expand Down
29 changes: 22 additions & 7 deletions src/conf_parse.peg
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ line <- ((setting / include / comment / ws+) (crlf / eof)) / crlf %{

%% A setting is a key and a value, joined by =, with surrounding
%% whitespace ignored.
setting <- ws* key ws* "=" ws* value ws* comment? %{
setting <- ws* key ws* "=" ws* (escaped_value / unescaped_value) ws* comment? %{
[ _, Key, _, _Eq, _, Value, _, _ ] = Node,
{Key, Value}
{Key, try_unicode_characters_to_list(Value, Idx)}
%};

%% A key is a series of dot-separated identifiers.
Expand All @@ -67,8 +67,14 @@ key <- head:word tail:("." word)* %{
[try_unicode_characters_to_list(H, Idx)| [try_unicode_characters_to_list(W, Idx) || [_, W] <- T]]
%};

%% An escaped value is any character between single quotes except for EOF
escaped_value <- "'" (!"'" .)* "'" %{
Stripped = string:trim(Node, both, [$']),
try_unicode_characters_to_list(Stripped, Idx)
%};

%% A value is any character, with trailing whitespace stripped.
value <- (!((ws* crlf) / comment) .)+ %{
unescaped_value <- (!((ws* crlf) / comment) .)+ %{
try_unicode_characters_to_list(Node, Idx)
%};

Expand Down Expand Up @@ -229,10 +235,19 @@ utf8_test() ->
?assertMatch(Expected, Actual),
ok.

invalid_utf8_test() ->
InvalidCodePoint = 16#11FFFF,
Expected = {error, <<"setting = thing">>, [InvalidCodePoint, $\n]},
Actual = conf_parse:parse("setting = thing" ++ [InvalidCodePoint] ++ "\n"),
invalid_included_file_test() ->
Conf = conf_parse:file("test/invalid_include_file.conf"),
?assertMatch({[], _PathWithNewLineAndCarriage, {{line,_}, {column, _}}}, Conf),
ok.

invalid_included_dir_test() ->
Conf = conf_parse:file("test/invalid_include_dir.conf"),
?assertMatch({[], _PathWithNewLineAndCarriage, {{line, _},{column, _}}}, Conf),
ok.

escaped_string_test() ->
Expected = [{["setting"],"e9238-7_49%#sod7"}],
Actual = conf_parse:parse("setting = 'e9238-7_49%#sod7'" ++ "\n"),
?assertMatch(Expected, Actual),
ok.

Expand Down
21 changes: 21 additions & 0 deletions test/cuttlefish_integration_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,27 @@ tagged_string_test() ->
NewConfig = cuttlefish_generator:map(Schema, Conf),
?assertEqual({tagged, "e614d97599dab483f"}, proplists:get_value(tagged_key, proplists:get_value(cuttlefish, NewConfig))).

escaped_value_case1_test() ->
Schema = cuttlefish_schema:files(["test/escaped_values.schema"]),

Conf1 = conf_parse:parse("escaped.value = 'e9238-7_49%#sod7'\n"),
Config1 = cuttlefish_generator:map(Schema, Conf1),
%% with escaping, the '#' character and everything that follows it is preserved
?assertEqual("e9238-7_49%#sod7", proplists:get_value(value, proplists:get_value(escaped, Config1))),

Conf2 = conf_parse:parse(<<"non_escaped.value = 557sd79238749%#sod7f9s87ee4\n">>),
Config2 = cuttlefish_generator:map(Schema, Conf2),
%% without escaping, everything after the '#' is cut off
?assertEqual("557sd79238749%", proplists:get_value(value, proplists:get_value(non_escaped, Config2))).

escaped_value_case2_test() ->
Schema = cuttlefish_schema:files(["test/escaped_values.schema"]),

%% characters that may appear in machine-generated passwords
Conf1 = conf_parse:parse("escaped.value = '!@$#%^&*+-_()|<>'\n"),
Config1 = cuttlefish_generator:map(Schema, Conf1),
?assertEqual("!@$#%^&*+-_()|<>", proplists:get_value(value, proplists:get_value(escaped, Config1))).

proplist_equals(Expected, Actual) ->
ExpectedKeys = lists:sort(proplists:get_keys(Expected)),
ActualKeys = lists:sort(proplists:get_keys(Actual)),
Expand Down
7 changes: 7 additions & 0 deletions test/escaped_values.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{mapping, "escaped.value", "escaped.value", [
{datatype, [string]}
]}.

{mapping, "non_escaped.value", "non_escaped.value", [
{datatype, [string]}
]}.

0 comments on commit ea862b4

Please sign in to comment.