File Coverage

blib/lib/Test/HexDifferences/HexDump.pm
Criterion Covered Total %
statement 81 85 95.2
branch 30 36 83.3
condition 10 12 83.3
subroutine 10 10 100.0
pod 1 1 100.0
total 132 144 91.6


line stmt bran cond sub pod time code
1             package Test::HexDifferences::HexDump; ## no critic (TidyCode)
2            
3 6     6   113427 use strict;
  6         11  
  6         237  
4 6     6   31 use warnings;
  6         10  
  6         281  
5            
6             our $VERSION = '0.008';
7            
8 6     6   3079 use Hash::Util qw(lock_keys);
  6         12123  
  6         28  
9 6         65 use Sub::Exporter -setup => {
10             exports => [
11             qw(hex_dump),
12             ],
13             groups => {
14             default => [ qw(hex_dump) ],
15             },
16 6     6   2201 };
  6         25867  
17            
18             my $default_format = "%a : %4C : %d\n";
19            
20             sub hex_dump {
21 9     9 1 173 my ($data, $attr_ref) = @_;
22            
23 9 50       30 defined $data
24             or return $data;
25 9 50       24 ref $data
26             and return $data;
27 9 100       28 $attr_ref
28             = ref $attr_ref eq 'HASH'
29             ? $attr_ref
30             : {};
31 9   66     117 my $data_pool = {
      100        
32             # global
33             data => $data,
34             format => $attr_ref->{format} || "$default_format%*x",
35             address => $attr_ref->{address} || 0,
36             output => q{},
37             # to format a block
38             format_block => undef,
39             data_length => undef,
40             is_multibyte_error => undef,
41             };
42 9         34 lock_keys %{$data_pool};
  9         47  
43             BLOCK:
44 9         135 while ( length $data_pool->{data} ) {
45 20         35 _next_format($data_pool);
46 20         33 _format_items($data_pool);
47             }
48            
49 9         65 return $data_pool->{output};
50             }
51            
52             sub _next_format {
53 26     26   1254 my $data_pool = shift;
54            
55 26         215 my $is_match = $data_pool->{format} =~ s{
56             \A
57             ( .*? [^%] ) # format of the block
58             % ( 0* [1-9] \d* | [*] ) x # repetition factor
59             } {
60 25 100       90 my $new_count = $2 eq q{*} ? q{*} : $2 - 1;
61 25         51 $data_pool->{format_block} = $1;
62 25 100       93 $new_count
63             ? "$1\%${new_count}x"
64             : q{};
65             }xmse;
66 26 100 66     123 if ( $data_pool->{is_multibyte_error} || ! $is_match ) {
67 1         4 $data_pool->{format} = "$default_format%*x";
68 1         3 $data_pool->{format_block} = $default_format;
69 1         2 $data_pool->{is_multibyte_error} = 0;
70 1         3 return;
71             }
72            
73 25         39 return;
74             }
75            
76             sub _format_items {
77 20     20   26 my $data_pool = shift;
78            
79 20         25 $data_pool->{data_length} = 0;
80 246 50       440 RUN: {
81             # % written as %%
82 20         19 $data_pool->{format_block} =~ s{
83             \A % ( % )
84             } {
85 0         0 do {
86 0         0 $data_pool->{output} .= $1;
87 0         0 q{};
88             }
89             }xmse and redo RUN;
90             # \n written as %\n will be ignored
91 246 100       392 $data_pool->{format_block} =~ s{
92             \A % [\n]
93             }{}xms and redo RUN;
94             # address
95 238 100       287 _format_address($data_pool)
96             and redo RUN;
97             # words
98 218 100       269 _format_word($data_pool)
99             and redo RUN;
100             # display ASCII
101 192 100       225 _format_ascii($data_pool)
102             and redo RUN;
103             # display any other char
104 181 100       383 $data_pool->{format_block} =~ s{
105             \A (.)
106             } {
107 161         133 do {
108 161         198 $data_pool->{output} .= $1;
109 161         404 q{};
110             }
111             }xmse and redo RUN;
112 20 50       40 if ( $data_pool->{data_length} ) {
113             # clear already displayed data
114 20         39 substr $data_pool->{data}, 0, $data_pool->{data_length}, q{};
115 20         29 $data_pool->{data_length} = 0;
116             }
117             }
118            
119 20         54 return;
120             }
121            
122             sub _format_address {
123 238     238   199 my $data_pool = shift;
124            
125 238         603 return $data_pool->{format_block} =~ s{
126             \A % ( 0* [48]? ) a
127             } {
128 20         21 do {
129 20   100     84 my $length = $1 || 4;
130 20         77 $data_pool->{output}
131             .= sprintf "%0${length}X", $data_pool->{address};
132 20         80 q{};
133             }
134             }xmse;
135             }
136            
137             my $big_endian = q{>};
138             my $little_endian = q{<};
139             my $machine_endian
140             = ( pack 'S', 1 ) eq ( pack 'n', 1 )
141             ? $big_endian # network order
142             : $little_endian;
143             my %format_of = (
144             'C' => { # unsigned char
145             bytes => 1,
146             endian => $big_endian,
147             },
148             'S' => { # unsigned 16-bit, endian depends on machine
149             bytes => 2,
150             endian => $machine_endian,
151             },
152             'S<' => { # unsigned 16-bit, little-endian
153             bytes => 2,
154             endian => $little_endian,
155             },
156             'S>' => { # unsigned 16-bit, big-endian
157             bytes => 2,
158             endian => $big_endian,
159             },
160             'v' => { # unsigned 16-bit, little-endian
161             bytes => 2,
162             endian => $little_endian,
163             },
164             'n' => { # unsigned 16-bit, big-endian
165             bytes => 2,
166             endian => $big_endian,
167             },
168             'L' => { # unsigned 32-bit, endian depends on machine
169             bytes => 4,
170             endian => $machine_endian,
171             },
172             'L<' => { # unsigned 32-bit, little-endian
173             bytes => 4,
174             endian => $little_endian,
175             },
176             'L>' => { # unsigned 32-bit, big-endian
177             bytes => 4,
178             endian => $big_endian,
179             },
180             'V' => { # unsigned 32-bit, little-endian
181             bytes => 4,
182             endian => $little_endian,
183             },
184             'N' => { # unsigned 32-bit, big-endian
185             bytes => 4,
186             endian => $big_endian,
187             },
188             'Q' => { # unsigned 64-bit, endian depends on machine
189             bytes => 8,
190             endian => $machine_endian,
191             },
192             'Q<' => { # unsigned 64-bit, little-endian
193             bytes => 8,
194             endian => $little_endian,
195             },
196             'Q>' => { # unsigned 64-bit, big-endian
197             bytes => 8,
198             endian => $big_endian,
199             },
200             );
201            
202             sub _format_word {
203 218     218   175 my $data_pool = shift;
204            
205 218         483 return $data_pool->{format_block} =~ s{
206             \A
207             % ( 0* [1-9] \d* )?
208             ( [LSQ] [<>] | [CVNvnLSQ] )
209             } {
210 26         46 do {
211 26         69 my ($byte_length, $endian)
212 26         27 = @{ $format_of{$2} }{ qw(bytes endian) };
213             $data_pool->{output} .= join q{ }, map {
214 26   100     129 (
215             length $data_pool->{data}
216             >= $data_pool->{data_length} + $byte_length
217             )
218             ? do {
219 48         149 my @unpacked
220             = unpack
221             q{C} x $byte_length,
222             substr
223             $data_pool->{data},
224             $data_pool->{data_length},
225             $byte_length;
226 48 100       89 if ( $endian eq q{<} ) {
227 8         8 @unpacked = reverse @unpacked;
228             }
229 48         102 my $hex = sprintf
230             '%02X' x $byte_length,
231             @unpacked;
232 48         46 $data_pool->{data_length} += $byte_length;
233 48         43 $data_pool->{address} += $byte_length;
234 48         106 $hex;
235             }
236 58 100       122 : do {
237 10 50       26 if ( $byte_length > 1 ) {
238 0         0 $data_pool->{is_multibyte_error}++;
239             }
240 10         22 q{ } x 2 x $byte_length;
241             };
242             } 1 .. ( $1 || 1 );
243 26         90 q{};
244             }
245             }xmse;
246             }
247            
248             sub _format_ascii {
249 192     192   153 my $data_pool = shift;
250            
251 192         371 return $data_pool->{format_block} =~ s{
252             \A %d
253             } {
254 11         11 do {
255 11         21 my $data = substr $data_pool->{data}, 0, $data_pool->{data_length};
256 11         31 $data =~ s{
257             ( ['"\\] )
258             | ( [!-~] )
259             | .
260             } {
261 32 100       107 defined $1 ? q{.}
    50          
262             : defined $2 ? $2
263             : q{.}
264             }xmsge;
265 11         21 $data_pool->{output} .= $data;
266 11         37 q{};
267             }
268             }xmse;
269             }
270            
271             # $Id$
272            
273             1;
274            
275             __END__