Skip to content

Commit

Permalink
Allow shell expanded .conf values
Browse files Browse the repository at this point in the history
Allow for shell commmand substitution of .conf values,
only the `ShellCommand` form is allowed.
  • Loading branch information
lrascao committed Nov 11, 2020
1 parent 1a009a4 commit 64c7aca
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 7 deletions.
29 changes: 26 additions & 3 deletions src/conf_parse.erl
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ file_test() ->
], Conf),
ok.

shell_value_test() ->
Conf = conf_parse:file("test/shell_value.conf"),
?assertEqual([
{["value1"], {shell, "cat value1"}},
{["value2"], "43"}
], Conf),
ok.

utf8_test() ->
Conf = conf_parse:parse("setting = thing" ++ [338] ++ "\n"),
?assertEqual([{["setting"],
Expand Down Expand Up @@ -152,7 +160,7 @@ parse(Input) when is_binary(Input) ->

-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 'value'/2, fun 'shell_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}
end).
Expand All @@ -166,7 +174,7 @@ parse(Input) when is_binary(Input) ->

-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) ->
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, fun 'shell_value'/2])), p_anything()])))(I,D) end, fun(Node, Idx) ->
case unicode:characters_to_binary(Node, utf8, latin1) of
{_Status, _Begining, _Rest} ->
{error, {conf_to_latin1, line(Idx)}};
Expand All @@ -175,6 +183,21 @@ parse(Input) when is_binary(Input) ->
end
end).

-spec 'shell_value'(input(), index()) -> parse_result().
'shell_value'(Input, Index) ->
p(Input, Index, 'shell_value', fun(I,D) -> (p_seq([p_zero_or_more(fun 'ws'/2), fun 'shell_start'/2, p_zero_or_more(fun 'ws'/2), p_zero_or_more(p_seq([p_not(fun 'shell_end'/2), p_anything()])), fun 'shell_end'/2]))(I,D) end, fun(Node, _Idx) ->
[_, _, _, Cmd, _] = Node,
{shell, unicode:characters_to_list(Cmd)}
end).

-spec 'shell_start'(input(), index()) -> parse_result().
'shell_start'(Input, Index) ->
p(Input, Index, 'shell_start', fun(I,D) -> (p_string(<<"`">>))(I,D) end, fun(_Node, _Idx) ->ws end).

-spec 'shell_end'(input(), index()) -> parse_result().
'shell_end'(Input, Index) ->
p(Input, Index, 'shell_end', fun(I,D) -> (p_string(<<"`">>))(I,D) end, fun(Node, Idx) ->transform('shell_end', Node, Idx) end).

-spec 'comment'(input(), index()) -> parse_result().
'comment'(Input, Index) ->
p(Input, Index, 'comment', fun(I,D) -> (p_seq([p_zero_or_more(fun 'ws'/2), p_string(<<"#">>), p_zero_or_more(p_seq([p_not(fun 'crlf'/2), p_anything()]))]))(I,D) end, fun(_Node, _Idx) ->comment end).
Expand All @@ -198,7 +221,7 @@ parse(Input) when is_binary(Input) ->
p(Input, Index, 'ws', fun(I,D) -> (p_one_or_more(p_charclass(<<"[\s\t]">>)))(I,D) end, fun(_Node, _Idx) ->ws end).



transform(_,Node,_Index) -> Node.
-file("peg_includes.hrl", 1).
-type index() :: {{line, pos_integer()}, {column, pos_integer()}}.
-type input() :: binary().
Expand Down
24 changes: 21 additions & 3 deletions src/conf_parse.peg
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ line <- ((setting / 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* (value / shell_value) ws* comment? %{
[ _, Key, _, _Eq, _, Value, _, _ ] = Node,
{Key, Value}
%};
Expand All @@ -66,8 +66,8 @@ key <- head:word tail:("." word)* %{
[unicode:characters_to_list(H)| [ unicode:characters_to_list(W) || [_, W] <- T]]
%};

%% A value is any character, with trailing whitespace stripped.
value <- (!((ws* crlf) / comment) .)+ %{
%% A value is any character except a shell value or comment, with trailing whitespace stripped.
value <- (!((ws* crlf) / comment / shell_value) .)+ %{
case unicode:characters_to_binary(Node, utf8, latin1) of
{_Status, _Begining, _Rest} ->
{error, {conf_to_latin1, line(Idx)}};
Expand All @@ -76,6 +76,16 @@ value <- (!((ws* crlf) / comment) .)+ %{
end
%};

%% A shell value is a shell command wrapped around '$(' ')'
shell_value <- ws* shell_start ws* (!shell_end .)* shell_end %{
[_, _, _, Cmd, _] = Node,
{shell, unicode:characters_to_list(Cmd)}
%};

%%% These are the delimiters for shell values
shell_start <- "`" `ws`;
shell_end <- "`";

%% A comment is any line that begins with a # sign, leading whitespace
%% allowed.
comment <- ws* "#" (!crlf .)* `comment`;
Expand Down Expand Up @@ -175,6 +185,14 @@ file_test() ->
], Conf),
ok.

shell_value_test() ->
Conf = conf_parse:file("test/shell_value.conf"),
?assertEqual([
{["value1"], {shell, "cat value1"}},
{["value2"], "43"}
], Conf),
ok.

utf8_test() ->
Conf = conf_parse:parse("setting = thing" ++ [338] ++ "\n"),
?assertEqual([{["setting"],
Expand Down
31 changes: 30 additions & 1 deletion src/cuttlefish_conf.erl
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,34 @@ file(Filename) ->
{_, Values} = lists:unzip(Conf),
case cuttlefish_error:filter(Values) of
{errorlist, []} ->
remove_duplicates(Conf);
% expand any non-literal values (ie. shell values)
expand_values(Filename, remove_duplicates(Conf));
{errorlist, ErrorList} ->
NewErrorList = [ {error, {in_file, {Filename, E}}} || {error, E} <- ErrorList ],
{errorlist, NewErrorList}
end
end.

expand_values(Filename, Conf) ->
lists:map(fun({K, {shell, Cmd}}) ->
% run the shell command, capture the result
% and that will be the new value, all shell
% processing will be relative to the directory
% where the conf filename being processed is located
% first get the current cwd so we can restore it later
{ok, Cwd0} = file:get_cwd(),
file:set_cwd(filename:dirname(Filename)),
ShellValue = os:cmd(Cmd),
% restore the original cwd
file:set_cwd(Cwd0),
% strip all linefeed/carriage return from the end
{K, re:replace(ShellValue, "[\n\r]", "", [global, {return, list}])};
(Value) ->
% normal value, nothing to do
Value
end,
Conf).

-spec generate([cuttlefish_mapping:mapping()]) -> [string()].
generate(Mappings) ->
lists:foldl(
Expand Down Expand Up @@ -341,6 +362,14 @@ generate_element_hidden_test() ->
?assertEqual([], cuttlefish_lager_test_backend:get_logs()),
ok.

shell_value_test() ->
Conf = file("test/shell_value.conf"),
?assertEqual(lists:sort([
{["value1"],"42"},
{["value2"], "43"}
]), lists:sort(Conf)),
ok.

assert_no_output(Setting) ->
Mapping = cuttlefish_mapping:parse(
{mapping,
Expand Down
2 changes: 2 additions & 0 deletions test/shell_value.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
value1 = `cat value1`
value2 = 43
1 change: 1 addition & 0 deletions test/value1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
42

0 comments on commit 64c7aca

Please sign in to comment.