File Coverage

blib/lib/Data/Tubes/Util/Output.pm
Criterion Covered Total %
statement 103 112 91.9
branch 34 48 70.8
condition 7 12 58.3
subroutine 21 23 91.3
pod 9 9 100.0
total 174 204 85.2


line stmt bran cond sub pod time code
1             package Data::Tubes::Util::Output;
2 3     3   95621 use strict;
  3         18  
  3         95  
3 3     3   15 use warnings;
  3         6  
  3         86  
4 3     3   582 use English qw< -no_match_vars >;
  3         3684  
  3         15  
5 3     3   1163 use 5.010;
  3         11  
6 3     3   29 use File::Path qw< make_path >;
  3         6  
  3         209  
7 3     3   23 use File::Basename qw< dirname >;
  3         6  
  3         340  
8             our $VERSION = '0.740';
9              
10 3     3   561 use Log::Log4perl::Tiny qw< :easy :dead_if_first >;
  3         15740  
  3         20  
11 3     3   2561 use Mo qw< default >;
  3         1655  
  3         16  
12             has binmode => (default => ':raw');
13             has footer => ();
14             has header => ();
15             has interlude => ();
16             has output => (default => \*STDOUT);
17             has policy => (default => undef);
18             has track => (
19             default => sub {
20             return {
21             files => 0,
22             records => 0,
23             chars_file => 0,
24             chars_total => 0,
25             };
26             }
27             );
28              
29             sub open {
30 31     31 1 61 my ($self, $hint) = @_;
31              
32             # reset some tracking parameters
33 31         50 my $track = $self->track();
34 31         167 $track->{files}++;
35 31         43 $track->{records} = 0;
36 31         42 $track->{chars_file} = 0;
37              
38             # get new filehandle
39             my ($fh, $fh_releaser) =
40 31         58 @{$track}{qw< current_fh current_fh_releaser>} = $self->get_fh($hint);
  31         96  
41              
42             # do header handling
43 31         107 $self->_print($fh, $self->header(), $track);
44              
45 31         117 return $fh;
46             } ## end sub open
47              
48             sub __open_file {
49 30     30   218 my ($filename, $binmode) = @_;
50              
51             # ensure its directory exists
52 30         2260 make_path(dirname($filename), {error => \my $errors});
53 30 50       193 if (@$errors) {
54 0         0 my ($error) = values %{$errors->[0]};
  0         0  
55 0         0 LOGCONFESS "make_path() for '$filename': $error";
56             }
57              
58             # can open the file, at last
59 30 50   1   1616 CORE::open my $fh, '>', $filename
  1         7  
  1         2  
  1         6  
60             or LOGCONFESS "open('$filename'): $OS_ERROR";
61 30         1115 binmode $fh, $binmode;
62              
63 30         1530 return $fh;
64             } ## end sub __open_file
65              
66             sub get_fh {
67 31     31 1 49 my ($self, $handle) = @_;
68 31   33     97 $handle //= $self->output();
69              
70             # define a default releaser, but not for GLOBs as they have their own
71             # life outside of here
72             my $releaser = ref($handle) eq 'GLOB' ? undef : sub {
73 30 50   30   1258 CORE::close $_[0] or LOGCONFESS "close(): $OS_ERROR";
74 30         99 return undef;
75 31 100       307 };
76              
77             # if $handle is a factory, treat it as such
78 31 100       79 if (ref($handle) eq 'CODE') {
79 29         68 my @items = $handle->($self);
80 29         62 $handle = shift @items;
81              
82             # override the $releaser if and only if the factory instructed to
83             # do so. Otherwise, the default one will be kept.
84 29 50       96 $releaser = shift @items if @items;
85             } ## end if (ref($handle) eq 'CODE')
86              
87             # now, we either have a filehandle, or a filename
88 31 100       67 return ($handle, $releaser) if ref($handle) eq 'GLOB';
89 30         101 return (__open_file($handle, $self->binmode()), $releaser);
90             } ## end sub get_fh
91              
92             sub release_fh {
93 31     31 1 56 my ($self, $fh) = @_;
94 31         49 my $track = $self->track();
95 31 100       193 if (my $releaser = delete $track->{current_fh_releaser}) {
96 30         53 $releaser->($fh);
97             }
98 31         61 delete $track->{current_fh};
99 31         173 return undef;
100             } ## end sub release_fh
101              
102             sub close {
103 31     31 1 57 my ($self, $fh, $track) = @_;
104              
105             # do footer handling
106 31         61 $self->_print($fh, $self->footer(), $track);
107              
108             # call close, prepare $fh for other possible records
109 31         61 return $self->release_fh($fh);
110             } ## end sub close
111              
112             sub just_close {
113 18     18 1 23 my $self = shift;
114 18         52 my $track = $self->track();
115 18 100       247 my $fh = $track->{current_fh} or return;
116 7         19 $self->close($fh, $track);
117 7         69 return;
118             } ## end sub just_close
119              
120             sub print {
121 37     37 1 1549 my $self = shift;
122              
123 37   33     81 my $iterator = ref($_[0]) && $_[0];
124 37         71 my $checker = $self->checker();
125 37         146 my $track = $self->track();
126 37         124 my $fh = $track->{current_fh};
127 37         73 my $interlude = $self->interlude();
128              
129 37         118 while ('necessary') {
130 76 50       166 my $record = $iterator ? $iterator->() : shift(@_);
131 76 100       157 last unless defined $record;
132              
133             # get filehandle if needed
134 39   66     122 $fh ||= $self->open();
135              
136             # print interlude if we have previous records, increase count
137             $self->_print($fh, $interlude, $track)
138 39 100       79 if $track->{records};
139              
140             # print record
141 39         81 $self->_print($fh, $record, $track);
142              
143             # increment number of records, for next print
144 39         49 $track->{records}++;
145              
146             # do checks if activated
147 39 100 100     96 $fh = $self->close($fh, $track)
148             if $checker && (!$checker->($self));
149             } ## end while ('necessary')
150              
151 37         89 return;
152             } ## end sub print
153              
154             sub _print {
155 109     109   360 my ($self, $fh, $data, $track) = @_;
156 109 100       187 return unless defined $data;
157 91 50       141 $data = $data->($self) if ref $data;
158              
159             # do print data
160 91 50       182 ref($fh) or LOGCONFESS("$fh is not a reference");
161 91 50       106 print {$fh} $data or LOGCONFESS "print(): $OS_ERROR";
  91         266  
162              
163             # update trackers
164 91         147 my $new_chars = length($data);
165 91         116 $track->{chars_file} += $new_chars;
166 91         104 $track->{chars_total} += $new_chars;
167              
168 91         133 return $new_chars;
169             } ## end sub _print
170              
171             sub default_check {
172 28     28 1 44 my $self = shift;
173              
174 28 50       59 my $policy = $self->policy()
175             or return 1; # no policy, always fine
176 28         225 my $track = $self->track();
177 28 50       163 if (my $mr = $policy->{records_threshold}) {
178 28 100       120 return 0 if $track->{records} >= $mr;
179             }
180 4 50       9 if (my $cpf = $policy->{characters_threshold}) {
181 0 0       0 return 0 if $track->{chars_file} >= $cpf;
182             }
183 4         15 return 1;
184             } ## end sub default_check
185              
186             sub checker {
187 37     37 1 46 my $self = shift;
188              
189             # allow for overriding tout-court
190 37 50       144 if (my $method = $self->can('check')) {
191 0         0 return $method; # will eventually be called in the right way
192             }
193              
194             # if no policy is set, there's no reason to do checks
195 37 100       86 my $policy = $self->policy() or return;
196              
197             # at this point, let's use the default_check, whatever it is
198 27         229 return $self->can('default_check');
199             } ## end sub checker
200              
201 18     18   1143 sub DESTROY { shift->just_close() }
202              
203             sub writer {
204 0     0 1 0 my $package = shift;
205 0         0 my $self = $package->new(@_);
206 0     0   0 return sub { return $self->print(@_) };
  0         0  
207             }
208              
209             1;