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 20     20   348 use 5.022;
  20         65  
7 20     20   92 use strict;
  20         45  
  20         376  
8 20     20   93 use warnings;
  20         37  
  20         615  
9              
10 20     20   13129 use Data::Dumper;
  20         140648  
  20         1533  
11 20     20   167 use Exporter 'import';
  20         48  
  20         24414  
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 138     138 0 655 my ($options, $stdout) = @_;
32 138 50       506 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 138 50       501 print "All output is going to STDOUT.\n" if $options->{debug_mode};
42 138         398 $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 138     17   1821 binmode($stdout, ":encoding($options->{output_encoding})");
  17         133  
  17         32  
  17         101  
47             }
48              
49             sub close_global_output {
50 136     136 0 279 my ($options) = @_;
51 136 50       569 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 155     155 0 322 my ($handle, $options) = @_;
65 155         703 local $/ = undef; # enable slurp mode;
66 155         5265 my $content = <$handle>;
67 155 50       2298 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 155         249 my @content;
77 155 100       421 if ($options->{preserve_eol}) {
78 1         37 @content = $content =~ /\G(.*?(?n:$options->{input_separator}))/gcms;
79             } else {
80 154         2470 @content = $content =~ /\G(.*?)(?n:$options->{input_separator})/gcms;
81             }
82 155         383 my $missing_final_separator = 0;
83 155 100 100     809 if ((pos($content) // 0) < length($content)) {
84 10         21 $missing_final_separator = 1;
85 10 50       43 print "The last line has no separator.\n" if $options->{debug_mode};
86 10   100     42 push @content, substr($content, pos($content) // 0);
87             }
88 155         1083 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 57 my ($path, $options) = @_;
95 26 50       113 print "Reading file: ${path}\n" if $options->{debug_mode};
96 26 50       945 open (my $fh, "<:encoding($options->{input_encoding})", $path)
97             or die "Cannot open file '$path': $!.\n";
98 26         1807 my @data = read_handle($fh, $options);
99 26 50       426 close($fh) or die "Cannot close the file '$path': $!.\n";
100 26         202 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 129     129 0 254 my ($options, $stdin) = @_;
107 129 50       443 print "Reading STDIN\n" if $options->{debug_mode};
108 129         1360 binmode($stdin, ":encoding($options->{input_encoding})");
109 129         9898 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 155     155 0 334 my ($input, $options, $stdin) = @_;
118 155 100       402 if (ref($input)) {
119 129 50       356 if ($input == \$stdin_marker) {
120 129         316 return read_stdin($options, $stdin);
121             } else {
122 0         0 die "Should not happen (".Dumper($input).")\n";
123             }
124             }
125 26         110 return read_file($input, $options);
126             }
127              
128             # write_content($handle, \@content, $missing_final_separator, \%options)
129             sub write_handle {
130 162     162 0 466 my ($handle, $content, $missing_final_separator, $options) = @_;
131 162 100       420 return unless @$content;
132 156         410 local $, = $options->{output_separator};
133             local $\ = $options->{output_separator}
134 156 100 100     1387 if $options->{fix_final_separator} || !$missing_final_separator;
135 156         1670 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 14 my ($file_name, $content, $missing_final_separator, $append, $options) = @_;
142 6 100       11 my $m = $append ? '>>' : '>';
143 6 50       20 print "Outputing result to: ${m}${file_name}\n" if $options->{debug_mode};
144 6 50       321 open (my $out_fh, "${m}:encoding($options->{output_encoding})", $file_name)
145             or die "Cannot open output file '${file_name}': $!.\n";
146 6         432 write_handle($out_fh, $content, $missing_final_separator, $options);
147 6 50       549 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 150     150 0 365 my ($file_name, $content, $missing_final_separator, $options) = @_;
153 150 50       419 if ($options->{in_place}) {
154 0         0 write_file($file_name, $content, $missing_final_separator, 0, $options);
155             } else {
156 150         398 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 15 my ($file_name, $content, $missing_final_separator, $options) = @_;
168 6 50       26 print "Outputing side result to: ${file_name}\n" if $options->{debug_mode};
169 6 50       15 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         21 $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;