-module(bucfile). -export([ expand_path/1, normalize_path/1, user_home/0, make_dir/1, remove_recursive/1, copy/2, copy/3, copyfile/2, copyfile/3, relative_from/2, realpath/1, wildcard/2, wildcard/3, match/2, match/3, is_executable/1, is_executable/2, is_symlink/1, is_broken/1 ]). -include_lib("kernel/include/file.hrl"). -type copyfile_option() :: preserve_file_info | default_file_info | {directory_mode, integer()} | {regular_file_mode, integer()} | {executable_file_mode, integer()}. -type copy_option() :: recursive | {exclude, [file:filename()]} | {only, [file:filename()]} | copyfile_option(). %% @doc %% Expand the given path %% %% Example: %% %%
%% "/home/user" = bucfile:expand_path("~").
%% <<"/home/user">> = bucfile:expand_path(<<"~">>).
%%
%% @end
-spec expand_path(Path :: string() | binary()) -> binary() | list().
expand_path(Path) when is_binary(Path) ->
do_as_list(?MODULE, expand_path, Path);
expand_path(Path) when is_list(Path) ->
normalize_path(filename:absname(expand_home(Path))).
%% @doc
%% Normalize the given path
%%
%% Example:
%%
%%
%% "/" = bucfile:normalize_path("/toto/tutu/../../../../../..").
%% <<"/">> = bucfile:normalize_path(<<"/toto/tutu/../../../../../..">>).
%% "/toto/titi" = bucfile:normalize_path("/toto/tata/../titi").
%%
%% @end
-spec normalize_path(Path :: string() | binary()) -> string() | binary().
normalize_path(Path) when is_binary(Path) ->
do_as_list(?MODULE, normalize_path, Path);
normalize_path(Path) when is_list(Path) ->
normalize_path(filename:split(Path), []).
normalize_path([".."|T], []) ->
normalize_path(T, []);
normalize_path([".."|T], [_|Acc]) ->
normalize_path(T, Acc);
normalize_path(["."|T], Acc) ->
normalize_path(T, Acc);
normalize_path([H|T], Acc) ->
normalize_path(T, [H|Acc]);
normalize_path([], Acc) ->
case length(Acc) of
0 -> "/";
_ -> filename:join(lists:reverse(Acc))
end.
%% @doc
%% Return the HOME directory
%%
%% Example:
%%
%%
%% "/home/user" = bucfile:user_home().
%%
%% @end
-spec user_home() -> string().
user_home() ->
case os:type() of
{win32, _} -> get_windows_home();
_ -> get_unix_home()
end.
%% @doc
%% Create the given directory if it not exist
%% @end
-spec make_dir(Path :: string() | binary()) -> ok | {error, term()}.
make_dir(Path) when is_binary(Path) ->
make_dir(binary_to_list(Path));
make_dir(Path) ->
filelib:ensure_dir(filename:join([Path, "."])).
%% @doc
%% Remove, recursively the given path
%% @end
-spec remove_recursive(Path :: string() | binary()) -> ok | {error, term()}.
remove_recursive(Path) ->
case filelib:is_dir(Path) of
false ->
file:delete(Path);
true ->
lists:foreach(fun remove_recursive/1, sub_files(Path)),
file:del_dir(Path)
end.
%% @equiv copy(Source, Destination, [preserve_file_info, recursive])
-spec copy(Source :: file:filename_all() | file:dirname_all(),
Destination :: file:filename_all() | file:dirname_all()) -> ok | {error, term()}.
copy(Source, Destination) ->
copy(Source, Destination, [preserve_file_info, recursive]).
%% @doc
%% Copy a Source to a Destination
%%
%% Available options:
%%
%% bucfile:match("a/b/c", "**/b/**").
%% % => true
%% bucfile:match("a/b/c", "**/a/**").
%% % => false
%% bucfile:match("/a/b/c", "**/a/**").
%% % => true
%% bucfile:match("a/b/c", "**/a/**", [expand_path]).
%% % => true
%% bucfile:match("a/b/c", "**/a/**", [{cd, "/tmp"}]).
%% % => true
%% bucfile:match("a/b/c", "**/tmp/**", [{cd, "/tmp"}]).
%% % => true
%% bucfile:match("a/b/c", "**/tmp/**", [{cd, "tmp"}]).
%% % => false
%% bucfile:match("a/b/c", "**/tmp/**", [expand_path, {cd, "tmp"}]).
%% % => true
%%
%% @end
match(Path, Expression, Options) ->
Path1 = case lists:keyfind(cd, 1, Options) of
{cd, CD} -> filename:join([CD, Path]);
_ -> Path
end,
Path2 = case lists:member(expand_path, Options) of
true -> expand_path(Path1);
false -> Path1
end,
Expression0 = bucstring:gsub(Expression, ".", "\\."),
Expression1 = bucstring:gsub(Expression0, "?", "."),
Expression2 = bucstring:gsub(Expression1, "*", "[^/]*"),
Expression3 = bucstring:gsub(Expression2, "[^/]*[^/]*", ".*"),
Expression4 = "^" ++ Expression3 ++ "$",
{Expression5, _} = lists:foldl(fun
(${, {Acc, none}) ->
{[$(|Acc], in};
($}, {Acc, in}) ->
{[$)|Acc], none};
($,, {Acc, in}) ->
{[$||Acc], in};
(32, {Acc, in}) ->
{Acc, in};
(C, {Acc, T}) ->
{[C|Acc], T}
end, {"", none}, Expression4),
Expression6 = lists:reverse(Expression5),
case re:run(Path2, Expression6) of
nomatch -> false;
_ -> true
end.
%% @doc
%% Return true if Path is a symlink, false otherwise
%% @end
is_symlink(Path) ->
case file:read_link_info(Path) of
{ok, #file_info{type = symlink}} ->
true;
_ ->
false
end.
%% @doc
%% Return true if Path is a broken symlink.
%% @end
is_broken(Path) ->
case is_symlink(Path) of
true ->
case file:read_link_info(Path) of
{ok, #file_info{access = Access}} when Access =/= none, Access =/= undefined ->
false;
_ ->
true
end;
false ->
false
end.
% private
get_real_path(Path) ->
case filename:split(Path) of
["/"|_] -> {ok, Path};
FilePath3 ->
case file:get_cwd() of
{ok, Dir} -> {ok, realpath(filename:join(filename:split(Dir) ++ FilePath3))};
E -> E
end
end.
relative_from1([C|File], [C|From]) ->
relative_from1(File, From);
relative_from1(File, From) ->
[".." || X <- From, X =/= "/"] ++ File.
realpath([], Result) ->
Result;
realpath([Current|List], Result) when Current =:= "..", length(Result) > 0 ->
case lists:reverse(Result) of
[".."|_] ->
realpath(List, Result ++ [Current]);
_ ->
case re:run(Result, "^.*/$") of
{match, _} ->
realpath(List, Result);
nomatch ->
realpath(List, lists:reverse(tl(lists:reverse(Result))))
end
end;
realpath([Current|List], Result) when Current =:= "." ->
realpath(List, Result);
realpath([Current|List], Result) ->
realpath(List, Result ++ [Current]).
sub_files(From) ->
{ok, SubFiles} = file:list_dir(From),
[filename:join(From, SubFile) || SubFile <- SubFiles].
expand_home([$~|Rest]) ->
user_home() ++ Rest;
expand_home(Path) -> Path.
get_unix_home() ->
os:getenv("HOME").
get_windows_home() ->
filename:absname(
case os:getenv("USERPROFILE") of
false ->
get_windows_home(os:getenv("HOMEDRIVE"));
Path -> Path
end
).
get_windows_home(false) -> false;
get_windows_home(HomeDrive) -> get_windows_home(HomeDrive, os:getenv("HOMEPATH")).
get_windows_home(_, false) -> false;
get_windows_home(HomeDrive, HomePath) -> HomeDrive ++ HomePath.
do_as_list(Module, Function, Binary) when is_atom(Module), is_atom(Function), is_binary(Binary) ->
list_to_binary(erlang:apply(Module, Function, [binary_to_list(Binary)]));
do_as_list(Module, Function, Binaries) when is_atom(Module), is_atom(Function), is_list(Binaries) ->
Lists = lists:map(fun binary_to_list/1, Binaries),
list_to_binary(erlang:apply(Module, Function, Lists)).