%% -------------------------------------------------------------------
%%
%% conf_parse: for all your .conf parsing needs.
%%
%% Copyright (c) 2013 Basho Technologies, Inc.  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.
%%
%% -------------------------------------------------------------------

%% A configuration file may have zero-or-more lines.
config <- line* %{
    [ L || L <- Node, is_setting(L) ]
%};

%% Lines are actual settings, comments, or horizontal whitespace,
%% terminated by an end-of-line or end-of-file.
line <- ((setting / 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`;

%% 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.
%%
%% 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").
-compile(export_all).
-endif.

%% @doc Only let through lines that are not comments or whitespace.
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.

utf8_test() ->
    Conf = conf_parse:parse("setting = thing" ++ [338] ++ "\n"),
    ?assertEqual([{["setting"],
            {error, {conf_to_latin1, 1}}
        }], Conf),
    ok.
-endif.
%}
