-module(file_utils).
-export([list_dir/3, file_type/1, diff/2]).
-include_lib("kernel/include/file.hrl").
-type ext_posix()::posix()|'badarg'.
-type posix()::atom().
-spec list_dir(file:filename(), string(), boolean()) ->
{error, ext_posix()} | {ok, [file:filename()]}.
list_dir(Dir, Extension, Dirs) ->
case file:list_dir(Dir) of
{error, _} = Error-> Error;
{ok, Filenames} ->
FullFilenames = [filename:join(Dir, F) || F <-Filenames ],
Matches1 = case Dirs of
true ->
[F || F <- FullFilenames,
file_type(F) =:= {ok, 'directory'}];
false -> []
end,
Matches2 = [F || F <- FullFilenames,
file_type(F) =:= {ok, 'regular'},
filename:extension(F) =:= Extension],
{ok, lists:sort(Matches1 ++ Matches2)}
end.
-spec file_type(file:filename()) ->
{ok, 'device' | 'directory' | 'regular' | 'other'} |
{error, ext_posix()}.
file_type(Filename) ->
case file:read_file_info(Filename) of
{ok, FI} -> {ok, FI#file_info.type};
Error -> Error
end.
-type diff_result()::'same' | {'differ', diff_list()} |
{error, {file:filename(), term()}}.
-type diff_list()::[{id(), line(), string()}].
-type id()::'new'|'old'.
-type line()::non_neg_integer().
-spec diff(file:filename(), file:filename()) -> diff_result().
diff(Filename1, Filename2) ->
File1 =
case file:open(Filename1, [read]) of
{ok, F1} -> {file, F1};
_ -> empty
end,
File2 =
case file:open(Filename2, [read]) of
{ok, F2} -> {file, F2};
_ -> empty
end,
case diff1(File1, File2) of
{error, {N, Error}} ->
case N of
1 -> {error, {Filename1, Error}};
2 -> {error, {Filename2, Error}}
end;
[] -> 'same';
DiffList -> {'differ', DiffList}
end.
diff1(File1, File2) ->
case file_to_lines(File1) of
{error, Error} -> {error, {1, Error}};
Lines1 ->
case file_to_lines(File2) of
{error, Error} -> {error, {2, Error}};
Lines2 ->
Common = lcs_fast(Lines1, Lines2),
diff2(Lines1, 1, Lines2, 1, Common, [])
end
end.
diff2([], _, [], _, [], Acc) -> lists:keysort(2,Acc);
diff2([H1|T1], N1, [], N2, [], Acc) ->
diff2(T1, N1+1, [], N2, [], [{new, N1, H1}|Acc]);
diff2([], N1, [H2|T2], N2, [], Acc) ->
diff2([], N1, T2, N2+1, [], [{old, N2, H2}|Acc]);
diff2([H1|T1], N1, [H2|T2], N2, [], Acc) ->
diff2(T1, N1+1, T2, N2+1, [], [{new, N1, H1}, {old, N2, H2}|Acc]);
diff2([H1|T1]=L1, N1, [H2|T2]=L2, N2, [HC|TC]=LC, Acc) ->
case H1 =:= H2 of
true -> diff2(T1, N1+1, T2, N2+1, TC, Acc);
false ->
case H1 =:= HC of
true -> diff2(L1, N1, T2, N2+1, LC, [{old, N2, H2}|Acc]);
false -> diff2(T1, N1+1, L2, N2, LC, [{new, N1, H1}|Acc])
end
end.
-spec lcs_fast([string()], [string()]) -> [string()].
lcs_fast(S1, S2) ->
M = length(S1),
N = length(S2),
Acc = array:new(M*N, {default, 0}),
{L, _} = lcs_fast(S1, S2, 1, 1, N, Acc),
L.
-spec lcs_fast([string()], [string()],
pos_integer(), pos_integer(),
non_neg_integer(), array:array()) -> {[string()], array:array()}.
lcs_fast([], _, _, _, _, Acc) ->
{[], Acc};
lcs_fast(_, [], _, _, _, Acc) ->
{[], Acc};
lcs_fast([H1|T1] = S1, [H2|T2] = S2, N1, N2, N, Acc) ->
I = (N1-1) * N + N2 - 1,
case array:get(I, Acc) of
0 ->
case string:equal(H1, H2) of
true ->
{T, NAcc} = lcs_fast(T1, T2, N1+1, N2+1, N, Acc),
L = [H1|T],
{L, array:set(I, L, NAcc)};
false ->
{L1, NAcc1} = lcs_fast(S1, T2, N1, N2+1, N, Acc),
{L2, NAcc2} = lcs_fast(T1, S2, N1+1, N2, N, NAcc1),
L = longest(L1, L2),
{L, array:set(I, L, NAcc2)}
end;
L ->
{L, Acc}
end.
-spec longest([string()], [string()]) -> [string()].
longest(S1, S2) ->
case length(S1) > length(S2) of
true -> S1;
false -> S2
end.
file_to_lines(empty) ->
[];
file_to_lines({file, File}) ->
case file_to_lines(File, []) of
{error, _} = Error -> Error;
Lines -> lists:reverse(Lines)
end.
file_to_lines(File, Acc) ->
case io:get_line(File, "") of
{error, _}=Error -> Error;
eof -> Acc;
A -> file_to_lines(File, [A|Acc])
end.