Make proper and neotoma dev-only dependencies
This patch makes erlware_commons easier to include as a dependency by removing depedencies that are not needed at run time. The top-level Makefile creates a .DEV_MODE marker file which is detected by rebar.config.script. When the marker file is present, the development only dependencies proper and neotoma are included and a macro 'DEV_ONLY' is defined. The macro is used to only enable the proper tests for development mode. The ec_semver_parser.peg is now located in priv/ and is moved into src/ by the Makefile. The generated ec_semver_parser.erl is now under version control; it need not be rebuilt by all projects wishing to include erlware_commons. It will be rebuilt, as before this change, on every make invocation.
This commit is contained in:
parent
38cd7a4d62
commit
d9c6ec1d28
7 changed files with 286 additions and 20 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -10,4 +10,5 @@ ebin/*
|
||||||
_build
|
_build
|
||||||
erl_crash.dump
|
erl_crash.dump
|
||||||
*.pyc
|
*.pyc
|
||||||
src/ec_semver_parser.erl
|
|
||||||
|
src/ec_semver_parser.peg
|
||||||
|
|
8
Makefile
8
Makefile
|
@ -19,15 +19,20 @@ ERLWARE_COMMONS_PLT=$(CURDIR)/.erlware_commons_plt
|
||||||
|
|
||||||
all: compile doc test #dialyzer #fail on travis
|
all: compile doc test #dialyzer #fail on travis
|
||||||
|
|
||||||
deps:
|
deps: .DEV_MODE
|
||||||
$(REBAR) get-deps compile
|
$(REBAR) get-deps compile
|
||||||
|
|
||||||
|
.DEV_MODE:
|
||||||
|
touch $@
|
||||||
|
cp priv/ec_semver_parser.peg src
|
||||||
|
|
||||||
get-deps:
|
get-deps:
|
||||||
$(REBAR) get-deps compile
|
$(REBAR) get-deps compile
|
||||||
|
|
||||||
compile: deps
|
compile: deps
|
||||||
$(REBAR) skip_deps=true compile
|
$(REBAR) skip_deps=true compile
|
||||||
|
|
||||||
|
|
||||||
doc: compile
|
doc: compile
|
||||||
- $(REBAR) skip_deps=true doc
|
- $(REBAR) skip_deps=true doc
|
||||||
|
|
||||||
|
@ -84,5 +89,6 @@ clean:
|
||||||
distclean: clean
|
distclean: clean
|
||||||
rm -rf $(ERLWARE_COMMONS_PLT).$(ERL_VER)
|
rm -rf $(ERLWARE_COMMONS_PLT).$(ERL_VER)
|
||||||
rm -rvf $(CURDIR)/deps
|
rm -rvf $(CURDIR)/deps
|
||||||
|
rm -rvf .DEV_MODE
|
||||||
|
|
||||||
rebuild: distclean get-deps all
|
rebuild: distclean get-deps all
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
|
%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
|
||||||
|
|
||||||
%% Dependencies ================================================================
|
%% Dependencies ================================================================
|
||||||
{deps, [{neotoma, "",
|
{deps, [{rebar_vsn_plugin, ".*",
|
||||||
{git, "https://github.com/seancribbs/neotoma.git", {branch, master}}},
|
{git, "https://github.com/erlware/rebar_vsn_plugin.git",
|
||||||
{proper, "", {git, "https://github.com/bkearns/proper.git", {branch, master}}},
|
{branch, "master"}}}]}.
|
||||||
{rebar_vsn_plugin, ".*", {git, "https://github.com/erlware/rebar_vsn_plugin.git",
|
|
||||||
{branch, "master"}}}]}.
|
|
||||||
|
|
||||||
{erl_first_files, ["ec_dictionary"]}.
|
{erl_first_files, ["ec_dictionary"]}.
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,42 @@
|
||||||
|
%% Merge the list values in `ToAdd' into the list found at key `Key'
|
||||||
|
%% in proplist `C'. Don't duplicate items. New Items are added to the
|
||||||
|
%% front of existing items. It is an error if the value at `Key' is
|
||||||
|
%% not a list in `C'.
|
||||||
|
MergeConfig = fun({Key, ToAdd}, C) ->
|
||||||
|
case lists:keyfind(Key, 1, C) of
|
||||||
|
false ->
|
||||||
|
lists:keystore(Key, 1, C, {Key, ToAdd});
|
||||||
|
{Key, List} when is_list(List) ->
|
||||||
|
%% remove items in ToAdd already in List
|
||||||
|
ToAdd1 = [ I || I <- ToAdd, not lists:member(I, List) ],
|
||||||
|
lists:keystore(Key, 1, C, {Key, ToAdd1 ++ List })
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
{match, [ErtsNumber]} = re:run(erlang:system_info(otp_release), "R(\\d+).+", [{capture, [1], list}]),
|
{match, [ErtsNumber]} = re:run(erlang:system_info(otp_release), "R(\\d+).+", [{capture, [1], list}]),
|
||||||
ErtsVsn = erlang:list_to_integer(ErtsNumber),
|
ErtsVsn = erlang:list_to_integer(ErtsNumber),
|
||||||
Opts1 = case lists:keysearch(erl_opts, 1, CONFIG) of
|
AddErlOpts = if
|
||||||
{value, {erl_opts, Opts0}} ->
|
ErtsVsn >= 15 ->
|
||||||
Opts0;
|
[{d, have_callback_support}];
|
||||||
false ->
|
true ->
|
||||||
[]
|
[]
|
||||||
end,
|
end,
|
||||||
Opts2 = if
|
|
||||||
ErtsVsn >= 15 ->
|
DevOnlyDeps = [{neotoma, "",
|
||||||
[{d, have_callback_support} | Opts1];
|
{git, "https://github.com/seancribbs/neotoma.git", {branch, master}}},
|
||||||
true ->
|
{proper, "",
|
||||||
Opts1
|
{git, "https://github.com/bkearns/proper.git", {branch, master}}}],
|
||||||
end,
|
|
||||||
lists:keystore(erl_opts, 1, CONFIG, {erl_opts, Opts2}).
|
Config1 = MergeConfig({erl_opts, AddErlOpts}, CONFIG),
|
||||||
|
|
||||||
|
ConfigPath = filename:dirname(SCRIPT),
|
||||||
|
DevMarker = filename:join([ConfigPath, ".DEV_MODE"]),
|
||||||
|
case filelib:is_file(DevMarker) of
|
||||||
|
true ->
|
||||||
|
lists:foldl(MergeConfig, Config1,
|
||||||
|
[{deps, DevOnlyDeps},
|
||||||
|
{erl_opts, [{d, 'DEV_ONLY'}]}]);
|
||||||
|
false ->
|
||||||
|
Config1
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
231
src/ec_semver_parser.erl
Normal file
231
src/ec_semver_parser.erl
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
-module(ec_semver_parser).
|
||||||
|
-export([parse/1,file/1]).
|
||||||
|
-compile({nowarn_unused_function,[p/4, p/5, p_eof/0, p_optional/1, p_not/1, p_assert/1, p_seq/1, p_and/1, p_choose/1, p_zero_or_more/1, p_one_or_more/1, p_label/2, p_string/1, p_anything/0, p_charclass/1, p_regexp/1, p_attempt/4, line/1, column/1]}).
|
||||||
|
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-spec file(file:name()) -> any().
|
||||||
|
file(Filename) -> {ok, Bin} = file:read_file(Filename), parse(Bin).
|
||||||
|
|
||||||
|
-spec parse(binary() | list()) -> any().
|
||||||
|
parse(List) when is_list(List) -> parse(list_to_binary(List));
|
||||||
|
parse(Input) when is_binary(Input) ->
|
||||||
|
setup_memo(),
|
||||||
|
Result = case 'semver'(Input,{{line,1},{column,1}}) of
|
||||||
|
{AST, <<>>, _Index} -> AST;
|
||||||
|
Any -> Any
|
||||||
|
end,
|
||||||
|
release_memo(), Result.
|
||||||
|
|
||||||
|
'semver'(Input, Index) ->
|
||||||
|
p(Input, Index, 'semver', fun(I,D) -> (p_seq([fun 'major_minor_patch_min_patch'/2, p_optional(p_seq([p_string(<<"-">>), fun 'alpha_part'/2, p_zero_or_more(p_seq([p_string(<<".">>), fun 'alpha_part'/2]))])), p_optional(p_seq([p_string(<<"+">>), fun 'alpha_part'/2, p_zero_or_more(p_seq([p_string(<<".">>), fun 'alpha_part'/2]))])), p_not(p_anything())]))(I,D) end, fun(Node, _Idx) -> ec_semver:internal_parse_version(Node) end).
|
||||||
|
|
||||||
|
'major_minor_patch_min_patch'(Input, Index) ->
|
||||||
|
p(Input, Index, 'major_minor_patch_min_patch', fun(I,D) -> (p_seq([p_choose([p_seq([p_optional(p_string(<<"v">>)), fun 'numeric_part'/2]), fun 'alpha_part'/2]), p_optional(p_seq([p_string(<<".">>), fun 'version_part'/2])), p_optional(p_seq([p_string(<<".">>), fun 'version_part'/2])), p_optional(p_seq([p_string(<<".">>), fun 'version_part'/2]))]))(I,D) end, fun(Node, Idx) ->transform('major_minor_patch_min_patch', Node, Idx) end).
|
||||||
|
|
||||||
|
'version_part'(Input, Index) ->
|
||||||
|
p(Input, Index, 'version_part', fun(I,D) -> (p_choose([fun 'numeric_part'/2, fun 'alpha_part'/2]))(I,D) end, fun(Node, Idx) ->transform('version_part', Node, Idx) end).
|
||||||
|
|
||||||
|
'numeric_part'(Input, Index) ->
|
||||||
|
p(Input, Index, 'numeric_part', fun(I,D) -> (p_one_or_more(p_charclass(<<"[0-9]">>)))(I,D) end, fun(Node, _Idx) ->erlang:list_to_integer(erlang:binary_to_list(erlang:iolist_to_binary(Node))) end).
|
||||||
|
|
||||||
|
'alpha_part'(Input, Index) ->
|
||||||
|
p(Input, Index, 'alpha_part', fun(I,D) -> (p_one_or_more(p_charclass(<<"[A-Za-z0-9]">>)))(I,D) end, fun(Node, _Idx) ->erlang:iolist_to_binary(Node) end).
|
||||||
|
|
||||||
|
|
||||||
|
transform(_,Node,_Index) -> Node.
|
||||||
|
|
||||||
|
p(Inp, Index, Name, ParseFun) ->
|
||||||
|
p(Inp, Index, Name, ParseFun, fun(N, _Idx) -> N end).
|
||||||
|
|
||||||
|
p(Inp, StartIndex, Name, ParseFun, TransformFun) ->
|
||||||
|
case get_memo(StartIndex, Name) of % See if the current reduction is memoized
|
||||||
|
{ok, Memo} -> %Memo; % If it is, return the stored result
|
||||||
|
Memo;
|
||||||
|
_ -> % If not, attempt to parse
|
||||||
|
Result = case ParseFun(Inp, StartIndex) of
|
||||||
|
{fail,_} = Failure -> % If it fails, memoize the failure
|
||||||
|
Failure;
|
||||||
|
{Match, InpRem, NewIndex} -> % If it passes, transform and memoize the result.
|
||||||
|
Transformed = TransformFun(Match, StartIndex),
|
||||||
|
{Transformed, InpRem, NewIndex}
|
||||||
|
end,
|
||||||
|
memoize(StartIndex, Name, Result),
|
||||||
|
Result
|
||||||
|
end.
|
||||||
|
|
||||||
|
setup_memo() ->
|
||||||
|
put({parse_memo_table, ?MODULE}, ets:new(?MODULE, [set])).
|
||||||
|
|
||||||
|
release_memo() ->
|
||||||
|
ets:delete(memo_table_name()).
|
||||||
|
|
||||||
|
memoize(Index, Name, Result) ->
|
||||||
|
Memo = case ets:lookup(memo_table_name(), Index) of
|
||||||
|
[] -> [];
|
||||||
|
[{Index, Plist}] -> Plist
|
||||||
|
end,
|
||||||
|
ets:insert(memo_table_name(), {Index, [{Name, Result}|Memo]}).
|
||||||
|
|
||||||
|
get_memo(Index, Name) ->
|
||||||
|
case ets:lookup(memo_table_name(), Index) of
|
||||||
|
[] -> {error, not_found};
|
||||||
|
[{Index, Plist}] ->
|
||||||
|
case proplists:lookup(Name, Plist) of
|
||||||
|
{Name, Result} -> {ok, Result};
|
||||||
|
_ -> {error, not_found}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
memo_table_name() ->
|
||||||
|
get({parse_memo_table, ?MODULE}).
|
||||||
|
|
||||||
|
p_eof() ->
|
||||||
|
fun(<<>>, Index) -> {eof, [], Index};
|
||||||
|
(_, Index) -> {fail, {expected, eof, Index}} end.
|
||||||
|
|
||||||
|
p_optional(P) ->
|
||||||
|
fun(Input, Index) ->
|
||||||
|
case P(Input, Index) of
|
||||||
|
{fail,_} -> {[], Input, Index};
|
||||||
|
{_, _, _} = Success -> Success
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
p_not(P) ->
|
||||||
|
fun(Input, Index)->
|
||||||
|
case P(Input,Index) of
|
||||||
|
{fail,_} ->
|
||||||
|
{[], Input, Index};
|
||||||
|
{Result, _, _} -> {fail, {expected, {no_match, Result},Index}}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
p_assert(P) ->
|
||||||
|
fun(Input,Index) ->
|
||||||
|
case P(Input,Index) of
|
||||||
|
{fail,_} = Failure-> Failure;
|
||||||
|
_ -> {[], Input, Index}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
p_and(P) ->
|
||||||
|
p_seq(P).
|
||||||
|
|
||||||
|
p_seq(P) ->
|
||||||
|
fun(Input, Index) ->
|
||||||
|
p_all(P, Input, Index, [])
|
||||||
|
end.
|
||||||
|
|
||||||
|
p_all([], Inp, Index, Accum ) -> {lists:reverse( Accum ), Inp, Index};
|
||||||
|
p_all([P|Parsers], Inp, Index, Accum) ->
|
||||||
|
case P(Inp, Index) of
|
||||||
|
{fail, _} = Failure -> Failure;
|
||||||
|
{Result, InpRem, NewIndex} -> p_all(Parsers, InpRem, NewIndex, [Result|Accum])
|
||||||
|
end.
|
||||||
|
|
||||||
|
p_choose(Parsers) ->
|
||||||
|
fun(Input, Index) ->
|
||||||
|
p_attempt(Parsers, Input, Index, none)
|
||||||
|
end.
|
||||||
|
|
||||||
|
p_attempt([], _Input, _Index, Failure) -> Failure;
|
||||||
|
p_attempt([P|Parsers], Input, Index, FirstFailure)->
|
||||||
|
case P(Input, Index) of
|
||||||
|
{fail, _} = Failure ->
|
||||||
|
case FirstFailure of
|
||||||
|
none -> p_attempt(Parsers, Input, Index, Failure);
|
||||||
|
_ -> p_attempt(Parsers, Input, Index, FirstFailure)
|
||||||
|
end;
|
||||||
|
Result -> Result
|
||||||
|
end.
|
||||||
|
|
||||||
|
p_zero_or_more(P) ->
|
||||||
|
fun(Input, Index) ->
|
||||||
|
p_scan(P, Input, Index, [])
|
||||||
|
end.
|
||||||
|
|
||||||
|
p_one_or_more(P) ->
|
||||||
|
fun(Input, Index)->
|
||||||
|
Result = p_scan(P, Input, Index, []),
|
||||||
|
case Result of
|
||||||
|
{[_|_], _, _} ->
|
||||||
|
Result;
|
||||||
|
_ ->
|
||||||
|
{fail, {expected, Failure, _}} = P(Input,Index),
|
||||||
|
{fail, {expected, {at_least_one, Failure}, Index}}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
p_label(Tag, P) ->
|
||||||
|
fun(Input, Index) ->
|
||||||
|
case P(Input, Index) of
|
||||||
|
{fail,_} = Failure ->
|
||||||
|
Failure;
|
||||||
|
{Result, InpRem, NewIndex} ->
|
||||||
|
{{Tag, Result}, InpRem, NewIndex}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
p_scan(_, [], Index, Accum) -> {lists:reverse( Accum ), [], Index};
|
||||||
|
p_scan(P, Inp, Index, Accum) ->
|
||||||
|
case P(Inp, Index) of
|
||||||
|
{fail,_} -> {lists:reverse(Accum), Inp, Index};
|
||||||
|
{Result, InpRem, NewIndex} -> p_scan(P, InpRem, NewIndex, [Result | Accum])
|
||||||
|
end.
|
||||||
|
|
||||||
|
p_string(S) when is_list(S) -> p_string(list_to_binary(S));
|
||||||
|
p_string(S) ->
|
||||||
|
Length = erlang:byte_size(S),
|
||||||
|
fun(Input, Index) ->
|
||||||
|
try
|
||||||
|
<<S:Length/binary, Rest/binary>> = Input,
|
||||||
|
{S, Rest, p_advance_index(S, Index)}
|
||||||
|
catch
|
||||||
|
error:{badmatch,_} -> {fail, {expected, {string, S}, Index}}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
p_anything() ->
|
||||||
|
fun(<<>>, Index) -> {fail, {expected, any_character, Index}};
|
||||||
|
(Input, Index) when is_binary(Input) ->
|
||||||
|
<<C/utf8, Rest/binary>> = Input,
|
||||||
|
{<<C/utf8>>, Rest, p_advance_index(<<C/utf8>>, Index)}
|
||||||
|
end.
|
||||||
|
|
||||||
|
p_charclass(Class) ->
|
||||||
|
{ok, RE} = re:compile(Class, [unicode, dotall]),
|
||||||
|
fun(Inp, Index) ->
|
||||||
|
case re:run(Inp, RE, [anchored]) of
|
||||||
|
{match, [{0, Length}|_]} ->
|
||||||
|
{Head, Tail} = erlang:split_binary(Inp, Length),
|
||||||
|
{Head, Tail, p_advance_index(Head, Index)};
|
||||||
|
_ -> {fail, {expected, {character_class, binary_to_list(Class)}, Index}}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
p_regexp(Regexp) ->
|
||||||
|
{ok, RE} = re:compile(Regexp, [unicode, dotall, anchored]),
|
||||||
|
fun(Inp, Index) ->
|
||||||
|
case re:run(Inp, RE) of
|
||||||
|
{match, [{0, Length}|_]} ->
|
||||||
|
{Head, Tail} = erlang:split_binary(Inp, Length),
|
||||||
|
{Head, Tail, p_advance_index(Head, Index)};
|
||||||
|
_ -> {fail, {expected, {regexp, binary_to_list(Regexp)}, Index}}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
line({{line,L},_}) -> L;
|
||||||
|
line(_) -> undefined.
|
||||||
|
|
||||||
|
column({_,{column,C}}) -> C;
|
||||||
|
column(_) -> undefined.
|
||||||
|
|
||||||
|
p_advance_index(MatchedInput, Index) when is_list(MatchedInput) orelse is_binary(MatchedInput)-> % strings
|
||||||
|
lists:foldl(fun p_advance_index/2, Index, unicode:characters_to_list(MatchedInput));
|
||||||
|
p_advance_index(MatchedInput, Index) when is_integer(MatchedInput) -> % single characters
|
||||||
|
{{line, Line}, {column, Col}} = Index,
|
||||||
|
case MatchedInput of
|
||||||
|
$\n -> {{line, Line+1}, {column, 1}};
|
||||||
|
_ -> {{line, Line}, {column, Col+1}}
|
||||||
|
end.
|
|
@ -5,6 +5,8 @@
|
||||||
%% proper:module(ec_dictionary_proper).
|
%% proper:module(ec_dictionary_proper).
|
||||||
-module(ec_dictionary_proper).
|
-module(ec_dictionary_proper).
|
||||||
|
|
||||||
|
-ifdef(DEV_ONLY).
|
||||||
|
|
||||||
-export([my_dict/0, dict/1, sym_dict/0, sym_dict/1, gb_tree/0, gb_tree/1, sym_dict2/0]).
|
-export([my_dict/0, dict/1, sym_dict/0, sym_dict/1, gb_tree/0, gb_tree/1, sym_dict2/0]).
|
||||||
|
|
||||||
-include_lib("proper/include/proper.hrl").
|
-include_lib("proper/include/proper.hrl").
|
||||||
|
@ -221,3 +223,4 @@ gb_tree(0) ->
|
||||||
gb_tree(N) ->
|
gb_tree(N) ->
|
||||||
gb_trees:enter(key(),value(),gb_tree(N-1)).
|
gb_trees:enter(key(),value(),gb_tree(N-1)).
|
||||||
|
|
||||||
|
-endif.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue