diff --git a/.travis.yml b/.travis.yml index 35c5897..efd1082 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,11 @@ -language: erlang +=nlanguage: erlang otp_release: - R15B02 - R15B01 - R15B - + - R14B04 + - R14B03 + - R14B02 before_script: "make get-deps" script: "make" branches: diff --git a/Makefile b/Makefile index ab8b307..538bc3e 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ $(ERLWARE_COMMONS_PLT): --apps erts kernel stdlib eunit -r deps dialyzer: $(ERLWARE_COMMONS_PLT) - dialyzer --fullpath --plt $(ERLWARE_COMMONS_PLT) -Wrace_conditions --src src + dialyzer --fullpath --plt $(ERLWARE_COMMONS_PLT) -Wrace_conditions -r ./ebin typer: typer --plt $(ERLWARE_COMMONS_PLT) -r ./src @@ -64,4 +64,4 @@ distclean: clean rm -rf $(ERLWARE_COMMONS_PLT) rm -rvf $(CURDIR)/deps/* -rebuild: distclean all +rebuild: distclean get-deps all diff --git a/rebar.config b/rebar.config index 08863d2..57dc49a 100644 --- a/rebar.config +++ b/rebar.config @@ -3,7 +3,7 @@ %% Dependencies ================================================================ {deps, [{neotoma, "", {git, "https://github.com/seancribbs/neotoma.git", {branch, master}}}, - {proper, "", {git, "https://github.com/manopapad/proper.git", {branch, master}}}, + {proper, "", {git, "https://github.com/bkearns/proper.git", {branch, master}}}, {rebar_vsn_plugin, ".*", {git, "https://github.com/erlware/rebar_vsn_plugin.git", {branch, "master"}}}]}. diff --git a/rebar.config.script b/rebar.config.script new file mode 100644 index 0000000..1d5996c --- /dev/null +++ b/rebar.config.script @@ -0,0 +1,15 @@ +{match, [ErtsNumber]} = re:run("R15B02", "R(\\d+).+", [{capture, [1], list}]), +ErtsVsn = erlang:list_to_integer(ErtsNumber), +Opts1 = case lists:keysearch(erl_opts, 1, CONFIG) of + {value, {erl_opts, Opts0}} -> + Opts0; + false -> + [] + end, +Opts2 = if + ErtsVsn >= 15 -> + [{d, have_callback_support} | Opts1]; + true -> + Opts1 + end, +lists:keystore(erl_opts, 1, CONFIG, {erl_opts, Opts2}). diff --git a/src/ec_compile.erl b/src/ec_compile.erl index 482bdb3..6c15520 100644 --- a/src/ec_compile.erl +++ b/src/ec_compile.erl @@ -13,9 +13,11 @@ erl_source_to_core_ast/1, erl_source_to_erl_ast/1, erl_source_to_asm/1, + erl_source_to_erl_syntax/1, erl_string_to_core_ast/1, erl_string_to_erl_ast/1, - erl_string_to_asm/1]). + erl_string_to_asm/1, + erl_string_to_erl_syntax/1]). %%%=================================================================== %%% API @@ -33,13 +35,13 @@ beam_to_erl_source(BeamFName, ErlFName) -> case beam_lib:chunks(BeamFName, [abstract_code]) of {ok, {_, [{abstract_code, {raw_abstract_v1,Forms}}]}} -> - Src = - erl_prettypr:format(erl_syntax:form_list(tl(Forms))), - {ok, Fd} = file:open(ErlFName, [write]), - io:fwrite(Fd, "~s~n", [Src]), - file:close(Fd); - Error -> - Error + Src = + erl_prettypr:format(erl_syntax:form_list(tl(Forms))), + {ok, Fd} = file:open(ErlFName, [write]), + io:fwrite(Fd, "~s~n", [Src]), + file:close(Fd); + Error -> + Error end. %% @doc compile an erlang source file into a Core Erlang AST @@ -67,6 +69,15 @@ erl_source_to_asm(Path) -> {ok, Contents} = file:read_file(Path), erl_string_to_asm(binary_to_list(Contents)). +%% @doc compile an erlang source file to a string that displays the +%% 'erl_syntax1 calls needed to reproduce those terms. +%% +%% @param Path - The path to the erlang source file +-spec erl_source_to_erl_syntax(file:filename()) -> string(). +erl_source_to_erl_syntax(Path) -> + {ok, Contents} = file:read_file(Path), + erl_string_to_erl_syntax(Contents). + %% @doc compile a string representing an erlang expression into an %% Erlang AST %% @@ -105,3 +116,16 @@ erl_string_to_core_ast(StringExpr) -> -spec erl_string_to_asm(string()) -> ErlangAsm::term(). erl_string_to_asm(StringExpr) -> compile:forms(erl_string_to_erl_ast(StringExpr), ['S']). + +%% @doc compile an erlang source file to a string that displays the +%% 'erl_syntax1 calls needed to reproduce those terms. +%% +%% @param StringExpr - The string representing the erlang code. +-spec erl_string_to_erl_syntax(string() | binary()) -> string(). +erl_string_to_erl_syntax(BinaryExpr) + when erlang:is_binary(BinaryExpr) -> + erlang:binary_to_list(BinaryExpr); +erl_string_to_erl_syntax(StringExpr) -> + {ok, Tokens, _} = erl_scan:string(StringExpr), + {ok, ErlAST} = erl_parse:parse_form(Tokens), + io:format(erl_prettypr:format(erl_syntax:meta(ErlAST))). diff --git a/src/ec_date.erl b/src/ec_date.erl index 8d79c34..16a0af7 100644 --- a/src/ec_date.erl +++ b/src/ec_date.erl @@ -42,9 +42,11 @@ -type hour() :: 0..23. -type minute() :: 0..59. -type second() :: 0..59. +-type microsecond() :: 0..1000000. + -type daynum() :: 1..7. -type date() :: {year(),month(),day()}. --type time() :: {hour(),minute(),second()}. +-type time() :: {hour(),minute(),second()} |{hour(),minute(),second(), microsecond()}. -type datetime() :: {date(),time()}. -type now() :: {integer(),integer(),integer()}. @@ -59,8 +61,9 @@ format(Format) -> -spec format(string(),datetime() | now()) -> string(). %% @doc format Date as Format -format(Format, {_,_,_}=Now) -> - format(Format, calendar:now_to_datetime(Now), []); +format(Format, {_,_,Ms}=Now) -> + {Date,{H,M,S}} = calendar:now_to_datetime(Now), + format(Format, {Date, {H,M,S,Ms}}, []); format(Format, Date) -> format(Format, Date, []). @@ -70,6 +73,7 @@ parse(Date) -> do_parse(Date, calendar:universal_time(),[]). -spec parse(string(),datetime() | now()) -> datetime(). + %% @doc parses the datetime from a string parse(Date, {_,_,_}=Now) -> do_parse(Date, calendar:now_to_datetime(Now), []); @@ -88,22 +92,61 @@ do_parse(Date, Now, Opts) -> true -> {D1, T1}; false -> erlang:throw({?MODULE, {bad_date, Date}}) end; - _ -> erlang:throw({?MODULE, {bad_date, Date}}) + {D1, _T1, {Ms}} = {{Y, M, D}, {H, M1, S}, {Ms}} + when is_number(Y), is_number(M), + is_number(D), is_number(H), + is_number(M1), is_number(S), + is_number(Ms) -> + case calendar:valid_date(D1) of + true -> {D1, {H,M1,S,Ms}}; + false -> erlang:throw({?MODULE, {bad_date, Date}}) + end; + Unknown -> erlang:throw({?MODULE, {bad_date, Date, Unknown }}) end. -spec nparse(string()) -> now(). %% @doc parses the datetime from a string into 'now' format nparse(Date) -> - DateTime = parse(Date), - GSeconds = calendar:datetime_to_gregorian_seconds(DateTime), - ESeconds = GSeconds - ?GREGORIAN_SECONDS_1970, - {ESeconds div 1000000, ESeconds rem 1000000, 0}. - + case parse(Date) of + {DateS, {H, M, S, Ms} } -> + GSeconds = calendar:datetime_to_gregorian_seconds({DateS, {H, M, S} }), + ESeconds = GSeconds - ?GREGORIAN_SECONDS_1970, + {ESeconds div 1000000, ESeconds rem 1000000, Ms}; + DateTime -> + GSeconds = calendar:datetime_to_gregorian_seconds(DateTime), + ESeconds = GSeconds - ?GREGORIAN_SECONDS_1970, + {ESeconds div 1000000, ESeconds rem 1000000, 0} + end. %% %% LOCAL FUNCTIONS %% + +%% Date/Times 22 Aug 2008 6:35.0001 PM +parse([Year,X,Month,X,Day,Hour,$:,Min,$:,Sec,$., Ms | PAM], _Now, _Opts) + when ?is_meridian(PAM) andalso + (?is_us_sep(X) orelse ?is_world_sep(X)) + andalso Year > 31 -> + {{Year, Month, Day}, {hour(Hour, PAM), Min, Sec}, {Ms}}; +parse([Month,X,Day,X,Year,Hour,$:,Min,$:,Sec,$., Ms | PAM], _Now, _Opts) + when ?is_meridian(PAM) andalso ?is_us_sep(X) -> + {{Year, Month, Day}, {hour(Hour, PAM), Min, Sec}, {Ms}}; +parse([Day,X,Month,X,Year,Hour,$:,Min,$:,Sec,$., Ms | PAM], _Now, _Opts) + when ?is_meridian(PAM) andalso ?is_world_sep(X) -> + {{Year, Month, Day}, {hour(Hour, PAM), Min, Sec}, {Ms}}; + +parse([Year,X,Month,X,Day,Hour,$:,Min,$:,Sec,$., Ms], _Now, _Opts) + when (?is_us_sep(X) orelse ?is_world_sep(X)) + andalso Year > 31 -> + {{Year, Month, Day}, {hour(Hour,[]), Min, Sec}, {Ms}}; +parse([Month,X,Day,X,Year,Hour,$:,Min,$:,Sec,$., Ms], _Now, _Opts) + when ?is_us_sep(X) -> + {{Year, Month, Day}, {hour(Hour, []), Min, Sec}, {Ms}}; +parse([Day,X,Month,X,Year,Hour,$:,Min,$:,Sec,$., Ms ], _Now, _Opts) + when ?is_world_sep(X) -> + {{Year, Month, Day}, {hour(Hour, []), Min, Sec}, {Ms}}; + %% Times - 21:45, 13:45:54, 13:15PM etc parse([Hour,$:,Min,$:,Sec | PAM], {Date, _Time}, _O) when ?is_meridian(PAM) -> {Date, {hour(Hour, PAM), Min, Sec}}; @@ -138,7 +181,8 @@ parse([Month,X,Day,X,Year,Hour | PAM], _Date, _Opts) when ?is_meridian(PAM) andalso ?is_us_sep(X) -> {{Year, Month, Day}, {hour(Hour, PAM), 0, 0}}; -%% Time is "6:35 PM" + +%% Time is "6:35 PM" ms return parse([Year,X,Month,X,Day,Hour,$:,Min | PAM], _Date, _Opts) when ?is_meridian(PAM) andalso (?is_us_sep(X) orelse ?is_world_sep(X)) @@ -164,7 +208,6 @@ parse([Day,X,Month,X,Year,Hour,$:,Min,$:,Sec | PAM], _Now, _Opts) when ?is_meridian(PAM) andalso ?is_world_sep(X) -> {{Year, Month, Day}, {hour(Hour, PAM), Min, Sec}}; - parse([Day,Month,Year,Hour | PAM], _Now, _Opts) when ?is_meridian(PAM) -> {{Year, Month, Day}, {hour(Hour, PAM), 0, 0}}; @@ -181,9 +224,18 @@ parse(_Tokens, _Now, _Opts) -> tokenise([], Acc) -> lists:reverse(Acc); +tokenise([N1, N2, N3, N4, N5, N6 | Rest], Acc) + when ?is_num(N1), ?is_num(N2), ?is_num(N3), ?is_num(N4), ?is_num(N5), ?is_num(N6) -> + tokenise(Rest, [ ltoi([N1, N2, N3, N4, N5, N6]) | Acc]); +tokenise([N1, N2, N3, N4, N5 | Rest], Acc) + when ?is_num(N1), ?is_num(N2), ?is_num(N3), ?is_num(N4), ?is_num(N5) -> + tokenise(Rest, [ ltoi([N1, N2, N3, N4, N5]) | Acc]); tokenise([N1, N2, N3, N4 | Rest], Acc) when ?is_num(N1), ?is_num(N2), ?is_num(N3), ?is_num(N4) -> tokenise(Rest, [ ltoi([N1, N2, N3, N4]) | Acc]); +tokenise([N1, N2, N3 | Rest], Acc) + when ?is_num(N1), ?is_num(N2), ?is_num(N3) -> + tokenise(Rest, [ ltoi([N1, N2, N3]) | Acc]); tokenise([N1, N2 | Rest], Acc) when ?is_num(N1), ?is_num(N2) -> tokenise(Rest, [ ltoi([N1, N2]) | Acc]); @@ -264,6 +316,8 @@ tokenise("TH"++Rest, Acc) -> tokenise(Rest, Acc); tokenise("ND"++Rest, Acc) -> tokenise(Rest, Acc); tokenise("ST"++Rest, Acc) -> tokenise(Rest, Acc); tokenise("OF"++Rest, Acc) -> tokenise(Rest, Acc); +tokenise("T"++Rest, Acc) -> tokenise(Rest, Acc); % 2012-12-12T12:12:12 ISO formatting. +tokenise([$. | Rest], Acc) -> tokenise(Rest, [$. | Acc]); % 2012-12-12T12:12:12.xxxx ISO formatting. tokenise([Else | Rest], Acc) -> tokenise(Rest, [{bad_token, Else} | Acc]). @@ -329,13 +383,13 @@ format([$z|T], {Date,_}=Dt, Acc) -> format(T, Dt, [itol(days_in_year(Date))|Acc]); %% Time Formats -format([$a|T], {_,{H,_,_}}=Dt, Acc) when H > 12 -> +format([$a|T], Dt={_,{H,_,_}}, Acc) when H > 12 -> format(T, Dt, ["pm"|Acc]); -format([$a|T], Dt, Acc) -> +format([$a|T], Dt={_,{_,_,_}}, Acc) -> format(T, Dt, ["am"|Acc]); format([$A|T], {_,{H,_,_}}=Dt, Acc) when H > 12 -> format(T, Dt, ["PM"|Acc]); -format([$A|T], Dt, Acc) -> +format([$A|T], Dt={_,{_,_,_}}, Acc) -> format(T, Dt, ["AM"|Acc]); format([$g|T], {_,{H,_,_}}=Dt, Acc) when H == 12; H == 0 -> format(T, Dt, ["12"|Acc]); @@ -355,6 +409,38 @@ format([$i|T], {_,{_,M,_}}=Dt, Acc) -> format(T, Dt, [pad2(M)|Acc]); format([$s|T], {_,{_,_,S}}=Dt, Acc) -> format(T, Dt, [pad2(S)|Acc]); +format([$f|T], {_,{_,_,_}}=Dt, Acc) -> + format(T, Dt, [itol(0)|Acc]); + +%% Time Formats ms +format([$a|T], Dt={_,{H,_,_,_}}, Acc) when H > 12 -> + format(T, Dt, ["pm"|Acc]); +format([$a|T], Dt={_,{_,_,_,_}}, Acc) -> + format(T, Dt, ["am"|Acc]); +format([$A|T], {_,{H,_,_,_}}=Dt, Acc) when H > 12 -> + format(T, Dt, ["PM"|Acc]); +format([$A|T], Dt={_,{_,_,_,_}}, Acc) -> + format(T, Dt, ["AM"|Acc]); +format([$g|T], {_,{H,_,_,_}}=Dt, Acc) when H == 12; H == 0 -> + format(T, Dt, ["12"|Acc]); +format([$g|T], {_,{H,_,_,_}}=Dt, Acc) when H > 12 -> + format(T, Dt, [itol(H-12)|Acc]); +format([$g|T], {_,{H,_,_,_}}=Dt, Acc) -> + format(T, Dt, [itol(H)|Acc]); +format([$G|T], {_,{H,_,_,_}}=Dt, Acc) -> + format(T, Dt, [itol(H)|Acc]); +format([$h|T], {_,{H,_,_,_}}=Dt, Acc) when H > 12 -> + format(T, Dt, [pad2(H-12)|Acc]); +format([$h|T], {_,{H,_,_,_}}=Dt, Acc) -> + format(T, Dt, [pad2(H)|Acc]); +format([$H|T], {_,{H,_,_,_}}=Dt, Acc) -> + format(T, Dt, [pad2(H)|Acc]); +format([$i|T], {_,{_,M,_,_}}=Dt, Acc) -> + format(T, Dt, [pad2(M)|Acc]); +format([$s|T], {_,{_,_,S,_}}=Dt, Acc) -> + format(T, Dt, [pad2(S)|Acc]); +format([$f|T], {_,{_,_,_,Ms}}=Dt, Acc) -> + format(T, Dt, [itol(Ms)|Acc]); %% Whole Dates format([$c|T], {{Y,M,D},{H,Min,S}}=Dt, Acc) -> @@ -519,6 +605,7 @@ ltoi(X) -> -include_lib("eunit/include/eunit.hrl"). -define(DATE, {{2001,3,10},{17,16,17}}). +-define(DATEMS, {{2001,3,10},{17,16,17,123456}}). -define(ISO, "o \\WW"). basic_format_test_() -> @@ -674,3 +761,20 @@ iso_test_() -> ?_assertEqual("2009 W53",format(?ISO,{{2009,12,31},{1,1,1}})), ?_assertEqual("2009 W53",format(?ISO,{{2010,1,3}, {1,1,1}})) ]. + +ms_test_() -> + Now=now(), + [ + ?_assertEqual({{2012,12,12}, {12,12,12,1234}}, parse("2012-12-12T12:12:12.1234")), + ?_assertEqual(format("H:m:s.f \\m \\i\\s \\m\\o\\n\\t\\h",?DATEMS), + "17:03:17.123456 m is month"), + ?_assertEqual(format("Y-m-d\\TH:i:s.f",?DATEMS), + "2001-03-10T17:16:17.123456"), + ?_assertEqual(format("Y-m-d\\TH:i:s.f",nparse("2001-03-10T05:16:17.123456")), + "2001-03-10T05:16:17.123456"), + ?_assertEqual(format("Y-m-d\\TH:i:s.f",nparse("2001-03-10T05:16:17.123456")), + "2001-03-10T05:16:17.123456"), + ?_assertEqual(format("Y-m-d\\TH:i:s.f",nparse("2001-03-10T15:16:17.123456")), + "2001-03-10T15:16:17.123456"), + ?_assertEqual(Now, nparse(format("Y-m-d\\TH:i:s.f", Now))) + ]. diff --git a/src/ec_dictionary.erl b/src/ec_dictionary.erl index 1061665..416df72 100644 --- a/src/ec_dictionary.erl +++ b/src/ec_dictionary.erl @@ -41,6 +41,8 @@ -type key(T) :: T. -type value(T) :: T. +-ifdef(have_callback_support). + -callback new() -> any(). -callback has_key(key(any()), any()) -> boolean(). -callback get(key(any()), any()) -> any(). @@ -52,6 +54,27 @@ -callback from_list([{key(any()), value(any())}]) -> any(). -callback keys(any()) -> [key(any())]. +-else. + +%% In the case where R14 or lower is being used to compile the system +%% we need to export a behaviour info +-export([behaviour_info/1]). +-spec behaviour_info(atom()) -> [{atom(), arity()}] | undefined. +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(_Other) -> + undefined. +-endif. + %%%=================================================================== %%% API %%%=================================================================== diff --git a/src/ec_semver.erl b/src/ec_semver.erl index ac810e5..5855c23 100644 --- a/src/ec_semver.erl +++ b/src/ec_semver.erl @@ -1,4 +1,3 @@ - %%%------------------------------------------------------------------- %%% @copyright (C) 2011, Erlware LLC %%% @doc @@ -211,14 +210,18 @@ internal_parse_version([MMP, AlphaPart, BuildPart, _]) -> %% @doc helper function for the peg grammer to parse the iolist into a major_minor_patch -spec parse_major_minor_patch_minpatch(iolist()) -> major_minor_patch_minpatch(). parse_major_minor_patch_minpatch([MajVsn, [], [], []]) -> - MajVsn; + strip_maj_version(MajVsn); parse_major_minor_patch_minpatch([MajVsn, [<<".">>, MinVsn], [], []]) -> - {MajVsn, MinVsn}; -parse_major_minor_patch_minpatch([MajVsn, [<<".">>, MinVsn], [<<".">>, PatchVsn], []]) -> - {MajVsn, MinVsn, PatchVsn}; -parse_major_minor_patch_minpatch([MajVsn, [<<".">>, MinVsn], - [<<".">>, PatchVsn], [<<".">>, MinPatch]]) -> - {MajVsn, MinVsn, PatchVsn, MinPatch}. + {strip_maj_version(MajVsn), MinVsn}; +parse_major_minor_patch_minpatch([MajVsn, + [<<".">>, MinVsn], + [<<".">>, PatchVsn], []]) -> + {strip_maj_version(MajVsn), MinVsn, PatchVsn}; +parse_major_minor_patch_minpatch([MajVsn, + [<<".">>, MinVsn], + [<<".">>, PatchVsn], + [<<".">>, MinPatch]]) -> + {strip_maj_version(MajVsn), MinVsn, PatchVsn, MinPatch}. %% @doc helper function for the peg grammer to parse the iolist into an alpha part -spec parse_alpha_part(iolist()) -> [alpha_part()]. @@ -245,6 +248,14 @@ format_alpha_part([<<".">>, AlphaPart]) -> %%%=================================================================== %%% Internal Functions %%%=================================================================== +-spec strip_maj_version(iolist()) -> version_element(). +strip_maj_version([<<"v">>, MajVsn]) -> + MajVsn; +strip_maj_version([[], MajVsn]) -> + MajVsn; +strip_maj_version(MajVsn) -> + MajVsn. + -spec to_list(integer() | binary() | string()) -> string() | binary(). to_list(Detail) when erlang:is_integer(Detail) -> erlang:integer_to_list(Detail); @@ -308,8 +319,12 @@ internal_pes(Vsn, LVsn) -> eql_test() -> ?assertMatch(true, eql("1.0.0-alpha", "1.0.0-alpha")), + ?assertMatch(true, eql("v1.0.0-alpha", + "1.0.0-alpha")), ?assertMatch(true, eql("1", "1.0.0")), + ?assertMatch(true, eql("v1", + "v1.0.0")), ?assertMatch(true, eql("1.0", "1.0.0")), ?assertMatch(true, eql("1.0.0", @@ -322,6 +337,8 @@ eql_test() -> "1.0.0-alpha.1+build.1")), ?assertMatch(true, eql("1.0-alpha.1+build.1", "1.0.0.0-alpha.1+build.1")), + ?assertMatch(true, eql("1.0-alpha.1+build.1", + "v1.0.0.0-alpha.1+build.1")), ?assertMatch(true, eql("aa", "aa")), ?assertMatch(true, eql("AA.BB", "AA.BB")), ?assertMatch(true, eql("BBB-super", "BBB-super")), diff --git a/src/ec_semver_parser.peg b/src/ec_semver_parser.peg index 09779ae..1210ff9 100644 --- a/src/ec_semver_parser.peg +++ b/src/ec_semver_parser.peg @@ -1,7 +1,7 @@ semver <- major_minor_patch_min_patch ("-" alpha_part ("." alpha_part)*)? ("+" alpha_part ("." alpha_part)*)? !. ` ec_semver:internal_parse_version(Node) ` ; -major_minor_patch_min_patch <- version_part ("." version_part)? ("." version_part)? ("." version_part)? ; +major_minor_patch_min_patch <- ("v"? numeric_part / alpha_part) ("." version_part)? ("." version_part)? ("." version_part)? ; version_part <- numeric_part / alpha_part ;