diff --git a/src/conf_parse.erl b/src/conf_parse.erl index 98301ee..0a4bf68 100644 --- a/src/conf_parse.erl +++ b/src/conf_parse.erl @@ -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"], @@ -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). @@ -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)}}; @@ -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). @@ -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(). diff --git a/src/conf_parse.peg b/src/conf_parse.peg index 4813e14..29e25e5 100644 --- a/src/conf_parse.peg +++ b/src/conf_parse.peg @@ -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} %}; @@ -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)}}; @@ -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`; @@ -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"], diff --git a/src/cuttlefish_conf.erl b/src/cuttlefish_conf.erl index 250d4d5..c9b8321 100644 --- a/src/cuttlefish_conf.erl +++ b/src/cuttlefish_conf.erl @@ -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( @@ -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, diff --git a/test/shell_value.conf b/test/shell_value.conf new file mode 100644 index 0000000..b1a2516 --- /dev/null +++ b/test/shell_value.conf @@ -0,0 +1,2 @@ +value1 = `cat value1` +value2 = 43 diff --git a/test/value1 b/test/value1 new file mode 100644 index 0000000..d81cc07 --- /dev/null +++ b/test/value1 @@ -0,0 +1 @@ +42