File Coverage

blib/lib/File/Process.pm
Criterion Covered Total %
statement 104 150 69.3
branch 39 52 75.0
condition 18 24 75.0
subroutine 18 27 66.6
pod 7 8 87.5
total 186 261 71.2


line stmt bran cond sub pod time code
1             package File::Process;
2              
3 5     5   4239 use strict;
  5         26  
  5         149  
4 5     5   24 use warnings;
  5         8  
  5         120  
5              
6 5     5   22 use Carp;
  5         14  
  5         352  
7 5     5   2716 use English qw(-no_match_vars);
  5         7738  
  5         27  
8 5     5   4271 use File::Process::Utils qw(:booleans :chars);
  5         14  
  5         1011  
9 5     5   2456 use IO::Scalar;
  5         20589  
  5         224  
10 5     5   36 use Scalar::Util qw( openhandle );
  5         9  
  5         232  
11 5     5   29 use Data::Dumper;
  5         22  
  5         277  
12              
13 5     5   31 use parent qw( Exporter );
  5         10  
  5         27  
14              
15             our $VERSION = '0.11';
16              
17             our %DEFAULT_PROCESSORS = (
18             pre => \&_pre,
19             next_line => \&_next_line,
20             filter => \&_filter,
21             process => \&_process,
22             post => \&_post,
23             );
24              
25 5     5   597 use parent qw(Exporter);
  5         28  
  5         18  
26              
27             our @EXPORT = qw( process_file ); ## no critic (ProhibitAutomaticExportation)
28              
29             our @EXPORT_OK = qw(
30             post
31             pre
32             process
33             process_csv
34             filter
35             next_line
36             );
37              
38             our %EXPORT_TAGS = (
39             'booleans' => [qw($TRUE $FALSE $SUCCESS $FAILURE)],
40             'all' => \@EXPORT_OK,
41             );
42              
43             caller or __PACKAGE__->main();
44              
45             ########################################################################
46             sub process_csv {
47             ########################################################################
48 9     9 1 26012 require File::Process::Utils;
49              
50 9         31 return File::Process::Utils::process_csv(@_);
51             }
52              
53             ########################################################################
54             sub process_file {
55             ########################################################################
56 18     18 1 17147 my ( $file, %args ) = @_;
57              
58 18         37 my $chomp = $args{'chomp'};
59              
60 18   33     63 $args{'file'} = $file || $EMPTY;
61              
62             my %processors
63 18         40 = map { ( $_, $args{$_} ) } qw( pre filter next_line process post );
  90         185  
64              
65 18         44 foreach (qw( pre filter next_line process post)) {
66 90 100       167 if ( !$processors{$_} ) {
67 68         116 $processors{$_} = $DEFAULT_PROCESSORS{$_};
68             }
69             }
70              
71 18         40 $args{'default_processors'} = \%DEFAULT_PROCESSORS;
72              
73 18         47 my ( $fh, $all_lines ) = $processors{'pre'}->( $file, \%args );
74              
75 18 50 33     109 if ( !$fh || !ref $all_lines ) {
76 0         0 croak
77             "invalid pre processor return values: wanted file handle, array ref\n";
78             }
79              
80 18         31 LINE: while (1) {
81 144         364 my $current_line = $processors{'next_line'}->( $fh, $all_lines, \%args );
82 144 100       461 last LINE if !defined $current_line;
83              
84 126         174 $args{'raw_count'}++;
85              
86 126         246 foreach my $p ( @processors{qw( filter process )} ) {
87              
88             $current_line
89 250         330 = eval { return $p->( $fh, $all_lines, \%args, $current_line ); };
  250         448  
90              
91 250 50       556 last LINE if $EVAL_ERROR;
92              
93 250 100       479 next LINE if !defined $current_line;
94             }
95              
96 124 100       207 if ( $args{merge_lines} ) {
97 4         10 $all_lines->print($current_line);
98             }
99             else {
100 120         170 push @{$all_lines}, $current_line;
  120         238  
101             }
102             }
103              
104 18 50       52 if ($EVAL_ERROR) {
105 0         0 croak "$EVAL_ERROR";
106             }
107              
108 18         56 return $processors{'post'}->( $fh, $all_lines, \%args );
109             }
110              
111             ########################################################################
112             sub _pre {
113             ########################################################################
114 18     18   39 my ( $file, $args ) = @_;
115              
116 18         28 my $fh;
117              
118 18 50       74 if ( openhandle $file ) {
119 18         33 $fh = $file;
120              
121 18         46 $args->{file} = ref $fh; # GLOB
122             }
123             else {
124 0 0       0 open $fh, '<', $file ## no critic (RequireBriefOpen)
125             or croak 'could not open ' . $file . $NL;
126              
127 0         0 $args->{'file'} = $file;
128             }
129              
130 18         35 $args->{'raw_count'} = 0;
131 18         25 $args->{'skipped'} = 0;
132 18         39 $args->{'start_time'} = time;
133              
134 18 100       50 my $lines = $args->{merge_lines} ? IO::Scalar->new : [];
135              
136 18         156 return ( $fh, $lines );
137             }
138              
139             ########################################################################
140             sub _next_line {
141             ########################################################################
142 52     52   100 my ( $fh, $all_lines, $args ) = @_;
143              
144 52         58 my $current_line;
145              
146 52 50       115 if ( openhandle $fh ) {
147 52 100       249 if ( !eof $fh ) {
148 44 50       119 defined( $current_line = readline $fh )
149             or croak "readline failed: $OS_ERROR\n";
150             }
151             }
152              
153 52         140 return $current_line;
154             }
155              
156             ########################################################################
157             sub _filter {
158             ########################################################################
159 126     126   300 my ( $fh, $all_lines, $args, $current_line ) = @_;
160              
161 126 50       258 if ( $args->{'chomp'} ) {
162 126         239 chomp $current_line;
163             }
164              
165 126 100 100     347 if ( $args->{'trim'} && $args->{'trim'} =~ /(front|both)/xsm ) {
166 12         30 $current_line =~ s/^\s+//xsm;
167             }
168              
169 126 100 100     291 if ( $args->{'trim'} && $args->{'trim'} =~ /(both|back)/xsm ) {
170 12         29 $current_line =~ s/\s+$//xsm;
171             }
172              
173             # skip?
174 126         165 my $skip = $FALSE;
175              
176 126 100 100     383 if ( $args->{'skip_blank_lines'} || $args->{'skip_comments'} ) {
177              
178 12 100 100     34 if ( $args->{'skip_blank_lines'} && "$current_line" eq $EMPTY ) {
179 1         2 $skip = $TRUE;
180             }
181              
182             # if we're not chomping, then consider new line a blank line?
183 12 50 33     23 if ( !$args->{chomp} && "$current_line" eq $NL ) {
184 0         0 $skip = $TRUE;
185             }
186              
187 12 100 100     76 if ( $args->{'skip_comments'} && $current_line =~ /^\#/xsm ) {
188 1         3 $skip = $TRUE;
189             }
190             }
191              
192 126 100       243 $args->{skipped} = $args->{skipped} + $skip ? 1 : 0;
193              
194 126 100       312 return $skip ? undef : $current_line;
195             }
196              
197             ########################################################################
198             sub _process {
199             ########################################################################
200 115     115   256 my ( $fh, $all_lines, $args, $current_line ) = @_;
201              
202 115         209 return $current_line;
203             }
204              
205             ########################################################################
206             sub _post {
207             ########################################################################
208 17     17   60 my ( $fh, $all_lines, $args ) = @_;
209              
210 17         44 $args->{end_time} = time;
211              
212 17         48 my $retval;
213              
214 17 100       33 if ( $args->{merge_lines} ) {
215 1         2 $retval = ${ $all_lines->sref };
  1         3  
216             }
217             else {
218 16         26 $retval = $all_lines;
219             }
220              
221 17 100       43 if ( !$args->{'keep_open'} ) {
222             close $fh
223 2 50       31 or croak 'could not close' . $args->{file} . $NL;
224             }
225              
226 17         27 return ( $retval, %{$args} );
  17         184  
227             }
228              
229             ########################################################################
230             sub post { ## no critic [Subroutines::RequireArgUnpacking]
231             ########################################################################
232 0     0 1 0 return $_[2]->{default_processors}->{post}->(@_);
233             }
234              
235             ########################################################################
236             sub filter { ## no critic [Subroutines::RequireArgUnpacking]
237             ########################################################################
238 0     0 1 0 return $_[2]->{default_processors}->{filter}->(@_);
239             }
240              
241             ########################################################################
242             sub pre { ## no critic [Subroutines::RequireArgUnpacking]
243             ########################################################################
244 10     10 1 145 return $_[1]->{default_processors}->{pre}->(@_);
245             }
246              
247             ########################################################################
248             sub process { ## no critic [Subroutines::RequireArgUnpacking]
249             ########################################################################
250 0     0 1   return $_[2]->{default_processors}->{process}->(@_);
251             }
252              
253             ########################################################################
254             sub next_line { ## no critic [Subroutines::RequireArgUnpacking]
255             ########################################################################
256 0     0 1   return $_[2]->{default_processors}->{next_line}->(@_);
257             }
258              
259             ########################################################################
260             sub main {
261             ########################################################################
262 0     0 0   require IO::Scalar;
263 0           require Data::Dumper;
264 0           require JSON::PP;
265 0           require Text::CSV_XS;
266              
267 0           JSON::PP->import('decode_json');
268              
269 0           Data::Dumper->import('Dumper');
270              
271             # +------------------+
272             # | READ A TEXT FILE |
273             # +------------------+
274              
275 0           my $buffer = <<'END_OF_TEXT';
276             line 1
277             line 2
278            
279             line 4
280              
281             line 5
282             END_OF_TEXT
283              
284 0           my $fh = IO::Scalar->new( \$buffer );
285              
286 0           print Dumper(
287             process_file(
288             $fh,
289             skip_blank_lines => $TRUE,
290             chomp => $TRUE,
291             trim => 'both'
292             )
293             );
294              
295 0           $fh = IO::Scalar->new( \$buffer );
296             print Dumper(
297             process_file(
298             $fh,
299             post => sub {
300 0     0     my @retval = post(@_);
301 0           $retval[0] = join $EMPTY, @{ $_[1] };
  0            
302 0           return @retval;
303             }
304             )
305 0           );
306              
307             # +------------------+
308             # | READ A JSON FILE |
309             # +------------------+
310              
311 0           my $json_text = <<'END_OF_TEXT';
312             {
313             "foo" : "bar",
314             "baz" : "buz"
315             }
316              
317             END_OF_TEXT
318              
319 0           $fh = IO::Scalar->new( \$json_text );
320              
321             print Dumper(
322             process_file(
323             $fh,
324             chomp => 1,
325             post => sub {
326 0     0     post(@_);
327 0           return decode_json( join $EMPTY, @{ $_[1] } );
  0            
328             }
329             )
330 0           );
331              
332 0           $fh = IO::Scalar->new( \$json_text );
333              
334 0           print Dumper(
335             decode_json( process_file( $fh, merge_lines => 1, chomp => 1 ) ) );
336              
337             # +-----------------+
338             # | READ A CSV FILE |
339             # +-----------------+
340              
341 0           my $csv_text = <<'END_OF_TEXT';
342             "id","first_name","last_name"
343             0,"Rob","Lauer"
344             END_OF_TEXT
345              
346 0           $fh = IO::Scalar->new( \$csv_text );
347              
348 0           my $csv = Text::CSV_XS->new;
349              
350             my ($csv_lines) = process_file(
351             $fh,
352             csv => $csv,
353             chomp => 1,
354             has_headers => 1,
355             pre => sub {
356 0     0     my ( $csv_fh, $args ) = @_;
357              
358 0 0         if ( $args->{'has_headers'} ) {
359 0           my @column_names = $args->{csv}->getline($csv_fh);
360 0           $args->{csv}->column_names(@column_names);
361             }
362              
363 0           return ( pre( $fh, $args ) );
364             },
365             next_line => sub {
366 0     0     my ( $csv_fh, $all_lines, $args ) = @_;
367 0           my $ref = $args->{csv}->getline_hr($csv_fh);
368 0           return $ref;
369             }
370 0           );
371              
372 0           print Dumper($csv_lines);
373              
374 0           exit 0;
375             }
376              
377             1;
378              
379             __END__