File Coverage

blib/lib/File/Process.pm
Criterion Covered Total %
statement 96 142 67.6
branch 40 52 76.9
condition 19 27 70.3
subroutine 15 24 62.5
pod 6 7 85.7
total 176 252 69.8


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