File Coverage

blib/lib/App/PTP/Files.pm
Criterion Covered Total %
statement 71 87 81.6
branch 29 58 50.0
condition 7 7 100.0
subroutine 16 17 94.1
pod 0 11 0.0
total 123 180 68.3


line stmt bran cond sub pod time code
1             # This module provides functions to read the input files and to write the
2             # resulting output.
3              
4             package App::PTP::Files;
5              
6 15     15   246 use 5.022;
  15         51  
7 15     15   75 use strict;
  15         29  
  15         276  
8 15     15   72 use warnings;
  15         27  
  15         362  
9              
10 15     15   9840 use Data::Dumper;
  15         103925  
  15         1237  
11 15     15   131 use Exporter 'import';
  15         30  
  15         18085  
12              
13             # Every public function used by the main code is exported by default.
14             our @EXPORT =
15             qw(init_global_output close_global_output read_input write_output);
16             our @EXPORT_OK = qw(write_side_output read_side_input);
17              
18             # The reference to this variable is used in the input list to specify that the
19             # standard input should be read.
20             our $stdin_marker = '';
21              
22             # The reference to this variable is used in the input list to specify that all
23             # the inputs have been merged.
24             our $merged_marker = '';
25              
26             my $global_output_fh;
27              
28             # $stdout is where the default (non-debug) output of the program should go. It
29             # will always be STDOUT, except during the tests.
30             sub init_global_output {
31 118     118 0 259 my ($options, $stdout) = @_;
32 118 50       423 if ($options->{output}) {
    50          
33 0 0       0 if ($options->{debug_mode}) {
34 0         0 print "All output is going to: $options->{output}\n";
35             }
36 0 0       0 my $mode = $options->{append} ? '>>' : '>';
37             open ($global_output_fh, "${mode}:encoding($options->{output_encoding})",
38             $options->{output})
39 0 0       0 or die "Cannot open output file '$options->{output}': $!.\n";
40             } elsif (not $options->{in_place}) {
41 118 50       344 print "All output is going to STDOUT.\n" if $options->{debug_mode};
42 118         320 $global_output_fh = $stdout;
43             }
44             # We're setting the correct binmode for STDOUT here, because it can be used
45             # when in_place is true, but also if the --tee command is used.
46 118     12   1383 binmode($stdout, ":encoding($options->{output_encoding})");
  12         103  
  12         26  
  12         61  
47             }
48              
49             sub close_global_output {
50 116     116 0 214 my ($options) = @_;
51 116 50       426 if ($options->{output}) {
52 0 0       0 close $global_output_fh
53             or die "Cannot close output file '$options->{output}': $!.\n";
54             }
55             }
56              
57             # read_handle(handle)
58             # Reads the content of the given handle and returns an array ref containing two
59             # elements. The first one is an array-ref with all the lines of the file and the
60             # second one is a variable indicating if the last line of the file had a final
61             # separator.
62             # This method uses the value of the `$intput_separator` global option.
63             sub read_handle {
64 134     134 0 272 my ($handle, $options) = @_;
65 134         581 local $/ = undef; # enable slurp mode;
66 134         4934 my $content = <$handle>;
67 134 50       2122 if (not defined $content) {
68 0 0       0 if ($@) {
69 0         0 chomp($@);
70 0         0 die "FATAL: Cannot read input: $@\n";
71             }
72             # Theoretically this should not happen. But, on 5.22 this seems to happens
73             # if the input file is empty.
74 0         0 $content = '';
75             }
76 134         226 my @content;
77 134 100       356 if ($options->{preserve_eol}) {
78 1         32 @content = $content =~ /\G(.*?(?n:$options->{input_separator}))/gcms;
79             } else {
80 133         2041 @content = $content =~ /\G(.*?)(?n:$options->{input_separator})/gcms;
81             }
82 134         321 my $missing_final_separator = 0;
83 134 100 100     698 if ((pos($content) // 0) < length($content)) {
84 9         27 $missing_final_separator = 1;
85 9 50       36 print "The last line has no separator.\n" if $options->{debug_mode};
86 9   100     40 push @content, substr($content, pos($content) // 0);
87             }
88 134         958 return (\@content, $missing_final_separator);
89             }
90              
91             # read_file(path)
92             # Opens the given file, applies the correct read option, and calls read_handle.
93             sub read_file {
94 23     23 0 46 my ($path, $options) = @_;
95 23 50       103 print "Reading file: ${path}\n" if $options->{debug_mode};
96 23 50       844 open (my $fh, "<:encoding($options->{input_encoding})", $path)
97             or die "Cannot open file '$path': $!.\n";
98 23         1568 my @data = read_handle($fh, $options);
99 23 50       362 close($fh) or die "Cannot close the file '$path': $!.\n";
100 23         165 return @data;
101             }
102              
103             # read_stdin()
104             # Applies the correct read option to STDIN and calls read_handle(STDIN).
105             sub read_stdin {
106 111     111 0 215 my ($options, $stdin) = @_;
107 111 50       424 print "Reading STDIN\n" if $options->{debug_mode};
108 111         1293 binmode($stdin, ":encoding($options->{input_encoding})");
109 111         8604 return read_handle($stdin, $options);
110             }
111              
112             # read_input($input, $options, \*STDIN)
113             # Checks whether the input is the $stdin_marker or a file name and calls the
114             # matching method to read it, the third argument is the file-handle to read
115             # when $input is the $stdin_marker (usually STDIN except in tests).
116             sub read_input {
117 134     134 0 280 my ($input, $options, $stdin) = @_;
118 134 100       335 if (ref($input)) {
119 111 50       312 if ($input == \$stdin_marker) {
120 111         258 return read_stdin($options, $stdin);
121             } else {
122 0         0 die "Should not happen (".Dumper($input).")\n";
123             }
124             }
125 23         59 return read_file($input, $options);
126             }
127              
128             # write_content($handle, \@content, $missing_final_separator, \%options)
129             sub write_handle {
130 136     136 0 263 my ($handle, $content, $missing_final_separator, $options) = @_;
131 136 100       349 return unless @$content;
132 132         275 local $, = $options->{output_separator};
133             local $\ = $options->{output_separator}
134 132 100 100     922 if $options->{fix_final_separator} || !$missing_final_separator;
135 132         1145 print $handle @$content;
136             }
137              
138             # write_file($output_file_name, \@content, $missing_final_separator, append,
139             # \%options)
140             sub write_file {
141 6     6 0 13 my ($file_name, $content, $missing_final_separator, $append, $options) = @_;
142 6 100       13 my $m = $append ? '>>' : '>';
143 6 50       19 print "Outputing result to: ${m}${file_name}\n" if $options->{debug_mode};
144 6 50       331 open (my $out_fh, "${m}:encoding($options->{output_encoding})", $file_name)
145             or die "Cannot open output file '${file_name}': $!.\n";
146 6         453 write_handle($out_fh, $content, $missing_final_separator, $options);
147 6 50       603 close $out_fh or die "Cannot close output file '${file_name}': $!.\n";
148             }
149              
150             # write_file($input_file_name, \@content, $missing_final_separator, \%options)
151             sub write_output {
152 130     130 0 318 my ($file_name, $content, $missing_final_separator, $options) = @_;
153 130 50       352 if ($options->{in_place}) {
154 0         0 write_file($file_name, $content, $missing_final_separator, 0, $options);
155             } else {
156 130         315 write_handle($global_output_fh, $content, $missing_final_separator,
157             $options);
158             }
159             }
160              
161             # These two methodes are used by commands which read or write to side input/
162             # output files. The difference is that they expect a '-' in the given filename
163             # instead of the #stdin_marker, when referring to the standard input (or
164             # output).
165             my %known_side_output;
166             sub write_side_output {
167 6     6 0 14 my ($file_name, $content, $missing_final_separator, $options) = @_;
168 6 50       27 print "Outputing side result to: ${file_name}\n" if $options->{debug_mode};
169 6 50       16 if ($file_name eq '-') {
170 0         0 write_handle(\*STDOUT, $content, $missing_final_separator, $options);
171             } else {
172             write_file($file_name, $content, $missing_final_separator,
173 6         23 $known_side_output{$file_name}++, $options);
174             }
175             }
176              
177             # Returns (\@content, $missing_final_separator).
178             sub read_side_input {
179 0     0 0 0 my ($input, $options) = @_;
180 0 0       0 if ($input eq '-') {
181 0         0 return read_stdin($options);
182             } else {
183 0         0 return read_file($input, $options);
184             }
185             }
186              
187             1;