%% ---
%% Excerpted from "Programming Erlang"
%% Copyrights apply to this code. It may not be used to create training material,
%% courses, books, articles, and the like. Contact us if you are in doubt.
%% We make no guarantees that this code is fit for any purpose.
%% Visit http://www.pragmaticprogrammer.com/titles/jaerlang for more book information.
%%---
-module(lib_misc).
%% commonly used routines
-export([consult/1,
dump/2,
first/1,
for/3,
is_prefix/2,
deliberate_error/1,
deliberate_error1/1,
duplicates/1,
downcase_char/1,
downcase_str/1,
extract_attribute/2,
eval_file/1,
every/3,
file_size_and_type/1,
flush_buffer/0,
foreachWordInFile/2,
foreachWordInString/2,
keep_alive/2,
glurk/2,
lookup/2,
odds_and_evens/1,
odds_and_evens_acc/1,
on_exit/2,
make_global/2,
make_test_strings/1,
merge_kv/1,
ndots/1,
test_function_over_substrings/2,
partition/2,
pmap/2,
pmap1/2,
priority_receive/0,
pythag/1,
replace/3,
split/2,
safe/1,
too_hot/0,
ls/1,
mini_shell/0,
odd/1,
outOfDate/2,
exists/1,
perms/1,
qsort/1,
random_seed/0,
read_file_as_lines/1,
remove_duplicates/1,
remove_prefix/2,
remove_leading_whitespace/1,
remove_trailing_whitespace/1,
rpc/2,
spawn_monitor/3,
sum/1,
sqrt/1,
string2term/1,
string2value/1,
term2string/1,
term2file/2,
file2term/1,
longest_common_prefix/1,
unconsult/2]).
-export([complete/2,
skip_blanks/1, trim_blanks/1, sleep/1, split_at_char/2,
%% mk_tree/1,
is_blank_line/1,
have_common_prefix/1]).
-import(lists, [all/2, any/2, filter/2, reverse/1, reverse/2,
foreach/2, map/2, member/2, sort/1]).
-define(NYI(X),(begin
io:format("*** NYI ~p ~p ~p~n",[?MODULE, ?LINE, X]),
exit(nyi)
end)).
glurk(X, Y) ->
?NYI({glurk, X, Y}).
-include_lib("kernel/include/file.hrl").
file_size_and_type(File) ->
case file:read_file_info(File) of
{ok, Facts} ->
{Facts#file_info.type, Facts#file_info.size};
_ ->
error
end.
ls(Dir) ->
{ok, L} = file:list_dir(Dir),
map(fun(I) -> {I, file_size_and_type(I)} end, sort(L)).
consult(File) ->
case file:open(File, read) of
{ok, S} ->
Val = consult1(S),
file:close(S),
{ok, Val};
{error, Why} ->
{error, Why}
end.
consult1(S) ->
case io:read(S, '') of
{ok, Term} -> [Term|consult1(S)];
eof -> [];
Error -> Error
end.
dump(File, Term) ->
Out = File ++ ".tmp",
io:format("** dumping to ~s~n",[Out]),
{ok, S} = file:open(Out, [write]),
io:format(S, "~p.~n",[Term]),
file:close(S).
partition(F, L) -> partition(F, L, [], []).
partition(F, [H|T], Yes, No) ->
case F(H) of
true -> partition(F, T, [H|Yes], No);
false -> partition(F, T, Yes, [H|No])
end;
partition(_, [], Yes, No) ->
{Yes, No}.
remove_duplicates(L) ->
remove_duplicates(lists:sort(L), []).
remove_duplicates([H|X=[H|_]], L) -> remove_duplicates(X, L);
remove_duplicates([H|T], L) -> remove_duplicates(T, [H|L]);
remove_duplicates([], L) -> L.
%% is_prefix(A, B) -> bool()
%% true if A is a prefix of B
is_prefix([], _) -> true;
is_prefix([H|T], [H|T1]) -> is_prefix(T, T1);
is_prefix(_, _) -> false.
first([_]) -> [];
first([H|T]) -> [H|first(T)].
sleep(T) ->
receive
after T ->
true
end.
flush_buffer() ->
receive
_Any ->
flush_buffer()
after 0 ->
true
end.
priority_receive() ->
receive
{alarm, X} ->
{alarm, X}
after 0 ->
receive
Any ->
Any
end
end.
duplicates(X) -> find_duplicates(sort(X), []).
find_duplicates([H,H|T], [H|_]=L) ->
find_duplicates(T, L);
find_duplicates([H,H|T], L) ->
find_duplicates(T, [H|L]);
find_duplicates([_|T], L) ->
find_duplicates(T, L);
find_duplicates([], L) ->
L.
%% complete(A, L) -> {yes, S}
%% error - means no string will ever match
%% {more,L} - means there are completions but I need more characters
%% L = [Str] = list of possible completions
%% {yes, S} - means there is a unique completion
%%
%% A = S = str(), L=[str()]
%% used to compute the smallest S
%% such that A ++ S is a member of all elements of L
complete(Str, L) ->
case filter(fun(I) -> is_prefix(Str, I) end, L) of
[] ->
error;
[L1] ->
J = remove_prefix(Str, L1),
{yes, J};
L1 ->
%% L1 is not empty so it's either more or a string
%% We know that Str is a prefix of all elements in L1
L2 = map(fun(I) -> remove_prefix(Str, I) end, L1),
%% L2 will also not be empty
%% io:format("L1=~p L2=~p~n",[L1,L2]),
case longest_common_prefix(L2) of
[] ->
{more, L1};
S ->
{yes, S}
end
end.
%% remove_prefix(X, Y) -> Z
%% finds Z such that X ++ Z = Y
%%
remove_prefix([H|T], [H|T1]) -> remove_prefix(T, T1);
remove_prefix([], L) -> L.
%% longest_common_prefix([str()]) -> str()
longest_common_prefix(L) ->
longest_common_prefix(L, []).
longest_common_prefix(Ls, L) ->
case have_common_prefix(Ls) of
{yes, H, Ls1} ->
longest_common_prefix(Ls1, [H|L]);
no ->
reverse(L)
end.
have_common_prefix([]) -> no;
have_common_prefix(L) ->
case any(fun is_empty_list/1, L) of
true -> no;
false ->
%% All lists have heads and tails
Heads = map(fun(I) -> hd(I) end, L),
H = hd(Heads),
case all(fun(X) -> hd(X) =:= H end, L) of
true ->
Tails = map(fun(I) -> tl(I) end, L),
{yes, H, Tails};
false ->
no
end
end.
is_empty_list([]) -> true;
is_empty_list(X) when list(X) -> false.
skip_blanks([$\s|T]) -> skip_blanks(T);
skip_blanks(X) -> X.
trim_blanks(X) -> reverse(skip_blanks(reverse(X))).
split_at_char(Str, C) -> split_at_char(Str, C, []).
split_at_char([C|T], C, L) -> {yes, reverse(L), T};
split_at_char([H|T], C, L) -> split_at_char(T, C, [H|L]);
split_at_char([], _, _) -> no.
%% read file into line buffer
read_file_as_lines(File) ->
case file:read_file(File) of
{ok, Bin} ->
{ok, split_into_lines(binary_to_list(Bin), 1, [])};
{error, _} ->
{error, eNoFile}
end.
split_into_lines([], _, L) ->
reverse(L);
split_into_lines(Str, Ln, L) ->
{Line, Rest} = get_line(Str, []),
split_into_lines(Rest, Ln+1, [{Ln,Line}|L]).
get_line([$\n|T], L) -> {reverse(L), T};
get_line([H|T], L) -> get_line(T, [H|L]);
get_line([], L) -> {reverse(L), []}.
is_blank_line([$\s|T]) -> is_blank_line(T);
is_blank_line([$\n|T]) -> is_blank_line(T);
is_blank_line([$\r|T]) -> is_blank_line(T);
is_blank_line([$\t|T]) -> is_blank_line(T);
is_blank_line([]) -> true;
is_blank_line(_) -> false.
%%----------------------------------------------------------------------
%% lookup
%%----------------------------------------------------------------------
%% split(Pred, L) -> {True, False}
split(F, L) -> split(F, L, [], []).
split(F, [H|T], True, False) ->
case F(H) of
true -> split(F, T, [H|True], False);
false -> split(F, T, True, [H|False])
end;
split(_, [], True, False) ->
{reverse(True), reverse(False)}.
%%----------------------------------------------------------------------
outOfDate(In, Out) ->
case exists(Out) of
true ->
case {last_modified(In), last_modified(Out)} of
{T1, T2} when T1 > T2 ->
true;
_ ->
false
end;
false ->
true
end.
last_modified(File) ->
case file:read_file_info(File) of
{ok, Info} ->
Info#file_info.mtime;
_ ->
0
end.
exists(File) ->
case file:read_file_info(File) of
{ok, _} ->
true;
_ ->
false
end.
%%----------------------------------------------------------------------
%% replace(Key,Val, [{Key,Val}]) -> [{Key,Val}]
%% replace and Key with Key,Val in the association list Old
replace(Key, Val, Old) ->
replace(Key, Val, Old, []).
replace(Key, Val1, [{Key,_Val}|T], L) ->
reverse(L, [{Key, Val1}|T]);
replace(Key, Val, [H|T], L) ->
replace(Key, Val, T, [H|L]);
replace(Key, Val, [], L) ->
[{Key,Val}|L].
%%----------------------------------------------------------------------
%% make_test_strings(Str)
%%
make_test_strings(Str) ->
L = length(Str),
make_test_strings(Str, L+1, 1).
make_test_strings(_, Max, Max) -> [];
make_test_strings(Str, Max, N) ->
[string:sub_string(Str, 1, N)|make_test_strings(Str, Max, N+1)].
test_function_over_substrings(F, Str) ->
L = make_test_strings(Str),
foreach(fun(S) ->
io:format("|~s|~n => ~p~n", [S, F(S)])
end, L).
%%----------------------------------------------------------------------
%% merge_kv(Kv) -> Kv'
%% Take a association list of {Key, Val} where Key can occure
%% More than once and make it into a list {Key, [Val]} where
%% each Key occurs only once
merge_kv(KV) -> merge_kv(KV, dict:new()).
merge_kv([{Key,Val}|T], D0) ->
case dict:find(Key, D0) of
{ok, L} -> merge_kv(T, dict:store(Key, [Val|L], D0));
error -> merge_kv(T, dict:store(Key, [Val], D0))
end;
merge_kv([], D) ->
dict:to_list(D).
%% rpc/2
%%
rpc(Pid, Q) ->
Pid ! {self(), Q},
receive
{Pid, Reply} ->
Reply
end.
%% odd(X)
%%
odd(X) ->
case X band 1 of
1 -> true;
0 -> false
end.
ndots([$.|T]) -> 1 + ndots(T);
ndots([_|T]) -> ndots(T);
ndots([]) -> 0.
term2file(File, Term) ->
file:write_file(File, term_to_binary(Term)).
file2term(File) ->
{ok, Bin} = file:read_file(File),
binary_to_term(Bin).
string2term(Str) ->
{ok,Tokens,_} = erl_scan:string(Str ++ "."),
{ok,Term} = erl_parse:parse_term(Tokens),
Term.
term2string(Term) ->
lists:flatten(io_lib:format("~p",[Term])).
downcase_str(Str) -> map(fun downcase_char/1, Str).
downcase_char(X) when $A =< X, X =< $Z -> X+ $a - $A;
downcase_char(X) -> X.
string2value(Str) ->
{ok, Tokens, _} = erl_scan:string(Str ++ "."),
{ok, Exprs} = erl_parse:parse_exprs(Tokens),
Bindings = erl_eval:new_bindings(),
{value, Value, _} = erl_eval:exprs(Exprs, Bindings),
Value.
mini_shell() ->
mini_shell(erl_eval:new_bindings()).
mini_shell(Bindings0) ->
case io:get_line('>>> ') of
"q\n" -> void;
Str ->
{Value, Bindings1} = string2value(Str, Bindings0),
io:format("~p~n",[Value]),
mini_shell(Bindings1)
end.
string2value(Str, Bindings0) ->
{ok, Tokens, _} = erl_scan:string(Str ++ "."),
{ok, Exprs} = erl_parse:parse_exprs(Tokens),
{value, Value, Bindings1} = erl_eval:exprs(Exprs, Bindings0),
{Value, Bindings1}.
eval_file(File) ->
{ok, S} = file:open(File, [read]),
Vals = eval_file(S, 1, erl_eval:new_bindings()),
file:close(S),
Vals.
eval_file(S, Line, B0) ->
case io:parse_erl_exprs(S, '', Line) of
{ok, Form, Line1} ->
{value, Value, B1} = erl_eval:exprs(Form, B0),
[Value|eval_file(S, Line1, B1)];
{eof, _} ->
[]
end.
remove_leading_whitespace([$\n|T]) -> remove_leading_whitespace(T);
remove_leading_whitespace([$\s|T]) -> remove_leading_whitespace(T);
remove_leading_whitespace([$\t|T]) -> remove_leading_whitespace(T);
remove_leading_whitespace(X) -> X.
remove_trailing_whitespace(X) ->
reverse(remove_leading_whitespace(reverse(X))).
safe(Fun) ->
case (catch Fun()) of
{'EXIT', Why} ->
{error, Why};
Other ->
Other
end.
too_hot() ->
event_handler:event(errors, too_hot).
%% spawn_monitor behaves just like spawn
spawn_monitor(_, false, Fun) ->
spawn(Fun);
spawn_monitor(Term, true, Fun) ->
spawn(fun() -> starter(Term, Fun) end).
starter(Term, Fun) ->
S = self(),
io:format("process:~p started at:~p ~p~n",
[self(), erlang:now(), Term]),
Monitor = spawn_link(fun() -> monitor(Term, S) end),
receive
{Monitor, ready} ->
Fun()
end.
monitor(Term, Parent) ->
process_flag(trap_exit, true),
Parent ! {self(), ready},
receive
{'EXIT', Parent, Why} ->
io:format("process:~p dies at:~p ~p reason:~p~n",
[self(), erlang:now(), Term, Why])
end.
keep_alive(Name, Fun) ->
register(Name, Pid = spawn(Fun)),
on_exit(Pid, fun(_Why) -> keep_alive(Name, Fun) end).
%% make_global(Name, Fun) checks if there is a global process with the
%% registered name Name. If there is no process it spawns a process to
%% evaluate Fun() and registers it with the name Name.
make_global(Name, Fun) ->
S = self(),
Pid = spawn(fun() -> make_global(S, Name, Fun) end),
receive
{Pid, Reply} ->
Reply
end.
make_global(Parent, Name, Fun) ->
case (catch register(Name, self())) of
true -> Fun();
_ -> true
end,
Parent ! {self(), ok}.
%% on_exit(Pid, Fun) links to Pid. If Pid dies with reason Why then
%% Fun(Why) is evaluated:
on_exit(Pid, Fun) ->
spawn(fun() ->
process_flag(trap_exit, true), %%
link(Pid), %%
receive
{'EXIT', Pid, Why} -> %%
Fun(Why) %%
end
end).
%% every(Pid, Time, Fun) links to Pid then every Time Fun() is
%% evaluated. If Pid exits, this process stops.
every(Pid, Time, Fun) ->
spawn(fun() ->
process_flag(trap_exit, true),
link(Pid),
every_loop(Pid, Time, Fun)
end).
every_loop(Pid, Time, Fun) ->
receive
{'EXIT', Pid, _Why} ->
true
after Time ->
Fun(),
every_loop(Pid, Time, Fun)
end.
for(Max, Max, F) -> [F(Max)];
for(I, Max, F) -> [F(I)|for(I+1, Max, F)].
qsort([]) -> [];
qsort([Pivot|T]) ->
qsort([X || X <- T, X < Pivot])
++ [Pivot] ++
qsort([X || X <- T, X >= Pivot]).
perms([]) -> [[]];
perms(L) -> [[H|T] || H <- L, T <- perms(L--[H])].
pythag(N) ->
[ {A,B,C} ||
A <- lists:seq(1,N),
B <- lists:seq(1,N),
C <- lists:seq(1,N),
A+B+C =< N,
A*A+B*B =:= C*C
].
extract_attribute(File, Key) ->
case beam_lib:chunks(File,[attributes]) of
{ok, {attrs, [{attributes,L}]}} ->
lookup(Key, L);
_ -> exit(badFile)
end.
lookup(Key, [{Key,Val}|_]) -> {ok, Val};
lookup(Key, [_|T]) -> lookup(Key, T);
lookup(_, []) -> error.
unconsult(File, L) ->
{ok, S} = file:open(File, write),
lists:foreach(fun(X) -> io:format(S, "~p.~n",[X]) end, L),
file:close(S).
random_seed() ->
{_,_,X} = erlang:now(),
{H,M,S} = time(),
H1 = H * X rem 32767,
M1 = M * X rem 32767,
S1 = S * X rem 32767,
put(random_seed, {H1,M1,S1}).
odds_and_evens(L) ->
Odds = [X || X <- L, (X rem 2) =:= 1],
Evens = [X || X <- L, (X rem 2) =:= 0],
{Odds, Evens}.
odds_and_evens_acc(L) ->
odds_and_evens_acc(L, [], []).
odds_and_evens_acc([H|T], Odds, Evens) ->
case (H rem 2) of
1 -> odds_and_evens_acc(T, [H|Odds], Evens);
0 -> odds_and_evens_acc(T, Odds, [H|Evens])
end;
odds_and_evens_acc([], Odds, Evens) ->
{Odds, Evens}.
sum(L) -> sum(L, 0).
sum([], N) -> N;
sum([H|T], N) -> sum(T, H+N).
sqrt(X) when X < 0 ->
erlang:error({squareRootNegativeArgument, X});
sqrt(X) ->
math:sqrt(X).
pmap(F, L) ->
S = self(),
%% make_ref() returns a unique reference
%% we'll match on this later
Ref = erlang:make_ref(),
Pids = map(fun(I) ->
spawn(fun() -> do_f(S, Ref, F, I) end)
end, L),
%% gather the results
gather(Pids, Ref).
do_f(Parent, Ref, F, I) ->
Parent ! {self(), Ref, (catch F(I))}.
gather([Pid|T], Ref) ->
receive
{Pid, Ref, Ret} -> [Ret|gather(T, Ref)]
end;
gather([], _) ->
[].
pmap1(F, L) ->
S = self(),
Ref = erlang:make_ref(),
foreach(fun(I) ->
spawn(fun() -> do_f1(S, Ref, F, I) end)
end, L),
%% gather the results
gather1(length(L), Ref, []).
do_f1(Parent, Ref, F, I) ->
Parent ! {Ref, (catch F(I))}.
gather1(0, _, L) -> L;
gather1(N, Ref, L) ->
receive
{Ref, Ret} -> gather1(N-1, Ref, [Ret|L])
end.
%% evalute F(Word) for each word in the file File
foreachWordInFile(File, F) ->
case file:read_file(File) of
{ok, Bin} -> foreachWordInString(binary_to_list(Bin), F);
_ -> void
end.
foreachWordInString(Str, F) ->
case get_word(Str) of
no ->
void;
{Word, Str1} ->
F(Word),
foreachWordInString(Str1, F)
end.
isWordChar(X) when $A=< X, X=<$Z -> true;
isWordChar(X) when $0=< X, X=<$9 -> true;
isWordChar(X) when $a=< X, X=<$z -> true;
isWordChar(_) -> false.
get_word([H|T]) ->
case isWordChar(H) of
true -> collect_word(T, [H]);
false -> get_word(T)
end;
get_word([]) ->
no.
collect_word([H|T]=All, L) ->
case isWordChar(H) of
true -> collect_word(T, [H|L]);
false -> {reverse(L), All}
end;
collect_word([], L) ->
{reverse(L), []}.
deliberate_error(A) ->
bad_function(A, 12),
lists:reverse(A).
bad_function(A, _) ->
{ok, Bin} = file:open({abc,123}, A),
binary_to_list(Bin).
deliberate_error1(A) ->
bad_function(A, 12).