diff --git a/.gitignore b/.gitignore index e85b87e..443f45b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,13 @@ -*.beam +.erlware_commons_plt +.eunit/* +deps/* +doc/*.html +doc/*.css +doc/edoc-info +doc/erlang.png +ebin/* .* _build erl_crash.dump *.pyc +src/ec_semver_parser.erl diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..35c5897 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: erlang +otp_release: + - R15B02 + - R15B01 + - R15B + +before_script: "make get-deps" +script: "make" +branches: + only: + - master + - next diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..933ce28 --- /dev/null +++ b/Makefile @@ -0,0 +1,64 @@ +# Copyright 2012 Erlware, LLC. All Rights Reserved. +# +# BSD License see COPYING + +ERL = $(shell which erl) + +ERLFLAGS= -pa $(CURDIR)/.eunit -pa $(CURDIR)/ebin -pa $(CURDIR)/*/ebin + +REBAR=$(shell which rebar) + +ifeq ($(REBAR),) +$(error "Rebar not available on this system") +endif + +ERLWARE_COMMONS_PLT=$(CURDIR)/.erlware_commons_plt + +.PHONY: all compile doc clean test dialyzer typer shell distclean pdf get-deps escript + +all: compile test dialyzer + +get-deps: + $(REBAR) get-deps + $(REBAR) compile + +compile: + $(REBAR) skip_deps=true compile + +doc: compile + $(REBAR) skip_deps=true doc + +test: compile + $(REBAR) skip_deps=true eunit + +$(ERLWARE_COMMONS_PLT): + @echo Building local plt at $(ERLWARE_COMMONS_PLT) + @echo + - dialyzer --output_plt $(ERLWARE_COMMONS_PLT) --build_plt \ + --apps erts kernel stdlib eunit -r deps + +dialyzer: $(ERLWARE_COMMONS_PLT) + dialyzer --plt $(ERLWARE_COMMONS_PLT) -Wrace_conditions --src src + +typer: + typer --plt $(ERLWARE_COMMONS_PLT) -r ./src + +shell: compile +# You often want *rebuilt* rebar tests to be available to the +# shell you have to call eunit (to get the tests +# rebuilt). However, eunit runs the tests, which probably +# fails (thats probably why You want them in the shell). This +# runs eunit but tells make to ignore the result. + - @$(REBAR) skip_deps=true eunit + @$(ERL) $(ERLFLAGS) + +clean: + $(REBAR) skip_deps=true clean + - rm $(CURDIR)/doc/*.html + - rm $(CURDIR)/doc/*.css + - rm $(CURDIR)/doc/*.png + - rm $(CURDIR)/doc/edoc-info + +distclean: clean + rm -rf $(ERLWARE_COMMONS_PLT) + rm -rvf $(CURDIR)/deps/* diff --git a/README.md b/README.md index 5681fe6..f2cab01 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ Erlware Commons =============== +Current Status +-------------- +[](http://travis-ci.org/erlware/erlware_commons) + Introduction ------------ diff --git a/rebar.config b/rebar.config index 27aa310..6284cba 100644 --- a/rebar.config +++ b/rebar.config @@ -1,3 +1,12 @@ +%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*- + +%% These are all only compile time dependencies +{deps, [{neotoma, "", + {git, "https://github.com/seancribbs/neotoma.git", {tag, "1.5"}}}, + {proper, "", {git, "https://github.com/manopapad/proper.git", {branch, master}}}]}. + +{erl_first_files, ["ec_dictionary"]}. + {erl_opts, [debug_info, - warnings_as_errors]}. \ No newline at end of file + warnings_as_errors]}. diff --git a/src/ec_assoc_list.erl b/src/ec_assoc_list.erl index 0ea9d68..e9bd0cc 100644 --- a/src/ec_assoc_list.erl +++ b/src/ec_assoc_list.erl @@ -4,8 +4,8 @@ %%% @doc %%% provides an implementation of ec_dictionary using an association %%% list as a basy +%%% see ec_dictionary %%% @end -%%% @see ec_dictionary %%%------------------------------------------------------------------- -module(ec_assoc_list). @@ -29,8 +29,10 @@ %%%=================================================================== %%% Types %%%=================================================================== --opaque dictionary(K, V) :: {ec_assoc_list, - [{ec_dictionary:key(K), ec_dictionary:value(V)}]}. +%% This should be opaque, but that kills dialyzer so for now we export it +%% however you should not rely on the internal representation here +-type dictionary(K, V) :: {ec_assoc_list, + [{ec_dictionary:key(K), ec_dictionary:value(V)}]}. %%%=================================================================== %%% API @@ -45,12 +47,12 @@ has_key(Key, {ec_assoc_list, Data}) -> lists:keymember(Key, 1, Data). -spec get(ec_dictionary:key(K), Object::dictionary(K, V)) -> - ec_dictionary:value(V). + ec_dictionary:value(V). get(Key, {ec_assoc_list, Data}) -> case lists:keyfind(Key, 1, Data) of {Key, Value} -> Value; - false -> + false -> throw(not_found) end. @@ -62,19 +64,19 @@ get(Key, Default, {ec_assoc_list, Data}) -> case lists:keyfind(Key, 1, Data) of {Key, Value} -> Value; - false -> + false -> Default end. -spec add(ec_dictionary:key(K), ec_dictionary:value(V), Object::dictionary(K, V)) -> - dictionary(K, V). + dictionary(K, V). add(Key, Value, {ec_assoc_list, _Data}=Dict) -> {ec_assoc_list, Rest} = remove(Key,Dict), {ec_assoc_list, [{Key, Value} | Rest ]}. -spec remove(ec_dictionary:key(K), Object::dictionary(K, _V)) -> - dictionary(K, _V). + dictionary(K, _V). remove(Key, {ec_assoc_list, Data}) -> {ec_assoc_list, lists:keydelete(Key, 1, Data)}. @@ -82,22 +84,22 @@ remove(Key, {ec_assoc_list, Data}) -> has_value(Value, {ec_assoc_list, Data}) -> lists:keymember(Value, 2, Data). --spec size(Object::dictionary(_K, _V)) -> integer(). +-spec size(Object::dictionary(_K, _V)) -> non_neg_integer(). size({ec_assoc_list, Data}) -> length(Data). -spec to_list(dictionary(K, V)) -> [{ec_dictionary:key(K), ec_dictionary:value(V)}]. to_list({ec_assoc_list, Data}) -> - Data. + Data. -spec from_list([{ec_dictionary:key(K), ec_dictionary:value(V)}]) -> - dictionary(K, V). + dictionary(K, V). from_list(List) when is_list(List) -> {ec_assoc_list, List}. -spec keys(dictionary(K, _V)) -> [ec_dictionary:key(K)]. keys({ec_assoc_list, Data}) -> lists:map(fun({Key, _Value}) -> - Key - end, Data). + Key + end, Data). diff --git a/src/ec_dict.erl b/src/ec_dict.erl index 2eb61e7..1c5d319 100644 --- a/src/ec_dict.erl +++ b/src/ec_dict.erl @@ -5,9 +5,9 @@ %%% This provides an implementation of the ec_dictionary type using %%% erlang dicts as a base. The function documentation for %%% ec_dictionary applies here as well. +%%% see ec_dictionary +%%% see dict %%% @end -%%% @see ec_dictionary -%%% @see dict %%%------------------------------------------------------------------- -module(ec_dict). @@ -31,7 +31,9 @@ %%%=================================================================== %%% Types %%%=================================================================== --opaque dictionary(_K, _V) :: dict(). +%% This should be opaque, but that kills dialyzer so for now we export it +%% however you should not rely on the internal representation here +-type dictionary(_K, _V) :: dict(). %%%=================================================================== %%% API @@ -88,7 +90,7 @@ has_value(Value, Data) -> false, Data). --spec size(Object::dictionary(_K, _V)) -> integer(). +-spec size(Object::dictionary(_K, _V)) -> non_neg_integer(). size(Data) -> dict:size(Data). diff --git a/src/ec_dictionary.erl b/src/ec_dictionary.erl index bc2fc91..1061665 100644 --- a/src/ec_dictionary.erl +++ b/src/ec_dictionary.erl @@ -10,9 +10,6 @@ %%%------------------------------------------------------------------- -module(ec_dictionary). -%%% Behaviour Callbacks --export([behaviour_info/1]). - %% API -export([new/1, has_key/2, @@ -38,30 +35,27 @@ {callback, data}). --opaque dictionary(_K, _V) :: #dict_t{}. +%% This should be opaque, but that kills dialyzer so for now we export it +%% however you should not rely on the internal representation here +-type dictionary(_K, _V) :: #dict_t{}. -type key(T) :: T. -type value(T) :: T. +-callback new() -> any(). +-callback has_key(key(any()), any()) -> boolean(). +-callback get(key(any()), any()) -> any(). +-callback add(key(any()), value(any()), T) -> T. +-callback remove(key(any()), T) -> T. +-callback has_value(value(any()), any()) -> boolean(). +-callback size(any()) -> non_neg_integer(). +-callback to_list(any()) -> [{key(any()), value(any())}]. +-callback from_list([{key(any()), value(any())}]) -> any(). +-callback keys(any()) -> [key(any())]. + %%%=================================================================== %%% API %%%=================================================================== -%% @doc export the behaviour callbacks for this type -%% @private -behaviour_info(callbacks) -> - [{new, 0}, - {has_key, 2}, - {get, 2}, - {add, 3}, - {remove, 2}, - {has_value, 2}, - {size, 1}, - {to_list, 1}, - {from_list, 1}, - {keys, 1}]; -behaviour_info(_) -> - undefined. - %% @doc create a new dictionary object from the specified module. The %% module should implement the dictionary behaviour. %% @@ -83,7 +77,7 @@ has_key(Key, #dict_t{callback = Mod, data = Data}) -> %% %% @param Dict The dictionary object to return the value from %% @param Key The key requested -%% @throws not_found when the key does not exist +%% when the key does not exist @throws not_found -spec get(key(K), dictionary(K, V)) -> value(V). get(Key, #dict_t{callback = Mod, data = Data}) -> Mod:get(Key, Data). diff --git a/src/ec_file.erl b/src/ec_file.erl index f26149e..c5cbe56 100644 --- a/src/ec_file.erl +++ b/src/ec_file.erl @@ -23,34 +23,24 @@ ]). -export_type([ - path/0, option/0 ]). -include_lib("kernel/include/file.hrl"). -%% User friendly exception message (remove line and module info once we -%% get them in stack traces) --define(UEX(Exception, UMSG, UVARS), - {uex, {?MODULE, - ?LINE, - Exception, - lists:flatten(io_lib:fwrite(UMSG, UVARS))}}). - -define(CHECK_PERMS_MSG, "Try checking that you have the correct permissions and try again~n"). %%============================================================================ %% Types %%============================================================================ --type path() :: string(). --type option() :: [atom()]. - +-type option() :: recursive. +-type void() :: ok. %%%=================================================================== %%% API %%%=================================================================== %% @doc copy an entire directory to another location. --spec copy(path(), path(), Options::[option()]) -> ok. +-spec copy(file:name(), file:name(), Options::[option()]) -> void(). copy(From, To, []) -> copy(From, To); copy(From, To, [recursive] = Options) -> @@ -63,12 +53,23 @@ copy(From, To, [recursive] = Options) -> end. %% @doc copy a file including timestamps,ownership and mode etc. --spec copy(From::string(), To::string()) -> ok. +-spec copy(From::file:filename(), To::file:filename()) -> ok | {error, Reason::term()}. copy(From, To) -> - try - ec_file_copy(From, To) - catch - _C:E -> throw(?UEX({copy_failed, E}, ?CHECK_PERMS_MSG, [])) + case file:copy(From, To) of + {ok, _} -> + case file:read_file_info(From) of + {ok, FileInfo} -> + case file:write_file_info(To, FileInfo) of + ok -> + ok; + {error, WFError} -> + {error, {write_file_info_failed, WFError}} + end; + {error, RFError} -> + {error, {read_file_info_failed, RFError}} + end; + {error, Error} -> + {error, {copy_failed, Error}} end. %% @doc return an md5 checksum string or a binary. Same as unix utility of @@ -81,21 +82,21 @@ md5sum(Value) -> %%
%% Example: remove("./tmp_dir", [recursive]). %%--spec remove(path(), Options::[option()]) -> ok | {error, Reason::term()}. +-spec remove(file:name(), Options::[option()]) -> ok | {error, Reason::term()}. remove(Path, Options) -> - try - ok = ec_file_remove(Path, Options) - catch - _C:E -> throw(?UEX({remove_failed, E}, ?CHECK_PERMS_MSG, [])) + case lists:member(recursive, Options) of + false -> file:delete(Path); + true -> remove_recursive(Path, Options) end. + %% @doc delete a file. --spec remove(path()) -> ok | {error, Reason::term()}. +-spec remove(file:name()) -> ok | {error, Reason::term()}. remove(Path) -> remove(Path, []). %% @doc indicates witha boolean if the path supplied refers to symlink. --spec is_symlink(path()) -> boolean(). +-spec is_symlink(file:name()) -> boolean(). is_symlink(Path) -> case file:read_link_info(Path) of {ok, #file_info{type = symlink}} -> @@ -107,93 +108,70 @@ is_symlink(Path) -> %% @doc make a unique temorory directory. Similar function to BSD stdlib %% function of the same name. --spec insecure_mkdtemp() -> TmpDirPath::path(). +-spec insecure_mkdtemp() -> TmpDirPath::file:name(). insecure_mkdtemp() -> random:seed(now()), UniqueNumber = erlang:integer_to_list(erlang:trunc(random:uniform() * 1000000000000)), TmpDirPath = filename:join([tmp(), lists:flatten([".tmp_dir", UniqueNumber])]), - case filelib:is_dir(TmpDirPath) of - true -> - throw(?UEX({mkdtemp_failed, file_exists}, "tmp directory exists", [])); - false -> - try - ok = mkdir_path(TmpDirPath), - TmpDirPath - catch - _C:E -> throw(?UEX({mkdtemp_failed, E}, ?CHECK_PERMS_MSG, [])) - end + case mkdir_path(TmpDirPath) of + ok -> TmpDirPath; + Error -> Error end. - %% @doc Makes a directory including parent dirs if they are missing. --spec mkdir_path(path()) -> ok. -mkdir_path(Path) -> +-spec mkdir_p(file:name()) -> ok | {error, Reason::term()}. +mkdir_p(Path) -> %% We are exploiting a feature of ensuredir that that creates all %% directories up to the last element in the filename, then ignores %% that last element. This way we ensure that the dir is created %% and not have any worries about path names DirName = filename:join([filename:absname(Path), "tmp"]), - try - ok = filelib:ensure_dir(DirName) - catch - _C:E -> throw(?UEX({mkdir_path_failed, E}, ?CHECK_PERMS_MSG, [])) - end. + filelib:ensure_dir(DirName). + + +%% @doc Makes a directory including parent dirs if they are missing. +-spec mkdir_path(file:name()) -> ok | {error, Reason::term()}. +mkdir_path(Path) -> + mkdir_p(Path). %% @doc consult an erlang term file from the file system. %% Provide user readible exeption on failure. --spec consult(FilePath::path()) -> term(). +-spec consult(FilePath::file:name()) -> term(). consult(FilePath) -> case file:consult(FilePath) of {ok, [Term]} -> Term; - {error, Error} -> - Msg = "The file at ~p~n" ++ - "is either not a valid Erlang term, does not to exist~n" ++ - "or you lack the permissions to read it. Please check~n" ++ - "to see if the file exists and that it has the correct~n" ++ - "permissions~n", - throw(?UEX({failed_to_consult_file, {FilePath, Error}}, - Msg, [FilePath])) + Error -> + Error end. - %% @doc read a file from the file system. Provide UEX exeption on failure. --spec read(FilePath::string()) -> binary(). +-spec read(FilePath::file:filename()) -> binary() | {error, Reason::term()}. read(FilePath) -> - try - {ok, FileBin} = file:read_file(FilePath), - FileBin - catch - _C:E -> throw(?UEX({read_failed, {FilePath, E}}, - "Read failed for the file ~p with ~p~n" ++ - ?CHECK_PERMS_MSG, - [FilePath, E])) - end. + %% Now that we are moving away from exceptions again this becomes + %% a bit redundant but we want to be backwards compatible as much + %% as possible in the api. + file:read_file(FilePath). + %% @doc write a file to the file system. Provide UEX exeption on failure. --spec write(FileName::string(), Contents::string()) -> ok. +-spec write(FileName::file:filename(), Contents::string()) -> ok | {error, Reason::term()}. write(FileName, Contents) -> - case file:write_file(FileName, Contents) of - ok -> - ok; - {error, Reason} -> - Msg = "Writing the file ~s to disk failed with reason ~p.~n" ++ - ?CHECK_PERMS_MSG, - throw(?UEX({write_file_failure, {FileName, Reason}}, - Msg, - [FileName, Reason])) - end. + %% Now that we are moving away from exceptions again this becomes + %% a bit redundant but we want to be backwards compatible as much + %% as possible in the api. + file:write_file(FileName, Contents). %% @doc write a term out to a file so that it can be consulted later. --spec write_term(string(), term()) -> ok. +-spec write_term(file:filename(), term()) -> ok | {error, Reason::term()}. write_term(FileName, Term) -> write(FileName, lists:flatten(io_lib:fwrite("~p. ", [Term]))). %% @doc Finds files and directories that match the regexp supplied in %% the TargetPattern regexp. --spec find(FromDir::path(), TargetPattern::string()) -> [path()]. +-spec find(FromDir::file:name(), TargetPattern::string()) -> [file:name()]. find([], _) -> []; find(FromDir, TargetPattern) -> @@ -214,7 +192,7 @@ find(FromDir, TargetPattern) -> %%%=================================================================== %%% Internal Functions %%%=================================================================== --spec find_in_subdirs(path(), string()) -> [path()]. +-spec find_in_subdirs(file:name(), string()) -> [file:name()]. find_in_subdirs(FromDir, TargetPattern) -> lists:foldl(fun(CheckFromDir, Acc) when CheckFromDir == FromDir -> @@ -228,14 +206,8 @@ find_in_subdirs(FromDir, TargetPattern) -> [], filelib:wildcard(filename:join(FromDir, "*"))). --spec ec_file_remove(path(), [{atom(), any()}]) -> ok. -ec_file_remove(Path, Options) -> - case lists:member(recursive, Options) of - false -> file:delete(Path); - true -> remove_recursive(Path, Options) - end. --spec remove_recursive(path(), Options::list()) -> ok. +-spec remove_recursive(file:name(), Options::list()) -> ok | {error, Reason::term()}. remove_recursive(Path, Options) -> case filelib:is_dir(Path) of false -> @@ -244,10 +216,10 @@ remove_recursive(Path, Options) -> lists:foreach(fun(ChildPath) -> remove_recursive(ChildPath, Options) end, filelib:wildcard(filename:join(Path, "*"))), - ok = file:del_dir(Path) + file:del_dir(Path) end. --spec tmp() -> path(). +-spec tmp() -> file:name(). tmp() -> case erlang:system_info(system_architecture) of "win32" -> @@ -257,7 +229,7 @@ tmp() -> end. %% Copy the subfiles of the From directory to the to directory. --spec copy_subfiles(path(), path(), [option()]) -> ok. +-spec copy_subfiles(file:name(), file:name(), [option()]) -> void(). copy_subfiles(From, To, Options) -> Fun = fun(ChildFrom) -> @@ -266,17 +238,11 @@ copy_subfiles(From, To, Options) -> end, lists:foreach(Fun, filelib:wildcard(filename:join(From, "*"))). --spec ec_file_copy(path(), path()) -> ok. -ec_file_copy(From, To) -> - {ok, _} = file:copy(From, To), - {ok, FileInfo} = file:read_file_info(From), - ok = file:write_file_info(To, FileInfo). - --spec make_dir_if_dir(path()) -> ok. +-spec make_dir_if_dir(file:name()) -> ok | {error, Reason::term()}. make_dir_if_dir(File) -> case filelib:is_dir(File) of true -> ok; - false -> ok = mkdir_path(File) + false -> mkdir_path(File) end. %% @doc convert a list of integers into hex. @@ -320,7 +286,7 @@ file_test() -> filelib:ensure_dir(TermFileCopy), write_term(TermFile, "term"), ?assertMatch("term", consult(TermFile)), - ?assertMatch(<<"\"term\". ">>, read(TermFile)), + ?assertMatch({ok, <<"\"term\". ">>}, read(TermFile)), copy(filename:dirname(TermFile), filename:dirname(TermFileCopy), [recursive]), @@ -356,5 +322,4 @@ find_test() -> find(BaseDir, "file[a-z]+\$")), remove(BaseDir, [recursive]). - -endif. diff --git a/src/ec_gb_trees.erl b/src/ec_gb_trees.erl index d9fa761..7427740 100644 --- a/src/ec_gb_trees.erl +++ b/src/ec_gb_trees.erl @@ -4,9 +4,9 @@ %%% @doc %%% This provides an implementation of the type ec_dictionary using %%% gb_trees as a backin +%%% see ec_dictionary +%%% see gb_trees %%% @end -%%% @see ec_dictionary -%%% @see gb_trees %%%------------------------------------------------------------------- -module(ec_gb_trees). @@ -14,27 +14,29 @@ %% API -export([new/0, - has_key/2, - get/2, - get/3, - add/3, - remove/2, - has_value/2, - size/1, - to_list/1, - from_list/1, - keys/1]). + has_key/2, + get/2, + get/3, + add/3, + remove/2, + has_value/2, + size/1, + to_list/1, + from_list/1, + keys/1]). -export_type([dictionary/2]). %%%=================================================================== %%% Types %%%=================================================================== --opaque dictionary(K, V) :: {non_neg_integer(), ec_gb_tree_node(K, V)}. +%% This should be opaque, but that kills dialyzer so for now we export it +%% however you should not rely on the internal representation here +-type dictionary(K, V) :: {non_neg_integer(), ec_gb_tree_node(K, V)}. -type ec_gb_tree_node(K, V) :: 'nil' | {K, V, - ec_gb_tree_node(K, V), - ec_gb_tree_node(K, V)}. + ec_gb_tree_node(K, V), + ec_gb_tree_node(K, V)}. %%%=================================================================== %%% API @@ -57,10 +59,10 @@ new() -> -spec has_key(ec_dictionary:key(K), Object::dictionary(K, _V)) -> boolean(). has_key(Key, Data) -> case gb_trees:lookup(Key, Data) of - {value, _Val} -> - true; - none -> - false + {value, _Val} -> + true; + none -> + false end. %% @doc given a key return that key from the dictionary. If the key is @@ -68,27 +70,27 @@ has_key(Key, Data) -> %% %% @param Object The dictionary object to return the value from %% @param Key The key requested -%% @throws not_found when the key does not exist +%% when the key does not exist @throws not_found -spec get(ec_dictionary:key(K), Object::dictionary(K, V)) -> ec_dictionary:value(V). get(Key, Data) -> case gb_trees:lookup(Key, Data) of - {value, Value} -> - Value; - none -> - throw(not_found) + {value, Value} -> + Value; + none -> + throw(not_found) end. -spec get(ec_dictionary:key(K), - ec_dictionary:value(V), - Object::dictionary(K, V)) -> + ec_dictionary:value(V), + Object::dictionary(K, V)) -> ec_dictionary:value(V). get(Key, Default, Data) -> case gb_trees:lookup(Key, Data) of - {value, Value} -> - Value; - none -> - Default + {value, Value} -> + Value; + none -> + Default end. %% @doc add a new value to the existing dictionary. Return a new @@ -98,7 +100,7 @@ get(Key, Default, Data) -> %% @param Key the key to add %% @param Value the value to add -spec add(ec_dictionary:key(K), ec_dictionary:value(V), - Object::dictionary(K, V)) -> + Object::dictionary(K, V)) -> dictionary(K, V). add(Key, Value, Data) -> gb_trees:enter(Key, Value, Data). @@ -124,12 +126,12 @@ has_value(Value, Data) -> %% @doc return the current number of key value pairs in the dictionary %% %% @param Object the object return the size for. --spec size(Object::dictionary(_K, _V)) -> integer(). +-spec size(Object::dictionary(_K, _V)) -> non_neg_integer(). size(Data) -> gb_trees:size(Data). -spec to_list(dictionary(K, V)) -> [{ec_dictionary:key(K), - ec_dictionary:value(V)}]. + ec_dictionary:value(V)}]. to_list(Data) -> gb_trees:to_list(Data). @@ -137,10 +139,10 @@ to_list(Data) -> dictionary(K, V). from_list(List) when is_list(List) -> lists:foldl(fun({Key, Value}, Dict) -> - gb_trees:enter(Key, Value, Dict) - end, - gb_trees:empty(), - List). + gb_trees:enter(Key, Value, Dict) + end, + gb_trees:empty(), + List). -spec keys(dictionary(K,_V)) -> [ec_dictionary:key(K)]. keys(Data) -> @@ -189,12 +191,12 @@ add_test() -> Dict01 = ec_dictionary:add(Key1, Value1, Dict0), Dict02 = ec_dictionary:add(Key3, Value3, - ec_dictionary:add(Key2, Value2, - Dict01)), + ec_dictionary:add(Key2, Value2, + Dict01)), Dict1 = - ec_dictionary:add(Key5, Value5, - ec_dictionary:add(Key4, Value4, - Dict02)), + ec_dictionary:add(Key5, Value5, + ec_dictionary:add(Key4, Value4, + Dict02)), ?assertMatch(Value1, ec_dictionary:get(Key1, Dict1)), ?assertMatch(Value2, ec_dictionary:get(Key2, Dict1)), @@ -204,7 +206,7 @@ add_test() -> Dict2 = ec_dictionary:add(Key3, Value5, - ec_dictionary:add(Key2, Value4, Dict1)), + ec_dictionary:add(Key2, Value4, Dict1)), ?assertMatch(Value1, ec_dictionary:get(Key1, Dict2)), @@ -216,7 +218,7 @@ add_test() -> ?assertThrow(not_found, ec_dictionary:get(should_blow_up, Dict2)), ?assertThrow(not_found, ec_dictionary:get("This should blow up too", - Dict2)). + Dict2)). diff --git a/src/ec_lists.erl b/src/ec_lists.erl index 1545eeb..cbc1f0b 100644 --- a/src/ec_lists.erl +++ b/src/ec_lists.erl @@ -23,7 +23,7 @@ %% the third value is the element passed to the function. The purpose %% of this is to allow a list to be searched where some internal state %% is important while the input element is not. --spec search(fun(), list()) -> {ok, Result::term(), Element::term()}. +-spec search(fun(), list()) -> {ok, Result::term(), Element::term()} | not_found. search(Fun, [H|T]) -> case Fun(H) of {ok, Value} -> diff --git a/src/ec_orddict.erl b/src/ec_orddict.erl index 51a7d40..fdd8a38 100644 --- a/src/ec_orddict.erl +++ b/src/ec_orddict.erl @@ -5,9 +5,9 @@ %%% This provides an implementation of the ec_dictionary type using %%% erlang orddicts as a base. The function documentation for %%% ec_dictionary applies here as well. +%%% see ec_dictionary +%%% see orddict %%% @end -%%% @see ec_dictionary -%%% @see orddict %%%------------------------------------------------------------------- -module(ec_orddict). @@ -31,7 +31,9 @@ %%%=================================================================== %%% Types %%%=================================================================== --opaque dictionary(K, V) :: [{K, V}]. +%% This should be opaque, but that kills dialyzer so for now we export it +%% however you should not rely on the internal representation here +-type dictionary(K, V) :: [{K, V}]. %%%=================================================================== %%% API @@ -46,59 +48,59 @@ has_key(Key, Data) -> orddict:is_key(Key, Data). -spec get(ec_dictionary:key(K), Object::dictionary(K, V)) -> - ec_dictionary:value(V). + ec_dictionary:value(V). get(Key, Data) -> case orddict:find(Key, Data) of {ok, Value} -> Value; - error -> + error -> throw(not_found) end. -spec get(ec_dictionary:key(K), Default::ec_dictionary:value(V), Object::dictionary(K, V)) -> - ec_dictionary:value(V). + ec_dictionary:value(V). get(Key, Default, Data) -> case orddict:find(Key, Data) of {ok, Value} -> Value; - error -> + error -> Default end. -spec add(ec_dictionary:key(K), ec_dictionary:value(V), Object::dictionary(K, V)) -> - dictionary(K, V). + dictionary(K, V). add(Key, Value, Data) -> orddict:store(Key, Value, Data). -spec remove(ec_dictionary:key(K), Object::dictionary(K, V)) -> - dictionary(K, V). + dictionary(K, V). remove(Key, Data) -> orddict:erase(Key, Data). -spec has_value(ec_dictionary:value(V), Object::dictionary(_K, V)) -> boolean(). has_value(Value, Data) -> orddict:fold(fun(_, NValue, _) when NValue == Value -> - true; - (_, _, Acc) -> - Acc - end, - false, - Data). + true; + (_, _, Acc) -> + Acc + end, + false, + Data). --spec size(Object::dictionary(_K, _V)) -> integer(). +-spec size(Object::dictionary(_K, _V)) -> non_neg_integer(). size(Data) -> orddict:size(Data). -spec to_list(dictionary(K, V)) -> - [{ec_dictionary:key(K), ec_dictionary:value(V)}]. + [{ec_dictionary:key(K), ec_dictionary:value(V)}]. to_list(Data) -> orddict:to_list(Data). -spec from_list([{ec_dictionary:key(K), ec_dictionary:value(V)}]) -> - dictionary(K, V). + dictionary(K, V). from_list(List) when is_list(List) -> orddict:from_list(List). diff --git a/src/ec_plists.erl b/src/ec_plists.erl index a90bc38..cd14697 100644 --- a/src/ec_plists.erl +++ b/src/ec_plists.erl @@ -13,6 +13,13 @@ filter/2, filter/3]). +-export_type([thunk/0]). + +%%============================================================================= +%% Types +%%============================================================================= +-type thunk() :: fun((any()) -> any()). + %%============================================================================= %% Public API %%============================================================================= @@ -25,7 +32,7 @@ map(Fun, List) -> map(Fun, List, infinity). --spec map(fun(), [any()], non_neg_integer()) -> [any()]. +-spec map(thunk(), [any()], timeout() | infinity) -> [any()]. map(Fun, List, Timeout) -> run_list_fun_in_parallel(map, Fun, List, Timeout). @@ -43,29 +50,29 @@ map(Fun, List, Timeout) -> %% 2> ftmap(fun(N) -> factorial(N) end, [1, 2, 1000000, "not num"], 100) %% [{value, 1}, {value, 2}, timeout, {badmatch, ...}] %% --spec ftmap(fun(), [any()]) -> [{value, any()} | any()]. +-spec ftmap(thunk(), [any()]) -> [{value, any()} | any()]. ftmap(Fun, List) -> ftmap(Fun, List, infinity). --spec ftmap(fun(), [any()], non_neg_integer()) -> [{value, any()} | any()]. +-spec ftmap(thunk(), [any()], timeout() | infinity) -> [{value, any()} | any()]. ftmap(Fun, List, Timeout) -> run_list_fun_in_parallel(ftmap, Fun, List, Timeout). %% @doc Returns a list of the elements in the supplied list which %% the function Fun returns true. A timeout is optional. In the %% event of a timeout the filter operation fails. --spec filter(fun(), [any()]) -> [any()]. +-spec filter(thunk(), [any()]) -> [any()]. filter(Fun, List) -> filter(Fun, List, infinity). --spec filter(fun(), [any()], integer()) -> [any()]. +-spec filter(thunk(), [any()], timeout() | infinity) -> [any()]. filter(Fun, List, Timeout) -> run_list_fun_in_parallel(filter, Fun, List, Timeout). %%============================================================================= %% Internal API %%============================================================================= --spec run_list_fun_in_parallel(atom(), fun(), [any()], integer()) -> [any()]. +-spec run_list_fun_in_parallel(atom(), thunk(), [any()], timeout() | infinity) -> [any()]. run_list_fun_in_parallel(ListFun, Fun, List, Timeout) -> LocalPid = self(), Pids = @@ -79,7 +86,7 @@ run_list_fun_in_parallel(ListFun, Fun, List, Timeout) -> end, List), gather(ListFun, Pids). --spec wait(pid(), fun(), any(), integer()) -> any(). +-spec wait(pid(), thunk(), any(), timeout() | infinity) -> any(). wait(Parent, Fun, E, Timeout) -> WaitPid = self(), Child = spawn(fun() -> @@ -88,7 +95,7 @@ wait(Parent, Fun, E, Timeout) -> wait(Parent, Child, Timeout). --spec wait(pid(), pid(), integer()) -> any(). +-spec wait(pid(), pid(), timeout() | infinity) -> any(). wait(Parent, Child, Timeout) -> receive {Child, Ret} -> @@ -146,7 +153,7 @@ filter_gather([{Pid, E} | Rest]) -> filter_gather([]) -> []. --spec do_f(pid(), fun(), any()) -> no_return(). +-spec do_f(pid(), thunk(), any()) -> no_return(). do_f(Parent, F, E) -> try Result = F(E), diff --git a/src/ec_rbdict.erl b/src/ec_rbdict.erl index bb89674..f28f625 100644 --- a/src/ec_rbdict.erl +++ b/src/ec_rbdict.erl @@ -51,8 +51,8 @@ %%% l/rbalance, the colour, in store etc. is actually slower than not %%% doing it. Measured. %%% +%%% see ec_dictionary %%% @end -%%% @see ec_dictionary %%%------------------------------------------------------------------- -module(ec_rbdict). @@ -68,12 +68,13 @@ %%%=================================================================== %%% Types %%%=================================================================== - --opaque dictionary(K, V) :: empty | {color(), - dictionary(K, V), - ec_dictionary:key(K), - ec_dictionary:value(V), - dictionary(K, V)}. +%% This should be opaque, but that kills dialyzer so for now we export it +%% however you should not rely on the internal representation here +-type dictionary(K, V) :: empty | {color(), + dictionary(K, V), + ec_dictionary:key(K), + ec_dictionary:value(V), + dictionary(K, V)}. -type color() :: r | b. @@ -116,7 +117,7 @@ get(K, Default, {_, _, K1, _, Right}) when K > K1 -> get(_, _, {_, _, _, Val, _}) -> Val. --spec add(ec_dicitonary:key(K), ec_dictionary:value(V), +-spec add(ec_dictionary:key(K), ec_dictionary:value(V), dictionary(K, V)) -> dictionary(K, V). add(Key, Value, Dict) -> {_, L, K1, V1, R} = add1(Key, Value, Dict), @@ -133,17 +134,17 @@ has_value(Value, Dict) -> end, false, Dict). --spec size(dictionary(_K, _V)) -> integer(). +-spec size(dictionary(_K, _V)) -> non_neg_integer(). size(T) -> size1(T). -spec to_list(dictionary(K, V)) -> - [{ec_dictionary:key(K), ec_dictionary:value(V)}]. + [{ec_dictionary:key(K), ec_dictionary:value(V)}]. to_list(T) -> to_list(T, []). -spec from_list([{ec_dictionary:key(K), ec_dictionary:value(V)}]) -> - dictionary(K, V). + dictionary(K, V). from_list(L) -> lists:foldl(fun ({K, V}, D) -> add(K, V, D) @@ -158,7 +159,7 @@ keys(Dict) -> %%% Enternal functions %%%=================================================================== -spec keys(dictionary(K, _V), [ec_dictionary:key(K)]) -> - [ec_dictionary:key(K)]. + [ec_dictionary:key(K)]. keys(empty, Tail) -> Tail; keys({_, L, K, _, R}, Tail) -> @@ -166,7 +167,7 @@ keys({_, L, K, _, R}, Tail) -> -spec erase_aux(ec_dictionary:key(K), dictionary(K, V)) -> - {dictionary(K, V), boolean()}. + {dictionary(K, V), boolean()}. erase_aux(_, empty) -> {empty, false}; erase_aux(K, {b, A, Xk, Xv, B}) -> @@ -227,7 +228,7 @@ erase_aux(K, {r, A, Xk, Xv, B}) -> end. -spec erase_min(dictionary(K, V)) -> - {dictionary(K, V), {ec_dictionary:key(K), ec_dictionary:value(V)}, boolean}. + {dictionary(K, V), {ec_dictionary:key(K), ec_dictionary:value(V)}, boolean()}. erase_min({b, empty, Xk, Xv, empty}) -> {empty, {Xk, Xv}, true}; erase_min({b, empty, Xk, Xv, {r, A, Yk, Yv, B}}) -> @@ -239,15 +240,15 @@ erase_min({r, empty, Xk, Xv, A}) -> erase_min({b, A, Xk, Xv, B}) -> {A1, Min, Dec} = erase_min(A), if Dec -> - {T, Dec1} = unbalright(b, A1, Xk, Xv, B), - {T, Min, Dec1}; + {T, Dec1} = unbalright(b, A1, Xk, Xv, B), + {T, Min, Dec1}; true -> {{b, A1, Xk, Xv, B}, Min, false} end; erase_min({r, A, Xk, Xv, B}) -> {A1, Min, Dec} = erase_min(A), if Dec -> - {T, Dec1} = unbalright(r, A1, Xk, Xv, B), - {T, Min, Dec1}; + {T, Dec1} = unbalright(r, A1, Xk, Xv, B), + {T, Min, Dec1}; true -> {{r, A1, Xk, Xv, B}, Min, false} end. @@ -274,7 +275,8 @@ unbalright(b, A, Xk, Xv, D}, false}. --spec fold(fun(), dictionary(K, V), dictionary(K, V)) -> dictionary(K, V). +-spec fold(fun((ec_dictionary:key(K), ec_dictionary:value(V), any()) -> any()), + any(), dictionary(K, V)) -> any(). fold(_, Acc, empty) -> Acc; fold(F, Acc, {_, A, Xk, Xv, B}) -> fold(F, F(Xk, Xv, fold(F, Acc, B)), A). @@ -295,9 +297,9 @@ to_list({_, A, Xk, Xv, B}, List) -> %% Balance a tree afer (possibly) adding a node to the left/right. -spec lbalance(color(), dictionary(K, V), - ec_dictinary:key(K), ec_dictionary:value(V), + ec_dictionary:key(K), ec_dictionary:value(V), dictionary(K, V)) -> - dictionary(K, V). + dictionary(K, V). lbalance(b, {r, {r, A, Xk, Xv, B}, Yk, Yv, C}, Zk, Zv, D) -> {r, {b, A, Xk, Xv, B}, Yk, Yv, {b, C, Zk, Zv, D}}; @@ -307,9 +309,9 @@ lbalance(b, {r, A, Xk, Xv, {r, B, Yk, Yv, C}}, Zk, Zv, lbalance(C, A, Xk, Xv, B) -> {C, A, Xk, Xv, B}. -spec rbalance(color(), dictionary(K, V), - ec_dictinary:key(K), ec_dictionary:value(V), + ec_dictionary:key(K), ec_dictionary:value(V), dictionary(K, V)) -> - dictionary(K, V). + dictionary(K, V). rbalance(b, A, Xk, Xv, {r, {r, B, Yk, Yv, C}, Zk, Zv, D}) -> {r, {b, A, Xk, Xv, B}, Yk, Yv, {b, C, Zk, Zv, D}}; diff --git a/src/ec_semver.erl b/src/ec_semver.erl index 77b43cd..4dc83e9 100644 --- a/src/ec_semver.erl +++ b/src/ec_semver.erl @@ -7,93 +7,213 @@ %%%------------------------------------------------------------------- -module(ec_semver). --exports([ - compare/2 - ]). +-export([parse/1, + eql/2, + gt/2, + gte/2, + lt/2, + lte/2, + pes/2, + between/3]). --export_type([ - semvar/0 - ]). +%% For internal use by the ec_semver_parser peg +-export([internal_parse_version/1]). + +-export_type([semver/0, + version_string/0, + any_version/0]). %%%=================================================================== %%% Public Types %%%=================================================================== --type semvar() :: string(). --type parsed_semvar() :: {MajorVsn::string(), - MinorVsn::string(), - PatchVsn::string(), - PathString::string()}. +-type major_minor_patch() :: + non_neg_integer() + | {non_neg_integer(), non_neg_integer()} + | {non_neg_integer(), non_neg_integer(), non_neg_integer()}. + +-type alpha_part() :: integer() | binary(). + +-type semver() :: {major_minor_patch(), {PreReleaseVersion::[alpha_part()], + BuildVersion::[alpha_part()]}}. + +-type version_string() :: string() | binary(). + +-type any_version() :: version_string() | semver(). %%%=================================================================== %%% API %%%=================================================================== -%% @doc Is semver version string A bigger than version string B? -%%
-%% Example: compare("3.2.5alpha", "3.10.6") returns: false -%%--spec compare(VsnA::string(), VsnB::string()) -> boolean(). -compare(VsnA, VsnB) -> - compare_toks(tokens(VsnA),tokens(VsnB)). +%% @doc parse a string or binary into a valid semver representation +-spec parse(any_version()) -> semver(). +parse(Version) when erlang:is_list(Version) -> + ec_semver_parser:parse(Version); +parse(Version) when erlang:is_binary(Version) -> + ec_semver_parser:parse(Version); +parse(Version) -> + Version. + +%% @doc test for quality between semver versions +-spec eql(any_version(), any_version()) -> boolean(). +eql(VsnA, VsnB) -> + NVsnA = normalize(parse(VsnA)), + NVsnB = normalize(parse(VsnB)), + NVsnA =:= NVsnB. + +%% @doc Test that VsnA is greater than VsnB +-spec gt(any_version(), any_version()) -> boolean(). +gt(VsnA, VsnB) -> + {MMPA, {AlphaA, PatchA}} = normalize(parse(VsnA)), + {MMPB, {AlphaB, PatchB}} = normalize(parse(VsnB)), + ((MMPA > MMPB) + orelse + ((MMPA =:= MMPB) + andalso + ((AlphaA =:= [] andalso AlphaB =/= []) + orelse + ((not (AlphaB =:= [] andalso AlphaA =/= [])) + andalso + (AlphaA > AlphaB)))) + orelse + ((MMPA =:= MMPB) + andalso + (AlphaA =:= AlphaB) + andalso + ((PatchB =:= [] andalso PatchA =/= []) + orelse + PatchA > PatchB))). + +%% @doc Test that VsnA is greater than or equal to VsnB +-spec gte(any_version(), any_version()) -> boolean(). +gte(VsnA, VsnB) -> + NVsnA = normalize(parse(VsnA)), + NVsnB = normalize(parse(VsnB)), + gt(NVsnA, NVsnB) orelse eql(NVsnA, NVsnB). + +%% @doc Test that VsnA is less than VsnB +-spec lt(any_version(), any_version()) -> boolean(). +lt(VsnA, VsnB) -> + {MMPA, {AlphaA, PatchA}} = normalize(parse(VsnA)), + {MMPB, {AlphaB, PatchB}} = normalize(parse(VsnB)), + ((MMPA < MMPB) + orelse + ((MMPA =:= MMPB) + andalso + ((AlphaB =:= [] andalso AlphaA =/= []) + orelse + ((not (AlphaA =:= [] andalso AlphaB =/= [])) + andalso + (AlphaA < AlphaB)))) + orelse + ((MMPA =:= MMPB) + andalso + (AlphaA =:= AlphaB) + andalso + ((PatchA =:= [] andalso PatchB =/= []) + orelse + PatchA < PatchB))). + +%% @doc Test that VsnA is less than or equal to VsnB +-spec lte(any_version(), any_version()) -> boolean(). +lte(VsnA, VsnB) -> + NVsnA = normalize(parse(VsnA)), + NVsnB = normalize(parse(VsnB)), + lt(NVsnA, NVsnB) orelse eql(NVsnA, NVsnB). + +%% @doc Test that VsnMatch is greater than or equal to Vsn1 and +%% less than or equal to Vsn2 +-spec between(any_version(), any_version(), any_version()) -> boolean(). +between(Vsn1, Vsn2, VsnMatch) -> + NVsnA = normalize(parse(Vsn1)), + NVsnB = normalize(parse(Vsn2)), + NVsnMatch = normalize(parse(VsnMatch)), + gte(NVsnMatch, NVsnA) andalso + lte(NVsnMatch, NVsnB). + +%% @doc check that VsnA is Approximately greater than VsnB +%% +%% Specifying ">= 2.6.5" is an optimistic version constraint. All +%% versions greater than the one specified, including major releases +%% (e.g. 3.0.0) are allowed. +%% +%% Conversely, specifying "~> 2.6" is pessimistic about future major +%% revisions and "~> 2.6.5" is pessimistic about future minor +%% revisions. +%% +%% "~> 2.6" matches cookbooks >= 2.6.0 AND < 3.0.0 +%% "~> 2.6.5" matches cookbooks >= 2.6.5 AND < 2.7.0 +pes(VsnA, VsnB) -> + internal_pes(parse(VsnA), parse(VsnB)). + +%%%=================================================================== +%%% Friend Functions +%%%=================================================================== +%% @doc helper function for the peg grammer to parse the iolist into a semver +-spec internal_parse_version(iolist()) -> semver(). +internal_parse_version([MMP, AlphaPart, BuildPart, _]) -> + {parse_major_minor_patch(MMP), {parse_alpha_part(AlphaPart), + parse_alpha_part(BuildPart)}}. + +%% @doc helper function for the peg grammer to parse the iolist into a major_minor_patch +-spec parse_major_minor_patch(iolist()) -> major_minor_patch(). +parse_major_minor_patch([MajVsn, [], []]) -> + MajVsn; +parse_major_minor_patch([MajVsn, [<<".">>, MinVsn], []]) -> + {MajVsn, MinVsn}; +parse_major_minor_patch([MajVsn, [<<".">>, MinVsn], [<<".">>, PatchVsn]]) -> + {MajVsn, MinVsn, PatchVsn}. + +%% @doc helper function for the peg grammer to parse the iolist into an alpha part +-spec parse_alpha_part(iolist()) -> [alpha_part()]. +parse_alpha_part([]) -> + []; +parse_alpha_part([_, AV1, Rest]) -> + [erlang:iolist_to_binary(AV1) | + [format_alpha_part(Part) || Part <- Rest]]. + +%% @doc according to semver alpha parts that can be treated like +%% numbers must be. We implement that here by taking the alpha part +%% and trying to convert it to a number, if it succeeds we use +%% it. Otherwise we do not. +-spec format_alpha_part(iolist()) -> integer() | binary(). +format_alpha_part([<<".">>, AlphaPart]) -> + Bin = erlang:iolist_to_binary(AlphaPart), + try + erlang:list_to_integer(erlang:binary_to_list(Bin)) + catch + error:badarg -> + Bin + end. %%%=================================================================== %%% Internal Functions %%%=================================================================== +%% @doc normalize the semver so they can be compared +-spec normalize(semver()) -> semver(). +normalize({Vsn, Rest}) + when erlang:is_integer(Vsn) -> + {{Vsn, 0, 0}, Rest}; +normalize({{Maj, Min}, Rest}) -> + {{Maj, Min, 0}, Rest}; +normalize(Other) -> + Other. --spec tokens(semvar()) -> parsed_semvar(). -tokens(Vsn) -> - [MajorVsn, MinorVsn, RawPatch] = string:tokens(Vsn, "."), - {PatchVsn, PatchString} = split_patch(RawPatch), - {MajorVsn, MinorVsn, PatchVsn, PatchString}. +%% @doc to do the pessimistic compare we need a parsed semver. This is +%% the internal implementation of the of the pessimistic run. The +%% external just ensures that versions are parsed. +internal_pes(VsnA, {{LM, LMI}, _}) -> + gte(VsnA, {{LM, LMI, 0}, {[], []}}) andalso + lt(VsnA, {{LM + 1, 0, 0}, {[], []}}); +internal_pes(VsnA, {{LM, LMI, LP}, _}) -> + gte(VsnA, {{LM, LMI, LP}, {[], []}}) + andalso + lt(VsnA, {{LM, LMI + 1, 0}, {[], []}}); +internal_pes(Vsn, LVsn) -> + gte(Vsn, LVsn). --spec split_patch(string()) -> - {PatchVsn::string(), PatchStr::string()}. -split_patch(RawPatch) -> - {PatchVsn, PatchStr} = split_patch(RawPatch, {"", ""}), - {lists:reverse(PatchVsn), PatchStr}. --spec split_patch(string(), {AccPatchVsn::string(), AccPatchStr::string()}) -> - {PatchVsn::string(), PatchStr::string()}. -split_patch([], Acc) -> - Acc; -split_patch([Dig|T], {PatchVsn, PatchStr}) when Dig >= $0 andalso Dig =< $9 -> - split_patch(T, {[Dig|PatchVsn], PatchStr}); -split_patch(PatchStr, {PatchVsn, ""}) -> - {PatchVsn, PatchStr}. --spec compare_toks(parsed_semvar(), parsed_semvar()) -> boolean(). -compare_toks({MajA, MinA, PVA, PSA}, {MajB, MinB, PVB, PSB}) -> - compare_toks2({to_int(MajA), to_int(MinA), to_int(PVA), PSA}, - {to_int(MajB), to_int(MinB), to_int(PVB), PSB}). - --spec compare_toks2(parsed_semvar(), parsed_semvar()) -> boolean(). -compare_toks2({MajA, _MinA, _PVA, _PSA}, {MajB, _MinB, _PVB, _PSB}) - when MajA > MajB -> - true; -compare_toks2({_Maj, MinA, _PVA, _PSA}, {_Maj, MinB, _PVB, _PSB}) - when MinA > MinB -> - true; -compare_toks2({_Maj, _Min, PVA, _PSA}, {_Maj, _Min, PVB, _PSB}) - when PVA > PVB -> - true; -compare_toks2({_Maj, _Min, _PV, ""}, {_Maj, _Min, _PV, PSB}) when PSB /= ""-> - true; -compare_toks2({_Maj, _Min, _PV, PSA}, {_Maj, _Min, _PV, ""}) when PSA /= ""-> - false; -compare_toks2({_Maj, _Min, _PV, PSA}, {_Maj, _Min, _PV, PSB}) when PSA > PSB -> - true; -compare_toks2(_ToksA, _ToksB) -> - false. - --spec to_int(string()) -> integer(). -to_int(String) -> - try - list_to_integer(String) - catch - error:badarg -> - throw(invalid_semver_string) - end. %%%=================================================================== %%% Test Functions @@ -102,18 +222,305 @@ to_int(String) -> -ifndef(NOTEST). -include_lib("eunit/include/eunit.hrl"). -split_patch_test() -> - ?assertMatch({"123", "alpha1"}, split_patch("123alpha1")). +eql_test() -> + ?assertMatch(true, eql("1.0.0-alpha", + "1.0.0-alpha")), + ?assertMatch(true, eql("1", + "1.0.0")), + ?assertMatch(true, eql("1.0", + "1.0.0")), + ?assertMatch(true, eql("1.0.0", + "1")), + ?assertMatch(true, eql("1.0+alpha.1", + "1.0.0+alpha.1")), + ?assertMatch(true, eql("1.0-alpha.1+build.1", + "1.0.0-alpha.1+build.1")), + ?assertMatch(true, not eql("1.0.0", + "1.0.1")), + ?assertMatch(true, not eql("1.0.0-alpha", + "1.0.1+alpha")), + ?assertMatch(true, not eql("1.0.0+build.1", + "1.0.1+build.2")). -compare_test() -> - ?assertMatch(true, compare("1.2.3", "1.2.3alpha")), - ?assertMatch(true, compare("1.2.3beta", "1.2.3alpha")), - ?assertMatch(true, compare("1.2.4", "1.2.3")), - ?assertMatch(true, compare("1.3.3", "1.2.3")), - ?assertMatch(true, compare("2.2.3", "1.2.3")), - ?assertMatch(true, compare("4.2.3", "3.10.3")), - ?assertMatch(false, compare("1.2.3", "2.2.3")), - ?assertThrow(invalid_semver_string, compare("1.b.2", "1.3.4")), - ?assertThrow(invalid_semver_string, compare("1.2.2", "1.3.t")). + +gt_test() -> + ?assertMatch(true, gt("1.0.0-alpha.1", + "1.0.0-alpha")), + ?assertMatch(true, gt("1.0.0-beta.2", + "1.0.0-alpha.1")), + ?assertMatch(true, gt("1.0.0-beta.11", + "1.0.0-beta.2")), + ?assertMatch(true, gt("1.0.0-rc.1", "1.0.0-beta.11")), + ?assertMatch(true, gt("1.0.0-rc.1+build.1", "1.0.0-rc.1")), + ?assertMatch(true, gt("1.0.0", "1.0.0-rc.1+build.1")), + ?assertMatch(true, gt("1.0.0+0.3.7", "1.0.0")), + ?assertMatch(true, gt("1.3.7+build", "1.0.0+0.3.7")), + ?assertMatch(true, gt("1.3.7+build.2.b8f12d7", + "1.3.7+build")), + ?assertMatch(true, gt("1.3.7+build.11.e0f985a", + "1.3.7+build.2.b8f12d7")), + ?assertMatch(true, not gt("1.0.0-alpha", + "1.0.0-alpha.1")), + ?assertMatch(true, not gt("1.0.0-alpha.1", + "1.0.0-beta.2")), + ?assertMatch(true, not gt("1.0.0-beta.2", + "1.0.0-beta.11")), + ?assertMatch(true, not gt("1.0.0-beta.11", + "1.0.0-rc.1")), + ?assertMatch(true, not gt("1.0.0-rc.1", + "1.0.0-rc.1+build.1")), + ?assertMatch(true, not gt("1.0.0-rc.1+build.1", + "1.0.0")), + ?assertMatch(true, not gt("1.0.0", + "1.0.0+0.3.7")), + ?assertMatch(true, not gt("1.0.0+0.3.7", + "1.3.7+build")), + ?assertMatch(true, not gt("1.3.7+build", + "1.3.7+build.2.b8f12d7")), + ?assertMatch(true, not gt("1.3.7+build.2.b8f12d7", + "1.3.7+build.11.e0f985a")), + ?assertMatch(true, not gt("1.0.0-alpha", + "1.0.0-alpha")), + ?assertMatch(true, not gt("1", + "1.0.0")), + ?assertMatch(true, not gt("1.0", + "1.0.0")), + ?assertMatch(true, not gt("1.0.0", + "1")), + ?assertMatch(true, not gt("1.0+alpha.1", + "1.0.0+alpha.1")), + ?assertMatch(true, not gt("1.0-alpha.1+build.1", + "1.0.0-alpha.1+build.1")). + +lt_test() -> + ?assertMatch(true, lt("1.0.0-alpha", + "1.0.0-alpha.1")), + ?assertMatch(true, lt("1.0.0-alpha.1", + "1.0.0-beta.2")), + ?assertMatch(true, lt("1.0.0-beta.2", + "1.0.0-beta.11")), + ?assertMatch(true, lt("1.0.0-beta.11", + "1.0.0-rc.1")), + ?assertMatch(true, lt("1.0.0-rc.1", + "1.0.0-rc.1+build.1")), + ?assertMatch(true, lt("1.0.0-rc.1+build.1", + "1.0.0")), + ?assertMatch(true, lt("1.0.0", + "1.0.0+0.3.7")), + ?assertMatch(true, lt("1.0.0+0.3.7", + "1.3.7+build")), + ?assertMatch(true, lt("1.3.7+build", + "1.3.7+build.2.b8f12d7")), + ?assertMatch(true, lt("1.3.7+build.2.b8f12d7", + "1.3.7+build.11.e0f985a")), + ?assertMatch(true, not lt("1.0.0-alpha", + "1.0.0-alpha")), + ?assertMatch(true, not lt("1", + "1.0.0")), + ?assertMatch(true, not lt("1.0", + "1.0.0")), + ?assertMatch(true, not lt("1.0.0", + "1")), + ?assertMatch(true, not lt("1.0+alpha.1", + "1.0.0+alpha.1")), + ?assertMatch(true, not lt("1.0-alpha.1+build.1", + "1.0.0-alpha.1+build.1")), + ?assertMatch(true, not lt("1.0.0-alpha.1", + "1.0.0-alpha")), + ?assertMatch(true, not lt("1.0.0-beta.2", + "1.0.0-alpha.1")), + ?assertMatch(true, not lt("1.0.0-beta.11", + "1.0.0-beta.2")), + ?assertMatch(true, not lt("1.0.0-rc.1", "1.0.0-beta.11")), + ?assertMatch(true, not lt("1.0.0-rc.1+build.1", "1.0.0-rc.1")), + ?assertMatch(true, not lt("1.0.0", "1.0.0-rc.1+build.1")), + ?assertMatch(true, not lt("1.0.0+0.3.7", "1.0.0")), + ?assertMatch(true, not lt("1.3.7+build", "1.0.0+0.3.7")), + ?assertMatch(true, not lt("1.3.7+build.2.b8f12d7", + "1.3.7+build")), + ?assertMatch(true, not lt("1.3.7+build.11.e0f985a", + "1.3.7+build.2.b8f12d7")). + + +gte_test() -> + ?assertMatch(true, gte("1.0.0-alpha", + "1.0.0-alpha")), + + ?assertMatch(true, gte("1", + "1.0.0")), + + ?assertMatch(true, gte("1.0", + "1.0.0")), + + ?assertMatch(true, gte("1.0.0", + "1")), + + ?assertMatch(true, gte("1.0+alpha.1", + "1.0.0+alpha.1")), + + ?assertMatch(true, gte("1.0-alpha.1+build.1", + "1.0.0-alpha.1+build.1")), + + ?assertMatch(true, gte("1.0.0-alpha.1", + "1.0.0-alpha")), + ?assertMatch(true, gte("1.0.0-beta.2", + "1.0.0-alpha.1")), + ?assertMatch(true, gte("1.0.0-beta.11", + "1.0.0-beta.2")), + ?assertMatch(true, gte("1.0.0-rc.1", "1.0.0-beta.11")), + ?assertMatch(true, gte("1.0.0-rc.1+build.1", "1.0.0-rc.1")), + ?assertMatch(true, gte("1.0.0", "1.0.0-rc.1+build.1")), + ?assertMatch(true, gte("1.0.0+0.3.7", "1.0.0")), + ?assertMatch(true, gte("1.3.7+build", "1.0.0+0.3.7")), + ?assertMatch(true, gte("1.3.7+build.2.b8f12d7", + "1.3.7+build")), + ?assertMatch(true, gte("1.3.7+build.11.e0f985a", + "1.3.7+build.2.b8f12d7")), + ?assertMatch(true, not gte("1.0.0-alpha", + "1.0.0-alpha.1")), + ?assertMatch(true, not gte("1.0.0-alpha.1", + "1.0.0-beta.2")), + ?assertMatch(true, not gte("1.0.0-beta.2", + "1.0.0-beta.11")), + ?assertMatch(true, not gte("1.0.0-beta.11", + "1.0.0-rc.1")), + ?assertMatch(true, not gte("1.0.0-rc.1", + "1.0.0-rc.1+build.1")), + ?assertMatch(true, not gte("1.0.0-rc.1+build.1", + "1.0.0")), + ?assertMatch(true, not gte("1.0.0", + "1.0.0+0.3.7")), + ?assertMatch(true, not gte("1.0.0+0.3.7", + "1.3.7+build")), + ?assertMatch(true, not gte("1.0.0", + "1.0.0+build.1")), + ?assertMatch(true, not gte("1.3.7+build", + "1.3.7+build.2.b8f12d7")), + ?assertMatch(true, not gte("1.3.7+build.2.b8f12d7", + "1.3.7+build.11.e0f985a")). +lte_test() -> + ?assertMatch(true, lte("1.0.0-alpha", + "1.0.0-alpha.1")), + ?assertMatch(true, lte("1.0.0-alpha.1", + "1.0.0-beta.2")), + ?assertMatch(true, lte("1.0.0-beta.2", + "1.0.0-beta.11")), + ?assertMatch(true, lte("1.0.0-beta.11", + "1.0.0-rc.1")), + ?assertMatch(true, lte("1.0.0-rc.1", + "1.0.0-rc.1+build.1")), + ?assertMatch(true, lte("1.0.0-rc.1+build.1", + "1.0.0")), + ?assertMatch(true, lte("1.0.0", + "1.0.0+0.3.7")), + ?assertMatch(true, lte("1.0.0+0.3.7", + "1.3.7+build")), + ?assertMatch(true, lte("1.3.7+build", + "1.3.7+build.2.b8f12d7")), + ?assertMatch(true, lte("1.3.7+build.2.b8f12d7", + "1.3.7+build.11.e0f985a")), + ?assertMatch(true, lte("1.0.0-alpha", + "1.0.0-alpha")), + ?assertMatch(true, lte("1", + "1.0.0")), + ?assertMatch(true, lte("1.0", + "1.0.0")), + ?assertMatch(true, lte("1.0.0", + "1")), + ?assertMatch(true, lte("1.0+alpha.1", + "1.0.0+alpha.1")), + ?assertMatch(true, lte("1.0-alpha.1+build.1", + "1.0.0-alpha.1+build.1")), + ?assertMatch(true, not lt("1.0.0-alpha.1", + "1.0.0-alpha")), + ?assertMatch(true, not lt("1.0.0-beta.2", + "1.0.0-alpha.1")), + ?assertMatch(true, not lt("1.0.0-beta.11", + "1.0.0-beta.2")), + ?assertMatch(true, not lt("1.0.0-rc.1", "1.0.0-beta.11")), + ?assertMatch(true, not lt("1.0.0-rc.1+build.1", "1.0.0-rc.1")), + ?assertMatch(true, not lt("1.0.0", "1.0.0-rc.1+build.1")), + ?assertMatch(true, not lt("1.0.0+0.3.7", "1.0.0")), + ?assertMatch(true, not lt("1.3.7+build", "1.0.0+0.3.7")), + ?assertMatch(true, not lt("1.3.7+build.2.b8f12d7", + "1.3.7+build")), + ?assertMatch(true, not lt("1.3.7+build.11.e0f985a", + "1.3.7+build.2.b8f12d7")). + + +between_test() -> + ?assertMatch(true, between("1.0.0-alpha", + "1.0.0-alpha.3", + "1.0.0-alpha.2")), + ?assertMatch(true, between("1.0.0-alpha.1", + "1.0.0-beta.2", + "1.0.0-alpha.25")), + ?assertMatch(true, between("1.0.0-beta.2", + "1.0.0-beta.11", + "1.0.0-beta.7")), + ?assertMatch(true, between("1.0.0-beta.11", + "1.0.0-rc.3", + "1.0.0-rc.1")), + ?assertMatch(true, between("1.0.0-rc.1", + "1.0.0-rc.1+build.3", + "1.0.0-rc.1+build.1")), + ?assertMatch(true, between("1.0.0-rc.1+build.1", + "1.0.0", + "1.0.0-rc.33")), + ?assertMatch(true, between("1.0.0", + "1.0.0+0.3.7", + "1.0.0+0.2")), + ?assertMatch(true, between("1.0.0+0.3.7", + "1.3.7+build", + "1.2")), + ?assertMatch(true, between("1.3.7+build", + "1.3.7+build.2.b8f12d7", + "1.3.7+build.1")), + ?assertMatch(true, between("1.3.7+build.2.b8f12d7", + "1.3.7+build.11.e0f985a", + "1.3.7+build.10.a36faa")), + ?assertMatch(true, between("1.0.0-alpha", + "1.0.0-alpha", + "1.0.0-alpha")), + ?assertMatch(true, between("1", + "1.0.0", + "1.0.0")), + ?assertMatch(true, between("1.0", + "1.0.0", + "1.0.0")), + ?assertMatch(true, between("1.0.0", + "1", + "1")), + ?assertMatch(true, between("1.0+alpha.1", + "1.0.0+alpha.1", + "1.0.0+alpha.1")), + ?assertMatch(true, between("1.0-alpha.1+build.1", + "1.0.0-alpha.1+build.1", + "1.0.0-alpha.1+build.1")), + ?assertMatch(true, not between("1.0.0-alpha.1", + "1.0.0-alpha.22", + "1.0.0")), + ?assertMatch(true, not between("1.0.0", + "1.0.0-alpha.1", + "2.0")), + ?assertMatch(true, not between("1.0.0-beta.1", + "1.0.0-beta.11", + "1.0.0-alpha")), + ?assertMatch(true, not between("1.0.0-beta.11", "1.0.0-rc.1", "1.0.0-rc.22")). + +pes_test() -> + ?assertMatch(true, pes("2.6.0", "2.6")), + ?assertMatch(true, pes("2.7", "2.6")), + ?assertMatch(true, pes("2.8", "2.6")), + ?assertMatch(true, pes("2.9", "2.6")), + ?assertMatch(true, not pes("3.0.0", "2.6")), + ?assertMatch(true, not pes("2.5", "2.6")), + ?assertMatch(true, pes("2.6.5", "2.6.5")), + ?assertMatch(true, pes("2.6.6", "2.6.5")), + ?assertMatch(true, pes("2.6.7", "2.6.5")), + ?assertMatch(true, pes("2.6.8", "2.6.5")), + ?assertMatch(true, pes("2.6.9", "2.6.5")), + ?assertMatch(true, not pes("2.7", "2.6.5")), + ?assertMatch(true, not pes("2.5", "2.6.5")). -endif. diff --git a/src/ec_semver_parser.peg b/src/ec_semver_parser.peg new file mode 100644 index 0000000..9636d95 --- /dev/null +++ b/src/ec_semver_parser.peg @@ -0,0 +1,13 @@ +semver <- major_minor_patch ("-" alpha_part ("." alpha_part)*)? ("+" alpha_part ("." alpha_part)*)? !. + ` ec_semver:internal_parse_version(Node) ` ; + +major_minor_patch <- version_part ("." version_part)? ("." version_part)? ; + +version_part <- [0-9]+ `erlang:list_to_integer(erlang:binary_to_list(erlang:iolist_to_binary(Node)))` ; + +alpha_part <- [A-Za-z0-9-]+ ; + +%% This only exists to get around a bug in erlang where if +%% warnings_as_errors is specified `nowarn` directives are ignored + + `-compile(export_all).` \ No newline at end of file diff --git a/src/ec_string.erl b/src/ec_string.erl deleted file mode 100644 index 2a06257..0000000 --- a/src/ec_string.erl +++ /dev/null @@ -1,128 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @copyright (C) 2011, Erlware LLC -%%% @doc -%%% Helper functions for working with strings. -%%% @end -%%%------------------------------------------------------------------- --module(ec_string). - --export([ - compare_versions/2 - ]). -%%%=================================================================== -%%% API -%%%=================================================================== - -%% @doc Is arbitrary version string A bigger than version string B? -%% Valid version string elements are either separated by . or - or both. -%% Final version string elements may have a numeric followed directly by an -%% alpha numeric and will be compared separately as in 12alpha. -%% -%%
-%% Example: compare_versions("3-2-5-alpha", "3.10.6") will return false -%% compare_versions("3-2-alpha", "3.2.1-alpha") will return false -%% compare_versions("3-2alpha", "3.2.1-alpha") will return false -%% compare_versions("3.2.2", "3.2.2") will return false -%% compare_versions("3.2.1", "3.2.1-rc2") will return true -%% compare_versions("3.2.2", "3.2.1") will return true -%%--spec compare_versions(VsnA::string(), VsnB::string()) -> boolean(). -compare_versions(VsnA, VsnB) -> - compare(string:tokens(VsnA, ".-"),string:tokens(VsnB, ".-")). - -%%%=================================================================== -%%% Internal Functions -%%%=================================================================== - --spec compare(string(), string()) -> boolean(). -compare([Str|TA], [Str|TB]) -> - compare(TA, TB); -compare([StrA|TA], [StrB|TB]) -> - fine_compare(split_numeric_alpha(StrA), TA, - split_numeric_alpha(StrB), TB); -compare([], [Str]) -> - not compare_against_nothing(Str); -compare([Str], []) -> - compare_against_nothing(Str); -compare([], [_,_|_]) -> - false; -compare([_,_|_], []) -> - true; -compare([], []) -> - false. - --spec compare_against_nothing(string()) -> boolean(). -compare_against_nothing(Str) -> - case split_numeric_alpha(Str) of - {_StrDig, ""} -> true; - {"", _StrAlpha} -> false; - {_StrDig, _StrAlpha} -> true - end. - --spec fine_compare({string(), string()}, string(), - {string(), string()}, string()) -> - boolean(). -fine_compare({_StrDigA, StrA}, TA, {_StrDigB, _StrB}, _TB) - when StrA /= "", TA /= [] -> - throw(invalid_version_string); -fine_compare({_StrDigA, _StrA}, _TA, {_StrDigB, StrB}, TB) - when StrB /= "", TB /= [] -> - throw(invalid_version_string); -fine_compare({"", _StrA}, _TA, {StrDigB, _StrB}, _TB) when StrDigB /= "" -> - false; -fine_compare({StrDigA, _StrA}, _TA, {"", _StrB}, _TB) when StrDigA /= "" -> - true; -fine_compare({StrDig, ""}, _TA, {StrDig, StrB}, _TB) when StrB /= "" -> - true; -fine_compare({StrDig, StrA}, _TA, {StrDig, ""}, _TB) when StrA /= "" -> - false; -fine_compare({StrDig, StrA}, _TA, {StrDig, StrB}, _TB) -> - StrA > StrB; -fine_compare({StrDigA, _StrA}, _TA, {StrDigB, _StrB}, _TB) -> - list_to_integer(StrDigA) > list_to_integer(StrDigB). - -%% In the case of a version sub part with a numeric then an alpha, -%% split out the numeric and alpha "24alpha" becomes {"24", "alpha"} --spec split_numeric_alpha(string()) -> - {PatchVsn::string(), PatchStr::string()}. -split_numeric_alpha(RawVsn) -> - {Num, Str} = split_numeric_alpha(RawVsn, {"", ""}), - {lists:reverse(Num), Str}. - --spec split_numeric_alpha(string(), {PatchVsnAcc::string(), - PatchStrAcc::string()}) -> - {PatchVsn::string(), PatchStr::string()}. -split_numeric_alpha([], Acc) -> - Acc; -split_numeric_alpha([Dig|T], {PatchVsn, PatchStr}) - when Dig >= $0 andalso Dig =< $9 -> - split_numeric_alpha(T, {[Dig|PatchVsn], PatchStr}); -split_numeric_alpha(PatchStr, {PatchVsn, ""}) -> - {PatchVsn, PatchStr}. - -%%%=================================================================== -%%% Test Functions -%%%=================================================================== - --ifndef(NOTEST). --include_lib("eunit/include/eunit.hrl"). - -split_numeric_alpha_test() -> - ?assertMatch({"123", "alpha1"}, split_numeric_alpha("123alpha1")). - -compare_versions_test() -> - ?assertMatch(true, compare_versions("1.2.3", "1.2.3alpha")), - ?assertMatch(true, compare_versions("1.2.3-beta", "1.2.3-alpha")), - ?assertMatch(true, compare_versions("1-2-3", "1-2-3alpha")), - ?assertMatch(true, compare_versions("1-2-3", "1-2-3-rc3")), - ?assertMatch(true, compare_versions("1.2.3beta", "1.2.3alpha")), - ?assertMatch(true, compare_versions("1.2.4", "1.2.3")), - ?assertMatch(true, compare_versions("1.3.3", "1.2.3")), - ?assertMatch(true, compare_versions("2.2.3", "1.2.3")), - ?assertMatch(true, compare_versions("4.2.3", "3.10.3")), - ?assertMatch(false, compare_versions("1.2.3", "2.2.3")), - ?assertMatch(false, compare_versions("1.2.2", "1.3.t")), - ?assertMatch(false, compare_versions("1.2t", "1.3.t")), - ?assertThrow(invalid_version_string, compare_versions("1.b.2", "1.3.4")). - --endif. diff --git a/src/ec_talk.erl b/src/ec_talk.erl index 0823169..4a82d1b 100644 --- a/src/ec_talk.erl +++ b/src/ec_talk.erl @@ -49,7 +49,7 @@ %%============================================================================ -type prompt() :: string(). -type type() :: boolean | number | string. --type supported() :: string() | boolean() | number(). +-type supported() :: boolean() | number() | string(). %%============================================================================ %% API @@ -100,8 +100,11 @@ ask_default(Prompt, string, Default) -> %% between min and max. -spec ask(prompt(), number(), number()) -> number(). ask(Prompt, Min, Max) - when is_list(Prompt), is_number(Min), is_number(Max) -> - Res = ask(Prompt, fun get_integer/1, none), + when erlang:is_list(Prompt), + erlang:is_number(Min), + erlang:is_number(Max), + Min =< Max -> + Res = ask_convert(Prompt, fun get_integer/1, number, none), case (Res >= Min andalso Res =< Max) of true -> Res; @@ -115,14 +118,16 @@ ask(Prompt, Min, Max) %% ============================================================================ %% @doc Actually does the work of asking, checking result and %% translating result into the requested format. --spec ask_convert(prompt(), fun(), type(), supported()) -> supported(). +-spec ask_convert(prompt(), fun((any()) -> any()), type(), supported() | none) -> supported(). ask_convert(Prompt, TransFun, Type, Default) -> - NewPrompt = Prompt ++ case Default of - none -> - []; - Default -> - " (" ++ sin_utils:term_to_list(Default) ++ ")" - end ++ "> ", + NewPrompt = + erlang:binary_to_list(erlang:iolist_to_binary([Prompt, + case Default of + none -> + []; + Default -> + [" (", io_lib:format("~p", [Default]) , ")"] + end, "> "])), Data = string:strip(string:strip(io:get_line(NewPrompt)), both, $\n), Ret = TransFun(Data), case Ret of diff --git a/src/erlware_commons.app.src b/src/erlware_commons.app.src index a10e939..8aaa5f3 100644 --- a/src/erlware_commons.app.src +++ b/src/erlware_commons.app.src @@ -1,20 +1,7 @@ %% -*- mode: Erlang; fill-column: 75; comment-column: 50; -*- {application, erlware_commons, [{description, "Additional standard library for Erlang"}, - {vsn, "0.7.0"}, - {modules, [ - ec_talk, - ec_lists, - ec_plists, - ec_file, - ec_string, - ec_semver, - ec_date, - ec_dictionary, - ec_assoc_list, - ec_dict, - ec_gb_trees, - ec_rbdict, - ec_orddict]}, + {vsn, "0.8.0"}, + {modules, []}, {registered, []}, {applications, [kernel, stdlib]}]}.