Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

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.