| 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 |  | 214 | use 5.022; | 
|  | 15 |  |  |  |  | 40 |  | 
| 7 | 15 |  |  | 15 |  | 57 | use strict; | 
|  | 15 |  |  |  |  | 24 |  | 
|  | 15 |  |  |  |  | 246 |  | 
| 8 | 15 |  |  | 15 |  | 69 | use warnings; | 
|  | 15 |  |  |  |  | 23 |  | 
|  | 15 |  |  |  |  | 338 |  | 
| 9 |  |  |  |  |  |  |  | 
| 10 | 15 |  |  | 15 |  | 8241 | use Data::Dumper; | 
|  | 15 |  |  |  |  | 91382 |  | 
|  | 15 |  |  |  |  | 941 |  | 
| 11 | 15 |  |  | 15 |  | 98 | use Exporter 'import'; | 
|  | 15 |  |  |  |  | 27 |  | 
|  | 15 |  |  |  |  | 15908 |  | 
| 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 | 120 |  |  | 120 | 0 | 210 | my ($options, $stdout) = @_; | 
| 32 | 120 | 50 |  |  |  | 372 | 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 | 120 | 50 |  |  |  | 289 | print "All output is going to STDOUT.\n" if $options->{debug_mode}; | 
| 42 | 120 |  |  |  |  | 286 | $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 | 120 |  |  | 12 |  | 1192 | binmode($stdout, ":encoding($options->{output_encoding})"); | 
|  | 12 |  |  |  |  | 78 |  | 
|  | 12 |  |  |  |  | 22 |  | 
|  | 12 |  |  |  |  | 51 |  | 
| 47 |  |  |  |  |  |  | } | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | sub close_global_output { | 
| 50 | 118 |  |  | 118 | 0 | 181 | my ($options) = @_; | 
| 51 | 118 | 50 |  |  |  | 352 | 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 | 136 |  |  | 136 | 0 | 225 | my ($handle, $options) = @_; | 
| 65 | 136 |  |  |  |  | 484 | local $/ = undef; # enable slurp mode; | 
| 66 | 136 |  |  |  |  | 3887 | my $content = <$handle>; | 
| 67 | 136 | 50 |  |  |  | 1713 | 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 | 136 |  |  |  |  | 202 | my @content; | 
| 77 | 136 | 100 |  |  |  | 316 | if ($options->{preserve_eol}) { | 
| 78 | 1 |  |  |  |  | 36 | @content = $content =~ /\G(.*?(?n:$options->{input_separator}))/gcms; | 
| 79 |  |  |  |  |  |  | } else { | 
| 80 | 135 |  |  |  |  | 1675 | @content = $content =~ /\G(.*?)(?n:$options->{input_separator})/gcms; | 
| 81 |  |  |  |  |  |  | } | 
| 82 | 136 |  |  |  |  | 273 | my $missing_final_separator = 0; | 
| 83 | 136 | 100 | 100 |  |  | 553 | if ((pos($content) // 0) < length($content)) { | 
| 84 | 9 |  |  |  |  | 17 | $missing_final_separator = 1; | 
| 85 | 9 | 50 |  |  |  | 30 | print "The last line has no separator.\n" if $options->{debug_mode}; | 
| 86 | 9 |  | 100 |  |  | 33 | push @content, substr($content, pos($content) // 0); | 
| 87 |  |  |  |  |  |  | } | 
| 88 | 136 |  |  |  |  | 742 | 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 | 40 | my ($path, $options) = @_; | 
| 95 | 23 | 50 |  |  |  | 75 | print "Reading file: ${path}\n" if $options->{debug_mode}; | 
| 96 | 23 | 50 |  |  |  | 713 | open (my $fh, "<:encoding($options->{input_encoding})", $path) | 
| 97 |  |  |  |  |  |  | or die "Cannot open file '$path': $!.\n"; | 
| 98 | 23 |  |  |  |  | 1307 | my @data = read_handle($fh, $options); | 
| 99 | 23 | 50 |  |  |  | 301 | close($fh) or die "Cannot close the file '$path': $!.\n"; | 
| 100 | 23 |  |  |  |  | 143 | 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 | 113 |  |  | 113 | 0 | 179 | my ($options, $stdin) = @_; | 
| 107 | 113 | 50 |  |  |  | 295 | print "Reading STDIN\n" if $options->{debug_mode}; | 
| 108 | 113 |  |  |  |  | 1047 | binmode($stdin, ":encoding($options->{input_encoding})"); | 
| 109 | 113 |  |  |  |  | 7743 | 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 | 136 |  |  | 136 | 0 | 237 | my ($input, $options, $stdin) = @_; | 
| 118 | 136 | 100 |  |  |  | 285 | if (ref($input)) { | 
| 119 | 113 | 50 |  |  |  | 253 | if ($input == \$stdin_marker) { | 
| 120 | 113 |  |  |  |  | 223 | return read_stdin($options, $stdin); | 
| 121 |  |  |  |  |  |  | } else { | 
| 122 | 0 |  |  |  |  | 0 | die "Should not happen (".Dumper($input).")\n"; | 
| 123 |  |  |  |  |  |  | } | 
| 124 |  |  |  |  |  |  | } | 
| 125 | 23 |  |  |  |  | 57 | return read_file($input, $options); | 
| 126 |  |  |  |  |  |  | } | 
| 127 |  |  |  |  |  |  |  | 
| 128 |  |  |  |  |  |  | # write_content($handle, \@content, $missing_final_separator, \%options) | 
| 129 |  |  |  |  |  |  | sub write_handle { | 
| 130 | 138 |  |  | 138 | 0 | 229 | my ($handle, $content, $missing_final_separator, $options) = @_; | 
| 131 | 138 | 100 |  |  |  | 268 | return unless @$content; | 
| 132 | 134 |  |  |  |  | 241 | local $, = $options->{output_separator}; | 
| 133 |  |  |  |  |  |  | local $\ = $options->{output_separator} | 
| 134 | 134 | 100 | 100 |  |  | 771 | if $options->{fix_final_separator} || !$missing_final_separator; | 
| 135 | 134 |  |  |  |  | 971 | 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 |  |  |  | 15 | my $m = $append ? '>>' : '>'; | 
| 143 | 6 | 50 |  |  |  | 20 | print "Outputing result to: ${m}${file_name}\n" if $options->{debug_mode}; | 
| 144 | 6 | 50 |  |  |  | 322 | 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 |  |  |  | 586 | 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 | 132 |  |  | 132 | 0 | 250 | my ($file_name, $content, $missing_final_separator, $options) = @_; | 
| 153 | 132 | 50 |  |  |  | 304 | if ($options->{in_place}) { | 
| 154 | 0 |  |  |  |  | 0 | write_file($file_name, $content, $missing_final_separator, 0, $options); | 
| 155 |  |  |  |  |  |  | } else { | 
| 156 | 132 |  |  |  |  | 255 | 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 |  |  |  | 25 | 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 |  |  |  |  | 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; |