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   100695 use strict;
  3         16  
  3         96  
3 3     3   14 use warnings;
  3         8  
  3         94  
4 3     3   607 use English qw< -no_match_vars >;
  3         3969  
  3         21  
5 3     3   1112 use 5.010;
  3         11  
6 3     3   17 use File::Path qw< make_path >;
  3         7  
  3         209  
7 3     3   19 use File::Basename qw< dirname >;
  3         5  
  3         310  
8             our $VERSION = '0.738';
9              
10 3     3   660 use Log::Log4perl::Tiny qw< :easy :dead_if_first >;
  3         16745  
  3         33  
11 3     3   2663 use Mo qw< default >;
  3         1560  
  3         17  
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 80 my ($self, $hint) = @_;
31              
32             # reset some tracking parameters
33 31         63 my $track = $self->track();
34 31         172 $track->{files}++;
35 31         52 $track->{records} = 0;
36 31         52 $track->{chars_file} = 0;
37              
38             # get new filehandle
39             my ($fh, $fh_releaser) =
40 31         72 @{$track}{qw< current_fh current_fh_releaser>} = $self->get_fh($hint);
  31         96  
41              
42             # do header handling
43 31         99 $self->_print($fh, $self->header(), $track);
44              
45 31         114 return $fh;
46             } ## end sub open
47              
48             sub __open_file {
49 30     30   222 my ($filename, $binmode) = @_;
50              
51             # ensure its directory exists
52 30         2455 make_path(dirname($filename), {error => \my $errors});
53 30 50       167 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   1769 CORE::open my $fh, '>', $filename
  1         8  
  1         2  
  1         8  
60             or LOGCONFESS "open('$filename'): $OS_ERROR";
61 30         1414 binmode $fh, $binmode;
62              
63 30         1758 return $fh;
64             } ## end sub __open_file
65              
66             sub get_fh {
67 31     31 1 47 my ($self, $handle) = @_;
68 31   33     112 $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   1447 CORE::close $_[0] or LOGCONFESS "close(): $OS_ERROR";
74 30         99 return undef;
75 31 100       368 };
76              
77             # if $handle is a factory, treat it as such
78 31 100       84 if (ref($handle) eq 'CODE') {
79 29         75 my @items = $handle->($self);
80 29         67 $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       74 $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       89 return ($handle, $releaser) if ref($handle) eq 'GLOB';
89 30         84 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         60 my $track = $self->track();
95 31 100       200 if (my $releaser = delete $track->{current_fh_releaser}) {
96 30         63 $releaser->($fh);
97             }
98 31         71 delete $track->{current_fh};
99 31         190 return undef;
100             } ## end sub release_fh
101              
102             sub close {
103 31     31 1 63 my ($self, $fh, $track) = @_;
104              
105             # do footer handling
106 31         75 $self->_print($fh, $self->footer(), $track);
107              
108             # call close, prepare $fh for other possible records
109 31         73 return $self->release_fh($fh);
110             } ## end sub close
111              
112             sub just_close {
113 18     18 1 26 my $self = shift;
114 18         42 my $track = $self->track();
115 18 100       272 my $fh = $track->{current_fh} or return;
116 7         19 $self->close($fh, $track);
117 7         75 return;
118             } ## end sub just_close
119              
120             sub print {
121 37     37 1 1623 my $self = shift;
122              
123 37   33     92 my $iterator = ref($_[0]) && $_[0];
124 37         79 my $checker = $self->checker();
125 37         173 my $track = $self->track();
126 37         129 my $fh = $track->{current_fh};
127 37         82 my $interlude = $self->interlude();
128              
129 37         130 while ('necessary') {
130 76 50       180 my $record = $iterator ? $iterator->() : shift(@_);
131 76 100       163 last unless defined $record;
132              
133             # get filehandle if needed
134 39   66     125 $fh ||= $self->open();
135              
136             # print interlude if we have previous records, increase count
137             $self->_print($fh, $interlude, $track)
138 39 100       92 if $track->{records};
139              
140             # print record
141 39         89 $self->_print($fh, $record, $track);
142              
143             # increment number of records, for next print
144 39         61 $track->{records}++;
145              
146             # do checks if activated
147 39 100 100     108 $fh = $self->close($fh, $track)
148             if $checker && (!$checker->($self));
149             } ## end while ('necessary')
150              
151 37         102 return;
152             } ## end sub print
153              
154             sub _print {
155 109     109   372 my ($self, $fh, $data, $track) = @_;
156 109 100       224 return unless defined $data;
157 91 50       167 $data = $data->($self) if ref $data;
158              
159             # do print data
160 91 50       168 ref($fh) or LOGCONFESS("$fh is not a reference");
161 91 50       134 print {$fh} $data or LOGCONFESS "print(): $OS_ERROR";
  91         322  
162              
163             # update trackers
164 91         144 my $new_chars = length($data);
165 91         142 $track->{chars_file} += $new_chars;
166 91         121 $track->{chars_total} += $new_chars;
167              
168 91         139 return $new_chars;
169             } ## end sub _print
170              
171             sub default_check {
172 28     28 1 63 my $self = shift;
173              
174 28 50       62 my $policy = $self->policy()
175             or return 1; # no policy, always fine
176 28         208 my $track = $self->track();
177 28 50       204 if (my $mr = $policy->{records_threshold}) {
178 28 100       131 return 0 if $track->{records} >= $mr;
179             }
180 4 50       10 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 56 my $self = shift;
188              
189             # allow for overriding tout-court
190 37 50       157 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       92 my $policy = $self->policy() or return;
196              
197             # at this point, let's use the default_check, whatever it is
198 27         240 return $self->can('default_check');
199             } ## end sub checker
200              
201 18     18   1044 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;