Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

308 lines (253 sloc) 9.239 kB
%%%-------------------------------------------------------------------
%%% Created : 14 Oct 2005 by Torbjorn Tornkvist <tobbe@tornkvist.org>
%%% Desc. : A naive Mnesia table viewer Yaws-app interface.
%%%
%%% @author Torbjorn Tornkvist <tobbe@tornkvist.org>
%%%
%%% @doc Ymnesia is a Yaws appmod to view Mnesia tables.
%%% Add <b>ymnesia</b> as an appmod to your Yaws configuration.
%%% Point your browser to: &lt;url&gt;/ymnesia
%%%
%%% <ul>
%%% <li> You can search on arbitrary Erlang terms.
%%% <br/>(<i>atoms need to be single-quoted</i>)</li>
%%% <li> The checkbox control if the attribute should be shown in
%%% the result. <br/>No checkbox means: <i>show all attributes</i>.</li>
%%% </ul>
%%%
%%% <p>
%%% To test it, add it as an appmod to you Yaws configuration, e.g:
%%%
%%% appmods = [{"showdb", ymnesia}}
%%%
%%% then point your browser to: http://&lt;host>/showdb/
%%% </p>
%%% @end
%%%
%%%-------------------------------------------------------------------
-module(ymnesia).
-export([out/1]).
-import(lists, [map/2, foldl/3, reverse/1]).
-include("../include/yaws_api.hrl").
-define(elog(X,Y), error_logger:info_msg("*elog ~p:~p: " X,
[?MODULE, ?LINE | Y])).
%%% Possible to call mnesia in another way
-define(MNESIA(Mod, Args), apply(mnesia, Mod, Args)).
%%-define(MNESIA(Mod, Args), fdapi:call_in_master(mnesia, Mod, Args)).
%%% @private
out(A) ->
%%?elog("Inside Ymnesia~n", []),
case string:tokens(A#arg.appmoddata, "/.") of
["table" | _] ->
case is_post(A) of
true ->
L = yaws_api:parse_post(A),
{Cbox, Rest} = extract_cbox(L),
Name = lk("tablename", Rest),
Ls = select_fields(Rest),
Sp = (catch select_pattern(Name, Ls)),
case catch table(Cbox, Sp, l2a(Name)) of
{'EXIT', Reason} ->
?elog("Error , reason: ~p~n", [Reason]),
error_page("table not found: "++Name);
Else ->
Else
end;
false ->
return_top_page()
end;
_ ->
return_top_page()
end.
%%% Get the fields to be part of the select
select_fields([{"tablename",_}|T]) -> select_fields(T);
select_fields([{_,undefined}|T]) -> select_fields(T);
select_fields([H|T]) -> [H|select_fields(T)];
select_fields([]) -> [].
select_pattern(Name, Ls) ->
Wp = ?MNESIA(table_info, [l2a(Name), wild_pattern]),
mk_select_pattern(map(fun(A) -> a2l(A) end,get_attributes(l2a(Name))),
Ls, Wp, 2).
mk_select_pattern([A|As], [{A,V}|T], Wp, N) ->
mk_select_pattern(As, T, setelement(N, Wp, be_smart(V)), N+1);
mk_select_pattern([_|As], L, Wp, N) ->
mk_select_pattern(As, L, Wp, N+1);
mk_select_pattern([], [], Wp, _) ->
Wp.
%%% Try to be intelligent and convert the Key to the datatype that
%%% could be expected. Note: atom is anything between single quotes.
be_smart([$'|T]) -> l2a(eat_until($', T));
be_smart([$[|_] = L) -> str2term(L);
be_smart([${|_] = L) -> str2term(L);
be_smart(L) ->
case be_smart(L, false) of
integer -> list_to_integer(L);
float -> list_to_float(L);
_ -> L
end.
eat_until(H, [H|_]) -> [];
eat_until(X, [H|T]) -> [H|eat_until(X,T)].
be_smart([H|T], false) when H>$0, H=<$9 ->
be_smart(T, integer);
be_smart([H|T], integer) when H>$0, H=<$9 ->
be_smart(T, integer);
be_smart([H|T], integer) when H>$. ->
be_smart(T, float);
be_smart([_|T], _) ->
be_smart(T, list);
be_smart([], Type) ->
Type.
return_top_page() ->
{ehtml,
[{head, [],
[meta() ++
style()]},
{body, [],
mk_table_tab()}]}.
is_post(A) ->
case (A#arg.req)#http_request.method of
'GET' -> false;
'POST' -> true
end.
meta() ->
[{pre_html,
"<META HTTP-EQUIV=\"EXPIRES\" CONTENT=\""
"Sun, 16 Oct 2004 11:12:01 GMT\">"}].
style() ->
[{style, [{type, "text/css"}],
[{pre_html,
["table {border-collapse: collapse; border: solid black 1px;}\n"
"p {padding: 5px; font-weight: bold;}\n"
"input[type=text] {vertical-align: bottom; width: 100%; font-size: 80%;}\n"
"input[type=checkbox] {vertical-align: top; font-size: 80%;}\n"
"span.attribute {vertical-align: top; font-size: 80%;}\n"
"th {padding: 5px; border: solid black 1px;}\n"
"td {padding: 5px; border: solid black 1px;}\n"
]}]}].
%%% Build the result page.
table(Cbox, Sp, Table) when is_atom(Table) ->
case catch ?MNESIA(table_info, [Table, attributes]) of
Headers when is_list(Headers) ->
Vp = view_pattern(Cbox, map(fun(X) -> a2l(X) end, Headers)),
{Q, Result} = do_query(Sp),
{ehtml,
[{head, [],
[meta() ++
style()]},
{body, [],
[{'div', [],
{p, [], "Query: "++Q}},
{'div', [],
{p, [], "Table: "++a2l(Table)}} |
mk_tab(Vp, Headers, t2l(Result))]}]};
Else ->
Else
end.
%%% Create a pattern denoting which fields to show in the result.
view_pattern(Cs, L) -> view_pattern(Cs, L, 1).
view_pattern([Cbox|Cs], [Cbox|T], N) -> [N | view_pattern(Cs, T, N+1)];
view_pattern(Cs, [_|T], N) -> view_pattern(Cs, T, N+1);
view_pattern([], [], _) -> [].
%%% Create a table of: Table | Table-attribute-1 | ... | Table-attribute-N
%%% where each table is a Form
mk_table_tab() ->
Rows = get_tables(),
[{'div', [],
[{table, [],
map(fun(Row) ->
{tr, [],
{form, [{action, "table.yaws"},
{method, "post"},
{name, Row}],
[{td, [], sublnk(a2l(Row))} |
mk_input_fields(Row)]}}
end, Rows)}]}].
%%% Create each table cell; consisting of the attribute name and an input field.
mk_input_fields(Table) ->
As = get_attributes(Table),
Max = max_noof_attrs(),
map(fun(0) ->
{td, [], []};
(Attribute) ->
A = a2l(Attribute),
{td, [],
[{input, [{type, "checkbox"}, {name, "cbox_"++A}]},
{span, [{class, "attribute"}], A},
{input, [{type, "text"}, {name, A}]}]}
end, As ++ lists:duplicate(Max-length(As), 0)).
extract_cbox(L) ->
extract_cbox(L, [], []).
extract_cbox([{"cbox_"++Cbox,_}|T], Cs, Rs) ->
extract_cbox(T, [Cbox|Cs], Rs);
extract_cbox([H|T], Cs, Rs) ->
extract_cbox(T, Cs, [H|Rs]);
extract_cbox([], Cs, Rs) ->
{reverse(Cs), reverse(Rs)}.
%%% Build the result table.
mk_tab(Vp, Headers, Rows) ->
[{'div', [],
[{table, [],
[{tr, [],
[{th, [], a2l(X)} || X <- vp(Vp,Headers)]} |
map(fun(Row) ->
{tr, [],
[{td, [], massage(W)} || W <- vp(Vp,Row)]}
end, Rows)]}]}].
%%% Match the view pattern to select which entries to let through.
vp([], L) -> L;
vp(Vp, L) -> vp(Vp, L, 1).
vp([N|Vp], [H|T], N) -> [H|vp(Vp, T, N+1)];
vp(Vp, [_|T], N) -> vp(Vp, T, N+1);
vp([], [], _) -> [].
%%% Create a link that submit the form: onclick
sublnk(E) ->
{a, [{href, "#"},
{onclick, E++".submit();"}],
[E,
{input, [{type, "hidden"},
{name, "tablename"},
{value, E}]}]}.
massage(W) ->
lists:flatten(io_lib:format("~p",[W])).
do_query(Sp) ->
{lists:flatten(io_lib:format("mnesia:match_object(~p)", [Sp])),
lists:keysort(2, ?MNESIA(dirty_match_object, [Sp]))}.
error_page(Msg) ->
{html,
[Msg]}.
get_tables() ->
?MNESIA(system_info, [tables]) -- [schema].
get_attributes(Table) ->
?MNESIA(table_info, [Table, attributes]).
max_noof_attrs() ->
foldl(fun(Table, Max) ->
erlang:max(length(get_attributes(Table)), Max)
end, 0, get_tables()).
a2l(A) when is_atom(A) -> atom_to_list(A);
a2l(L) when is_list(L) -> L.
l2a(L) when is_list(L) -> list_to_atom(L);
l2a(A) when is_atom(A) -> A.
lk(Key, L) ->
{value, {_,Val}} = lists:keysearch(Key, 1, L),
Val.
t2l(L) ->
map(fun(T) -> tail(tuple_to_list(T)) end, L).
tail([]) -> [];
tail(L) -> tl(L).
str2term(Str) ->
hd(str2terms(Str ++ ". ")).
%% str2tokens:str2terms("{hello,23}. [arne,43]. 5.6. {5.5,1.0}. ").
%% ==> [{hello,23},[arne,43],5.60000,{5.50000,1.00000}]
str2terms(String) ->
tokenlists2terms(str2tokenlists(String)).
str2tokenlists("") -> [];
str2tokenlists(String) ->
case erl_scan:tokens([], String, 1) of
{done, {ok, Tokens, _}, Rest} ->
[Tokens | str2tokenlists(Rest)]
end.
tokenlists2terms(Lists) ->
lists:map(fun(L) ->
{ok, Term} = erl_parse:parse_term(L),
Term
end, Lists).
Jump to Line
Something went wrong with that request. Please try again.