274 lines
10 KiB
Erlang
274 lines
10 KiB
Erlang
|
|
%%----------------------------------------------------
|
||
|
|
% 通常工具包
|
||
|
|
%% @author whjing2011@gmail.com
|
||
|
|
%%----------------------------------------------------
|
||
|
|
-module(util).
|
||
|
|
-export([
|
||
|
|
is_exists_fun/2
|
||
|
|
,multi_exec/3
|
||
|
|
,multi_exec/4
|
||
|
|
,sync_multi_exec/3
|
||
|
|
,sync_multi_exec/4
|
||
|
|
,template/2
|
||
|
|
,template_replace/2
|
||
|
|
,batch_replace/2
|
||
|
|
,replace/3
|
||
|
|
,to_string/1
|
||
|
|
,to_list/1
|
||
|
|
,to_binary/1
|
||
|
|
,to_integer/1
|
||
|
|
,string_to_term/1
|
||
|
|
,term_to_string/1
|
||
|
|
,make_dir/1
|
||
|
|
,printf/3
|
||
|
|
]).
|
||
|
|
-include("common.hrl").
|
||
|
|
|
||
|
|
% @doc 判断模块是否存在指定方法
|
||
|
|
-spec is_exists_fun(atom(), tuple()) -> true | false.
|
||
|
|
is_exists_fun(Mod, Fun) ->
|
||
|
|
{_, FunList} = lists:keyfind(exports, 1, Mod:module_info()),
|
||
|
|
[Fun1 || Fun1 <- FunList, Fun1 =:= Fun] =/= [].
|
||
|
|
|
||
|
|
%% @doc 批量处理 不保证顺序
|
||
|
|
-spec multi_exec(list(), Fun::function(), ProcNum::pos_integer()) -> [Ret::any()].
|
||
|
|
multi_exec(List, Fun, ProcNum) ->
|
||
|
|
multi_exec(List, Fun, ProcNum, undefined).
|
||
|
|
multi_exec(List, Fun, ProcNum, RetFun) ->
|
||
|
|
multi_exec(0, ProcNum, 1, List, Fun, RetFun, []).
|
||
|
|
multi_exec(0, _ProcNum, _Num, [], _Fun, _RetFun, RetList) ->
|
||
|
|
RetList;
|
||
|
|
multi_exec(N, 1, Num, [I | List], Fun, RetFun, RetList) ->
|
||
|
|
Ret = Fun(Num, I),
|
||
|
|
multi_exec(N, 1, Num + 1, List, Fun, RetFun, do_exce_ret(Ret, RetFun, RetList));
|
||
|
|
multi_exec(N, ProcNum, Num, [I | List], Fun, RetFun, RetList) when N < ProcNum ->
|
||
|
|
Pid = self(),
|
||
|
|
spawn(fun() -> Pid ! {self(), Pid, catch Fun(Num, I)} end),
|
||
|
|
multi_exec(N + 1, ProcNum, Num + 1, List, Fun, RetFun, RetList);
|
||
|
|
multi_exec(N, ProcNum, Num, List, Fun, RetFun, RetList) ->
|
||
|
|
Pid = self(),
|
||
|
|
receive
|
||
|
|
{_FromPid, Pid, Ret} ->
|
||
|
|
multi_exec(N - 1, ProcNum, Num, List, Fun, RetFun, do_exce_ret(Ret, RetFun, RetList))
|
||
|
|
end.
|
||
|
|
|
||
|
|
%% @doc 批量处理 保证顺序
|
||
|
|
-spec sync_multi_exec(list(), Fun::function(), ProcNum::pos_integer()) -> [Ret::any()].
|
||
|
|
sync_multi_exec(List, Fun, ProcNum) ->
|
||
|
|
sync_multi_exec(List, Fun, ProcNum, undefined).
|
||
|
|
sync_multi_exec(List, Fun, ProcNum, RetFun) ->
|
||
|
|
sync_multi_exec(0, queue:new(), ProcNum, 1, List, Fun, RetFun, []).
|
||
|
|
sync_multi_exec(0, _Queue, _ProcNum, _Num, [], _Fun, _RetFun, RetList) ->
|
||
|
|
RetList;
|
||
|
|
sync_multi_exec(N, Queue, 1, Num, [I | List], Fun, RetFun, RetList) ->
|
||
|
|
Ret = Fun(Num, I),
|
||
|
|
sync_multi_exec(N, Queue, 1, Num + 1, List, Fun, RetFun, do_exce_ret(Ret, RetFun, RetList));
|
||
|
|
sync_multi_exec(N, Queue, ProcNum, Num, [I | List], Fun, RetFun, RetList) when N < ProcNum ->
|
||
|
|
Pid = self(),
|
||
|
|
P = spawn(fun() -> Pid ! {self(), Pid, catch Fun(Num, I)} end),
|
||
|
|
sync_multi_exec(N + 1, queue:in(P, Queue), ProcNum, Num + 1, List, Fun, RetFun, RetList);
|
||
|
|
sync_multi_exec(N, Queue, ProcNum, Num, List, Fun, RetFun, RetList) ->
|
||
|
|
Pid = self(),
|
||
|
|
{{value, FromPid}, NewQueue} = queue:out(Queue),
|
||
|
|
receive
|
||
|
|
{FromPid, Pid, Ret} -> %% 先执行的先接收
|
||
|
|
sync_multi_exec(N - 1, NewQueue, ProcNum, Num, List, Fun, RetFun, do_exce_ret(Ret, RetFun, RetList))
|
||
|
|
end.
|
||
|
|
do_exce_ret(false, _RetFun, RetList) -> RetList;
|
||
|
|
do_exce_ret(error, _RetFun, RetList) -> RetList;
|
||
|
|
do_exce_ret(skip, _RetFun, RetList) -> RetList;
|
||
|
|
do_exce_ret(Ret, undefined, RetList) -> [Ret | RetList];
|
||
|
|
do_exce_ret(Ret, RetFun, RetList) ->
|
||
|
|
case RetFun(Ret) of
|
||
|
|
false -> RetList;
|
||
|
|
{ok, NewRet} -> [NewRet | RetList];
|
||
|
|
_ -> [Ret | RetList]
|
||
|
|
end.
|
||
|
|
|
||
|
|
%% @doc 替换模板变量
|
||
|
|
-spec template(string(), [{atom(), string()}]) -> string() | {error, term()}.
|
||
|
|
template(File, Vars) ->
|
||
|
|
case file:read_file(File) of
|
||
|
|
{error, Reason} -> {error, Reason};
|
||
|
|
{ok, Content} ->
|
||
|
|
V = [{lists:concat(["{{", K, "}}"]), to_list(V)} || {K, V} <- Vars],
|
||
|
|
template_replace(bitstring_to_list(Content), V)
|
||
|
|
end.
|
||
|
|
template_replace(Text, []) -> Text;
|
||
|
|
template_replace(Text, [{K, V} | L]) ->
|
||
|
|
%% 用re:replace性能差到不能看...
|
||
|
|
%% T = re:replace(Text, K, V, [caseless, global]),
|
||
|
|
T = replace(Text, K, V),
|
||
|
|
template_replace(T, L).
|
||
|
|
|
||
|
|
%% @doc 替换字符串
|
||
|
|
%% @todo 有必要再优化下性能
|
||
|
|
-spec replace(string(), string(), string()) -> string().
|
||
|
|
replace([], _Search, _Replace) -> "";
|
||
|
|
replace(Str, Search, Replace) ->
|
||
|
|
replace(Str, Search, Replace, length(Search), []).
|
||
|
|
replace(Str, Search, Replace, Len, Rtn) ->
|
||
|
|
case string:str(Str, Search) of
|
||
|
|
0 -> Rtn ++ Str;
|
||
|
|
P ->
|
||
|
|
S = string:substr(Str, 1, P - 1) ++ Replace,
|
||
|
|
replace(string:substr(Str, P + Len), Search, Replace, Len, Rtn ++ S)
|
||
|
|
end.
|
||
|
|
|
||
|
|
%% @doc 大批量替换字符串(优先替换最长表达式)
|
||
|
|
-spec batch_replace(string(), list()) -> string().
|
||
|
|
batch_replace(Text, []) -> Text;
|
||
|
|
batch_replace(Text, Vars) ->
|
||
|
|
KeyM = init_batch_replace_index(Vars, #{}),
|
||
|
|
do_batch_replace(Text, [], KeyM).
|
||
|
|
do_batch_replace([], Body, _KeyM) ->
|
||
|
|
lists:reverse(lists:flatten(Body));
|
||
|
|
do_batch_replace(S = [I | T], Body, KeyM) ->
|
||
|
|
case do_batch_replace_find(S, [], KeyM, []) of
|
||
|
|
{true, {Rest, S1}} ->
|
||
|
|
S2 = lists:reverse(maps:get({s,lists:reverse(S1)}, KeyM)),
|
||
|
|
do_batch_replace(Rest, [S2 | Body], KeyM);
|
||
|
|
false ->
|
||
|
|
do_batch_replace(T, [I | Body], KeyM)
|
||
|
|
end.
|
||
|
|
do_batch_replace_find([], _Head, _KeyM, []) ->
|
||
|
|
false;
|
||
|
|
do_batch_replace_find([], _Head, _KeyM, [Buff | _]) ->
|
||
|
|
{true, Buff};
|
||
|
|
do_batch_replace_find([I | T], Head, KeyM, Buffs) ->
|
||
|
|
NewHead = [I | Head],
|
||
|
|
case maps:get(NewHead, KeyM, undefined) of
|
||
|
|
continue ->
|
||
|
|
do_batch_replace_find(T, NewHead, KeyM, Buffs);
|
||
|
|
over ->
|
||
|
|
{true, {T, NewHead}};
|
||
|
|
include ->
|
||
|
|
do_batch_replace_find(T, NewHead, KeyM, [{T, NewHead}]);
|
||
|
|
_ ->
|
||
|
|
do_batch_replace_find([], [], KeyM, Buffs)
|
||
|
|
end.
|
||
|
|
|
||
|
|
init_batch_replace_index([], M) ->
|
||
|
|
M;
|
||
|
|
init_batch_replace_index([{S1,S2} | T], M) ->
|
||
|
|
M1 = maps:put({s,S1}, S2, M),
|
||
|
|
NewM = do_init_batch_replace_index(S1, [], M1),
|
||
|
|
init_batch_replace_index(T, NewM).
|
||
|
|
do_init_batch_replace_index([], _Head, M) ->
|
||
|
|
M;
|
||
|
|
do_init_batch_replace_index([I], Head, M) ->
|
||
|
|
NewHead = [I | Head],
|
||
|
|
case maps:get(NewHead, M, undefined) of
|
||
|
|
continue -> maps:put(NewHead, include, M);
|
||
|
|
over -> M;
|
||
|
|
include -> M;
|
||
|
|
_ -> maps:put(NewHead, over, M)
|
||
|
|
end;
|
||
|
|
do_init_batch_replace_index([I | T], Head, M) ->
|
||
|
|
NewHead = [I | Head],
|
||
|
|
case maps:get(NewHead, M, undefined) of
|
||
|
|
include ->
|
||
|
|
do_init_batch_replace_index(T, NewHead, M);
|
||
|
|
over ->
|
||
|
|
NewM = maps:put(NewHead, include, M),
|
||
|
|
do_init_batch_replace_index(T, NewHead, NewM);
|
||
|
|
continue ->
|
||
|
|
do_init_batch_replace_index(T, NewHead, M);
|
||
|
|
_ ->
|
||
|
|
NewM = maps:put(NewHead, continue, M),
|
||
|
|
do_init_batch_replace_index(T, NewHead, NewM)
|
||
|
|
end.
|
||
|
|
|
||
|
|
%% @doc 将任意类型的数据转成string()类型
|
||
|
|
-spec to_string(any()) -> string().
|
||
|
|
to_string(X) -> lists:flatten(io_lib:format("~w", [X])).
|
||
|
|
|
||
|
|
%% @doc 将任意类型的数据转成list()类型(主要用于控制台打印).
|
||
|
|
%% <div>注意:tuple类型有特殊处理</div>
|
||
|
|
-spec to_list(any()) -> list().
|
||
|
|
to_list(X) when is_integer(X) -> integer_to_list(X);
|
||
|
|
to_list(X) when is_float(X) -> float_to_list(X);
|
||
|
|
to_list(X) when is_atom(X) -> atom_to_list(X);
|
||
|
|
to_list(X) when is_binary(X) -> binary_to_list(X);
|
||
|
|
to_list(X) when is_pid(X) -> pid_to_list(X);
|
||
|
|
to_list(X) when is_function(X) -> erlang:fun_to_list(X);
|
||
|
|
to_list(X) when is_port(X) -> erlang:port_to_list(X);
|
||
|
|
to_list(X) when is_tuple(X) -> do_tuple(tuple_to_list(X), []);
|
||
|
|
to_list(X) when is_list(X) -> X.
|
||
|
|
do_tuple([], L) ->
|
||
|
|
["{" | lists:reverse(["}" | L])];
|
||
|
|
do_tuple([T], L) ->
|
||
|
|
do_tuple([], [to_list(T) | L]);
|
||
|
|
do_tuple([H | T], L) ->
|
||
|
|
S = to_list(H),
|
||
|
|
S1 = [S | ", "],
|
||
|
|
do_tuple(T, [S1 | L]).
|
||
|
|
|
||
|
|
%% 转换成数值
|
||
|
|
to_integer(V) when is_list(V) -> list_to_integer(V);
|
||
|
|
to_integer(V) when is_binary(V) -> list_to_integer(binary_to_list(V));
|
||
|
|
to_integer(V) when is_atom(V) -> list_to_integer(atom_to_list(V));
|
||
|
|
to_integer(V) when is_integer(V) -> V.
|
||
|
|
|
||
|
|
%% @doc term反序列化,string转换为term
|
||
|
|
-spec string_to_term(String) -> {error, Reason} | {ok, term()} when
|
||
|
|
String :: undefined | string() | bitstring(),
|
||
|
|
Reason :: term().
|
||
|
|
string_to_term(undefined) -> {ok, undefined};
|
||
|
|
string_to_term("undefined") -> {ok, undefined};
|
||
|
|
string_to_term(String) when is_bitstring(String) ->
|
||
|
|
string_to_term(binary_to_list(String));
|
||
|
|
string_to_term(String) ->
|
||
|
|
case erl_scan:string(String ++ ".") of
|
||
|
|
{ok, Tokens, _} -> erl_parse:parse_term(Tokens);
|
||
|
|
{error, Err, _} -> {error, Err}
|
||
|
|
end.
|
||
|
|
|
||
|
|
%% @doc term序列化,term转换为string格式
|
||
|
|
-spec term_to_string(term()) -> string().
|
||
|
|
term_to_string(Term) -> io_lib:format("~w", [Term]).
|
||
|
|
|
||
|
|
%% @doc 将Val值转换为binary格式(8位二进制)
|
||
|
|
%% @todo 貌似没有什么用,考虑删除掉
|
||
|
|
-spec to_binary(integer()) -> binary().
|
||
|
|
to_binary(Val) when is_integer(Val) -> list_to_binary(integer_to_list(Val));
|
||
|
|
to_binary(Val) when is_float(Val) -> list_to_binary(float_to_list(Val));
|
||
|
|
to_binary(Val) when is_list(Val) -> list_to_binary(Val);
|
||
|
|
to_binary(Val) when is_binary(Val) -> Val;
|
||
|
|
to_binary(_Val) -> <<>>.
|
||
|
|
|
||
|
|
%% @doc 建立指定目录
|
||
|
|
-spec make_dir(string()) -> atom().
|
||
|
|
make_dir(Dir) ->
|
||
|
|
case filelib:is_dir(Dir) of
|
||
|
|
false -> file:make_dir(Dir);
|
||
|
|
_ -> skip
|
||
|
|
end.
|
||
|
|
|
||
|
|
%%--------------------------------------------------------------------
|
||
|
|
%% @doc
|
||
|
|
%% 彩色输出
|
||
|
|
%% @end
|
||
|
|
%%--------------------------------------------------------------------
|
||
|
|
-spec printf(T :: atom(), F :: string(), A :: list())-> ok.
|
||
|
|
printf(Tag, F, A) when is_list(F) ->
|
||
|
|
{NewF, NewA} =
|
||
|
|
case string:to_lower(erlang:system_info(otp_release)) of
|
||
|
|
R when R > "r16" orelse R > "16" ->
|
||
|
|
case Tag of
|
||
|
|
info -> %% 绿色
|
||
|
|
{"~s" ++ F ++ "~s", [?GREEN] ++ A ++ [?DEF_COLOR]};
|
||
|
|
error -> %% 红色
|
||
|
|
{"~s" ++ F ++ "~s", [?RED] ++ A ++ [?DEF_COLOR]};
|
||
|
|
warning -> %% 黄色
|
||
|
|
{"~s" ++ F ++ "~s", [?YELLOW] ++ A ++ [?DEF_COLOR]};
|
||
|
|
purple -> %% 紫色
|
||
|
|
{"~s" ++ F ++ "~s", [?PURPLE] ++ A ++ [?DEF_COLOR]}
|
||
|
|
end;
|
||
|
|
_ ->
|
||
|
|
{F, A}
|
||
|
|
end,
|
||
|
|
?P(NewF, NewA);
|
||
|
|
printf(_, F, A) ->
|
||
|
|
?P(F, A).
|