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 19     19   275 use 5.022;
  19         56  
7 19     19   78 use strict;
  19         38  
  19         319  
8 19     19   74 use warnings;
  19         32  
  19         529  
9              
10 19     19   10540 use Data::Dumper;
  19         117049  
  19         1379  
11 19     19   134 use Exporter 'import';
  19         29  
  19         20299  
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 write_handle);
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 134     134 0 292 my ($options, $stdout) = @_;
32 134 50       468 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 134 50       393 print "All output is going to STDOUT.\n" if $options->{debug_mode};
42 134         379 $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 134     16   1700 binmode($stdout, ":encoding($options->{output_encoding})");
  16         114  
  16         32  
  16         81  
47             }
48              
49             sub close_global_output {
50 132     132 0 300 my ($options) = @_;
51 132 50       490 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 151     151 0 344 my ($handle, $options) = @_;
65 151         600 local $/ = undef; # enable slurp mode;
66 151         4905 my $content = <$handle>;
67 151 50       2093 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 151         226 my @content;
77 151 100       396 if ($options->{preserve_eol}) {
78 1         33 @content = $content =~ /\G(.*?(?n:$options->{input_separator}))/gcms;
79             } else {
80 150         2232 @content = $content =~ /\G(.*?)(?n:$options->{input_separator})/gcms;
81             }
82 151         361 my $missing_final_separator = 0;
83 151 100 100     757 if ((pos($content) // 0) < length($content)) {
84 10         19 $missing_final_separator = 1;
85 10 50       35 print "The last line has no separator.\n" if $options->{debug_mode};
86 10   100     37 push @content, substr($content, pos($content) // 0);
87             }
88 151         954 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 26     26 0 52 my ($path, $options) = @_;
95 26 50       109 print "Reading file: ${path}\n" if $options->{debug_mode};
96 26 50       888 open (my $fh, "<:encoding($options->{input_encoding})", $path)
97             or die "Cannot open file '$path': $!.\n";
98 26         1631 my @data = read_handle($fh, $options);
99 26 50       361 close($fh) or die "Cannot close the file '$path': $!.\n";
100 26         186 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 125     125 0 246 my ($options, $stdin) = @_;
107 125 50       403 print "Reading STDIN\n" if $options->{debug_mode};
108 125         1320 binmode($stdin, ":encoding($options->{input_encoding})");
109 125         8140 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 151     151 0 324 my ($input, $options, $stdin) = @_;
118 151 100       364 if (ref($input)) {
119 125 50       328 if ($input == \$stdin_marker) {
120 125         312 return read_stdin($options, $stdin);
121             } else {
122 0         0 die "Should not happen (".Dumper($input).")\n";
123             }
124             }
125 26         72 return read_file($input, $options);
126             }
127              
128             # write_content($handle, \@content, $missing_final_separator, \%options)
129             sub write_handle {
130 158     158 0 433 my ($handle, $content, $missing_final_separator, $options) = @_;
131 158 100       430 return unless @$content;
132 152         385 local $, = $options->{output_separator};
133             local $\ = $options->{output_separator}
134 152 100 100     1237 if $options->{fix_final_separator} || !$missing_final_separator;
135 152         1645 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 12 my ($file_name, $content, $missing_final_separator, $append, $options) = @_;
142 6 100       11 my $m = $append ? '>>' : '>';
143 6 50       16 print "Outputing result to: ${m}${file_name}\n" if $options->{debug_mode};
144 6 50       254 open (my $out_fh, "${m}:encoding($options->{output_encoding})", $file_name)
145             or die "Cannot open output file '${file_name}': $!.\n";
146 6         347 write_handle($out_fh, $content, $missing_final_separator, $options);
147 6 50       433 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 146     146 0 339 my ($file_name, $content, $missing_final_separator, $options) = @_;
153 146 50       366 if ($options->{in_place}) {
154 0         0 write_file($file_name, $content, $missing_final_separator, 0, $options);
155             } else {
156 146         372 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 11 my ($file_name, $content, $missing_final_separator, $options) = @_;
168 6 50       32 print "Outputing side result to: ${file_name}\n" if $options->{debug_mode};
169 6 50       14 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         17 $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;