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.21';
4             }
5              
6             # ABSTRACT: embed arbitrary data in a file - writer class
7              
8 8     8   1375 use strict;
  8         15  
  8         388  
9 8     8   40 use warnings;
  8         14  
  8         308  
10 8     8   37 use English qw< -no_match_vars >;
  8         9  
  8         61  
11 8     8   9295 use Data::Embed::Reader;
  8         42  
  8         280  
12 8     8   53 use Log::Log4perl::Tiny qw< :easy :dead_if_first >;
  8         11  
  8         64  
13 8     8   2651 use Data::Embed::Util qw< :constants escape >;
  8         15  
  8         1060  
14 8     8   46 use Fcntl qw< :seek >;
  8         12  
  8         856  
15 8     8   47 use Scalar::Util qw< refaddr >;
  8         11  
  8         16480  
16              
17              
18              
19             sub __output_for_new {
20 55     55   74 my $self = shift;
21 55         78 my $package = ref $self;
22 55         117 my $output = $self->{output} = $self->{args}{output};
23 55         76 $self->{output_same_as_input} = 0; # by default
24              
25             # The simple stuff: not present/defined, or the classical "-" string
26 55 100 100     392 if ((! defined($output)) || (! length($output)) || ($output eq '-')) {
      100        
27 24         53 DEBUG $package, "::__output_for_new(): output to STDOUT";
28 24 50       586 open my $fh, '>&', \*STDOUT
29             or LOGCROAK "dup(): $OS_ERROR";
30 24 50       84 binmode $fh
31             or LOGCROAK "binmode(\\*STDOUT): $OS_ERROR";
32 24         60 $self->{output_name} = '';
33 24         30 $self->{output_fh} = $fh;
34 24         32 $self->{output_type} = 'filehandle';
35 24         42 return $self;
36             }
37              
38 31         54 my $oref = ref $output;
39 31 100       70 if (! $oref) { # filename
40 9         21 DEBUG $package, '::__output:for_new(): output to a file';
41 9         103 $self->{output_type} = 'file';
42 9         12 $self->{output_name} = $output;
43              
44             # same file as input? If yes, do not clobber
45 9 100 100     33 if (($self->{input_type} eq 'file') && ($output eq $self->{input})) {
46 3 50       76 open my $fh, '+<', $output
47             or LOGCROAK "open('$output'): $OS_ERROR";
48 3 50       10 binmode $fh
49             or LOGCROAK "binmode('$output'): $OS_ERROR";
50 3         5 $self->{output_fh} = $fh;
51 3         4 $self->{output_same_as_input} = 1;
52 3         8 return $self;
53             }
54              
55 6 50       349 open my $fh, '>', $output
56             or LOGCROAK "open('$output'): $OS_ERROR";
57 6 50       17 binmode $fh
58             or LOGCROAK "binmode('$output'): $OS_ERROR";
59 6         12 $self->{output_fh} = $fh;
60 6         14 return $self;
61             }
62              
63 22 100       50 if ($oref eq 'SCALAR') { # reference to a scalar, similar to filename
64 16         40 DEBUG $package, '::__output:for_new(): output to a scalar ref';
65 16         208 $self->{output_type} = 'scalar-ref';
66 16         51 $self->{output_name} = "{$output}";
67              
68             # same file as input? If yes, do not clobber
69 16 100 100     84 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         1 $self->{output_same_as_input} = 1;
76 1         2 return $self;
77             }
78              
79 15 50       239 open my $fh, '>', $output
80             or LOGCROAK "open('$output'): $OS_ERROR";
81 15 50       3245 binmode $fh
82             or LOGCROAK "binmode('$output'): $OS_ERROR";
83 15         52 $self->{output_fh} = $fh;
84 15         53 return $self;
85             }
86              
87             # Otherwise, we will have to assume that it is a filehandle
88 6         18 $self->{output_name} = '';
89 6         9 $self->{output_fh} = $output;
90 6         7 $self->{output_type} = 'filehandle';
91 6   66     27 $self->{output_same_as_input} = ($self->{input_type} eq 'filehandle')
92             && (refaddr($output) eq refaddr($self->{input_fh}));
93 6         10 return $self;
94             }
95              
96             sub __input_for_new {
97 55     55   82 my $self = shift;
98 55         92 my $package = ref $self;
99 55         172 my $input = $self->{input} = $self->{args}{input};
100              
101             # if not defined, it just does not exist
102 55 100       165 if (! defined($input)) {
103 23         80 DEBUG $package, "::__input_for_new(): no input";
104 23         459 $self->{input_name} = '*undef*';
105 23         43 $self->{input_fh} = undef;
106 23         38 $self->{input_type} = 'undef';
107 23         8946 return $self;
108             }
109              
110             # the classical "-" string
111 32 100       98 if ($input eq '-') {
112 7         34 DEBUG $package, "::__input_for_new(): input from STDIN";
113 7 50       269 open my $fh, '<&', \*STDIN
114             or LOGCROAK "dup(): $OS_ERROR";
115 7 50       40 binmode $fh
116             or LOGCROAK "binmode(\\*STDIN): $OS_ERROR";
117 7         19 $self->{input_name} = '';
118 7         14 $self->{input_fh} = $fh;
119 7         13 $self->{input_type} = 'filehandle';
120 7         15 return $self;
121             }
122              
123 25         40 my $iref = ref $input;
124 25 100       52 if (! $iref) { # filename
125 10         25 DEBUG $package, '::__input:for_new(): input from file';
126 10 50       423 open my $fh, '<', $input
127             or LOGCROAK "open('$input'): $OS_ERROR";
128 10 50       39 binmode $fh
129             or LOGCROAK "binmode('$input'): $OS_ERROR";
130 10         19 $self->{input_name} = $input;
131 10         19 $self->{input_fh} = $fh;
132 10         15 $self->{input_type} = 'file';
133 10         24 return $self;
134             }
135              
136 15 100       41 if ($iref eq 'SCALAR') { # reference to a scalar, similar to filename
137 8         26 DEBUG $package, '::__input:for_new(): input from a scalar ref';
138 8 50       183 open my $fh, '<', $input
139             or LOGCROAK "open('$input'): $OS_ERROR";
140 8 50       29 binmode $fh
141             or LOGCROAK "binmode('$input'): $OS_ERROR";
142 8         26 $self->{input_name} = "{$input}";
143 8         15 $self->{input_fh} = $fh;
144 8         13 $self->{input_type} = 'scalar-ref';
145 8         14 return $self;
146             }
147              
148             # Otherwise, we will have to assume that it is a filehandle
149 7         17 $self->{input_name} = '';
150 7         16 $self->{input_fh} = $input;
151 7         14 $self->{input_type} = 'filehandle';
152 7         12 return $self;
153             }
154              
155             sub new {
156 55     55 1 82 my $package = shift;
157 55 50 66     447 my %args = (scalar(@_) && ref($_[0])) ? %{$_[0]} : @_;
  0         0  
158              
159             # Undocumented, keep additional parameters around...
160 55         176 my $self = bless {args => \%args}, $package;
161              
162             # first of all, resolve the input
163 55         165 $self->__input_for_new();
164              
165             # then the output (might depend on the input)
166 55         136 $self->__output_for_new();
167              
168             # if there is an input, transfer to the output if it is the case
169 55 100       140 if ($self->{input_fh}) {
170 32 100       75 if ($self->{output_same_as_input}) { # don't copy, assume seekable input
171 4         29 my $reader = Data::Embed::Reader->new($self->{input_fh});
172 4         13 my $ifile = $reader->_index(); # private method called
173 4         15 my @index = $ifile->contents();
174 4         4 shift @index; # eliminate STARTER
175 4         4 pop @index; # eliminate TERMINATOR
176 4         8 $self->{index} = \@index; # initialize previous index
177             # put output handle in right position
178 4         69 seek $self->{output_fh}, $ifile->{offset}, SEEK_SET;
179             }
180             else {
181 28         44 my $starter = STARTER;
182 28         39 my $terminator = TERMINATOR;
183 28         29 my (@index, $index_completed);
184 28         49 my $ifh = $self->{input_fh};
185 28         42 my $ofh = $self->{output_fh};
186             INPUT:
187 28         246 while (<$ifh>) {
188 28 50       69 if (! @index) {
    0          
189 28 50       72 if ($_ eq $starter) {
190 0         0 push @index, $_;
191 0         0 next INPUT;
192             }
193             else {
194 28         29 print {$ofh} $_;
  28         265  
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         41 shift @index; # eliminate STARTER
218 28         33 pop @index; # eliminate TERMINATOR
219 28         88 $self->{index} = \@index; # initialize previous index
220             }
221             }
222              
223             # now output_fh is at the right place for new stuff!
224              
225 55         373 return $self;
226             } ## end sub new
227              
228              
229             sub add {
230 99     99 1 28835 my $self = shift;
231 99 50 33     630 my %args = (scalar(@_) && ref($_[0])) ? %{$_[0]} : @_;
  0         0  
232              
233             # DWIM!
234 99 100       238 if (defined $args{input}) {
235 4 100       19 if ($args{input} eq '-') {
236 1 50       15 open my $fh, '<&', \*STDIN
237             or LOGCROAK "dup(): $OS_ERROR";
238 1 50       5 binmode $fh
239             or LOGCROAK "binmode(\\*STDIN): $OS_ERROR";
240 1         4 $args{fh} = $fh;
241             }
242             else {
243 3         11 my $ref = ref $args{input};
244 3 100 100     18 if ((! $ref) || ($ref eq 'SCALAR')) {
245 2         10 $args{filename} = $args{input};
246             }
247             else {
248 1         6 $args{fh} = $args{input};
249             }
250             }
251             }
252              
253 99 100       372 if (defined $args{fh}) {
    100          
    50          
254 3         14 return $self->add_fh(@args{qw< name fh >});
255             }
256             elsif (defined $args{filename}) {
257 4         19 return $self->add_file(@args{qw< name filename >});
258             }
259             elsif (defined $args{data}) {
260 92         199 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 2569 my ($self, $name, $filename) = @_;
269 103 50       196 $name = '' unless defined $name;
270 103 100       228 my $print_name =
271             (ref($filename) eq 'SCALAR') ? 'internal data' : $filename;
272 103         373 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   2278 open my $fh, '<', $filename
  7         43  
  7         7  
  7         46  
277             or LOGCROAK "open('$print_name'): $OS_ERROR";
278 103 50       3618 binmode $fh
279             or LOGCROAK "binmode('$print_name') failed";
280              
281 103         239 return $self->add_fh($name, $fh);
282             } ## end sub add_file
283              
284              
285             sub add_data {
286 94     94 1 660 my ($self, $name) = @_;
287 94         179 return $self->add_file($name, \$_[2]);
288             }
289              
290              
291             sub add_fh {
292 108     108 1 988 my ($self, $name, $input_fh) = @_;
293              
294 108         151 my $output_fh = $self->{output_fh};
295 108         108 my $data_length = 0; # go!
296 108         338 while (!eof $input_fh) {
297 108         101 my $buffer;
298 108 50       379 defined(my $nread = read $input_fh, $buffer, 4096)
299             or LOGCROAK "read(): $OS_ERROR";
300 108 50       191 last unless $nread; # safe side?
301 108 50       105 print {$output_fh} $buffer
  108         381  
302             or LOGCROAK "print(): $OS_ERROR";
303 108         290 $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       94 print {$output_fh} "\n\n"
  108         225  
309             or LOGCROAK "print(): $OS_ERROR";
310              
311 108 50       196 $name = '' unless defined $name;
312 108         95 push @{$self->{index}}, sprintf "%d %s\n", $data_length, escape($name);
  108         408  
313              
314 108         1068 return $self;
315             }
316              
317              
318             sub write_index {
319 55     55 1 34159 my $self = shift;
320 55         78 my ($output_fh, $index) = @{$self}{qw< output_fh index >};
  55         121  
321 55 50       63 print {$output_fh} STARTER, @$index, TERMINATOR
  55         243  
322             or LOGCROAK "print(): $OS_ERROR";
323 55         250 delete $self->{$_} for qw< output_fh index >;
324 55         1752 return;
325             } ## end sub add_fh
326              
327             sub DESTROY {
328 55     55   67931 my $self = shift;
329 55 100       213 $self->write_index() if exists $self->{output_fh};
330 55         2153 return;
331             }
332              
333             1;
334              
335             __END__