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