Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

removing type tuples

  • Loading branch information...
commit b07e433279ece3cbd0068e6048f4aa6d9892fcf2 1 parent 94f106a
Jacob Vorreuter authored
3  .gitignore
View
@@ -1,2 +1,3 @@
ebin/*.beam
-ebin/*.app
+ebin/*.app
+ebin/*.boot
3  README.markdown
View
@@ -27,10 +27,11 @@ excavator runs off of template files written in Erlang. Templates contain custom
assign(user_ids, {regexp, users_txt, "id=([0-9]+)"}),
assign(pokemons, {http, get, "http://yummymeatwhiz.com/?page_id=223"})
assign(new_pokemon, {http, post, "http://yummymeatwhiz.com/?page_id=223", [{application_id, "monkeybrains"}], <<"Let me show you it">>})
-* __gassign__: the same as assign, associates value with state key in the global scope
+* __gassign__: the same as assign, but associates value with state key in the global scope
* __add__: similar to assign, but instead of overwriting the value of a state key it will append the value and build up a list
add(user_ids, {xpath, user_xml, "//id/text()"})
+* __gadd__: the same as add, but global
* __assert__: assert that the value associated with a state key meets certain criteria. The following assertions are supported:
assert(user_id, string)
7 include/excavator.hrl
View
@@ -22,12 +22,13 @@
-define(CRITICAL_REPORT(Args), ex_logger:critical_report(Args)).
-define(CRITICAL_MSG(Format, Args), ex_logger:critical_msg(Format, Args)).
+-record(http_response, {status, headers, body}).
+
-define(ADD, fun ex_util:add/3).
+-define(GLOBAL_ADD, fun ex_util:add/3).
-define(STORE, fun ex_util:store/3).
--define(STORE_VALUE, fun ex_util:store_value/3).
-define(GLOBAL_STORE, fun ex_util:global_store/3).
-define(FETCH, fun ex_util:fetch/2).
--define(FETCH_VALUE, fun ex_util:fetch_value/2).
-define(CONFIGURE, fun ex_util:configure/3).
-define(FETCH_CONFIG, fun ex_util:fetch_config/2).
--define(EVALUATE, fun ex_util:evaluate/2).
+-define(EXPAND, fun ex_eval:expand/2).
166 src/ex_consumer.erl
View
@@ -59,7 +59,7 @@ execute(State, [{Default, Function}|TailFunctions], Args) ->
%% == Template Functions
%% =============================================================================
assign(State, Key, Term) ->
- ?STORE(State, Key, compute(State, Term)).
+ ?STORE(State, Key, ?EXPAND(State, Term)).
assign_next_state(#state{instructions=[_|TailInstrs]}=S) ->
S#state{instructions=TailInstrs, request_times=update_request_times(S)}.
@@ -70,7 +70,7 @@ assign_print(State, Key, _) ->
%% =============================================================================
gassign(State, Key, Term) ->
- ?GLOBAL_STORE(State, Key, compute(State, Term)).
+ ?GLOBAL_STORE(State, Key, ?EXPAND(State, Term)).
gassign_print(State, Key, _) ->
?INFO_MSG(">> assign/3: ~p~n", [Key]),
@@ -78,15 +78,26 @@ gassign_print(State, Key, _) ->
%% =============================================================================
add(State, Key, Term) ->
- ?ADD(State, Key, compute(State, Term)).
+ ?ADD(State, Key, ?EXPAND(State, Term)).
add_print(State, Key, _) ->
?INFO_MSG(">> add/3: ~p~n", [Key]),
State.
%% =============================================================================
+gadd(State, Key, Term) ->
+ ?GLOBAL_ADD(State, Key, ?EXPAND(State, Term)).
+
+gadd_print(State, Key, _) ->
+ ?INFO_MSG(">> gadd/3: ~p~n", [Key]),
+ State.
+
+%% =============================================================================
assert(State, Key, Assertion) ->
- assert_true(Key, ?FETCH(State, Key), Assertion),
+ case assert_true(Key, ?EXPAND(State, Key), Assertion) of
+ true -> ok;
+ false -> exit({assertion_failed, {Key, ?EXPAND(State, Key), Assertion}})
+ end,
State.
assert_print(State, Key, Assertion) ->
@@ -103,7 +114,7 @@ commit(State, Key, Value) ->
end.
commit(State, Key, Value, {CallbackModule, CallbackFunction}) ->
- Value1 = ?EVALUATE(State, Value),
+ Value1 = ?EXPAND(State, Value),
case apply(CallbackModule, CallbackFunction, [Key, Value1]) of
State1 when is_record(State1, state) ->
State1;
@@ -118,7 +129,7 @@ each(State, Key, Source, _) ->
each_next_state(#state{instructions=[_|TailInstructions]}=State, _, Source, NewInstrs) ->
case ?FETCH(State, Source) of
- {_, []} -> %% last Source value; remove "each" instruction from list
+ [] -> %% last Source value; remove "each" instruction from list
Parent = State#state{instructions=TailInstructions},
State#state{instructions=NewInstrs, parent=Parent};
_ -> %% Source still has values for next time around
@@ -134,7 +145,7 @@ condition_next_state(State, Op, TrueInstrs) ->
condition_next_state(State, Op, TrueInstrs, []).
condition_next_state(#state{instructions=[_|TailInstructions]}=State, {op, Op, Left, Right}, TrueInstrs, FalseInstrs) ->
- case is_condition(State, Op, expand_operand(State, Left), expand_operand(State, Right)) of
+ case is_condition(State, Op, ?EXPAND(State, Left), ?EXPAND(State, Right)) of
true ->
Parent = State#state{instructions=TailInstructions},
State#state{instructions=TrueInstrs, parent=Parent};
@@ -179,32 +190,12 @@ function(State, Fun) when is_function(Fun) ->
%% =============================================================================
print(State, Key) ->
- ?INFO_REPORT({print, compute(State, Key)}),
+ ?INFO_REPORT({print, ?EXPAND(State, Key)}),
State.
%% =============================================================================
%% == Internal Functions
%% =============================================================================
-flatten_url(State, {Url, Props}) ->
- Url1 = flatten_url(State, Url),
- Props1 = [
- case V of
- V1 when is_atom(V1) ->
- {K, to_string(?FETCH(State, V))};
- _ ->
- {K, V}
- end || {K,V} <- Props],
- Props2 = mochiweb_util:urlencode(Props1),
- Url1 ++ "?" ++ Props2;
-
-flatten_url(State, Url) ->
- lists:flatten([begin
- case I of
- String when is_list(String) -> String;
- Atom when is_atom(Atom) -> to_string(?FETCH(State, Atom));
- Other -> Other
- end
- end || I <- Url]).
mk_f(Function, Postfix) ->
list_to_atom(lists:concat([Function, "_", Postfix])).
@@ -229,9 +220,9 @@ handle_failure(#state{fail={Err1, FailInstrs}}=S, Err2) ->
handle_failure(_, Err) -> exit(Err).
is_condition(S, Op, {op, Op1, Left, Right}, B) ->
- is_condition(S, Op, is_condition(S, Op1,expand_operand(S, Left),expand_operand(S, Right)), B);
+ is_condition(S, Op, is_condition(S, Op1,?EXPAND(S, Left),?EXPAND(S, Right)), B);
is_condition(S, Op, A, {op, Op1, Left, Right}) ->
- is_condition(S, Op, A, is_condition(S, Op1,expand_operand(S, Left),expand_operand(S, Right)));
+ is_condition(S, Op, A, is_condition(S, Op1,?EXPAND(S, Left),?EXPAND(S, Right)));
is_condition(_, 'orelse', A, B) when A orelse B -> true;
is_condition(_, 'andalso', A, B) when A andalso B -> true;
is_condition(_, '==', A, A) -> true;
@@ -244,19 +235,6 @@ is_condition(_, '<', A, B) when A < B -> true;
is_condition(_, '=<', A, B) when A =< B -> true;
is_condition(_, _, _, _) -> false.
-expand_operand(State, Operand) ->
- case Operand of
- Key when is_atom(Key) ->
- case ?FETCH(State, Key) of
- undefined ->
- Operand;
- {_, Value} ->
- Value
- end;
- Other ->
- Other
- end.
-
compare(Item, Item) -> true;
compare(Item1, Item2) when is_tuple(Item1), is_tuple(Item2) ->
@@ -275,92 +253,48 @@ compare(Item1, Item2) when is_list(Item1), is_list(Item2) ->
compare(_, _) -> false.
-compute(State, {FirstLast, Key}) when FirstLast==first;FirstLast==last ->
- case ?FETCH(State, Key) of
- {Type, Values} when (Type==list_of_nodes orelse Type==list_of_strings) andalso is_list(Values) ->
- [Val|_] = if FirstLast==first -> Values; true -> lists:reverse(Values) end,
- Val;
- Other ->
- exit({bad_data, {FirstLast, Key, Other}})
+assert_true(_K, V, string) when is_record(V, http_response) ->
+ case V#http_response.body of
+ String when is_list(String), length(String) > 0 -> true;
+ _ -> false
end;
-compute(State, {http, Method, Url}) when Method==options;Method==get;Method==head;Method==delete;Method==trace ->
- compute(State, {http, Method, Url, [], []});
-compute(State, {http, Method, Url, Headers}) when Method==options;Method==get;Method==head;Method==delete;Method==trace ->
- compute(State, {http, Method, Url, Headers, []});
-compute(State, {http, Method, Url, Headers, Body}) ->
- Url1 = flatten_url(State, Url),
- ex_web:request(Method, Url1, Headers, Body);
-compute(State, {xpath, Source, XPath}) ->
- ex_xpath:run(XPath, ?FETCH(State, Source));
-compute(State, {regexp, Source, Regexp}) ->
- ex_re:run(Regexp, ?FETCH(State, Source));
-compute(_State, {range, Current, Last}) ->
- {range, Current, Last};
-compute(_State, {Type, Value}) when is_atom(Type) ->
- {Type, Value};
-compute(_State, [H|_]=String) when is_integer(H) ->
- {string, String};
-compute(_State, [[H|_]|_]=Strings) when is_integer(H) ->
- {list_of_strings, [{string, String} || String <- Strings]};
-compute(_State, Int) when is_integer(Int) ->
- {integer, Int};
-compute(_State, {_,_,_}=Node) ->
- {node, Node};
-compute(_State, [{_,_,_}|_]=Nodes) ->
- {list_of_nodes, [{node, Node} || Node <- Nodes]};
-compute(State, Key) when is_atom(Key) ->
- case ?FETCH(State, Key) of
- undefined -> Key;
- Value -> Value
+assert_true(_K, V, {status, Status}) when is_record(V, http_response) ->
+ V#http_response.status == Status;
+assert_true(_K, V, list_of_strings) when is_list(V) ->
+ case lists:usort([ex_util:typeof(I) || I <- V]) of
+ [string] -> true;
+ _ -> false
end;
-compute(State, [Atom|_]=List) when is_atom(Atom) ->
- {mixed, [?FETCH(State, Item) || Item <- List]}.
-
-assert_true(_K, {nil, Val}, nil) when Val==[]; Val==undefined -> ok;
-assert_true(_K, {string, Val}, string) when is_list(Val), length(Val) > 0 -> ok;
-assert_true(_K, {node, Val}, node) when is_tuple(Val) -> ok;
-assert_true(_K, {list_of_strings, Val}, list_of_strings) when is_list(Val) -> [assert_true(_K, Item, string) || Item <- Val], ok;
-assert_true(_K, {list_of_nodes, Val}, list_of_nodes) when is_list(Val) -> [assert_true(_K, Item, node) || Item <- Val], ok;
-assert_true(_K, {http_response, _S, _H, Body}, string) when is_list(Body), length(Body) > 0 -> ok;
-assert_true(_K, {http_response, Status, _H, _B}, {status, Status}) -> ok;
-assert_true(_K, {mixed, List}, mixed) when is_list(List) -> ok;
-assert_true(Key, Val, Assertion) -> exit({assertion_failed, {Key, Val, Assertion}}).
+assert_true(_K, V, list_of_nodes) when is_list(V) ->
+ case lists:usort([ex_util:typeof(I) || I <- V]) of
+ [node] -> true;
+ _ -> false
+ end;
+assert_true(_K, V, Assertion) ->
+ ex_util:typeof(V) == Assertion.
store_next_value(State, Key, Source, {range, Last, Last}) when is_integer(Last) ->
- OldState = ?STORE(State, Source, {nil, []}),
- ?STORE(OldState, Key, {string, integer_to_list(Last)});
+ OldState = ?STORE(State, Source, []),
+ ?STORE(OldState, Key, integer_to_list(Last));
store_next_value(State, Key, Source, {range, Current, Last}) when is_integer(Current), is_integer(Last) ->
OldState = ?STORE(State, Source, {range, Current+1, Last}),
- ?STORE(OldState, Key, {string, integer_to_list(Current)});
+ ?STORE(OldState, Key, integer_to_list(Current));
-store_next_value(_State, _Key, Source, {_Type, Val}) when Val==undefined; Val==[] ->
+store_next_value(_State, _Key, Source, Val) when Val==undefined; Val==[] ->
exit({?MODULE, ?LINE, fetch_failed, Source, Val});
-store_next_value(State, Key, Source, {Type, [Val]}) -> %% last item in list
- OldState = ?STORE(State, Source, {Type, []}),
- ?STORE(OldState, Key, typify_value(Type, Val));
-
-store_next_value(State, Key, Source, {Type, [Val|Tail]}) ->
- OldState = ?STORE(State, Source, {Type, Tail}), %% insert list tail for source key
- ?STORE(OldState, Key, typify_value(Type, Val)). %% insert item from source
-
-typify_value(list_of_strings, {string, String}) ->
- {string, String};
-typify_value(list_of_strings, String) when is_list(String) ->
- {string, String};
-typify_value(list_of_nodes, {node, Node}) ->
- {node, Node};
-typify_value(list_of_nodes, Node) ->
- {node, Node}.
+store_next_value(State, Key, Source, [Val]) -> %% last item in list
+ OldState = ?STORE(State, Source, []),
+ ?STORE(OldState, Key, Val);
+store_next_value(State, Key, Source, [Val|Tail]) ->
+ OldState = ?STORE(State, Source, Tail), %% insert list tail for source key
+ ?STORE(OldState, Key, Val). %% insert item from source
+
update_request_times(#state{request_times=Times}) ->
Secs = ex_util:seconds(),
[Secs|lists:filter(
fun(S) ->
S >= Secs-1
- end, Times)].
-
-to_string(List) when is_list(List) -> List;
-to_string({string, String}) -> String;
-to_string({node, Node}) -> to_string(ex_xpath:reassemble({node, Node})).
+ end, Times)].
88 src/ex_eval.erl
View
@@ -0,0 +1,88 @@
+%% Copyright (c) 2009 Jacob Vorreuter <jacob.vorreuter@gmail.com>
+%%
+%% Permission is hereby granted, free of charge, to any person
+%% obtaining a copy of this software and associated documentation
+%% files (the "Software"), to deal in the Software without
+%% restriction, including without limitation the rights to use,
+%% copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following
+%% conditions:
+%%
+%% The above copyright notice and this permission notice shall be
+%% included in all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+%% OTHER DEALINGS IN THE SOFTWARE.
+-module(ex_eval).
+-export([expand/2]).
+
+-include("excavator.hrl").
+
+%% {first, key}
+%% {last, key}
+expand(State, {FirstLast, Key}) when (FirstLast==first orelse FirstLast==last) andalso is_atom(Key) ->
+ case ?FETCH(State, Key) of
+ Values when is_list(Values) ->
+ [Val|_] = if FirstLast==first -> Values; true -> lists:reverse(Values) end,
+ Val;
+ undefined ->
+ {expand(State, FirstLast), Key}
+ end;
+
+%% HTTP requests
+expand(State, {http, Method, Url}) when Method==options;Method==get;Method==head;Method==delete;Method==trace ->
+ expand(State, {http, Method, Url, [], []});
+expand(State, {http, Method, Url, Headers}) when Method==options;Method==get;Method==head;Method==delete;Method==trace ->
+ expand(State, {http, Method, Url, Headers, []});
+expand(State, {http, Method, Url, Headers, Body}) ->
+ Url1 = flatten_url(State, Url),
+ ex_web:request(Method, Url1, Headers, Body);
+
+%% XPath
+expand(State, {xpath, Source, XPath}) ->
+ ex_xpath:run(XPath, expand(State, Source));
+
+%% Regexp
+expand(State, {regexp, Source, Regexp}) ->
+ ex_re:run(State, Regexp, expand(State, Source));
+
+%% Range
+expand(_State, {range, Current, Last}) ->
+ {range, Current, Last};
+
+%% Key
+expand(State, Key) when is_atom(Key) ->
+ case ?FETCH(State, Key) of
+ undefined -> Key;
+ Value -> expand(State, Value)
+ end;
+
+%% List
+expand(State, List) when is_list(List) ->
+ [expand(State, I) || I <- List];
+
+%% Tuple
+expand(State, Tuple) when is_tuple(Tuple) ->
+ list_to_tuple([expand(State, I) || I <- tuple_to_list(Tuple)]);
+
+%% Other
+expand(_, Other) -> Other.
+
+%% =============================================================================
+%% == Internal Functions
+%% =============================================================================
+flatten_url(State, {Url, Props}) ->
+ Url1 = flatten_url(State, Url),
+ Props1 = [{K, expand(State, V)} || {K,V} <- Props],
+ Props2 = mochiweb_util:urlencode(Props1),
+ Url1 ++ "?" ++ Props2;
+
+flatten_url(State, Url) ->
+ lists:flatten([expand(State, I) || I <- Url]).
2  src/ex_pp.erl
View
@@ -95,7 +95,7 @@ build_instr({call, _, {atom, _, Instr}, Args})
when Instr==configure; Instr==assign; Instr==assert;
Instr==print; Instr==function;
Instr==onfail; Instr==commit; Instr==add;
- Instr==gassign ->
+ Instr==gassign; Instr==gadd ->
{tuple, ?L, [{atom,?L,instr}, {atom,?L,Instr}, to_cons(Args)]};
build_instr(Instr) ->
52 src/ex_re.erl
View
@@ -21,38 +21,40 @@
%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
%% OTHER DEALINGS IN THE SOFTWARE.
-module(ex_re).
--export([run/2]).
+-export([run/3]).
-%% @spec run(Regexp, Subject) -> Result
-%% Regexp = {re_pattern, _, _, _}
-%% Subject = {Type, Value}
-%% Result = {nil, _} | {string, _} | {list_of_strings, _}
-run(Regexp, {node, Subject}) when is_tuple(Regexp), is_tuple(Subject) ->
- run(Regexp, ex_xpath:reassemble({node, Subject}));
-
-run(Regexp, {list_of_nodes, Nodes}) when is_tuple(Regexp), is_list(Nodes) ->
- String1 = lists:concat([begin
- {string, String} = ex_xpath:reassemble({node, Subject}),
- String
- end || {node, Subject} <- Nodes]),
- run(Regexp, {string, String1});
-
-run(Regexp, {list_of_strings, Strings}) when is_tuple(Regexp), is_list(Strings) ->
- String1 = lists:concat([String || {string, String} <- Strings]),
- run(Regexp, {string, String1});
-
-run(Regexp, {string, Subject}) when is_tuple(Regexp), is_list(Subject) ->
+-include("excavator.hrl").
+
+run(State, Regexp, Term) when is_tuple(Regexp) ->
+ Subject = stringify(State, Term),
case re:run(Subject, Regexp, [global]) of
nomatch ->
- {nil, []};
+ [];
{match, Match} ->
case process(Match, Subject, []) of
- [] -> {nil, []};
- [String] when is_list(String) -> {string, String};
- [String|_] = List when is_list(String) -> {list_of_strings, lists:reverse(List)}
+ [] -> [];
+ [String] when is_list(String) -> String;
+ List when is_list(List) -> lists:reverse(List)
end
end.
+stringify(_, HttpResponse) when is_record(HttpResponse, http_response) ->
+ HttpResponse#http_response.body;
+stringify(_, {A,B,C}) when is_binary(A), is_list(B), is_list(C) ->
+ ex_xpath:reassemble({A,B,C});
+stringify(S, List) when is_list(List) ->
+ lists:flatten([stringify(S, I) || I <- List]);
+stringify(S, Tuple) when is_tuple(Tuple) ->
+ list_to_tuple([stringify(S, I) || I<- tuple_to_list(Tuple)]);
+stringify(S, Key) when is_atom(Key) ->
+ case ?FETCH(S, Key) of
+ undefined -> lists:flatten(io_lib:format("~p", [Key]));
+ Value -> stringify(S, Value)
+ end;
+stringify(_, Int) when is_integer(Int) -> Int;
+stringify(_, Term) ->
+ lists:flatten(io_lib:format("~p", [Term])).
+
process([], _, Acc) -> Acc;
process([ [ {_,_}, {Start,Length} ] |Tail], Subject, Acc) ->
@@ -60,4 +62,4 @@ process([ [ {_,_}, {Start,Length} ] |Tail], Subject, Acc) ->
process(Tail, Subject, Acc1);
process(A,_,_) ->
- erlang:error("Cannot process regexp results", [A]).
+ exit({error, {"Cannot process regexp results", A}}).
69 src/ex_util.erl
View
@@ -21,7 +21,7 @@
%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
%% OTHER DEALINGS IN THE SOFTWARE.
-module(ex_util).
--export([add/3, store/3, store_value/3, global_store/3, fetch/2, fetch_value/2, configure/3, fetch_config/2, evaluate/2, seconds/0]).
+-export([add/3, global_add/3, store/3, global_store/3, fetch/2, configure/3, fetch_config/2, seconds/0, typeof/1]).
-include("excavator.hrl").
@@ -33,12 +33,18 @@ add(#state{dictionary=D}=S, K, V) ->
error -> [V]
end,
S#state{dictionary=dict:store(K, V2, D)}.
+
+global_add(#state{parent=P}=S, K, V) ->
+ P1 = case P of
+ undefined -> P;
+ _ -> global_add(P, K, V)
+ end,
+ S1 = add(S, K, V),
+ io:format("S1#state{parent=P1}: ~p~n", [S1#state{parent=P1}]),
+ S1#state{parent=P1}.
store(#state{dictionary=D}=S, K, V) ->
S#state{dictionary=dict:store(K, V, D)}.
-
-store_value(S, K, V) ->
- store(S, K, {get_type(V), V}).
global_store(#state{parent=P}=S, K, V) ->
P1 = case P of
@@ -53,39 +59,7 @@ fetch(#state{dictionary=D}, K) ->
{ok, V} -> V;
error -> undefined
end.
-
-fetch_value(#state{dictionary=D}, K) ->
- case dict:find(K, D) of
- {ok, {_T, V}} -> strip_types(V);
- error -> undefined
- end.
-
-strip_types({Type, Val}) when Type==string;Type==nil;Type==node;Type==list_of_nodes;Type==list_of_strings;Type==mixed ->
- Val;
-strip_types(List) when is_list(List) ->
- [strip_types(I) || I <- List];
-strip_types(Val) ->
- Val.
-
-get_type(undefined) -> nil;
-get_type([]) -> nil;
-get_type([{A,B,C}|Rest]) when is_binary(A), is_list(B), is_list(C) ->
- case [undefined || {D,E,F} <- Rest, is_binary(D), is_list(E), is_list(F)] of
- [] -> list_of_nodes;
- _ -> mixed
- end;
-get_type([A|Rest]) when is_integer(A) ->
- case [I || I <- Rest, not is_integer(I)] of
- [] -> string;
- _ -> mixed
- end;
-get_type([A|Rest]) when is_list(A) ->
- case [I || I <- Rest, not is_list(I)] of
- [] -> list_of_strings;
- _ -> mixed
- end;
-get_type({A,B,C}) when is_binary(A), is_list(B), is_list(C) -> node.
-
+
configure(#state{configuration=C}=S, K, V) ->
S#state{configuration=dict:store(K, V, C)}.
@@ -95,14 +69,17 @@ fetch_config(#state{configuration=C}, K) ->
error -> undefined
end.
-evaluate(State, Tuple) when is_tuple(Tuple) ->
- list_to_tuple([evaluate(State, I) || I <- tuple_to_list(Tuple)]);
-evaluate(State, Key) when is_atom(Key) ->
- ?FETCH_VALUE(State, Key);
-evaluate(State, List) when is_list(List) ->
- [evaluate(State, I) || I <- List];
-evaluate(_State, Other) -> Other.
-
seconds() ->
{_,Secs,Micro} = erlang:now(),
- Secs + (Micro / 1000000).
+ Secs + (Micro / 1000000).
+
+typeof({A,B,C}) when is_binary(A), is_list(B), is_list(C) -> node;
+typeof(HttpResponse) when is_record(HttpResponse, http_response) -> http_response;
+typeof({range, A, B}) when is_integer(A), is_integer(B) -> range;
+typeof([H|_]=List) when is_integer(H) ->
+ lists:foldl(
+ fun (I, string) when is_integer(I) -> string;
+ (_, _) -> list
+ end, string, List);
+typeof(List) when is_list(List) -> list;
+typeof(_) -> term.
81 src/ex_xpath.erl
View
@@ -23,58 +23,37 @@
-module(ex_xpath).
-export([run/2, reassemble/1]).
-%% @spec run(XPath, Subject) -> Result
-%% XPath = string()
-%% Subject = {Type, Value}
-%% Result = {nil, []} | {node, _} | {list_of_nodes, _} | {string, _} | {list_of_strings, _}
+-include("excavator.hrl").
+
run(XPath, {http_response, _, _, Body}) ->
- run(XPath, {string, Body});
+ run(XPath, Body);
-run(XPath, {string, Subject0}) when is_list(XPath), is_list(Subject0) ->
- case mochiweb_html:parse(Subject0) of
- Subject when is_tuple(Subject) ->
- run(XPath, {node, Subject});
- _ ->
- exit({?MODULE, ?LINE, XPath, Subject0})
- end;
-
-run(XPath, {node, Subject}) when is_list(XPath), is_tuple(Subject) ->
- R = mochiweb_xpath:execute(XPath, Subject),
- case R of
- [] ->
- {nil, []};
- [I|_] = String when is_integer(I) ->
- {string, String};
- List when is_list(List) ->
- lists:foldr(
- fun (Bin, {list_of_strings, Acc}) when is_binary(Bin) ->
- {list_of_strings, [{string, binary_to_list(Bin)}|Acc]};
- (Bin, {string, Acc}) when is_binary(Bin) ->
- {list_of_strings, [{string, binary_to_list(Bin)}, {string, Acc}]};
- (Bin, {undefined, []}) when is_binary(Bin) ->
- {string, binary_to_list(Bin)};
- (Bin, {node, _}=Acc) when is_binary(Bin) ->
- {mixed, [{string, binary_to_list(Bin)},Acc]};
- (Bin, {list_of_nodes, Acc}) when is_binary(Bin) ->
- {mixed, [{string, binary_to_list(Bin)}|Acc]};
- (Bin, {mixed, Acc}) when is_binary(Bin) ->
- {mixed, [{string, binary_to_list(Bin)}|Acc]};
- ({_,_,_}=Node, {list_of_nodes, Acc}) ->
- {list_of_nodes, [{node, Node}|Acc]};
- ({_,_,_}=Node, {undefined, []}) ->
- {list_of_nodes, [{node, Node}]};
- ({_,_,_}=Node, {list_of_strings, Acc}) ->
- {mixed, [{node, Node}|Acc]};
- ({_,_,_}=Node, {mixed, Acc}) ->
- {mixed, [{node, Node}|Acc]};
- ({_,_,_}=Node, {string, _}=Acc) ->
- {mixed, [{node, Node},Acc]};
- (Other, {Type,Acc}) ->
- exit({?MODULE, ?LINE, Other, {Type,Acc}})
- end, {undefined, []}, List);
- _ ->
- exit({?MODULE, ?LINE, XPath, Subject})
+run(XPath, {A,B,C}) when is_binary(A), is_list(B), is_list(C) ->
+ run_internal(XPath, {A,B,C});
+
+run(XPath, Subject0) when is_list(XPath), is_list(Subject0) ->
+ case ex_util:typeof(Subject0) of
+ string ->
+ case mochiweb_html:parse(Subject0) of
+ Subject when is_tuple(Subject) ->
+ run_internal(XPath, Subject);
+ _ ->
+ exit({?MODULE, ?LINE, XPath, Subject0})
+ end;
+ list ->
+ run_internal(XPath, Subject0)
+ end.
+
+run_internal(XPath, Subject) ->
+ Results =
+ [case Result of
+ Bin when is_binary(Bin) -> binary_to_list(Bin);
+ _ -> Result
+ end || Result <- mochiweb_xpath:execute(XPath, Subject)],
+ case Results of
+ [Single] -> Single;
+ _ -> Results
end.
-reassemble({node, Node}) ->
- {string, binary_to_list(iolist_to_binary(mochiweb_html:to_html(Node)))}.
+reassemble({A,B,C}) when is_binary(A), is_list(B), is_list(C) ->
+ binary_to_list(iolist_to_binary(mochiweb_html:to_html({A,B,C}))).
8 t/excavator_t_001.t
View
@@ -24,7 +24,7 @@ start() ->
BadAlbumIDs = ["9246d2023c3bee56"],
ValidateAlbumID = fun(S) ->
- {string, AID} = ex_util:fetch(S, album_id),
+ AID = ex_util:fetch(S, album_id),
{http_response, Status, _, _} = ex_util:fetch(S, album_page),
Result =
case [lists:member(AID, GoodAlbumIDs), lists:member(AID, BadAlbumIDs)] of
@@ -37,12 +37,12 @@ start() ->
end,
ValidateOnFail = fun(S) ->
- {string, AID} = ex_util:fetch(S, album_id),
+ AID = ex_util:fetch(S, album_id),
etap:ok(not lists:member(AID, GoodAlbumIDs) andalso lists:member(AID, BadAlbumIDs), "failed ok")
end,
ValidateCommitData = fun(S) ->
- Commit = ex_util:evaluate(S, {album_id, album_name}),
+ Commit = ex_eval:expand(S, {album_id, album_name}),
etap:ok(lists:member(Commit, lists:zip(GoodAlbumIDs, GoodAlbumNames)), "commit data ok")
end,
@@ -63,7 +63,7 @@ start() ->
[ {instr, assert, [album_page, {status, 200}]},
{instr, assert, [album_page, string]},
{instr, assign, [album_name_node, {xpath, album_page, "//div[@class='album-name']"}]},
- {instr, assert, [album_name_node, list_of_nodes]},
+ {instr, assert, [album_name_node, node]},
{instr, assign, [album_name, {regexp, album_name_node, compile_re(" &gt; (.*)</div>")}]},
{instr, assert, [album_name, string]},
{instr, commit, [{album, beatles}, {album_id, album_name}]},
13 t/excavator_t_002.t
View
@@ -63,18 +63,18 @@ start() ->
],
ValidateUser = fun(S) ->
- {string, PageNum} = ex_util:fetch(S, page_num),
- {string, Username} = ex_util:fetch(S, username),
+ PageNum = ex_util:fetch(S, page_num),
+ Username = ex_util:fetch(S, username),
etap:ok(lists:member(Username, proplists:get_value(list_to_integer(PageNum), TestData)), "valid user: " ++ Username)
end,
ValidateResults = fun(S) ->
- {string, PageNum} = ex_util:fetch(S, page_num),
+ PageNum = ex_util:fetch(S, page_num),
etap:ok(lists:member(list_to_integer(PageNum), [1,2,3]), "results ok")
end,
ValidateOnFail = fun(S) ->
- {string, PageNum} = ex_util:fetch(S, page_num),
+ PageNum = ex_util:fetch(S, page_num),
etap:ok(lists:member(list_to_integer(PageNum), [4]), "fail ok")
end,
@@ -87,8 +87,9 @@ start() ->
{instr, assign, [search_results, {xpath, search_result_page, "//li[@class='result ']"}]},
{instr, assert, [search_results, list_of_nodes]},
{instr, each, [search_result, search_results, [
- {instr, assign, [username, {xpath, search_result, "//div[@class='msg']/a[1]/text()"}]},
- {instr, assert, [username, string]},
+ {instr, assign, [username, {xpath, search_result, "//div[@class='msg']/a[1]/text()"}]},
+ {instr, print, [username]},
+ {instr, assert, [username, string]},
{instr, assign, [msg, {xpath, search_result, "string(//span[starts-with(@class, 'msgtxt')]/*)"}]},
{instr, assert, [msg, string]},
{instr, function, [ValidateUser]}
0  t/github_t_001.t → t/excavator_t_003.t
View
File renamed without changes
8 templates/github.ex
View
@@ -43,12 +43,12 @@ main(Users) ->
]).
validate_supported_event_types(S) ->
- {string, ET} = ex_util:fetch(S, event_type),
- {string, Auth} = ex_util:fetch(S, author),
- {string, U} = ex_util:fetch(S, user),
+ ET = ex_util:fetch(S, event_type),
+ Auth = ex_util:fetch(S, author),
+ U = ex_util:fetch(S, user),
etap:ok(ET == "PushEvent" orelse ET == "IssuesEvent", "event type matches"),
etap:is(string:to_lower(Auth), string:to_lower(U), "author matches").
validate_unsupported_event_types(S) ->
- {string, ET} = ex_util:fetch(S, event_type),
+ ET = ex_util:fetch(S, event_type),
etap:ok(ET =/= "PushEvent" andalso ET =/= "IssuesEvent", "unsupported event type matches").
Please sign in to comment.
Something went wrong with that request. Please try again.