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 17     17   227 use 5.022;
  17         47  
7 17     17   66 use strict;
  17         25  
  17         242  
8 17     17   96 use warnings;
  17         33  
  17         460  
9              
10 17     17   9007 use Data::Dumper;
  17         98645  
  17         1318  
11 17     17   116 use Exporter 'import';
  17         29  
  17         17128  
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 129     129 0 257 my ($options, $stdout) = @_;
32 129 50       458 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 129 50       350 print "All output is going to STDOUT.\n" if $options->{debug_mode};
42 129         355 $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 129     14   1609 binmode($stdout, ":encoding($options->{output_encoding})");
  14         98  
  14         24  
  14         67  
47             }
48              
49             sub close_global_output {
50 127     127 0 212 my ($options) = @_;
51 127 50       448 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 145     145 0 289 my ($handle, $options) = @_;
65 145         553 local $/ = undef; # enable slurp mode;
66 145         4515 my $content = <$handle>;
67 145 50       1907 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 145         221 my @content;
77 145 100       348 if ($options->{preserve_eol}) {
78 1         31 @content = $content =~ /\G(.*?(?n:$options->{input_separator}))/gcms;
79             } else {
80 144         1994 @content = $content =~ /\G(.*?)(?n:$options->{input_separator})/gcms;
81             }
82 145         301 my $missing_final_separator = 0;
83 145 100 100     670 if ((pos($content) // 0) < length($content)) {
84 10         21 $missing_final_separator = 1;
85 10 50       36 print "The last line has no separator.\n" if $options->{debug_mode};
86 10   100     41 push @content, substr($content, pos($content) // 0);
87             }
88 145         890 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 24     24 0 46 my ($path, $options) = @_;
95 24 50       86 print "Reading file: ${path}\n" if $options->{debug_mode};
96 24 50       736 open (my $fh, "<:encoding($options->{input_encoding})", $path)
97             or die "Cannot open file '$path': $!.\n";
98 24         1367 my @data = read_handle($fh, $options);
99 24 50       294 close($fh) or die "Cannot close the file '$path': $!.\n";
100 24         168 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 121     121 0 251 my ($options, $stdin) = @_;
107 121 50       347 print "Reading STDIN\n" if $options->{debug_mode};
108 121         1317 binmode($stdin, ":encoding($options->{input_encoding})");
109 121         7814 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 145     145 0 281 my ($input, $options, $stdin) = @_;
118 145 100       345 if (ref($input)) {
119 121 50       322 if ($input == \$stdin_marker) {
120 121         284 return read_stdin($options, $stdin);
121             } else {
122 0         0 die "Should not happen (".Dumper($input).")\n";
123             }
124             }
125 24         53 return read_file($input, $options);
126             }
127              
128             # write_content($handle, \@content, $missing_final_separator, \%options)
129             sub write_handle {
130 153     153 0 359 my ($handle, $content, $missing_final_separator, $options) = @_;
131 153 100       371 return unless @$content;
132 148         361 local $, = $options->{output_separator};
133             local $\ = $options->{output_separator}
134 148 100 100     1146 if $options->{fix_final_separator} || !$missing_final_separator;
135 148         1374 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 11 my ($file_name, $content, $missing_final_separator, $append, $options) = @_;
142 6 100       12 my $m = $append ? '>>' : '>';
143 6 50       15 print "Outputing result to: ${m}${file_name}\n" if $options->{debug_mode};
144 6 50       294 open (my $out_fh, "${m}:encoding($options->{output_encoding})", $file_name)
145             or die "Cannot open output file '${file_name}': $!.\n";
146 6         362 write_handle($out_fh, $content, $missing_final_separator, $options);
147 6 50       462 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 141     141 0 302 my ($file_name, $content, $missing_final_separator, $options) = @_;
153 141 50       339 if ($options->{in_place}) {
154 0         0 write_file($file_name, $content, $missing_final_separator, 0, $options);
155             } else {
156 141         324 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       23 print "Outputing side result to: ${file_name}\n" if $options->{debug_mode};
169 6 50       11 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         20 $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;