Permalink
Browse files

Merge branch 'release/1.0.0'

  • Loading branch information...
2 parents 06cf9b7 + f7adf75 commit 56f817053bc9b087ccb75204601dc7bf42a3c45e Shunichi Shinohara committed Aug 15, 2011
Showing with 308 additions and 192 deletions.
  1. +24 −11 README.rst
  2. +13 −9 exapmles/test.escript
  3. BIN rebar
  4. +0 −2 rebar.config
  5. +1 −1 src/eini.app.src
  6. +53 −44 src/eini.erl
  7. +11 −6 src/eini_lexer.xrl
  8. +13 −16 src/eini_parser.yrl
  9. +193 −103 test/eini_tests.erl
View
@@ -9,25 +9,38 @@ Example
Input file::
- [title1 "subtitle1"]
+ [title1]
key = value
key2 = value2
- [title1 "subtitle2"]
- key = value
[title2]
key = value
Result form::
[
- {"title1", [{"subtitle1",
- [{"key", "value"},
- {"key2", "value2"}]
- },
- {"subbitle2", [{"key", "value"}]}
- ]},
- {"title2", [{default, [{"key", "value"}]}]}
+ {title1,
+ [{key, <<"value">>},
+ {key2, <<"value2">>}]},
+ {title2,
+ [{key, <<"value">>}]}
].
+History
+=======
+
+1.0.0
+-----
+
+:release: 2011-08-15
+:summary: Initial release
+
+Copyright
+=========
+
+Copyright 2011 by Accense Technology, Inc.
+
+License
+=======
-Copyright by Accense Technology, Inc.
+Apache License v2.
+See ``LICENSE`` file for detail.
View
@@ -7,17 +7,21 @@
main(_) ->
code:add_patha("../ebin/"),
- String = "\n[cat1]\n\nkey=va[lu]e\n",
+ String = "[cat1]\nkey=value\n",
+%% String = "[cat\t1]",
go(String),
ok.
go(String) ->
- {ok, Tokens} = eini:lex(String),
- io:format("~p~n", [Tokens]),
- case eini:parse_tokens(Tokens) of
- {ok, Res} ->
- io:format("~p~n", [Res]);
+ case eini:lex(String) of
+ {ok, Tokens} ->
+ io:format("~p~n", [Tokens]),
+ case eini:parse_tokens(Tokens) of
+ {ok, Res} ->
+ io:format("~p~n", [Res]);
+ {error, {Line, Reason}} ->
+ io:format("Parse error at line ~B: ~s~n", [Line, Reason])
+ end;
{error, {Line, Reason}} ->
- io:format("Error at line ~B: ~s~n", [Line, Reason])
- end,
- ok.
+ io:format("Lex error at line ~B: ~s~n", [Line, Reason])
+ end.
View
BIN rebar
Binary file not shown.
View
@@ -1,5 +1,3 @@
-{require_otp_vsn, "R14B02"}.
-
{erl_opts, [warnings_as_errors,
warn_export_all,
warn_unused_import,
View
@@ -1,6 +1,6 @@
{application, eini, [
{description, "An Erlang INI parser"},
- {vsn, "0.1.0"},
+ {vsn, "1.0.0"},
{applications, [kernel, stdlib]},
{modules, []},
{registered, []}
View
@@ -19,56 +19,42 @@
-author('shino@accense.com').
--export([parse_string/1, parse_file/1]).
+-export([parse/1]).
+
%% for debug use
-export([lex/1, parse_tokens/1]).
-%% TODO(shino): Add spec's
+-type sections() :: [section()].
+-type section() :: {Title::atom(), [property()]}.
+-type property() :: {Key::atom(), Value::binary()}.
-%% Input:
-%%
-%% [title1 "subtitle1"]
-%% key = value
-%% [title1 "subtitle2"]
-%% key = value
-%% [title2]
-%% key = value
-%%
-%% Result form:
-%%
-%% [
-%% {"title1", [{"subtitle1", KVs}},
-%% {"subbitle2", KVs}}],
-%% {"title2", {default, KVs}}
-%% ].
-%%
-%% KVs are proplists of keys and values
-parse_string(String) when is_binary(String) ->
- parse_string(binary_to_list(String));
-parse_string(String) when is_list(String) ->
- case lex(String) of
+-type reason() :: {illegal_character, Line::integer(), Reason::string()}
+ | {syntax_error, Line::integer(), Reason::string()}
+ | {duplicate_title, Title::binary()}
+ | {duplicate_key, Title::binary(), Key::binary()}.
+
+-spec parse(Content:: string() | binary()) -> {ok, sections()}
+ | {error, reason()}.
+parse(Content) when is_binary(Content) ->
+ parse(binary_to_list(Content));
+parse(Content) when is_list(Content) ->
+ case lex(Content) of
{ok, Tokens} ->
- parse_and_collect(Tokens);
+ parse_and_validate(Tokens);
{error, Reason} ->
{error, Reason}
end.
-parse_and_collect(Tokens) ->
+parse_and_validate(Tokens) ->
case parse_tokens(Tokens) of
{ok, Parsed} ->
- collect_subsection(proplists:get_keys(Parsed), Parsed, []);
+ validate(Parsed);
{error, Reason} ->
{error, Reason}
end.
-parse_file(Filename) ->
- case file:read_file(Filename) of
- {ok, Binary} -> parse_string(Binary);
- Error -> Error
- end.
-
-lex(String) when is_binary(String) ->
- lex(binary_to_list(String));
+-spec lex(string()) -> {ok, list(Token::tuple())}
+ | {error, {illegal_character, Line::integer(), Reason::string()}}.
lex(String) when is_list(String) ->
%% Add \n char at the end if does NOT end by \n
%% TOD(shino): more simple logic?
@@ -88,21 +74,44 @@ lex(String) when is_list(String) ->
{ok, RestTokens};
{ok, Tokens, _EndLine} ->
{ok, Tokens};
- ErrorInfo ->
- {error, ErrorInfo}
+ {error, {ErrorLine, Mod, Reason}, _EndLine} ->
+ {error, {illegal_character, ErrorLine, Mod:format_error(Reason)}}
end.
+-spec parse_tokens(Token::tuple()) ->
+ {ok, sections()}
+ | {error, {syntax_error, Line::integer(), Reason::string()}}.
parse_tokens(Tokens) ->
case eini_parser:parse(Tokens) of
{ok, Res} ->
{ok, Res};
{error, {Line, Mod, Reason}} ->
- {error, {Line, Mod:format_error(Reason)}}
+ {error, {syntax_error, Line, Mod:format_error(Reason)}}
+ end.
+
+-spec validate(sections()) ->
+ {ok, sections()}
+ | {error, {duplicate_title, Title::binary()}}
+ | {error, {duplicate_key, Title::binary(), Key::binary()}}.
+validate(Sections) ->
+ validate(Sections, [], []).
+
+validate([], _AccTitles, AccSections) ->
+ {ok, lists:reverse(AccSections)};
+validate([{Title, Properties} = Section | Sections], AccTitles, AccSections) ->
+ case lists:member(Title, AccTitles) of
+ true ->
+ {error, {duplicate_title, Title}};
+ false ->
+ validate(Sections, [Title|AccTitles], [Section|AccSections], Properties, [])
end.
-collect_subsection([], _Parsed, Res) ->
- {ok, Res};
-collect_subsection([Key|Keys], Parsed, Res) ->
- Subsections = proplists:get_all_values(Key, Parsed),
- collect_subsection(Keys, Parsed, [{Key, Subsections} | Res]).
-
+validate(Sections, AccTitles, AccSections, [], _AccKeys) ->
+ validate(Sections, AccTitles, AccSections);
+validate(Sections, AccTitles, AccSections, [{Key, _Value}|Properties], AccKeys) ->
+ case lists:member(Key, AccKeys) of
+ true ->
+ {error, {duplicate_key, hd(AccTitles), Key}};
+ false ->
+ validate(Sections, AccTitles, AccSections, Properties, [Key|AccKeys])
+ end.
View
@@ -19,16 +19,22 @@
Definitions.
-K = [a-z][a-zA-Z0-9_\.]*
-V = [^=\[\]\s\t\n\r]+
+%% Characters for keys
+K = [a-zA-Z0-9_\.]+
+
+%% Characters for values, printable except =, [ and ]
+%% \x3b : $;
+%% \x3d : $=
+%% \x5b : $[
+%% \x5d : $]
+V = [\x21-\x3a\x3c\x3e-\x5a\x5c\x5e-\x7e]+
+
+%% spaces and breaks
S = [\s\t]
B = [\n\r]
Rules.
-%% skip comment line,which has ; at the beginning of line
-%% {B};.*{B} : {skip_token, "\n"}.
-
%% skip empty lines or lines with space/tab chars
{B}{S}*{B} : {skip_token, "\n"}.
@@ -42,7 +48,6 @@ Rules.
%% word-like tokens
{S}+ : {token, {blank, TokenLine, TokenChars}}.
-"{K}" : {token, {quoted, TokenLine, TokenChars}}.
{K} : {token, {word, TokenLine, TokenChars}}.
{V} : {token, {value, TokenLine, TokenChars}}.
View
@@ -33,7 +33,7 @@ Nonterminals
Terminals
'[' ']' '='
blank
- word quoted
+ word
value
comment
break.
@@ -48,24 +48,23 @@ whole -> blank_line skip_lines sections : '$3'.
sections -> '$empty' : [].
sections -> section sections : ['$1' | '$2'].
-section -> title_part properties :
- {title('$1'), {subtitle('$1'), '$2'}}.
+section -> title_part properties : {'$1', '$2'}.
-title_part -> title break : '$1'.
-title_part -> title blank break : '$1'.
-title_part -> title break skip_lines : '$1'.
-title_part -> title blank break skip_lines : '$1'.
+title_part -> title break : list_to_atom('$1').
+title_part -> title blank break : list_to_atom('$1').
+title_part -> title break skip_lines : list_to_atom('$1').
+title_part -> title blank break skip_lines : list_to_atom('$1').
-title -> '[' word ']' : {value_of('$2'), default}.
-title -> '[' word blank quoted ']' : {value_of('$2'), value_of('$4')}.
+title -> '[' word ']' : value_of('$2').
properties -> '$empty' : [].
properties -> property_with_skip_lines properties : ['$1' | '$2'].
property_with_skip_lines -> property : '$1'.
property_with_skip_lines -> property skip_lines : '$1'.
-property -> key_part '=' values break : {value_of('$1'), strip_values('$3')}.
+property -> key_part '=' values break :
+ {list_to_atom(value_of('$1')), strip_values('$3')}.
key_part -> word : '$1'.
key_part -> word blank : '$1'.
@@ -95,13 +94,11 @@ blank_line -> blank break : '$1'.
Erlang code.
+-spec value_of({atom(), _Line, TokenChars::string()}) -> TokenChars::string().
value_of(Token) ->
element(3, Token).
-title({Title, _Subtitle}) ->
- Title.
-subtitle({_Title, Subtitle}) ->
- Subtitle.
-
+-spec strip_values([TokenChars::string()]) -> Value::binary().
strip_values(Values) ->
- string:strip(string:strip(lists:flatten(Values), both, $\s), both, $\t).
+ String = string:strip(string:strip(lists:flatten(Values), both, $\s), both, $\t),
+ list_to_binary(String).
Oops, something went wrong.

0 comments on commit 56f8170

Please sign in to comment.