Files

274 lines
10 KiB
Erlang
Raw Permalink Normal View History

2026-05-23 22:10:14 +08:00
%%----------------------------------------------------
% 通常工具包
%% @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).