Current section

Files

Jump to
cuttlefish src conf_parse.peg
Raw

src/conf_parse.peg

%% -------------------------------------------------------------------
%%
%% conf_parse: for all your .conf parsing needs.
%%
%% Copyright (c) 2013 Basho Technologies, Inc. All Rights Reserved.
%% Copyright (c) 2019 Pivotal Software, Inc. All rights reserved.
%% Copyright (c) 2020 VMware, Inc. or its affiliates. All rights reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
%% except in compliance with the License. You may obtain
%% a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
%%
%% -------------------------------------------------------------------
% -------------------------------------------------------------------
% NOTE: IMPORTANT
%
% After using neotoma to re-generate conf_parse.erl, you MUST
% edit that file to change the exported file/1 function to this code:
%
% -spec file(file:name()) -> any().
% file(Filename) ->
% AbsFilename = filename:absname(Filename),
% case erl_prim_loader:get_file(AbsFilename) of
% {ok, Bin, _} -> parse(Bin);
% error -> {error, undefined}
% end.
%
% The reason is that the above code allows for reading cuttlefish
% schemas from .ez archives
% -------------------------------------------------------------------
%% A configuration file may have zero-or-more lines.
config <- line* %{
[ L || L <- Node, is_setting(L) ]
%};
%% Lines are actual settings, includes, comments, or horizontal whitespace,
%% terminated by an end-of-line or end-of-file.
line <- ((setting / include / comment / ws+) (crlf / eof)) / crlf %{
case Node of
[ Line, _EOL ] -> Line;
Line -> Line
end
%};
%% A setting is a key and a value, joined by =, with surrounding
%% whitespace ignored.
setting <- ws* key ws* "=" ws* value ws* comment? %{
[ _, Key, _, _Eq, _, Value, _, _ ] = Node,
{Key, Value}
%};
%% A key is a series of dot-separated identifiers.
key <- head:word tail:("." word)* %{
[{head, H}, {tail, T}] = Node,
[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) .)+ %{
case unicode:characters_to_binary(Node, utf8, latin1) of
{_Status, _Begining, _Rest} ->
{error, {conf_to_latin1, line(Idx)}};
Bin ->
binary_to_list(Bin)
end
%};
%% A comment is any line that begins with a # sign, leading whitespace
%% allowed.
comment <- ws* "#" (!crlf .)* `comment`;
%% An include is a line that begins with 'include' and something.
include <- ws* "include" ws* included_file_or_dir comment? %{
[_, _Include, _, Included, _] = Node,
{include, Included}
%};
included_file_or_dir <- [A-Za-z0-9-\_\.\*\/]+ %{
unicode:characters_to_binary(Node, utf8, latin1)
%};
%% A word is one or more of letters, numbers and dashes or
%% underscores.
word <- ("\\." / [A-Za-z0-9_-])+ %{
unescape_dots(unicode:characters_to_list(Node))
%};
%% An end-of-line is signified by a line-feed with an optional
%% preceding carriage-return.
crlf <- "\r"? "\n" `ws`;
%% The end-of-file is where no character matches.
eof <- !. `ws`;
%% Whitespace is either spaces or tabs.
ws <- [ \t]+ `ws`;
% Erlang code
%{
%% -------------------------------------------------------------------
%%
%% conf_parse: for all your .conf parsing needs.
%%
%% Copyright (c) 2013 Basho Technologies, Inc. All Rights Reserved.
%% Copyright (c) 2019 Pivotal Software, Inc. All rights reserved.
%% Copyright (c) 2020 VMware, Inc. or its affiliates. All rights reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
%% except in compliance with the License. You may obtain
%% a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
%%
%% -------------------------------------------------------------------
%% This module implements the parser for a sysctl-style
%% configuration format. Example:
%%
%% ```
%% riak.local.node = riak@127.0.0.1
%% riak.local.http = 127.0.0.1:8098
%% riak.local.pb = 127.0.0.1:8087
%% riak.local.storage.backend = bitcask'''
%%
%% This would parse into the following flat proplist:
%%
%% ```
%% [{<<"riak.local.node">>,<<"riak@127.0.0.1">>},
%% {<<"riak.local.http">>,<<"127.0.0.1:8098">>},
%% {<<"riak.local.pb">>,<<"127.0.0.1:8087">>},
%% {<<"riak.local.storage.backend">>,<<"bitcask">>}]'''
%%
%% Other modules in this application interpret and validate the
%% result of a successful parse.
%% @end
-define(line, true).
-define(FMT(F,A), lists:flatten(io_lib:format(F,A))).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
%% @doc Only let through lines that are not comments or whitespace.
is_setting(ws) -> false;
is_setting([ws]) -> false;
is_setting(comment) -> false;
is_setting(_) -> true.
%% @doc Removes escaped dots from keys
unescape_dots([$\\,$.|Rest]) ->
[$.|unescape_dots(Rest)];
unescape_dots([]) -> [];
unescape_dots([C|Rest]) ->
[C|unescape_dots(Rest)].
-ifdef(TEST).
file_test() ->
Conf = conf_parse:file("test/riak.conf"),
?assertEqual([
{["ring_size"],"32"},
{["anti_entropy"],"debug"},
{["log","error","file"],"/var/log/error.log"},
{["log","console","file"],"/var/log/console.log"},
{["log","syslog"],"on"},
{["listener","http","internal"],"127.0.0.1:8098"},
{["listener","http","external"],"10.0.0.1:80"}
], Conf),
ok.
included_file_test() ->
Conf = conf_parse:file("test/include_file.conf"),
?assertEqual([
{include,<<"riak.conf">>}
], Conf),
ok.
included_dir_test() ->
Conf = conf_parse:file("test/include_dir.conf"),
?assertEqual([
{include,<<"conf.d/*.conf">>}
], Conf),
ok.
escaped_dots_are_removed_test() ->
Conf = conf_parse:parse("#comment\nsetting\\.0 = thing0\n"),
?assertEqual([
{["setting.0"],"thing0"}
], Conf),
ok.
utf8_test() ->
Conf = conf_parse:parse("setting = thing" ++ [338] ++ "\n"),
?assertEqual([{["setting"],
{error, {conf_to_latin1, 1}}
}], Conf),
ok.
gh_1_two_tab_test() ->
Conf = conf_parse:parse("setting0 = thing0\n\t\t\nsetting1 = thing1\n"),
?assertEqual([
{["setting0"],"thing0"},
{["setting1"],"thing1"}
], Conf),
ok.
gh_1_three_tab_test() ->
Conf = conf_parse:parse("setting0 = thing0\n\t\t\t\nsetting1 = thing1\n"),
?assertEqual([
{["setting0"],"thing0"},
{["setting1"],"thing1"}
], Conf),
ok.
-endif.
%}