%% --- %% 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), %%