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   103763 use strict;
  6         12  
  6         201  
4 6     6   24 use warnings;
  6         8  
  6         252  
5            
6             our $VERSION = '0.008';
7            
8 6     6   3058 use Hash::Util qw(lock_keys);
  6         11267  
  6         27  
9 6         58 use Sub::Exporter -setup => {
10             exports => [
11             qw(hex_dump),
12             ],
13             groups => {
14             default => [ qw(hex_dump) ],
15             },
16 6     6   2113 };
  6         23126  
17            
18             my $default_format = "%a : %4C : %d\n";
19            
20             sub hex_dump {
21 11     11 1 42 my ($data, $attr_ref) = @_;
22            
23 11 50       29 defined $data
24             or return $data;
25 11 50       28 ref $data
26             and return $data;
27 11 100       30 $attr_ref
28             = ref $attr_ref eq 'HASH'
29             ? $attr_ref
30             : {};
31 11   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 11         16 lock_keys %{$data_pool};
  11         48  
43             BLOCK:
44 11         125 while ( length $data_pool->{data} ) {
45 22         40 _next_format($data_pool);
46 22         34 _format_items($data_pool);
47             }
48            
49 11         68 return $data_pool->{output};
50             }
51            
52             sub _next_format {
53 28     28   1252 my $data_pool = shift;
54            
55 28         190 my $is_match = $data_pool->{format} =~ s{
56             \A
57             ( .*? [^%] ) # format of the block
58             % ( 0* [1-9] \d* | [*] ) x # repetition factor
59             } {
60 27 100       84 my $new_count = $2 eq q{*} ? q{*} : $2 - 1;
61 27         46 $data_pool->{format_block} = $1;
62 27 100       92 $new_count
63             ? "$1\%${new_count}x"
64             : q{};
65             }xmse;
66 28 100 66     123 if ( $data_pool->{is_multibyte_error} || ! $is_match ) {
67 1         4 $data_pool->{format} = "$default_format%*x";
68 1         2 $data_pool->{format_block} = $default_format;
69 1         2 $data_pool->{is_multibyte_error} = 0;
70 1         2 return;
71             }
72            
73 27         35 return;
74             }
75            
76             sub _format_items {
77 22     22   23 my $data_pool = shift;
78            
79 22         41 $data_pool->{data_length} = 0;
80 268 50       478 RUN: {
81             # % written as %%
82 22         43 $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 268 100       420 $data_pool->{format_block} =~ s{
92             \A % [\n]
93             }{}xms and redo RUN;
94             # address
95 260 100       351 _format_address($data_pool)
96             and redo RUN;
97             # words
98 238 100       289 _format_word($data_pool)
99             and redo RUN;
100             # display ASCII
101 210 100       244 _format_ascii($data_pool)
102             and redo RUN;
103             # display any other char
104 197 100       353 $data_pool->{format_block} =~ s{
105             \A (.)
106             } {
107 175         134 do {
108 175         197 $data_pool->{output} .= $1;
109 175         429 q{};
110             }
111             }xmse and redo RUN;
112 22 50       41 if ( $data_pool->{data_length} ) {
113             # clear already displayed data
114 22         41 substr $data_pool->{data}, 0, $data_pool->{data_length}, q{};
115 22         25 $data_pool->{data_length} = 0;
116             }
117             }
118            
119 22         56 return;
120             }
121            
122             sub _format_address {
123 260     260   273 my $data_pool = shift;
124            
125 260         567 return $data_pool->{format_block} =~ s{
126             \A % ( 0* [48]? ) a
127             } {
128 22         54 do {
129 22   100     79 my $length = $1 || 4;
130 22         82 $data_pool->{output}
131             .= sprintf "%0${length}X", $data_pool->{address};
132 22         81 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 238     238   185 my $data_pool = shift;
204            
205 238         515 return $data_pool->{format_block} =~ s{
206             \A
207             % ( 0* [1-9] \d* )?
208             ( [LSQ] [<>] | [CVNvnLSQ] )
209             } {
210 28         54 do {
211 28         73 my ($byte_length, $endian)
212 28         28 = @{ $format_of{$2} }{ qw(bytes endian) };
213             $data_pool->{output} .= join q{ }, map {
214 28   100     121 (
215             length $data_pool->{data}
216             >= $data_pool->{data_length} + $byte_length
217             )
218             ? do {
219 50         144 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 50 100       94 if ( $endian eq q{<} ) {
227 8         9 @unpacked = reverse @unpacked;
228             }
229 50         91 my $hex = sprintf
230             '%02X' x $byte_length,
231             @unpacked;
232 50         51 $data_pool->{data_length} += $byte_length;
233 50         48 $data_pool->{address} += $byte_length;
234 50         112 $hex;
235             }
236 66 100       124 : do {
237 16 50       29 if ( $byte_length > 1 ) {
238 0         0 $data_pool->{is_multibyte_error}++;
239             }
240 16         32 q{ } x 2 x $byte_length;
241             };
242             } 1 .. ( $1 || 1 );
243 28         95 q{};
244             }
245             }xmse;
246             }
247            
248             sub _format_ascii {
249 210     210   180 my $data_pool = shift;
250            
251 210         414 return $data_pool->{format_block} =~ s{
252             \A %d
253             } {
254 13         11 do {
255 13         23 my $data = substr $data_pool->{data}, 0, $data_pool->{data_length};
256 13         33 $data =~ s{
257             ( ['"\\] )
258             | ( [!-~] )
259             | .
260             } {
261 34 100       103 defined $1 ? q{.}
    50          
262             : defined $2 ? $2
263             : q{.}
264             }xmsge;
265 13         22 $data_pool->{output} .= $data;
266 13         39 q{};
267             }
268             }xmse;
269             }
270            
271             # $Id$
272            
273             1;
274            
275             __END__