File Coverage

blib/lib/Data/Embed/Writer.pm
Criterion Covered Total %
statement 189 207 91.3
branch 68 106 64.1
condition 20 24 83.3
subroutine 18 18 100.0
pod 6 6 100.0
total 301 361 83.3


line stmt bran cond sub pod time code
1             package Data::Embed::Writer;
2             {
3             $Data::Embed::Writer::VERSION = '0.2_03';
4             }
5              
6             # ABSTRACT: embed arbitrary data in a file - writer class
7              
8 8     8   849 use strict;
  8         10  
  8         322  
9 8     8   37 use warnings;
  8         10  
  8         250  
10 8     8   32 use English qw< -no_match_vars >;
  8         9  
  8         50  
11 8     8   6614 use Data::Embed::Reader;
  8         36  
  8         277  
12 8     8   47 use Log::Log4perl::Tiny qw< :easy :dead_if_first >;
  8         12  
  8         52  
13 8     8   1964 use Data::Embed::Util qw< :constants escape >;
  8         11  
  8         761  
14 8     8   39 use Fcntl qw< :seek >;
  8         8  
  8         678  
15 8     8   39 use Scalar::Util qw< refaddr >;
  8         9  
  8         13747  
16              
17              
18              
19             sub __output_for_new {
20 55     55   66 my $self = shift;
21 55         71 my $package = ref $self;
22 55         111 my $output = $self->{output} = $self->{args}{output};
23 55         85 $self->{output_same_as_input} = 0; # by default
24              
25             # The simple stuff: not present/defined, or the classical "-" string
26 55 100 100     395 if ((! defined($output)) || (! length($output)) || ($output eq '-')) {
      100        
27 24         61 DEBUG $package, "::__output_for_new(): output to STDOUT";
28 24 50       752 open my $fh, '>&', \*STDOUT
29             or LOGCROAK "dup(): $OS_ERROR";
30 24 50       72 binmode $fh
31             or LOGCROAK "binmode(\\*STDOUT): $OS_ERROR";
32 24         76 $self->{output_name} = '';
33 24         36 $self->{output_fh} = $fh;
34 24         43 $self->{output_type} = 'filehandle';
35 24         44 return $self;
36             }
37              
38 31         49 my $oref = ref $output;
39 31 100       61 if (! $oref) { # filename
40 9         17 DEBUG $package, '::__output:for_new(): output to a file';
41 9         87 $self->{output_type} = 'file';
42 9         9 $self->{output_name} = $output;
43              
44             # same file as input? If yes, do not clobber
45 9 100 100     32 if (($self->{input_type} eq 'file') && ($output eq $self->{input})) {
46 3 50       60 open my $fh, '+<', $output
47             or LOGCROAK "open('$output'): $OS_ERROR";
48 3 50       8 binmode $fh
49             or LOGCROAK "binmode('$output'): $OS_ERROR";
50 3         4 $self->{output_fh} = $fh;
51 3         3 $self->{output_same_as_input} = 1;
52 3         6 return $self;
53             }
54              
55 6 50       352 open my $fh, '>', $output
56             or LOGCROAK "open('$output'): $OS_ERROR";
57 6 50       37 binmode $fh
58             or LOGCROAK "binmode('$output'): $OS_ERROR";
59 6         12 $self->{output_fh} = $fh;
60 6         15 return $self;
61             }
62              
63 22 100       57 if ($oref eq 'SCALAR') { # reference to a scalar, similar to filename
64 16         31 DEBUG $package, '::__output:for_new(): output to a scalar ref';
65 16         151 $self->{output_type} = 'scalar-ref';
66 16         43 $self->{output_name} = "{$output}";
67              
68             # same file as input? If yes, do not clobber
69 16 100 100     138 if (($self->{input_type} eq 'scalar-ref') && (refaddr($output) eq refaddr($self->{input}))) {
70 1 50       6 open my $fh, '+<', $output
71             or LOGCROAK "open('$output'): $OS_ERROR";
72 1 50       4 binmode $fh
73             or LOGCROAK "binmode('$output'): $OS_ERROR";
74 1         2 $self->{output_fh} = $fh;
75 1         2 $self->{output_same_as_input} = 1;
76 1         2 return $self;
77             }
78              
79 15 50       195 open my $fh, '>', $output
80             or LOGCROAK "open('$output'): $OS_ERROR";
81 15 50       2651 binmode $fh
82             or LOGCROAK "binmode('$output'): $OS_ERROR";
83 15         37 $self->{output_fh} = $fh;
84 15         31 return $self;
85             }
86              
87             # Otherwise, we will have to assume that it is a filehandle
88 6         17 $self->{output_name} = '';
89 6         7 $self->{output_fh} = $output;
90 6         9 $self->{output_type} = 'filehandle';
91 6   66     25 $self->{output_same_as_input} = ($self->{input_type} eq 'filehandle')
92             && (refaddr($output) eq refaddr($self->{input_fh}));
93 6         11 return $self;
94             }
95              
96             sub __input_for_new {
97 55     55   88 my $self = shift;
98 55         99 my $package = ref $self;
99 55         162 my $input = $self->{input} = $self->{args}{input};
100              
101             # if not defined, it just does not exist
102 55 100       145 if (! defined($input)) {
103 23         70 DEBUG $package, "::__input_for_new(): no input";
104 23         418 $self->{input_name} = '*undef*';
105 23         37 $self->{input_fh} = undef;
106 23         41 $self->{input_type} = 'undef';
107 23         36 return $self;
108             }
109              
110             # the classical "-" string
111 32 100       97 if ($input eq '-') {
112 7         29 DEBUG $package, "::__input_for_new(): input from STDIN";
113 7 50       293 open my $fh, '<&', \*STDIN
114             or LOGCROAK "dup(): $OS_ERROR";
115 7 50       35 binmode $fh
116             or LOGCROAK "binmode(\\*STDIN): $OS_ERROR";
117 7         16 $self->{input_name} = '';
118 7         16 $self->{input_fh} = $fh;
119 7         14 $self->{input_type} = 'filehandle';
120 7         17 return $self;
121             }
122              
123 25         40 my $iref = ref $input;
124 25 100       50 if (! $iref) { # filename
125 10         31 DEBUG $package, '::__input:for_new(): input from file';
126 10 50       500 open my $fh, '<', $input
127             or LOGCROAK "open('$input'): $OS_ERROR";
128 10 50       43 binmode $fh
129             or LOGCROAK "binmode('$input'): $OS_ERROR";
130 10         23 $self->{input_name} = $input;
131 10         18 $self->{input_fh} = $fh;
132 10         16 $self->{input_type} = 'file';
133 10         27 return $self;
134             }
135              
136 15 100       40 if ($iref eq 'SCALAR') { # reference to a scalar, similar to filename
137 8         28 DEBUG $package, '::__input:for_new(): input from a scalar ref';
138 8 50       274 open my $fh, '<', $input
139             or LOGCROAK "open('$input'): $OS_ERROR";
140 8 50       31 binmode $fh
141             or LOGCROAK "binmode('$input'): $OS_ERROR";
142 8         33 $self->{input_name} = "{$input}";
143 8         18 $self->{input_fh} = $fh;
144 8         18 $self->{input_type} = 'scalar-ref';
145 8         20 return $self;
146             }
147              
148             # Otherwise, we will have to assume that it is a filehandle
149 7         15 $self->{input_name} = '';
150 7         48 $self->{input_fh} = $input;
151 7         12 $self->{input_type} = 'filehandle';
152 7         13 return $self;
153             }
154              
155             sub new {
156 55     55 1 81 my $package = shift;
157 55 50 66     438 my %args = (scalar(@_) && ref($_[0])) ? %{$_[0]} : @_;
  0         0  
158              
159             # Undocumented, keep additional parameters around...
160 55         180 my $self = bless {args => \%args}, $package;
161              
162             # first of all, resolve the input
163 55         123 $self->__input_for_new();
164              
165             # then the output (might depend on the input)
166 55         125 $self->__output_for_new();
167              
168             # if there is an input, transfer to the output if it is the case
169 55 100       131 if ($self->{input_fh}) {
170 32 100       79 if ($self->{output_same_as_input}) { # don't copy, assume seekable input
171 4         24 my $reader = Data::Embed::Reader->new($self->{input_fh});
172 4         19 my $ifile = $reader->_index(); # private method called
173 4         15 my @index = $ifile->contents();
174 4         7 shift @index; # eliminate STARTER
175 4         5 pop @index; # eliminate TERMINATOR
176 4         10 $self->{index} = \@index; # initialize previous index
177             # put output handle in right position
178 4         58 seek $self->{output_fh}, $ifile->{offset}, SEEK_SET;
179             }
180             else {
181 28         42 my $starter = STARTER;
182 28         71 my $terminator = TERMINATOR;
183 28         29 my (@index, $index_completed);
184 28         70 my $ifh = $self->{input_fh};
185 28         41 my $ofh = $self->{output_fh};
186             INPUT:
187 28         279 while (<$ifh>) {
188 28 50       76 if (! @index) {
    0          
189 28 50       76 if ($_ eq $starter) {
190 0         0 push @index, $_;
191 0         0 next INPUT;
192             }
193             else {
194 28         55 print {$ofh} $_;
  28         298  
195             }
196             }
197             elsif (! $index_completed) { # accumulating index
198 0 0       0 if (m{\A \s* (\d+) \s+ (\S*) \s*\z}mxs) {
    0          
199 0         0 push @index, $_;
200             }
201             elsif ($_ eq $terminator) {
202 0         0 push @index, $_;
203 0         0 $index_completed = 1;
204             }
205             else { # not a valid index, flush accumulated lines
206 0         0 print {$ofh} @index;
  0         0  
207 0         0 @index = ();
208 0         0 $index_completed = undef; # paranoid
209             }
210             }
211             else { # accumulating and index completed, but other stuff...
212 0         0 print {$ofh} @index; # flush and reset
  0         0  
213 0         0 @index = ();
214 0         0 $index_completed = undef;
215             }
216             }
217 28         42 shift @index; # eliminate STARTER
218 28         31 pop @index; # eliminate TERMINATOR
219 28         81 $self->{index} = \@index; # initialize previous index
220             }
221             }
222              
223             # now output_fh is at the right place for new stuff!
224              
225 55         352 return $self;
226             } ## end sub new
227              
228              
229             sub add {
230 99     99 1 25394 my $self = shift;
231 99 50 33     654 my %args = (scalar(@_) && ref($_[0])) ? %{$_[0]} : @_;
  0         0  
232              
233             # DWIM!
234 99 100       221 if (defined $args{input}) {
235 4 100       12 if ($args{input} eq '-') {
236 1 50       12 open my $fh, '<&', \*STDIN
237             or LOGCROAK "dup(): $OS_ERROR";
238 1 50       4 binmode $fh
239             or LOGCROAK "binmode(\\*STDIN): $OS_ERROR";
240 1         2 $args{fh} = $fh;
241             }
242             else {
243 3         5 my $ref = ref $args{input};
244 3 100 100     16 if ((! $ref) || ($ref eq 'SCALAR')) {
245 2         5 $args{filename} = $args{input};
246             }
247             else {
248 1         4 $args{fh} = $args{input};
249             }
250             }
251             }
252              
253 99 100       357 if (defined $args{fh}) {
    100          
    50          
254 3         11 return $self->add_fh(@args{qw< name fh >});
255             }
256             elsif (defined $args{filename}) {
257 4         12 return $self->add_file(@args{qw< name filename >});
258             }
259             elsif (defined $args{data}) {
260 92         215 return $self->add_data(@args{qw< name data >});
261             }
262 0         0 LOGCROAK "add() needs some input";
263 0         0 return; # unreached
264             } ## end sub add
265              
266              
267             sub add_file {
268 103     103 1 1991 my ($self, $name, $filename) = @_;
269 103 50       195 $name = '' unless defined $name;
270 103 100       220 my $print_name =
271             (ref($filename) eq 'SCALAR') ? 'internal data' : $filename;
272 103         383 DEBUG "add_file(): $name => $filename";
273              
274             # To make it work with references to scalars in perl pre-5.14
275             # we split open() and binmode()
276 103 50   7   2164 open my $fh, '<', $filename
  7         44  
  7         8  
  7         45  
277             or LOGCROAK "open('$print_name'): $OS_ERROR";
278 103 50       3757 binmode $fh
279             or LOGCROAK "binmode('$print_name') failed";
280              
281 103         192 return $self->add_fh($name, $fh);
282             } ## end sub add_file
283              
284              
285             sub add_data {
286 94     94 1 493 my ($self, $name) = @_;
287 94         181 return $self->add_file($name, \$_[2]);
288             }
289              
290              
291             sub add_fh {
292 108     108 1 863 my ($self, $name, $input_fh) = @_;
293              
294 108         163 my $output_fh = $self->{output_fh};
295 108         110 my $data_length = 0; # go!
296 108         329 while (!eof $input_fh) {
297 108         98 my $buffer;
298 108 50       374 defined(my $nread = read $input_fh, $buffer, 4096)
299             or LOGCROAK "read(): $OS_ERROR";
300 108 50       170 last unless $nread; # safe side?
301 108 50       106 print {$output_fh} $buffer
  108         370  
302             or LOGCROAK "print(): $OS_ERROR";
303 108         266 $data_length += $nread;
304             } ## end while (!eof $input_fh)
305              
306             # Add separator, not really needed but might come handy for
307             # peeking into the file
308 108 50       109 print {$output_fh} "\n\n"
  108         214  
309             or LOGCROAK "print(): $OS_ERROR";
310              
311 108 50       181 $name = '' unless defined $name;
312 108         160 push @{$self->{index}}, sprintf "%d %s\n", $data_length, escape($name);
  108         363  
313              
314 108         1033 return $self;
315             }
316              
317              
318             sub write_index {
319 55     55 1 32017 my $self = shift;
320 55         86 my ($output_fh, $index) = @{$self}{qw< output_fh index >};
  55         157  
321 55 50       68 print {$output_fh} STARTER, @$index, TERMINATOR
  55         234  
322             or LOGCROAK "print(): $OS_ERROR";
323 55         277 delete $self->{$_} for qw< output_fh index >;
324 55         1977 return;
325             } ## end sub add_fh
326              
327             sub DESTROY {
328 55     55   233639 my $self = shift;
329 55 100       225 $self->write_index() if exists $self->{output_fh};
330 55         2380 return;
331             }
332              
333             1;
334              
335             __END__