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   265 use 5.022;
  17         55  
7 17     17   81 use strict;
  17         41  
  17         296  
8 17     17   72 use warnings;
  17         25  
  17         479  
9              
10 17     17   10770 use Data::Dumper;
  17         114467  
  17         1223  
11 17     17   128 use Exporter 'import';
  17         31  
  17         20070  
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 128     128 0 891 my ($options, $stdout) = @_;
32 128 50       454 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 128 50       376 print "All output is going to STDOUT.\n" if $options->{debug_mode};
42 128         356 $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 128     14   1549 binmode($stdout, ":encoding($options->{output_encoding})");
  14         102  
  14         28  
  14         67  
47             }
48              
49             sub close_global_output {
50 126     126 0 237 my ($options) = @_;
51 126 50       472 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 144     144 0 330 my ($handle, $options) = @_;
65 144         647 local $/ = undef; # enable slurp mode;
66 144         4931 my $content = <$handle>;
67 144 50       2184 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 144         246 my @content;
77 144 100       354 if ($options->{preserve_eol}) {
78 1         33 @content = $content =~ /\G(.*?(?n:$options->{input_separator}))/gcms;
79             } else {
80 143         2237 @content = $content =~ /\G(.*?)(?n:$options->{input_separator})/gcms;
81             }
82 144         346 my $missing_final_separator = 0;
83 144 100 100     711 if ((pos($content) // 0) < length($content)) {
84 10         17 $missing_final_separator = 1;
85 10 50       40 print "The last line has no separator.\n" if $options->{debug_mode};
86 10   100     43 push @content, substr($content, pos($content) // 0);
87             }
88 144         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 23     23 0 48 my ($path, $options) = @_;
95 23 50       88 print "Reading file: ${path}\n" if $options->{debug_mode};
96 23 50       851 open (my $fh, "<:encoding($options->{input_encoding})", $path)
97             or die "Cannot open file '$path': $!.\n";
98 23         1698 my @data = read_handle($fh, $options);
99 23 50       378 close($fh) or die "Cannot close the file '$path': $!.\n";
100 23         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 220 my ($options, $stdin) = @_;
107 121 50       386 print "Reading STDIN\n" if $options->{debug_mode};
108 121         1353 binmode($stdin, ":encoding($options->{input_encoding})");
109 121         8806 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 144     144 0 298 my ($input, $options, $stdin) = @_;
118 144 100       338 if (ref($input)) {
119 121 50       337 if ($input == \$stdin_marker) {
120 121         279 return read_stdin($options, $stdin);
121             } else {
122 0         0 die "Should not happen (".Dumper($input).")\n";
123             }
124             }
125 23         61 return read_file($input, $options);
126             }
127              
128             # write_content($handle, \@content, $missing_final_separator, \%options)
129             sub write_handle {
130 152     152 0 416 my ($handle, $content, $missing_final_separator, $options) = @_;
131 152 100       392 return unless @$content;
132 147         426 local $, = $options->{output_separator};
133             local $\ = $options->{output_separator}
134 147 100 100     1219 if $options->{fix_final_separator} || !$missing_final_separator;
135 147         1571 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 18 my ($file_name, $content, $missing_final_separator, $append, $options) = @_;
142 6 100       14 my $m = $append ? '>>' : '>';
143 6 50       23 print "Outputing result to: ${m}${file_name}\n" if $options->{debug_mode};
144 6 50       441 open (my $out_fh, "${m}:encoding($options->{output_encoding})", $file_name)
145             or die "Cannot open output file '${file_name}': $!.\n";
146 6         492 write_handle($out_fh, $content, $missing_final_separator, $options);
147 6 50       648 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 140     140 0 349 my ($file_name, $content, $missing_final_separator, $options) = @_;
153 140 50       407 if ($options->{in_place}) {
154 0         0 write_file($file_name, $content, $missing_final_separator, 0, $options);
155             } else {
156 140         348 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       30 print "Outputing side result to: ${file_name}\n" if $options->{debug_mode};
169 6 50       17 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         27 $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;