Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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...
commit 7611e1c55ecdfbd786be9afba4f5c7af06e16b2d 1 parent 7cc2ea1
Adam Rutkowski aerosol authored

Showing 2 changed files with 170 additions and 96 deletions. Show diff stats Hide diff stats

  1. +2 12 README.md
  2. +168 84 src/binpp.erl
14 README.md
Source Rendered
@@ -13,16 +13,6 @@ Example usage:
13 13 05 81 0F 09 15 C5 61 33 D8 54 91 52 5D 81 17 24 ....Åa3ØT‘R].$
14 14 11 14 60 23 D1 3D 55 80 ..`#Ñ=U€
15 15 ok
16   - 3> binpp:format(Bin, bin).
17   - "00001100 11110010 11001111 00110001 01010010 01000101 00101101 10000010
18   - 11010100 01000101 01010000 01011000 00001000 01010001 00010111 00100100
19   - 01010110 00000111 01000100 00010011 10000101 01100001 00110011 11011000
20   - 00111000 10010001 01011000 00001000 01010001 00010111 00100100 01010110
21   - 00001010 00010100 00100000 01001110 00100100 00010110 00001001 01100000
22   - 11110100 00001010 00010101 00010001 00000001 00110000 00010011 10001001
23   - 00000101 10000001 00001111 00001001 00010101 11000101 01100001 00110011
24   - 11011000 01010100 10010001 01010010 01011101 10000001 00010111 00100100
25   - 00010001 00010100 01100000 00100011 11010001 00111101 01010101 10000000"
26 16
27 17 Binpp will use io:format to output the formatted binary by default, however
28 18 there are options making pprint functions return formatted data instead
@@ -50,7 +40,7 @@ An additional API is provided for printing binary fragments:
50 40
51 41 Also, binary byte-to-byte comparsion:
52 42
53   - 6> binpp:cmprint(<<1,2,1024:512,3,4>>, <<1,3,1024:512,5,6>>).
  43 + 6> binpp:compare(<<1,2,1024:512,3,4>>, <<1,3,1024:512,5,6>>).
54 44 -- 02 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 03 -- -- -- -- -- -- -- -- -- -- -- -- -- --
55 45 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
56 46 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
@@ -60,7 +50,7 @@ Also, binary byte-to-byte comparsion:
60 50
61 51 Plus a handy little helper:
62 52
63   - 7> binpp:cmprint(<<1,2,255,3,1024:512>>, binpp:from_str("01 02 FF CC")).
  53 + 7> binpp:compare(<<1,2,255,3,1024:512>>, binpp:from_str("01 02 FF CC")).
64 54 -- -- -- 03 00 00 00 00 00 00 00 00 00 00 00 00 -- -- -- CC ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
65 55 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
66 56 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
252 src/binpp.erl
... ... @@ -1,105 +1,102 @@
1   -% DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2   -% Version 2, December 2004
3   -%
4   -% Copyright (C) 2011 Adam Rutkowski <adam@mtod.org>
5   -%
6   -% Everyone is permitted to copy and distribute verbatim or modified
7   -% copies of this license document, and changing it is allowed as long
8   -% as the name is changed.
9   -%
10   -% DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11   -% TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12   -%
13   -% 0. You just DO WHAT THE FUCK YOU WANT TO.
14   -
  1 +%%%
  2 +%%% DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
  3 +%%% Version 2, December 2004
  4 +%%%
  5 +%%% Copyright (C) 2011 Adam Rutkowski <hq@mtod.org>
  6 +%%%
  7 +%%% Everyone is permitted to copy and distribute verbatim or modified
  8 +%%% copies of this license document, and changing it is allowed as long
  9 +%%% as the name is changed.
  10 +%%%
  11 +%%% DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
  12 +%%% TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
  13 +%%%
  14 +
  15 +%% @doc Pretty printer for Erlang binaries
15 16 -module(binpp).
16   --author('Adam Rutkowski adam@mtod.org').
  17 +-author('Adam Rutkowski hq@mtod.org').
  18 +
17 19 -export([pprint/1, pprint/2, pprint/3]).
18   --export([cmprint/2]).
19   --export([from_str/1, from_str/2]).
20   --export([format/1, format/2]).
  20 +-export([compare/2]).
  21 +-export([from_str/1]).
21 22 -export([convert/1, convert/2]).
22 23
23 24 -opaque opt() :: {return, iolist} | {return, binary} | {printer, function()}.
24 25 -opaque opts() :: list(opt()).
25 26
  27 +%% Printer constants
  28 +-define(SPACE, $ ).
  29 +-define(SPECIAL, $.).
  30 +-define(FILL, $0).
  31 +
  32 +%% Comparison glyphs
  33 +-define(MISSING, "??").
  34 +-define(EQUAL, "--").
  35 +
26 36 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
27 37 % API %
28 38 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
29 39
30   --spec pprint(binary()) -> ok.
31   -
  40 +%% @doc Pretty print a binary to stdout using io:format/2
  41 +-spec pprint(binary() | bitstring()) -> ok.
32 42 pprint(Bin) ->
33 43 pprint(Bin, []).
34 44
35   --spec pprint(binary(), opts()) -> ok | any().
36   -
  45 +%% @doc Print a binary w/ custom function or return the prettified result.
  46 +-spec pprint(binary() | bitstring(), opts()) -> ok | any().
37 47 pprint(Bin, Opts) when is_list(Opts) ->
38 48 {ok, Octets} = convert(Bin, hex),
39 49 Buckets = buckets(16, Octets),
40 50 apply_opts(lists:map(fun print_bucket/1, Buckets), Opts).
41 51
42   --spec pprint(binary(), {non_neg_integer(), non_neg_integer()},
  52 +%% @doc Pretty print a slice of binary.
  53 +-spec pprint(binary() | bitstring(), {non_neg_integer(), non_neg_integer()},
43 54 opts()) -> ok | any().
44   -
45 55 pprint(Bin, {Pos, Len}, Opts) when Len =< size(Bin) ->
46 56 pprint(binary:part(Bin, Pos, Len), Opts);
47 57 pprint(Bin, {Pos, _}, Opts) ->
48 58 pprint(binary:part(Bin, Pos, size(Bin)-Pos), Opts).
49 59
50   --spec cmprint(binary(), binary()) -> ok.
51   -
52   -cmprint(Bin1, Bin2) when is_binary(Bin1) orelse is_bitstring(Bin1),
  60 +%% @doc Pretty print byte-to-byte comparsion of two binaries to stdout.
  61 +-spec compare(binary() | bitstring(), binary() | bitstring()) -> ok.
  62 +compare(Bin1, Bin2) when is_binary(Bin1) orelse is_bitstring(Bin1),
53 63 is_binary(Bin2) orelse is_bitstring(Bin2) ->
54 64 {ok, Octets1} = convert(Bin1, hex),
55 65 {ok, Octets2} = convert(Bin2, hex),
56 66 {ok, {D1, D2}} = diff(Octets1, Octets2),
57   - print_comparsion(buckets(16, D1), buckets(16, D2)).
  67 + print_comparison(buckets(16, D1), buckets(16, D2)).
58 68
59   --spec from_str(string(), hex) -> binary().
60   -
61   -from_str(Str, hex) when is_list(Str) ->
62   - Bytes = case lists:member($ , Str) of
  69 +%% @doc Construct binary from hexstring.
  70 +%% Hexstring octets can be optionally separated with spaces.
  71 +-spec from_str(string()) -> binary().
  72 +from_str(Str) when is_list(Str) ->
  73 + Bytes = case lists:member(?SPACE, Str) of
63 74 true ->
64   - string:tokens(Str, " ");
  75 + string:tokens(Str, ?SPACE);
65 76 false when length(Str) rem 2 =:= 0 ->
66   - buckets(2, Str)
  77 + buckets(2, Str);
  78 + false ->
  79 + erlang:error(badarg)
67 80 end,
68 81 list_to_binary([list_to_integer(B, 16) || B <- Bytes]).
69 82
70   --spec from_str(string()) -> binary().
71   -
72   -from_str(Str) when is_list(Str) ->
73   - from_str(Str, hex).
74   -
75   --spec format(binary(), atom()) -> ok.
76   -
77   -format(Bin, Base) ->
78   - {ok, Octets} = convert(Bin, Base),
79   - io:format("~p~n", [string:join(Octets, " ")]).
80   -
81   --spec format(binary()) -> ok.
82   -
83   -format(Bin) ->
84   - format(Bin, hex).
85   -
86   --spec convert(binary(), atom()) -> {ok, list()}.
  83 +%% @doc Convert binary to hex string.
  84 +-spec convert(binary() | bitstring()) -> {ok, list()}.
  85 +convert(Bin) when is_binary(Bin) orelse is_bitstring(Bin) ->
  86 + convert(Bin, hex).
87 87
  88 +%% @doc Convert binary to hex string or binary string.
  89 +-spec convert(binary() | bitstring(), hex | bin) -> {ok, list()}.
88 90 convert(Bin, hex) when is_binary(Bin) orelse is_bitstring(Bin) ->
89   - convert(Bin, [], fun hexstr/1);
90   -
  91 + convert(Bin, [], fun byte_to_hexstr/1);
91 92 convert(Bin, bin) when is_binary(Bin) orelse is_bitstring(Bin) ->
92   - convert(Bin, [], fun binstr/1).
93   -
94   --spec convert(binary()) -> {ok, list()}.
95   -
96   -convert(Bin) when is_binary(Bin) ->
97   - convert(Bin, hex).
  93 + convert(Bin, [], fun byte_to_binstr/1).
98 94
99 95 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
100   -% Core :) %
  96 +% Internals %
101 97 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
102 98
  99 +-spec apply_opts(iolist(), opts()) -> ok | iolist() | binary().
103 100 apply_opts(IoList, []) ->
104 101 io:format("~s~n", [IoList]);
105 102 apply_opts(IoList, [{return, iolist}]) ->
@@ -110,59 +107,70 @@ apply_opts(IoList, [{printer, Fun}]) when is_function(Fun) ->
110 107 Fun(IoList);
111 108 apply_opts(_, _) -> erlang:error(badarg).
112 109
  110 +-spec convert(binary() | bitstring(), list(), function()) -> {ok, string()}.
113 111 convert(<<>>, Acc, _) ->
114 112 {ok, lists:reverse(Acc)};
115   -
116   -%% byte align bistring() to make a complementary binary()
117 113 convert(Bin, [], FormatFun) when is_bitstring(Bin), not is_binary(Bin) ->
  114 + %% byte align bistring() to make a complementary binary()
118 115 Align = (8 - (bit_size(Bin) rem 8)),
119   - io:format("Warning! Aligned bitstring with ~.10B bit(s).~n", [Align]),
  116 + error_logger:info_msg("Aligned bitstring with ~.10B bit(s).~n", [Align]),
120 117 convert(<<Bin/binary, 0:Align>>, [], FormatFun);
121   -
122 118 convert(<<Bin:8/integer, Rest/binary>>, SoFar, FormatFun) ->
123 119 convert(Rest, [FormatFun(Bin)|SoFar], FormatFun).
124 120
125 121 print_bucket(Bucket) ->
126   - OctetLine = string:join(Bucket, " "),
  122 + OctetLine = string:join(Bucket, [?SPACE]),
127 123 OctetRepr = lists:map(
128 124 fun(B) ->
129 125 case list_to_integer(B, 16) of
130   - Code when Code >= 32 -> Code;
131   - _Else -> $.
  126 + Code when Code >= ?SPACE -> Code;
  127 + _ -> ?SPECIAL
132 128 end
133 129 end,
134 130 Bucket),
135   - io_lib:format("~s ~s~n", [string:left(OctetLine, 16*2 + 16, $ ), OctetRepr]).
  131 + io_lib:format("~s ~s~n", [string:left(OctetLine, 16*2 + 16, ?SPACE), OctetRepr]).
136 132
137   -print_comparsion([], []) ->
  133 +-spec print_comparison(list(), list()) -> ok.
  134 +print_comparison([], []) ->
138 135 ok;
139   -
140   -print_comparsion([L|LRest], [R|RRest]) ->
141   - Zfill = fun(Line) -> string:left(Line, 16*2 + 16, $ ) end,
142   - DiffL = Zfill(string:join(L, " ")),
143   - DiffR = Zfill(string:join(R, " ")),
  136 +print_comparison([L|LRest], [R|RRest]) ->
  137 + Zfill = fun(Line) -> string:left(Line, 16*2 + 16, ?SPACE) end,
  138 + DiffL = Zfill(string:join(L, [?SPACE])),
  139 + DiffR = Zfill(string:join(R, [?SPACE])),
144 140 io:format("~s ~s~n", [DiffL, DiffR]),
145   - print_comparsion(LRest, RRest).
  141 + print_comparison(LRest, RRest).
  142 +
  143 +-spec diff(list(), list()) -> {ok, {list(), list()}}.
  144 +diff(L1, L2) when is_list(L1), is_list(L2) ->
  145 + diff(L1, L2, [], []).
146 146
  147 +-spec diff(list(), list(), list(), list()) -> {ok, {list(), list()}}.
147 148 diff([], [], LD, RD) ->
148 149 {ok, {lists:reverse(LD), lists:reverse(RD)}};
149 150 diff([], [H2|R2], LD, RD) ->
150   - diff([], R2, ["??"|LD], [H2|RD]);
  151 + diff([], R2, [?MISSING|LD], [H2|RD]);
151 152 diff([H1|R1], [], LD, RD) ->
152   - diff(R1, [], [H1|LD], ["??"|RD]);
153   -diff([H1|R1], [H2|R2], LD, RD) when H1 =:= H2 ->
154   - diff(R1, R2, ["--"|LD], ["--"|RD]);
  153 + diff(R1, [], [H1|LD], [?MISSING|RD]);
  154 +diff([H1|R1], [H1|R2], LD, RD) -> %% H1 =:= H2
  155 + diff(R1, R2, [?EQUAL|LD], [?EQUAL|RD]);
155 156 diff([H1|R1], [H2|R2], LD, RD) ->
156 157 diff(R1, R2, [H1|LD], [H2|RD]).
157 158
158   -diff(L1, L2) when is_list(L1), is_list(L2) ->
159   - diff(L1, L2, [], []).
  159 +-spec byte_to_hexstr(byte()) -> string().
  160 +byte_to_hexstr(B) when B >= 0, B =< 255 ->
  161 + to_hexstr(B, 16, 2).
  162 +
  163 +-spec byte_to_binstr(byte()) -> string().
  164 +byte_to_binstr(B) when B >= 0, B =< 255 ->
  165 + to_hexstr(B, 2, 8).
160 166
161   -hexstr(B) -> string:right(integer_to_list(B, 16), 2, $0).
162   -binstr(B) -> string:right(integer_to_list(B, 2), 8, $0).
  167 +-spec to_hexstr(byte(), non_neg_integer(), non_neg_integer()) -> string().
  168 +to_hexstr(B, Base, Len) ->
  169 + string:right(integer_to_list(B, Base), Len, ?FILL).
163 170
164   -%% Divide list L into X lists of size N
  171 +%% @doc Divide list L into X lists of size N
165 172 %% courtesy of MononcQc
  173 +-spec buckets(non_neg_integer(), list()) -> list(list()).
166 174 buckets(N, L) ->
167 175 buckets(1, N, length(L) div N, L, [[]]).
168 176 buckets(_, _, 0, [], [[]|Acc]) ->
@@ -173,3 +181,79 @@ buckets(N, N, M, [H|T], [A|Acc]) ->
173 181 buckets(1, N, M-1, T, [[], lists:reverse([H|A]) | Acc]);
174 182 buckets(X, N, M, [H|T], [A|Acc]) ->
175 183 buckets(X+1, N, M, T, [[H|A]|Acc]).
  184 +
  185 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  186 +% Tests %
  187 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  188 +
  189 +-ifdef(TEST).
  190 +-include_lib("eunit/include/eunit.hrl").
  191 +
  192 +buckets_test_() ->
  193 + F = fun buckets/2,
  194 + Tests = [
  195 + { {1, [1,2,3,4]}, [ [1], [2], [3], [4] ] },
  196 + { {2, [1,2,3,4]}, [ [1,2], [3,4] ] },
  197 + { {3, [1,2,3,4]}, [ [1,2,3], [4] ] },
  198 + { {4, [1,2,3,4]}, [ [1,2,3,4] ] },
  199 + { {5, [1,2,3,4]}, [ [1,2,3,4] ] }
  200 + ],
  201 + [ { <<"Buckets test">>, fun() -> ?assertEqual(R, F(I1, I2)) end }
  202 + || { {I1, I2}, R } <- Tests ].
  203 +
  204 +byte_to_binstr_test_() ->
  205 + F = fun byte_to_binstr/1,
  206 + Tests = [
  207 + { 0, "00000000" },
  208 + { 1, "00000001" },
  209 + { 255, "11111111" }
  210 + ],
  211 + [ { iolist_to_binary(["Convert ", integer_to_list(I)]),
  212 + fun() -> ?assertEqual(R, F(I)) end }
  213 + || { I, R } <- Tests ].
  214 +
  215 +byte_to_hexstr_test_() ->
  216 + F = fun byte_to_hexstr/1,
  217 + Tests = [
  218 + { 0, "00" },
  219 + { 1, "01" },
  220 + { 255, "FF" }
  221 + ],
  222 + [ { iolist_to_binary(["Convert ", integer_to_list(I)]),
  223 + fun() -> ?assertEqual(R, F(I)) end }
  224 + || { I, R } <- Tests ].
  225 +
  226 +diff_test_() ->
  227 + F = fun diff/2,
  228 + Tests = [
  229 + { { [], [] }, { [], []} },
  230 + { { [1, 2], [1, 1] }, { [?EQUAL, 2], [?EQUAL, 1]} },
  231 + { { [1], [1, 1] }, { [?EQUAL, ?MISSING], [?EQUAL, 1]} },
  232 + { { [1, 2, 3], [2, 1] }, { [1, 2, 3], [2, 1, ?MISSING]} },
  233 + { { [], [2, 1] }, { [?MISSING, ?MISSING], [2, 1]} }
  234 + ],
  235 + [ { <<"Diff">>, fun() -> ?assertEqual({ok, R}, F(I1, I2)) end }
  236 + || { {I1, I2}, R } <- Tests ].
  237 +
  238 +print_bucket_test_() ->
  239 + F = fun print_bucket/1,
  240 + Tests = [
  241 + { ["00", "FF"],
  242 + ["00 FF ", ?SPACE, [?SPECIAL, 255], "\n"] },
  243 +
  244 + { ["41" || _ <- lists:seq(1, 16) ],
  245 + ["41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 ", ?SPACE, [$A || _ <- lists:seq(1, 16)], "\n"] }
  246 + ],
  247 + [ { iolist_to_binary(["Print ", I]), fun() -> ?assertEqual(R, F(I)) end }
  248 + || { I, R } <- Tests ].
  249 +
  250 +convert_test_() ->
  251 + F = fun convert/1,
  252 + Tests = [
  253 + { <<1,2,3>>, ["01", "02", "03"] },
  254 + { <<256:32>>, ["00", "00", "01", "00"] },
  255 + { <<"AAA">>, ["41", "41", "41"] }
  256 + ],
  257 + [ { <<"Convert">>, fun() -> ?assertEqual({ok, R}, F(I)) end } || { I, R } <- Tests ].
  258 +
  259 +-endif.

0 comments on commit 7611e1c

Please sign in to comment.
Something went wrong with that request. Please try again.