Permalink
Browse files

Change API / cleanup code. Details:

- Add comments
- Add/Fix typespecs
- Make format/1, format/2 private
- Rename cmprint/2 to compare/2
- Minor cleanups
  • Loading branch information...
1 parent 7cc2ea1 commit 7611e1c55ecdfbd786be9afba4f5c7af06e16b2d @aerosol aerosol committed Oct 29, 2012
Showing with 170 additions and 96 deletions.
  1. +2 −12 README.md
  2. +168 −84 src/binpp.erl
View
@@ -13,16 +13,6 @@ Example usage:
05 81 0F 09 15 C5 61 33 D8 54 91 52 5D 81 17 24 ....Åa3ØT‘R].$
11 14 60 23 D1 3D 55 80 ..`#Ñ=U€
ok
- 3> binpp:format(Bin, bin).
- "00001100 11110010 11001111 00110001 01010010 01000101 00101101 10000010
- 11010100 01000101 01010000 01011000 00001000 01010001 00010111 00100100
- 01010110 00000111 01000100 00010011 10000101 01100001 00110011 11011000
- 00111000 10010001 01011000 00001000 01010001 00010111 00100100 01010110
- 00001010 00010100 00100000 01001110 00100100 00010110 00001001 01100000
- 11110100 00001010 00010101 00010001 00000001 00110000 00010011 10001001
- 00000101 10000001 00001111 00001001 00010101 11000101 01100001 00110011
- 11011000 01010100 10010001 01010010 01011101 10000001 00010111 00100100
- 00010001 00010100 01100000 00100011 11010001 00111101 01010101 10000000"
Binpp will use io:format to output the formatted binary by default, however
there are options making pprint functions return formatted data instead
@@ -50,7 +40,7 @@ An additional API is provided for printing binary fragments:
Also, binary byte-to-byte comparsion:
- 6> binpp:cmprint(<<1,2,1024:512,3,4>>, <<1,3,1024:512,5,6>>).
+ 6> binpp:compare(<<1,2,1024:512,3,4>>, <<1,3,1024:512,5,6>>).
-- 02 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 03 -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
@@ -60,7 +50,7 @@ Also, binary byte-to-byte comparsion:
Plus a handy little helper:
- 7> binpp:cmprint(<<1,2,255,3,1024:512>>, binpp:from_str("01 02 FF CC")).
+ 7> binpp:compare(<<1,2,255,3,1024:512>>, binpp:from_str("01 02 FF CC")).
-- -- -- 03 00 00 00 00 00 00 00 00 00 00 00 00 -- -- -- CC ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
View
@@ -1,105 +1,102 @@
-% DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
-% Version 2, December 2004
-%
-% Copyright (C) 2011 Adam Rutkowski <adam@mtod.org>
-%
-% Everyone is permitted to copy and distribute verbatim or modified
-% copies of this license document, and changing it is allowed as long
-% as the name is changed.
-%
-% DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
-% TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-%
-% 0. You just DO WHAT THE FUCK YOU WANT TO.
-
+%%%
+%%% DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+%%% Version 2, December 2004
+%%%
+%%% Copyright (C) 2011 Adam Rutkowski <hq@mtod.org>
+%%%
+%%% Everyone is permitted to copy and distribute verbatim or modified
+%%% copies of this license document, and changing it is allowed as long
+%%% as the name is changed.
+%%%
+%%% DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+%%% TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+%%%
+
+%% @doc Pretty printer for Erlang binaries
-module(binpp).
--author('Adam Rutkowski adam@mtod.org').
+-author('Adam Rutkowski hq@mtod.org').
+
-export([pprint/1, pprint/2, pprint/3]).
--export([cmprint/2]).
--export([from_str/1, from_str/2]).
--export([format/1, format/2]).
+-export([compare/2]).
+-export([from_str/1]).
-export([convert/1, convert/2]).
-opaque opt() :: {return, iolist} | {return, binary} | {printer, function()}.
-opaque opts() :: list(opt()).
+%% Printer constants
+-define(SPACE, $ ).
+-define(SPECIAL, $.).
+-define(FILL, $0).
+
+%% Comparison glyphs
+-define(MISSING, "??").
+-define(EQUAL, "--").
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% API %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--spec pprint(binary()) -> ok.
-
+%% @doc Pretty print a binary to stdout using io:format/2
+-spec pprint(binary() | bitstring()) -> ok.
pprint(Bin) ->
pprint(Bin, []).
--spec pprint(binary(), opts()) -> ok | any().
-
+%% @doc Print a binary w/ custom function or return the prettified result.
+-spec pprint(binary() | bitstring(), opts()) -> ok | any().
pprint(Bin, Opts) when is_list(Opts) ->
{ok, Octets} = convert(Bin, hex),
Buckets = buckets(16, Octets),
apply_opts(lists:map(fun print_bucket/1, Buckets), Opts).
--spec pprint(binary(), {non_neg_integer(), non_neg_integer()},
+%% @doc Pretty print a slice of binary.
+-spec pprint(binary() | bitstring(), {non_neg_integer(), non_neg_integer()},
opts()) -> ok | any().
-
pprint(Bin, {Pos, Len}, Opts) when Len =< size(Bin) ->
pprint(binary:part(Bin, Pos, Len), Opts);
pprint(Bin, {Pos, _}, Opts) ->
pprint(binary:part(Bin, Pos, size(Bin)-Pos), Opts).
--spec cmprint(binary(), binary()) -> ok.
-
-cmprint(Bin1, Bin2) when is_binary(Bin1) orelse is_bitstring(Bin1),
+%% @doc Pretty print byte-to-byte comparsion of two binaries to stdout.
+-spec compare(binary() | bitstring(), binary() | bitstring()) -> ok.
+compare(Bin1, Bin2) when is_binary(Bin1) orelse is_bitstring(Bin1),
is_binary(Bin2) orelse is_bitstring(Bin2) ->
{ok, Octets1} = convert(Bin1, hex),
{ok, Octets2} = convert(Bin2, hex),
{ok, {D1, D2}} = diff(Octets1, Octets2),
- print_comparsion(buckets(16, D1), buckets(16, D2)).
+ print_comparison(buckets(16, D1), buckets(16, D2)).
--spec from_str(string(), hex) -> binary().
-
-from_str(Str, hex) when is_list(Str) ->
- Bytes = case lists:member($ , Str) of
+%% @doc Construct binary from hexstring.
+%% Hexstring octets can be optionally separated with spaces.
+-spec from_str(string()) -> binary().
+from_str(Str) when is_list(Str) ->
+ Bytes = case lists:member(?SPACE, Str) of
true ->
- string:tokens(Str, " ");
+ string:tokens(Str, ?SPACE);
false when length(Str) rem 2 =:= 0 ->
- buckets(2, Str)
+ buckets(2, Str);
+ false ->
+ erlang:error(badarg)
end,
list_to_binary([list_to_integer(B, 16) || B <- Bytes]).
--spec from_str(string()) -> binary().
-
-from_str(Str) when is_list(Str) ->
- from_str(Str, hex).
-
--spec format(binary(), atom()) -> ok.
-
-format(Bin, Base) ->
- {ok, Octets} = convert(Bin, Base),
- io:format("~p~n", [string:join(Octets, " ")]).
-
--spec format(binary()) -> ok.
-
-format(Bin) ->
- format(Bin, hex).
-
--spec convert(binary(), atom()) -> {ok, list()}.
+%% @doc Convert binary to hex string.
+-spec convert(binary() | bitstring()) -> {ok, list()}.
+convert(Bin) when is_binary(Bin) orelse is_bitstring(Bin) ->
+ convert(Bin, hex).
+%% @doc Convert binary to hex string or binary string.
+-spec convert(binary() | bitstring(), hex | bin) -> {ok, list()}.
convert(Bin, hex) when is_binary(Bin) orelse is_bitstring(Bin) ->
- convert(Bin, [], fun hexstr/1);
-
+ convert(Bin, [], fun byte_to_hexstr/1);
convert(Bin, bin) when is_binary(Bin) orelse is_bitstring(Bin) ->
- convert(Bin, [], fun binstr/1).
-
--spec convert(binary()) -> {ok, list()}.
-
-convert(Bin) when is_binary(Bin) ->
- convert(Bin, hex).
+ convert(Bin, [], fun byte_to_binstr/1).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% Core :) %
+% Internals %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec apply_opts(iolist(), opts()) -> ok | iolist() | binary().
apply_opts(IoList, []) ->
io:format("~s~n", [IoList]);
apply_opts(IoList, [{return, iolist}]) ->
@@ -110,59 +107,70 @@ apply_opts(IoList, [{printer, Fun}]) when is_function(Fun) ->
Fun(IoList);
apply_opts(_, _) -> erlang:error(badarg).
+-spec convert(binary() | bitstring(), list(), function()) -> {ok, string()}.
convert(<<>>, Acc, _) ->
{ok, lists:reverse(Acc)};
-
-%% byte align bistring() to make a complementary binary()
convert(Bin, [], FormatFun) when is_bitstring(Bin), not is_binary(Bin) ->
+ %% byte align bistring() to make a complementary binary()
Align = (8 - (bit_size(Bin) rem 8)),
- io:format("Warning! Aligned bitstring with ~.10B bit(s).~n", [Align]),
+ error_logger:info_msg("Aligned bitstring with ~.10B bit(s).~n", [Align]),
convert(<<Bin/binary, 0:Align>>, [], FormatFun);
-
convert(<<Bin:8/integer, Rest/binary>>, SoFar, FormatFun) ->
convert(Rest, [FormatFun(Bin)|SoFar], FormatFun).
print_bucket(Bucket) ->
- OctetLine = string:join(Bucket, " "),
+ OctetLine = string:join(Bucket, [?SPACE]),
OctetRepr = lists:map(
fun(B) ->
case list_to_integer(B, 16) of
- Code when Code >= 32 -> Code;
- _Else -> $.
+ Code when Code >= ?SPACE -> Code;
+ _ -> ?SPECIAL
end
end,
Bucket),
- io_lib:format("~s ~s~n", [string:left(OctetLine, 16*2 + 16, $ ), OctetRepr]).
+ io_lib:format("~s ~s~n", [string:left(OctetLine, 16*2 + 16, ?SPACE), OctetRepr]).
-print_comparsion([], []) ->
+-spec print_comparison(list(), list()) -> ok.
+print_comparison([], []) ->
ok;
-
-print_comparsion([L|LRest], [R|RRest]) ->
- Zfill = fun(Line) -> string:left(Line, 16*2 + 16, $ ) end,
- DiffL = Zfill(string:join(L, " ")),
- DiffR = Zfill(string:join(R, " ")),
+print_comparison([L|LRest], [R|RRest]) ->
+ Zfill = fun(Line) -> string:left(Line, 16*2 + 16, ?SPACE) end,
+ DiffL = Zfill(string:join(L, [?SPACE])),
+ DiffR = Zfill(string:join(R, [?SPACE])),
io:format("~s ~s~n", [DiffL, DiffR]),
- print_comparsion(LRest, RRest).
+ print_comparison(LRest, RRest).
+
+-spec diff(list(), list()) -> {ok, {list(), list()}}.
+diff(L1, L2) when is_list(L1), is_list(L2) ->
+ diff(L1, L2, [], []).
+-spec diff(list(), list(), list(), list()) -> {ok, {list(), list()}}.
diff([], [], LD, RD) ->
{ok, {lists:reverse(LD), lists:reverse(RD)}};
diff([], [H2|R2], LD, RD) ->
- diff([], R2, ["??"|LD], [H2|RD]);
+ diff([], R2, [?MISSING|LD], [H2|RD]);
diff([H1|R1], [], LD, RD) ->
- diff(R1, [], [H1|LD], ["??"|RD]);
-diff([H1|R1], [H2|R2], LD, RD) when H1 =:= H2 ->
- diff(R1, R2, ["--"|LD], ["--"|RD]);
+ diff(R1, [], [H1|LD], [?MISSING|RD]);
+diff([H1|R1], [H1|R2], LD, RD) -> %% H1 =:= H2
+ diff(R1, R2, [?EQUAL|LD], [?EQUAL|RD]);
diff([H1|R1], [H2|R2], LD, RD) ->
diff(R1, R2, [H1|LD], [H2|RD]).
-diff(L1, L2) when is_list(L1), is_list(L2) ->
- diff(L1, L2, [], []).
+-spec byte_to_hexstr(byte()) -> string().
+byte_to_hexstr(B) when B >= 0, B =< 255 ->
+ to_hexstr(B, 16, 2).
+
+-spec byte_to_binstr(byte()) -> string().
+byte_to_binstr(B) when B >= 0, B =< 255 ->
+ to_hexstr(B, 2, 8).
-hexstr(B) -> string:right(integer_to_list(B, 16), 2, $0).
-binstr(B) -> string:right(integer_to_list(B, 2), 8, $0).
+-spec to_hexstr(byte(), non_neg_integer(), non_neg_integer()) -> string().
+to_hexstr(B, Base, Len) ->
+ string:right(integer_to_list(B, Base), Len, ?FILL).
-%% Divide list L into X lists of size N
+%% @doc Divide list L into X lists of size N
%% courtesy of MononcQc
+-spec buckets(non_neg_integer(), list()) -> list(list()).
buckets(N, L) ->
buckets(1, N, length(L) div N, L, [[]]).
buckets(_, _, 0, [], [[]|Acc]) ->
@@ -173,3 +181,79 @@ buckets(N, N, M, [H|T], [A|Acc]) ->
buckets(1, N, M-1, T, [[], lists:reverse([H|A]) | Acc]);
buckets(X, N, M, [H|T], [A|Acc]) ->
buckets(X+1, N, M, T, [[H|A]|Acc]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Tests %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+buckets_test_() ->
+ F = fun buckets/2,
+ Tests = [
+ { {1, [1,2,3,4]}, [ [1], [2], [3], [4] ] },
+ { {2, [1,2,3,4]}, [ [1,2], [3,4] ] },
+ { {3, [1,2,3,4]}, [ [1,2,3], [4] ] },
+ { {4, [1,2,3,4]}, [ [1,2,3,4] ] },
+ { {5, [1,2,3,4]}, [ [1,2,3,4] ] }
+ ],
+ [ { <<"Buckets test">>, fun() -> ?assertEqual(R, F(I1, I2)) end }
+ || { {I1, I2}, R } <- Tests ].
+
+byte_to_binstr_test_() ->
+ F = fun byte_to_binstr/1,
+ Tests = [
+ { 0, "00000000" },
+ { 1, "00000001" },
+ { 255, "11111111" }
+ ],
+ [ { iolist_to_binary(["Convert ", integer_to_list(I)]),
+ fun() -> ?assertEqual(R, F(I)) end }
+ || { I, R } <- Tests ].
+
+byte_to_hexstr_test_() ->
+ F = fun byte_to_hexstr/1,
+ Tests = [
+ { 0, "00" },
+ { 1, "01" },
+ { 255, "FF" }
+ ],
+ [ { iolist_to_binary(["Convert ", integer_to_list(I)]),
+ fun() -> ?assertEqual(R, F(I)) end }
+ || { I, R } <- Tests ].
+
+diff_test_() ->
+ F = fun diff/2,
+ Tests = [
+ { { [], [] }, { [], []} },
+ { { [1, 2], [1, 1] }, { [?EQUAL, 2], [?EQUAL, 1]} },
+ { { [1], [1, 1] }, { [?EQUAL, ?MISSING], [?EQUAL, 1]} },
+ { { [1, 2, 3], [2, 1] }, { [1, 2, 3], [2, 1, ?MISSING]} },
+ { { [], [2, 1] }, { [?MISSING, ?MISSING], [2, 1]} }
+ ],
+ [ { <<"Diff">>, fun() -> ?assertEqual({ok, R}, F(I1, I2)) end }
+ || { {I1, I2}, R } <- Tests ].
+
+print_bucket_test_() ->
+ F = fun print_bucket/1,
+ Tests = [
+ { ["00", "FF"],
+ ["00 FF ", ?SPACE, [?SPECIAL, 255], "\n"] },
+
+ { ["41" || _ <- lists:seq(1, 16) ],
+ ["41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 ", ?SPACE, [$A || _ <- lists:seq(1, 16)], "\n"] }
+ ],
+ [ { iolist_to_binary(["Print ", I]), fun() -> ?assertEqual(R, F(I)) end }
+ || { I, R } <- Tests ].
+
+convert_test_() ->
+ F = fun convert/1,
+ Tests = [
+ { <<1,2,3>>, ["01", "02", "03"] },
+ { <<256:32>>, ["00", "00", "01", "00"] },
+ { <<"AAA">>, ["41", "41", "41"] }
+ ],
+ [ { <<"Convert">>, fun() -> ?assertEqual({ok, R}, F(I)) end } || { I, R } <- Tests ].
+
+-endif.

0 comments on commit 7611e1c

Please sign in to comment.