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 5     5   99108 use strict;
  5         13  
  5         137  
4 5     5   27 use warnings;
  5         12  
  5         208  
5            
6             our $VERSION = '0.008';
7            
8 5     5   2428 use Hash::Util qw(lock_keys);
  5         11047  
  5         33  
9 5         50 use Sub::Exporter -setup => {
10             exports => [
11             qw(hex_dump),
12             ],
13             groups => {
14             default => [ qw(hex_dump) ],
15             },
16 5     5   2028 };
  5         26126  
17            
18             my $default_format = "%a : %4C : %d\n";
19            
20             sub hex_dump {
21 9     9 1 170 my ($data, $attr_ref) = @_;
22            
23 9 50       30 defined $data
24             or return $data;
25 9 50       26 ref $data
26             and return $data;
27 9 100       29 $attr_ref
28             = ref $attr_ref eq 'HASH'
29             ? $attr_ref
30             : {};
31             my $data_pool = {
32             # global
33             data => $data,
34             format => $attr_ref->{format} || "$default_format%*x",
35 9   66     103 address => $attr_ref->{address} || 0,
      100        
36             output => q{},
37             # to format a block
38             format_block => undef,
39             data_length => undef,
40             is_multibyte_error => undef,
41             };
42 9         19 lock_keys %{$data_pool};
  9         51  
43             BLOCK:
44 9         128 while ( length $data_pool->{data} ) {
45 20         55 _next_format($data_pool);
46 20         48 _format_items($data_pool);
47             }
48            
49 9         53 return $data_pool->{output};
50             }
51            
52             sub _next_format {
53 26     26   1545 my $data_pool = shift;
54            
55 26         159 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       101 my $new_count = $2 eq q{*} ? q{*} : $2 - 1;
61 25         62 $data_pool->{format_block} = $1;
62 25 100       97 $new_count
63             ? "$1\%${new_count}x"
64             : q{};
65             }xmse;
66 26 100 66     163 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         48 return;
74             }
75            
76             sub _format_items {
77 20     20   33 my $data_pool = shift;
78            
79 20         33 $data_pool->{data_length} = 0;
80             RUN: {
81             # % written as %%
82 20 50       29 $data_pool->{format_block} =~ s{
  246         632  
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       529 $data_pool->{format_block} =~ s{
92             \A % [\n]
93             }{}xms and redo RUN;
94             # address
95 238 100       433 _format_address($data_pool)
96             and redo RUN;
97             # words
98 218 100       438 _format_word($data_pool)
99             and redo RUN;
100             # display ASCII
101 192 100       383 _format_ascii($data_pool)
102             and redo RUN;
103             # display any other char
104 181 100       486 $data_pool->{format_block} =~ s{
105             \A (.)
106             } {
107 161         254 do {
108 161         294 $data_pool->{output} .= $1;
109 161         464 q{};
110             }
111             }xmse and redo RUN;
112 20 50       46 if ( $data_pool->{data_length} ) {
113             # clear already displayed data
114 20         44 substr $data_pool->{data}, 0, $data_pool->{data_length}, q{};
115 20         36 $data_pool->{data_length} = 0;
116             }
117             }
118            
119 20         54 return;
120             }
121            
122             sub _format_address {
123 238     238   351 my $data_pool = shift;
124            
125 238         678 return $data_pool->{format_block} =~ s{
126             \A % ( 0* [48]? ) a
127             } {
128 20         35 do {
129 20   100     76 my $length = $1 || 4;
130             $data_pool->{output}
131 20         81 .= sprintf "%0${length}X", $data_pool->{address};
132 20         77 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   307 my $data_pool = shift;
204            
205 218         605 return $data_pool->{format_block} =~ s{
206             \A
207             % ( 0* [1-9] \d* )?
208             ( [LSQ] [<>] | [CVNvnLSQ] )
209             } {
210 26         43 do {
211             my ($byte_length, $endian)
212 26         39 = @{ $format_of{$2} }{ qw(bytes endian) };
  26         95  
213             $data_pool->{output} .= join q{ }, map {
214 26   100     121 (
215             length $data_pool->{data}
216             >= $data_pool->{data_length} + $byte_length
217             )
218             ? do {
219             my @unpacked
220             = unpack
221             q{C} x $byte_length,
222             substr
223             $data_pool->{data},
224             $data_pool->{data_length},
225 48         175 $byte_length;
226 48 100       117 if ( $endian eq q{<} ) {
227 8         16 @unpacked = reverse @unpacked;
228             }
229 48         124 my $hex = sprintf
230             '%02X' x $byte_length,
231             @unpacked;
232 48         75 $data_pool->{data_length} += $byte_length;
233 48         78 $data_pool->{address} += $byte_length;
234 48         122 $hex;
235             }
236 58 100       152 : do {
237 10 50       28 if ( $byte_length > 1 ) {
238 0         0 $data_pool->{is_multibyte_error}++;
239             }
240 10         31 q{ } x 2 x $byte_length;
241             };
242             } 1 .. ( $1 || 1 );
243 26         103 q{};
244             }
245             }xmse;
246             }
247            
248             sub _format_ascii {
249 192     192   286 my $data_pool = shift;
250            
251 192         462 return $data_pool->{format_block} =~ s{
252             \A %d
253             } {
254 11         18 do {
255 11         25 my $data = substr $data_pool->{data}, 0, $data_pool->{data_length};
256 11         34 $data =~ s{
257             ( ['"\\] )
258             | ( [!-~] )
259             | .
260             } {
261 32 100       117 defined $1 ? q{.}
    50          
262             : defined $2 ? $2
263             : q{.}
264             }xmsge;
265 11         26 $data_pool->{output} .= $data;
266 11         38 q{};
267             }
268             }xmse;
269             }
270            
271             # $Id$
272            
273             1;
274            
275             __END__