Packages

divingbells is a escript for compressing and decompressing files

Current section

Files

Jump to
divingbells src divingbells.erl
Raw

src/divingbells.erl

%%% @author Robert Lasu
%%% @copyright 2025 Robert Lasu <robert.lasu@gmail.com>
%%% @doc Compress or decompress a file by uses Huffman coding, or Huffman coding-like algorithms
%%%
%%% @end
-module(divingbells).
-ifdef(TEST).
-compile(export_all).
-endif.
-export([main/1]).
-define(ERR(Msg), {error, {?MODULE, ?LINE, Msg}}).
-define(TBI(Msg), {tbi, {?MODULE, ?LINE, Msg}}).
-record(arguments, {f, d, compress = false, decompress = false}).
-type arguments() :: #arguments{f :: atom(), d :: atom(), compress :: boolean(), decompress :: boolean()}.
-spec main(Args) -> ok when
Args :: list().
%%% @doc Entry point for escript
%%%
%%% Returns ok
%%% @end
%%% @since 0.0.1
main(Args) ->
ParsedArgs = parse_args(Args),
io:format("~p~n", [ParsedArgs]),
{ok, Bin} = read(ParsedArgs#arguments.f),
{ok, File} = work({ParsedArgs#arguments.compress, ParsedArgs#arguments.decompress}, Bin),
write(ParsedArgs#arguments.d, File, ParsedArgs#arguments.compress).
-spec parse_args(Args) -> Arguments | no_return() when
Args :: list(),
Arguments :: arguments().
%%% @doc Parse the command line arguments.
%%%
%%% Recursion over `Args' and match to static accepted arguments.
%%% Returns a record, arguments(), `Arguments'.
%%% @end
%%% @see parse_args/1
%%% @since 0.0.1
parse_args(Args) ->
case parse_args(Args, #arguments{}) of
#arguments{f = undefined} ->
print_help("No source file defined");
ParsedArgs ->
ParsedArgs
end.
-spec parse_args(Args, ParsedArgs) -> Arguments | no_return() when
Args :: list(),
ParsedArgs :: arguments(),
Arguments :: arguments().
%%% @doc Parse the command line arguments.
%%%
%%% Recursion over `Args' and match to static accepted arguments.
%%% Returns a record, arguments(), `Arguments'.
%%% @end
%%% @see parse_args/1
%%% @since 0.0.1
parse_args([], Arguments) ->
case Arguments#arguments.d of
undefined when Arguments#arguments.compress == true ->
Arguments#arguments{d = Arguments#arguments.f};
_ ->
Arguments
end;
parse_args(["-h" | _], _) -> print_help();
parse_args(["--help" | _], _) -> print_help();
parse_args(["-C" | Args], Arguments) -> parse_args(Args, Arguments#arguments{compress = true});
parse_args(["--compress" | Args], Arguments) -> parse_args(Args, Arguments#arguments{compress = true});
parse_args(["-D" | Args], Arguments) -> parse_args(Args, Arguments#arguments{decompress = true});
parse_args(["--decompress" | Args], Arguments) -> parse_args(Args, Arguments#arguments{decompress = true});
parse_args(["-f", Arg | Args], Arguments) when Arguments#arguments.f == undefined -> parse_args(Args, Arguments#arguments{f = list_to_atom(Arg)});
parse_args(["--file", Arg | Args], Arguments) when Arguments#arguments.f == undefined -> parse_args(Args, Arguments#arguments{f = list_to_atom(Arg)});
parse_args(["-d", Arg | Args], Arguments) when Arguments#arguments.d == undefined -> parse_args(Args, Arguments#arguments{d = list_to_atom(Arg)});
parse_args(["--destination-file", Arg | Args], Arguments) when Arguments#arguments.d == undefined -> parse_args(Args, Arguments#arguments{d = list_to_atom(Arg)});
parse_args([_Arg | Args], Arguments) -> parse_args(Args, Arguments).
-spec work({C, D}, Bin) -> {ok, Data} | {error, Reason} | no_return() when
C :: boolean(),
D :: boolean(),
Bin :: bitstring(),
Data :: bitstring(),
Reason :: term().
%%% @doc Starts either a compression work or a decompression work.
%%%
%%% `C' and `D' is booleans representing if `Bin' is to be compressed or decompressed.
%%% It's an exclusive or operation and will exit the script if both or non is set.
%%% Returns `{ok, Data}' where `Data' is the compressed `Bin'.
%%% Throws `{error, Reason}' if any errors should aries.
%%% @end
%%% @since 0.0.1
work({false, true}, Bin) -> huffman:'_decompress'(Bin, []);
work({true, false}, Bin) -> huffman:'_compress'(Bin, []);
work({true, true}, _) -> print_help("Can't specify both compression and decompression");
work(_,_) -> print_help("Have to specify either compression or decompression").
-spec write(FileName, Data, Compress) -> ok | {error, Reason} when
FileName :: atom(),
Data :: bitstring(),
Compress :: boolean(),
Reason :: term().
%%% @doc Writes a bitstring to file and add .dbc extension iff the bitstring is compressed.
%%%
%%% If `Compressed' is true, ".dbc" is appended to the file name `FileName'.
%%% Returns ok
%%% Throws `{error, Reason}' if any errors should aries.
%%% @end
%%% @see write/2
%%% @since 0.0.1
write(FileName, Data, true) -> write(list_to_atom(atom_to_list(FileName) ++ ".dbc"), Data);
write(FileName, Data, _) -> write(FileName, Data).
-spec write(FileName, Data) -> ok | {error, Reason} when
FileName :: atom(),
Data :: bitstring(),
Reason :: term().
%%% @doc Writes bitstring to a file
%%%
%%% Writes `Data' to a file `FileName'
%%% Returns ok
%%% Throws `{error, Reason}' if any errors should aries.
%%% @end
%%% @see write/3
%%% @since 0.0.1
write(FileName, Data) when is_atom(FileName) ->
case file:write_file(FileName, Data) of
Default ->
Default
end.
-spec read(FileName) -> {ok, Bin} | {error, Reason} when
FileName :: atom(),
Bin :: bitstring(),
Reason :: term().
%%% @doc Read a file and returns a binary representation
%%% %%
%%% Returns `{ok, Bin}' where `Bin' is the binary content of `FileName'.
%%% Throws `{error, Reason}' if any errors should aries.
%%% @end
%%% @since 0.0.1
read(FileName) when is_atom(FileName) ->
case file:read_file(FileName) of
{ok, Bin} ->
{ok, Bin};
{error, Reason} ->
{error, Reason}
end.
-spec print_help() -> no_return().
%%% @doc Prints how to use the escript
%%% @end
%%% @see print_help/1
%%% @since 0.0.1
print_help() -> print_help("").
-spec print_help(Msg) -> no_return() when
Msg :: any().
%%% @doc Prints how to use the escript
%%% @end
%%% @see print_help/0
%%% @since 0.0.1
print_help(Msg) ->
io:format("\e[38;2;255;100;100m~p\e[0m~n~n", [Msg]),
io:format("Usage: divingbells [OPTIONS] -f [FILE]
-C, --compress compress FILE
-D, --decompress decompress FILE
-f, --file FILE to compress or decomress
-d, --destination-file file to write the result to
-h, --help print this help~n~n"),
halt(0).