| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # This module contains the function that are pushed in the pipelines and which | 
| 2 |  |  |  |  |  |  | # are operating on the input files. | 
| 3 |  |  |  |  |  |  |  | 
| 4 |  |  |  |  |  |  | package App::PTP::Commands; | 
| 5 |  |  |  |  |  |  |  | 
| 6 | 19 |  |  | 19 |  | 292 | use 5.022; | 
|  | 19 |  |  |  |  | 67 |  | 
| 7 | 19 |  |  | 19 |  | 118 | use strict; | 
|  | 19 |  |  |  |  | 65 |  | 
|  | 19 |  |  |  |  | 354 |  | 
| 8 | 19 |  |  | 17 |  | 205 | use warnings; | 
|  | 17 |  |  |  |  | 39 |  | 
|  | 17 |  |  |  |  | 584 |  | 
| 9 |  |  |  |  |  |  |  | 
| 10 | 17 |  |  | 17 |  | 107 | no warnings 'experimental::smartmatch'; | 
|  | 17 |  |  |  |  | 43 |  | 
|  | 17 |  |  |  |  | 796 |  | 
| 11 | 17 |  |  | 17 |  | 111 | use feature 'switch'; | 
|  | 17 |  |  |  |  | 53 |  | 
|  | 17 |  |  |  |  | 2833 |  | 
| 12 |  |  |  |  |  |  |  | 
| 13 | 17 |  |  | 17 |  | 7490 | use App::PTP::Files qw(write_side_output read_side_input write_handle); | 
|  | 17 |  |  |  |  | 40 |  | 
|  | 17 |  |  |  |  | 1081 |  | 
| 14 | 17 |  |  | 17 |  | 7057 | use App::PTP::PerlEnv; | 
|  | 17 |  |  |  |  | 39 |  | 
|  | 17 |  |  |  |  | 722 |  | 
| 15 | 17 |  |  | 17 |  | 6812 | use App::PTP::Util; | 
|  | 17 |  |  |  |  | 37 |  | 
|  | 17 |  |  |  |  | 585 |  | 
| 16 | 17 |  |  | 17 |  | 108 | use Cwd qw(abs_path); | 
|  | 17 |  |  |  |  | 32 |  | 
|  | 17 |  |  |  |  | 731 |  | 
| 17 | 17 |  |  | 17 |  | 102 | use Data::Dumper; | 
|  | 17 |  |  |  |  | 32 |  | 
|  | 17 |  |  |  |  | 655 |  | 
| 18 | 17 |  |  | 17 |  | 86 | use Exporter 'import'; | 
|  | 17 |  |  |  |  | 30 |  | 
|  | 17 |  |  |  |  | 522 |  | 
| 19 | 17 |  |  | 17 |  | 7813 | use File::Spec::Functions qw(rel2abs); | 
|  | 17 |  |  |  |  | 16186 |  | 
|  | 17 |  |  |  |  | 2907 |  | 
| 20 | 17 |  |  | 17 |  | 1798 | use List::Util qw(min max); | 
|  | 17 |  |  |  |  | 1843 |  | 
|  | 17 |  |  |  |  | 9007 |  | 
| 21 | 17 |  |  | 17 |  | 15929 | use Safe; | 
|  | 17 |  |  |  |  | 619118 |  | 
|  | 17 |  |  |  |  | 52034 |  | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | # Every public function is exported by default. | 
| 24 |  |  |  |  |  |  | my @all_cmd = | 
| 25 |  |  |  |  |  |  | qw(prepare_perl_env do_grep do_substitute do_perl | 
| 26 |  |  |  |  |  |  | do_execute do_load do_sort do_list_op do_tail do_head do_delete_marked | 
| 27 |  |  |  |  |  |  | do_insert_marked do_set_markers do_number_lines do_file_name do_line_count | 
| 28 |  |  |  |  |  |  | do_cut do_paste do_pivot do_tee do_shell do_eat); | 
| 29 |  |  |  |  |  |  | our @EXPORT_OK = (@all_cmd, 'warn_or_die_if_needed'); | 
| 30 |  |  |  |  |  |  | our %EXPORT_TAGS = (CMD => \@all_cmd); | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | our @markers;  # The current marker array, not shared in the safe. | 
| 33 |  |  |  |  |  |  | tie @App::PTP::PerlEnv::m, 'App::PTP::Util::MarkersArray'; | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | my $f_setter = tie $App::PTP::PerlEnv::f, 'App::PTP::Util::ReadOnlyVar'; | 
| 36 |  |  |  |  |  |  | my $F_setter = tie $App::PTP::PerlEnv::F, 'App::PTP::Util::ReadOnlyVar'; | 
| 37 |  |  |  |  |  |  | my $n_setter = tie $App::PTP::PerlEnv::n, 'App::PTP::Util::ReadOnlyVar'; | 
| 38 |  |  |  |  |  |  | my $N_setter = tie $App::PTP::PerlEnv::N, 'App::PTP::Util::ReadOnlyVar'; | 
| 39 |  |  |  |  |  |  | my $m_setter = tie $App::PTP::PerlEnv::m, 'App::PTP::Util::AliasVar'; | 
| 40 |  |  |  |  |  |  | # This variable is public because it is set in the App::PTP::process_all method. | 
| 41 |  |  |  |  |  |  | our $I_setter = tie $App::PTP::PerlEnv::I, 'App::PTP::Util::ReadOnlyVar'; | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | # This 'safe' provides an isolated environment for all the regex and perl code | 
| 44 |  |  |  |  |  |  | # execution provided by the user. | 
| 45 |  |  |  |  |  |  | my $safe;  # Sets to Safe->new() before each new file processed; | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | # Prepare the $safe variable so that it can execute code with access to our | 
| 48 |  |  |  |  |  |  | # PerlEnv. | 
| 49 |  |  |  |  |  |  | sub new_safe { | 
| 50 | 31 |  |  | 31 | 0 | 75 | my ($options) = @_; | 
| 51 | 31 | 100 | 100 |  |  | 101 | if ($safe and $options->{preserve_perl_env} and | 
|  |  |  | 66 |  |  |  |  | 
| 52 |  |  |  |  |  |  | $safe->reval('$PerlEnv_LOADED')) { | 
| 53 | 2 | 50 |  |  |  | 1009 | print "Skipping creation of new safe.\n" if $options->{debug_mode}; | 
| 54 | 2 |  |  |  |  | 7 | return; | 
| 55 |  |  |  |  |  |  | } | 
| 56 | 29 | 50 |  |  |  | 86 | print "Creating a new safe.\n" if $options->{debug_mode}; | 
| 57 | 29 |  |  |  |  | 160 | $safe = Safe->new(); | 
| 58 | 29 |  |  |  |  | 29564 | $safe->share_from('App::PTP::PerlEnv', \@App::PTP::PerlEnv::EXPORT_OK); | 
| 59 | 29 | 100 |  |  |  | 6893 | if ($options->{use_safe} > 1) { | 
| 60 | 14 |  |  |  |  | 55 | $safe->permit_only(qw(:base_core :base_mem :base_loop :base_math :base_orig | 
| 61 |  |  |  |  |  |  | :load)); | 
| 62 | 14 |  |  |  |  | 147 | $safe->deny(qw(tie untie bless)); | 
| 63 |  |  |  |  |  |  | } else { | 
| 64 | 15 |  |  |  |  | 62 | $safe->deny_only(qw(:subprocess :ownprocess :others :dangerous)); | 
| 65 |  |  |  |  |  |  | } | 
| 66 | 29 | 50 |  |  |  | 257 | if ($@) { | 
| 67 | 0 |  |  |  |  | 0 | chomp($@); | 
| 68 | 0 |  |  |  |  | 0 | die "INTERNAL ERROR: cannot load the PerlEnv module: ${@}\n"; | 
| 69 |  |  |  |  |  |  | } | 
| 70 |  |  |  |  |  |  | } | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | # Prepare an App::PTP::SafeEnv package with access to the PerlEnv one and | 
| 73 |  |  |  |  |  |  | # nothing else. | 
| 74 |  |  |  |  |  |  | sub reset_safe_env { | 
| 75 | 111 |  |  | 111 | 0 | 256 | my ($options) = @_; | 
| 76 | 111 | 50 | 66 |  |  | 296 | if ($options->{preserve_perl_env} and $App::PTP::SafeEnv::PerlEnv_LOADED) { | 
| 77 | 2 | 50 |  |  |  | 9 | print "Skipping reset of perl environment.\n" if $options->{debug_mode}; | 
| 78 | 2 |  |  |  |  | 5 | return; | 
| 79 |  |  |  |  |  |  | } | 
| 80 | 109 | 50 |  |  |  | 329 | print "Reseting the perl environment.\n" if $options->{debug_mode}; | 
| 81 |  |  |  |  |  |  | # We can't undef the hash, otherwise the code compiled in the eval below no | 
| 82 |  |  |  |  |  |  | # longer refers to the same package (this would work however with a | 
| 83 |  |  |  |  |  |  | # string-eval instead of a block-eval as the code would be compiled and | 
| 84 |  |  |  |  |  |  | # create a new package hash). | 
| 85 | 109 |  |  |  |  | 287 | %App::PTP::SafeEnv:: = (); | 
| 86 | 109 |  |  |  |  | 186 | eval { | 
| 87 |  |  |  |  |  |  | package App::PTP::SafeEnv; | 
| 88 | 109 |  |  |  |  | 14280 | App::PTP::PerlEnv->import(':all'); | 
| 89 | 109 |  |  |  |  | 368 | our $PerlEnv_LOADED = 1;  # For some reason the import does not work | 
| 90 |  |  |  |  |  |  | }; | 
| 91 | 109 | 50 |  |  |  | 343 | if ($@) { | 
| 92 | 0 |  |  |  |  | 0 | chomp($@); | 
| 93 | 0 |  |  |  |  | 0 | die "INTERNAL ERROR: cannot prepare the SafeEnv package: ${@}\n"; | 
| 94 |  |  |  |  |  |  | } | 
| 95 |  |  |  |  |  |  | } | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | # Delete the PerlEnv (both the safe and the eval based one). This method is only | 
| 98 |  |  |  |  |  |  | # meant to be called from tests. | 
| 99 |  |  |  |  |  |  | sub delete_perl_env { | 
| 100 | 128 |  |  | 128 | 0 | 2393577 | undef $safe; | 
| 101 | 128 |  |  |  |  | 4510 | %App::PTP::SafeEnv:: = (); | 
| 102 |  |  |  |  |  |  | } | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | # Should be called for each new file so that the environment seen by the user | 
| 105 |  |  |  |  |  |  | # supplied Perl code is empty. | 
| 106 |  |  |  |  |  |  | sub prepare_perl_env { | 
| 107 | 142 |  |  | 142 | 0 | 297 | my ($file_name, $options) = @_; | 
| 108 | 142 | 100 |  |  |  | 305 | if (ref($file_name)) { | 
| 109 | 123 |  |  |  |  | 420 | $f_setter->set('-'); | 
| 110 | 123 |  |  |  |  | 283 | $F_setter->set('-'); | 
| 111 |  |  |  |  |  |  | } else { | 
| 112 | 19 |  |  |  |  | 73 | $f_setter->set($file_name); | 
| 113 | 19 |  |  |  |  | 547 | $F_setter->set(abs_path($file_name)); | 
| 114 |  |  |  |  |  |  | } | 
| 115 | 142 | 100 |  |  |  | 377 | if ($options->{use_safe} > 0) { | 
| 116 | 31 |  |  |  |  | 63 | new_safe($options); | 
| 117 |  |  |  |  |  |  | } else { | 
| 118 | 111 |  |  |  |  | 273 | reset_safe_env($options); | 
| 119 |  |  |  |  |  |  | } | 
| 120 |  |  |  |  |  |  | } | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | # process($file_name, \@pipeline, \%options, \@content, $missing_final_sep) | 
| 123 |  |  |  |  |  |  | # Applies all the stage of the pipeline on the given content (which is modified | 
| 124 |  |  |  |  |  |  | # in place). | 
| 125 |  |  |  |  |  |  | sub process { | 
| 126 | 142 |  |  | 142 | 0 | 350 | my ($file_name, $pipeline, $options, $content, $missing_final_separator) = @_; | 
| 127 | 142 | 50 |  |  |  | 385 | if ($options->{debug_mode}) { | 
| 128 |  |  |  |  |  |  | # For long files, we print only the first and last lines. | 
| 129 | 142 |  |  |  |  | 403 | my @debug_content = @$content; | 
| 130 | 142 |  |  |  |  | 669 | my $omit_msg = sprintf "... (%d lines omitted)", (@$content - 8); | 
| 131 | 142 | 100 |  |  |  | 381 | splice @debug_content, 4, -4, $omit_msg if @$content > 10; | 
| 132 | 142 | 100 |  |  |  | 338 | if (ref($file_name)) { | 
| 133 | 123 |  |  |  |  | 467 | print "Processing $${file_name} with content: ".Dumper(\@debug_content); | 
| 134 |  |  |  |  |  |  | } else { | 
| 135 | 19 |  |  |  |  | 84 | print "Processing '${file_name}' with content: ".Dumper(\@debug_content); | 
| 136 |  |  |  |  |  |  | } | 
| 137 | 142 | 100 |  |  |  | 8246 | print "Has final separator: ".($missing_final_separator ? 'false' : 'true')."\n"; | 
| 138 |  |  |  |  |  |  | } | 
| 139 | 142 |  |  |  |  | 499 | prepare_perl_env($file_name, $options); | 
| 140 | 142 |  |  |  |  | 454 | @markers  = (0) x scalar(@$content); | 
| 141 | 142 |  |  |  |  | 296 | my $markers = \@markers; | 
| 142 | 142 |  |  |  |  | 332 | for my $stage (@$pipeline) { | 
| 143 | 189 |  |  |  |  | 1332 | my ($command, $code, $modes, @args) = @$stage; | 
| 144 | 189 |  |  |  |  | 770 | $N_setter->set(scalar(@$content)); | 
| 145 | 189 |  |  |  |  | 426 | $modes->{missing_final_separator} = $missing_final_separator; | 
| 146 | 189 |  |  |  |  | 403 | $modes->{file_name_ref} = \$_[0];  # this is an alias to the passed value. | 
| 147 | 189 | 50 |  |  |  | 549 | if ($options->{debug_mode}) { | 
| 148 | 189 |  |  |  |  | 394 | local $Data::Dumper::Indent = 0; | 
| 149 |  |  |  |  |  |  | printf "Executing command: %s(%s).\n", $command, | 
| 150 | 189 |  |  |  |  | 428 | join(', ', map { Dumper($_) } @args); | 
|  | 266 |  |  |  |  | 4275 |  | 
| 151 |  |  |  |  |  |  | } | 
| 152 | 189 |  |  |  |  | 7877 | &$code($content, $markers, $modes, $options, @args); | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  | } | 
| 155 |  |  |  |  |  |  |  | 
| 156 |  |  |  |  |  |  | sub base_prepare_re { | 
| 157 | 11 |  |  | 11 | 0 | 34 | my ($re, $modes) = @_; | 
| 158 | 11 | 50 |  |  |  | 35 | if ($modes->{quote_regex}) { | 
| 159 | 0 |  |  |  |  | 0 | $re = quotemeta($re); | 
| 160 |  |  |  |  |  |  | } | 
| 161 | 11 | 50 |  |  |  | 32 | if (not $modes->{case_sensitive}) { | 
| 162 | 0 |  |  |  |  | 0 | $re = '(?i)'.$re; | 
| 163 |  |  |  |  |  |  | } | 
| 164 | 11 |  |  |  |  | 27 | return $re; | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | # prepare_re('re', \%options) | 
| 168 |  |  |  |  |  |  | # Applies the modal option on the given regex. | 
| 169 |  |  |  |  |  |  | # This function is not exported. | 
| 170 |  |  |  |  |  |  | sub prepare_re { | 
| 171 | 11 |  |  | 11 | 0 | 25 | my ($re, $modes) = @_; | 
| 172 | 11 |  |  |  |  | 31 | $re = base_prepare_re($re, $modes); | 
| 173 | 11 |  |  |  |  | 19 | my $r; | 
| 174 | 11 | 50 |  |  |  | 33 | if ($modes->{regex_engine} ne 'perl') { | 
| 175 |  |  |  |  |  |  | # Some play to correctly escape whetever special characters might be in the | 
| 176 |  |  |  |  |  |  | # regex while preserving its semantics. This relies on the fact that the | 
| 177 |  |  |  |  |  |  | # 'Terse' option of Data::Dumper is set in the main program. | 
| 178 |  |  |  |  |  |  | # The regex-engine variable has been validated in the Args module. | 
| 179 | 0 |  |  |  |  | 0 | my $str_re = Dumper($re); | 
| 180 | 0 |  |  |  |  | 0 | $r = eval "use re::engine::$modes->{regex_engine}; | 
| 181 |  |  |  |  |  |  | \$re = $str_re; | 
| 182 |  |  |  |  |  |  | qr/\$re/s "; | 
| 183 | 0 | 0 |  |  |  | 0 | if ($@) { | 
| 184 | 0 |  |  |  |  | 0 | chomp($@); | 
| 185 | 0 |  |  |  |  | 0 | die "FATAL: Cannot use the specified regex engine: ${@}\n"; | 
| 186 |  |  |  |  |  |  | } | 
| 187 |  |  |  |  |  |  | } else { | 
| 188 | 11 |  |  |  |  | 173 | $r = qr/$re/s; | 
| 189 |  |  |  |  |  |  | } | 
| 190 | 11 |  |  |  |  | 36 | return $r; | 
| 191 |  |  |  |  |  |  | } | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | sub quote_for_re { | 
| 194 | 75 |  |  | 75 | 0 | 129 | my ($text, $modes) = @_; | 
| 195 | 75 | 100 |  |  |  | 154 | if ($modes->{quote_regex}) { | 
| 196 | 7 |  |  |  |  | 19 | return quotemeta($text); | 
| 197 |  |  |  |  |  |  | } else { | 
| 198 |  |  |  |  |  |  | # We quote just the '{' or '}' characters. | 
| 199 | 68 |  |  |  |  | 282 | return $text =~ s/(\{|\})/\\$1/gr; | 
| 200 |  |  |  |  |  |  | } | 
| 201 |  |  |  |  |  |  | } | 
| 202 |  |  |  |  |  |  |  | 
| 203 |  |  |  |  |  |  | sub prepare_re2 { | 
| 204 | 56 |  |  | 56 | 0 | 100 | my ($re, $modes) = @_; | 
| 205 | 56 |  |  |  |  | 128 | $re = quote_for_re($re, $modes); | 
| 206 | 56 | 100 |  |  |  | 153 | if (not $modes->{case_sensitive}) { | 
| 207 | 1 |  |  |  |  | 3 | $re = '(?i)'.$re; | 
| 208 |  |  |  |  |  |  | } | 
| 209 | 56 |  |  |  |  | 90 | my $use_statement = ''; | 
| 210 | 56 | 50 |  |  |  | 122 | if ($modes->{regex_engine} ne 'perl') { | 
| 211 | 0 |  |  |  |  | 0 | $use_statement = "use re::engine::$modes->{regex_engine};"; | 
| 212 |  |  |  |  |  |  | } | 
| 213 | 56 |  |  |  |  | 180 | return ($use_statement, "{${re}}"); | 
| 214 |  |  |  |  |  |  | } | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | # This interpolate str in the Perl env (unless -Q is in effect). | 
| 217 |  |  |  |  |  |  | sub maybe_interpolate { | 
| 218 | 12 |  |  | 12 | 0 | 29 | my ($str, $modes, $options, $command) = @_; | 
| 219 | 12 | 100 |  |  |  | 34 | if (not $modes->{quote_regex}) { | 
| 220 | 6 |  |  |  |  | 39 | $str = eval_in_safe_env("<<~\"PTP_EOF_WORD\"\n${str}\nPTP_EOF_WORD\n", $options); | 
| 221 | 6 | 50 |  |  |  | 29 | die "FATAL: Cannot eval string for --${command}: ${@}\n" if $@; | 
| 222 | 6 |  |  |  |  | 17 | chomp($str); | 
| 223 |  |  |  |  |  |  | } | 
| 224 | 12 |  |  |  |  | 30 | return $str; | 
| 225 |  |  |  |  |  |  | } | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | sub do_grep { | 
| 228 | 37 |  |  | 37 | 0 | 107 | my ($content, $markers, $modes, $options, $re) = @_; | 
| 229 | 37 |  |  |  |  | 87 | my ($use_stmt, $quoted_re) = prepare_re2($re, $modes); | 
| 230 | 37 | 50 |  |  |  | 127 | print "\$re = ${quoted_re}\n" if $options->{debug_mode}; | 
| 231 | 37 |  |  |  |  | 127 | my $wrapped = get_code_in_safe_env( | 
| 232 |  |  |  |  |  |  | "{; ${use_stmt} undef \$_ unless m ${quoted_re} }", $options, '--grep'); | 
| 233 | 37 |  |  |  |  | 97 | $. = 0; | 
| 234 | 37 |  |  |  |  | 80 | map { $m_setter->set(\$markers->[$.]); | 
|  | 218 |  |  |  |  | 14958 |  | 
| 235 | 218 |  |  |  |  | 708 | $n_setter->set($.++); | 
| 236 | 218 |  |  |  |  | 2756 | $wrapped->() } @$content; | 
| 237 |  |  |  |  |  |  | # This code is duplicated from do_perl: | 
| 238 | 37 |  |  |  |  | 4850 | for my $i (0 .. $#$content) { | 
| 239 | 218 | 100 |  |  |  | 432 | if (not defined $content->[$i]) { | 
|  |  | 50 |  |  |  |  |  | 
| 240 | 141 |  |  |  |  | 228 | undef $markers->[$i]; | 
| 241 |  |  |  |  |  |  | } elsif (not defined $markers->[$i]) { | 
| 242 | 0 |  |  |  |  | 0 | $markers->[$i] = '';  # We don't want undef here, as we will filter on it. | 
| 243 |  |  |  |  |  |  | } | 
| 244 |  |  |  |  |  |  | } | 
| 245 | 37 |  |  |  |  | 71 | @$content = grep { defined } @$content; | 
|  | 218 |  |  |  |  | 371 |  | 
| 246 | 37 |  |  |  |  | 73 | @$markers = grep { defined } @$markers; | 
|  | 218 |  |  |  |  | 671 |  | 
| 247 |  |  |  |  |  |  | } | 
| 248 |  |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  | sub do_substitute { | 
| 250 | 19 |  |  | 19 | 0 | 50 | my ($content, $markers, $modes, $options, $re, $subst) = @_; | 
| 251 | 19 | 50 |  |  |  | 45 | if ($options->{debug_mode}) { | 
| 252 | 19 |  |  |  |  | 51 | print "Before: \$re = ${re}; \$subst = ${subst}\n"; | 
| 253 |  |  |  |  |  |  | } | 
| 254 | 19 |  |  |  |  | 50 | my ($use_stmt, $quoted_re) = prepare_re2($re, $modes); | 
| 255 | 19 |  |  |  |  | 36 | my $quoted_subst = quote_for_re($subst, $modes); | 
| 256 | 19 | 100 |  |  |  | 44 | my $g = $modes->{global_match} ? 'g' : ''; | 
| 257 | 19 | 50 |  |  |  | 37 | if ($options->{debug_mode}) { | 
| 258 | 19 |  |  |  |  | 49 | print "After: \$re = ${quoted_re}; \$subst = ${quoted_subst}\n"; | 
| 259 |  |  |  |  |  |  | } | 
| 260 | 19 |  |  |  |  | 62 | my $wrapped = get_code_in_safe_env( | 
| 261 |  |  |  |  |  |  | "; ${use_stmt} s ${quoted_re}{${quoted_subst}}${g}", $options, | 
| 262 |  |  |  |  |  |  | '--substitute'); | 
| 263 | 19 |  |  |  |  | 50 | $. = 0; | 
| 264 | 19 |  |  |  |  | 47 | map { $m_setter->set(\$markers->[$.]); | 
|  | 73 |  |  |  |  | 4303 |  | 
| 265 | 73 |  |  |  |  | 226 | $n_setter->set($.++); | 
| 266 | 73 |  |  |  |  | 954 | $wrapped->() } @$content; | 
| 267 |  |  |  |  |  |  | } | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | sub warn_or_die_if_needed { | 
| 270 | 215 |  |  | 215 | 0 | 418 | my ($text, $modes) = @_; | 
| 271 | 215 | 100 |  |  |  | 895 | return 0 unless $@; | 
| 272 | 3 |  |  |  |  | 8 | chomp($@); | 
| 273 | 3 | 50 |  |  |  | 9 | if ($modes->{fatal_error}) { | 
| 274 | 0 |  |  |  |  | 0 | die "FATAL: ${text}: ${@}\n"; | 
| 275 |  |  |  |  |  |  | } else { | 
| 276 | 3 |  |  |  |  | 15 | print "WARNING: ${text}: ${@}\n"; | 
| 277 |  |  |  |  |  |  | } | 
| 278 | 3 |  |  |  |  | 9 | return 1; | 
| 279 |  |  |  |  |  |  | } | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | sub eval_in_safe_env { | 
| 282 | 149 |  |  | 149 | 0 | 297 | my ($code, $options) = @_; | 
| 283 | 149 | 50 |  |  |  | 367 | if ($options->{debug_mode} > 1) { | 
| 284 | 0 |  |  |  |  | 0 | print "Evaluating the following code: ${code}\n"; | 
| 285 |  |  |  |  |  |  | } | 
| 286 | 149 | 100 |  |  |  | 411 | if ($options->{use_safe} > 0) { | 
| 287 | 61 |  |  |  |  | 195 | return $safe->reval($code); | 
| 288 |  |  |  |  |  |  | } else { | 
| 289 | 88 |  |  | 8 |  | 7712 | return eval("package App::PTP::SafeEnv; | 
|  | 8 |  |  | 6 |  | 63 |  | 
|  | 8 |  |  | 6 |  | 16 |  | 
|  | 8 |  |  |  |  | 251 |  | 
|  | 8 |  |  |  |  | 44 |  | 
|  | 8 |  |  |  |  | 15 |  | 
|  | 8 |  |  |  |  | 577 |  | 
|  | 8 |  |  |  |  | 63 |  | 
|  | 8 |  |  |  |  | 23 |  | 
|  | 8 |  |  |  |  | 211 |  | 
|  | 8 |  |  |  |  | 47 |  | 
|  | 8 |  |  |  |  | 21 |  | 
|  | 8 |  |  |  |  | 627 |  | 
|  | 7 |  |  |  |  | 58 |  | 
|  | 7 |  |  |  |  | 18 |  | 
|  | 7 |  |  |  |  | 189 |  | 
|  | 7 |  |  |  |  | 39 |  | 
|  | 7 |  |  |  |  | 14 |  | 
|  | 7 |  |  |  |  | 438 |  | 
|  | 7 |  |  |  |  | 62 |  | 
|  | 7 |  |  |  |  | 16 |  | 
|  | 7 |  |  |  |  | 216 |  | 
|  | 7 |  |  |  |  | 41 |  | 
|  | 7 |  |  |  |  | 19 |  | 
|  | 7 |  |  |  |  | 514 |  | 
|  | 4 |  |  |  |  | 29 |  | 
|  | 4 |  |  |  |  | 9 |  | 
|  | 4 |  |  |  |  | 120 |  | 
|  | 4 |  |  |  |  | 20 |  | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 307 |  | 
|  | 4 |  |  |  |  | 28 |  | 
|  | 4 |  |  |  |  | 9 |  | 
|  | 4 |  |  |  |  | 93 |  | 
|  | 4 |  |  |  |  | 70 |  | 
|  | 4 |  |  |  |  | 9 |  | 
|  | 4 |  |  |  |  | 241 |  | 
|  | 4 |  |  |  |  | 31 |  | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 159 |  | 
|  | 4 |  |  |  |  | 20 |  | 
|  | 4 |  |  |  |  | 40 |  | 
|  | 4 |  |  |  |  | 348 |  | 
|  | 4 |  |  |  |  | 61 |  | 
|  | 4 |  |  |  |  | 18 |  | 
|  | 4 |  |  |  |  | 112 |  | 
|  | 4 |  |  |  |  | 20 |  | 
|  | 4 |  |  |  |  | 7 |  | 
|  | 4 |  |  |  |  | 252 |  | 
|  | 4 |  |  |  |  | 24 |  | 
|  | 4 |  |  |  |  | 10 |  | 
|  | 4 |  |  |  |  | 98 |  | 
|  | 4 |  |  |  |  | 18 |  | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 275 |  | 
|  | 4 |  |  |  |  | 25 |  | 
|  | 4 |  |  |  |  | 10 |  | 
|  | 4 |  |  |  |  | 110 |  | 
|  | 4 |  |  |  |  | 19 |  | 
|  | 4 |  |  |  |  | 9 |  | 
|  | 4 |  |  |  |  | 253 |  | 
|  | 4 |  |  |  |  | 26 |  | 
|  | 4 |  |  |  |  | 9 |  | 
|  | 4 |  |  |  |  | 90 |  | 
|  | 4 |  |  |  |  | 17 |  | 
|  | 4 |  |  |  |  | 7 |  | 
|  | 4 |  |  |  |  | 258 |  | 
|  | 3 |  |  |  |  | 21 |  | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 72 |  | 
|  | 3 |  |  |  |  | 13 |  | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 136 |  | 
|  | 3 |  |  |  |  | 22 |  | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 80 |  | 
|  | 3 |  |  |  |  | 18 |  | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 220 |  | 
|  | 7 |  |  |  |  | 53 |  | 
|  | 7 |  |  |  |  | 17 |  | 
|  | 7 |  |  |  |  | 218 |  | 
|  | 7 |  |  |  |  | 41 |  | 
|  | 7 |  |  |  |  | 51 |  | 
|  | 7 |  |  |  |  | 419 |  | 
|  | 1 |  |  |  |  | 8 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 36 |  | 
|  | 1 |  |  |  |  | 6 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 76 |  | 
| 290 |  |  |  |  |  |  | no strict; | 
| 291 |  |  |  |  |  |  | no warnings; | 
| 292 |  |  |  |  |  |  | ${code}"); | 
| 293 |  |  |  |  |  |  | } | 
| 294 |  |  |  |  |  |  | } | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | sub get_code_in_safe_env { | 
| 297 | 120 |  |  | 120 | 0 | 289 | my ($code, $options, $cmd) = @_; | 
| 298 | 120 |  |  |  |  | 302 | my $wrapped_code = eval_in_safe_env("sub { ${code} }", $options); | 
| 299 | 120 | 50 |  |  |  | 28969 | die "FATAL: Cannot wrap code for ${cmd}: ${@}" if $@; | 
| 300 | 120 |  |  |  |  | 265 | return $wrapped_code; | 
| 301 |  |  |  |  |  |  | } | 
| 302 |  |  |  |  |  |  |  | 
| 303 |  |  |  |  |  |  | sub do_perl { | 
| 304 | 59 |  |  | 59 | 0 | 159 | my ($content, $markers, $modes, $options, $cmd, $code) = @_; | 
| 305 | 59 |  |  |  |  | 134 | $. = 0; | 
| 306 | 59 |  |  |  |  | 299 | my $scmd = '-'.($cmd =~ s/^(..)/-$1/r); # --perl or -n. | 
| 307 | 59 |  |  |  |  | 195 | my $wrapped_code = get_code_in_safe_env($code, $options, $scmd); | 
| 308 |  |  |  |  |  |  | my @result = map { | 
| 309 | 59 |  |  |  |  | 151 | $m_setter->set(\$markers->[$.]); | 
|  | 200 |  |  |  |  | 788 |  | 
| 310 | 200 |  |  |  |  | 686 | $n_setter->set(++$.); | 
| 311 | 200 |  |  |  |  | 299 | my $input = $_; | 
| 312 |  |  |  |  |  |  | # Among other things, this ensures that the code is always executed in | 
| 313 |  |  |  |  |  |  | # a scalar context. | 
| 314 | 200 |  |  |  |  | 282 | my $r = eval { $wrapped_code->() }; | 
|  | 200 |  |  |  |  | 2467 |  | 
| 315 |  |  |  |  |  |  | # We can't use return as we're not in a sub. | 
| 316 | 200 | 100 |  |  |  | 28002 | if (warn_or_die_if_needed("Perl code failed in ${scmd}", $modes)) { | 
| 317 | 3 |  |  |  |  | 3 | given ($cmd) { | 
| 318 | 3 |  |  |  |  | 11 | 1 when 'filter'; | 
| 319 | 2 |  |  |  |  | 61 | $input when 'n'; | 
| 320 | 1 |  |  |  |  | 4 | $markers[$.] when 'mark-line'; | 
| 321 |  |  |  |  |  |  | } | 
| 322 |  |  |  |  |  |  | } else { | 
| 323 | 197 |  |  |  |  | 553 | $r; | 
| 324 |  |  |  |  |  |  | } | 
| 325 |  |  |  |  |  |  | } @$content; | 
| 326 |  |  |  |  |  |  |  | 
| 327 | 59 |  |  |  |  | 197 | $n_setter->set(undef); | 
| 328 | 59 |  |  |  |  | 157 | $m_setter->set(\undef); | 
| 329 |  |  |  |  |  |  |  | 
| 330 | 59 | 100 |  |  |  | 250 | if ($cmd eq 'perl') { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | # Do nothing with the result. | 
| 332 |  |  |  |  |  |  | } elsif ($cmd eq 'n') { | 
| 333 | 28 |  |  |  |  | 87 | @$content = @result; | 
| 334 |  |  |  |  |  |  | } elsif ($cmd eq 'filter') { | 
| 335 | 6 |  |  |  |  | 40 | for my $i (0 .. $#$content) { | 
| 336 | 23 | 100 | 100 |  |  | 159 | if (!$result[$i] xor $modes->{inverse_match}) { | 
| 337 | 12 |  |  |  |  | 26 | undef $content->[$i]; | 
| 338 |  |  |  |  |  |  | } | 
| 339 |  |  |  |  |  |  | } | 
| 340 |  |  |  |  |  |  | } elsif ($cmd eq 'mark-line') { | 
| 341 | 7 |  |  |  |  | 25 | @$markers = @result; | 
| 342 |  |  |  |  |  |  | } else { | 
| 343 | 0 |  |  |  |  | 0 | die "FATAL: Invalid command received for perl operation ($cmd).\n"; | 
| 344 |  |  |  |  |  |  | } | 
| 345 |  |  |  |  |  |  |  | 
| 346 | 59 |  |  |  |  | 189 | for my $i (0 .. $#$content) { | 
| 347 | 200 | 100 |  |  |  | 510 | if (not defined $content->[$i]) { | 
|  |  | 100 |  |  |  |  |  | 
| 348 | 12 |  |  |  |  | 20 | undef $markers->[$i]; | 
| 349 |  |  |  |  |  |  | } elsif (not defined $markers->[$i]) { | 
| 350 | 6 |  |  |  |  | 14 | $markers->[$i] = '';  # We don't want undef here, as we will filter on it. | 
| 351 |  |  |  |  |  |  | } | 
| 352 |  |  |  |  |  |  | } | 
| 353 | 59 |  |  |  |  | 116 | @$content = grep { defined } @$content; | 
|  | 200 |  |  |  |  | 419 |  | 
| 354 | 59 |  |  |  |  | 103 | @$markers = grep { defined } @$markers; | 
|  | 200 |  |  |  |  | 999 |  | 
| 355 |  |  |  |  |  |  | } | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | sub do_execute { | 
| 358 | 20 |  |  | 20 | 0 | 74 | my ($content, $markers, $modes, $options, $code) = @_; | 
| 359 | 20 |  |  |  |  | 66 | eval_in_safe_env($code, $options); | 
| 360 | 20 | 100 |  |  |  | 24873 | if ($@) { | 
| 361 | 2 |  |  |  |  | 8 | chomp($@); | 
| 362 | 2 |  |  |  |  | 41 | die "FATAL: Perl code failed in --execute: ${@}\n"; | 
| 363 |  |  |  |  |  |  | } | 
| 364 |  |  |  |  |  |  | } | 
| 365 |  |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | sub do_load { | 
| 367 | 3 |  |  | 3 | 0 | 13 | my ($content, $markers, $modes, $options, $file) = @_; | 
| 368 |  |  |  |  |  |  | # do can open relative paths, but in that case it looks them up in the @INC | 
| 369 |  |  |  |  |  |  | # directory, which we want to avoid. | 
| 370 |  |  |  |  |  |  | # We don't use abs_path here to not die (just yet) if the file does not exist. | 
| 371 | 3 |  |  |  |  | 14 | my $abs_path = rel2abs($file); | 
| 372 | 3 | 50 |  |  |  | 164 | print "Loading file: '$abs_path'\n" if $options->{debug_mode}; | 
| 373 | 3 | 50 |  |  |  | 17 | if (not defined eval_in_safe_env("do '${abs_path}';", $options)) { | 
| 374 | 0 | 0 |  |  |  | 0 | if ($@) { | 
|  |  | 0 |  |  |  |  |  | 
| 375 | 0 |  |  |  |  | 0 | die "FATAL: Perl code failed in --load: ${@}\n"; | 
| 376 |  |  |  |  |  |  | } elsif ($!) { | 
| 377 | 0 |  |  |  |  | 0 | die "FATAL: Cannot load file '$file' for --load: $!\n"; | 
| 378 |  |  |  |  |  |  | } | 
| 379 |  |  |  |  |  |  | } | 
| 380 |  |  |  |  |  |  | } | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | sub do_sort { | 
| 383 | 8 |  |  | 8 | 0 | 61 | my ($content, $markers, $modes, $options) = @_; | 
| 384 | 8 | 50 |  |  |  | 36 | if (ref($modes->{comparator}) eq 'CODE') { | 
|  |  | 100 |  |  |  |  |  | 
| 385 |  |  |  |  |  |  | # This branch is no longer used. | 
| 386 | 0 |  |  |  |  | 0 | @$content = sort { $modes->{comparator}() } @$content; | 
|  | 0 |  |  |  |  | 0 |  | 
| 387 |  |  |  |  |  |  | } elsif (ref($modes->{comparator}) eq 'SCALAR') { | 
| 388 | 6 | 100 |  |  |  | 10 | if (${$modes->{comparator}} eq 'default') { | 
|  | 6 | 100 |  |  |  | 20 |  | 
|  |  | 50 |  |  |  |  |  | 
| 389 | 4 |  |  |  |  | 27 | @$content = sort @$content; | 
| 390 | 2 |  |  |  |  | 10 | } elsif (${$modes->{comparator}} eq 'numeric') { | 
| 391 | 17 |  |  | 17 |  | 168 | no warnings "numeric"; | 
|  | 17 |  |  |  |  | 36 |  | 
|  | 17 |  |  |  |  | 1463 |  | 
| 392 | 1 |  |  |  |  | 12 | @$content = sort { $a <=> $b } @$content; | 
|  | 5 |  |  |  |  | 18 |  | 
| 393 | 1 |  |  |  |  | 8 | } elsif (${$modes->{comparator}} eq 'locale') { | 
| 394 | 17 |  |  | 17 |  | 7602 | use locale; | 
|  | 17 |  |  |  |  | 8582 |  | 
|  | 17 |  |  |  |  | 105 |  | 
| 395 | 1 |  |  |  |  | 7 | @$content = sort { $a cmp $b } @$content; | 
|  | 21 |  |  |  |  | 33 |  | 
| 396 |  |  |  |  |  |  | } else { | 
| 397 |  |  |  |  |  |  | die sprintf "INTERNAL ERROR: Invalid comparator (%s)\n", | 
| 398 | 0 |  |  |  |  | 0 | ${$modes->{comparator}}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 399 |  |  |  |  |  |  | } | 
| 400 |  |  |  |  |  |  | } else { | 
| 401 |  |  |  |  |  |  | die sprintf "INTERNAL ERROR: Invalid comparator type (%s)\n", | 
| 402 | 2 | 50 |  |  |  | 7 | Dumper($modes->{comparator}) if ref $modes->{comparator}; | 
| 403 | 2 |  |  |  |  | 5 | my $cmp = $modes->{comparator}; | 
| 404 | 2 |  |  |  |  | 9 | my $sort = get_code_in_safe_env("sort { $cmp } \@_", $options, | 
| 405 |  |  |  |  |  |  | 'custom comparator'); | 
| 406 | 2 |  |  |  |  | 55 | @$content = $sort->(@$content); | 
| 407 |  |  |  |  |  |  | } | 
| 408 | 8 |  |  |  |  | 42 | @$markers = (0) x scalar(@$content); | 
| 409 |  |  |  |  |  |  | } | 
| 410 |  |  |  |  |  |  |  | 
| 411 |  |  |  |  |  |  | sub do_list_op { | 
| 412 | 1 |  |  | 1 | 0 | 4 | my ($content, $markers, $modes, $options, $sub, $apply_on_markers) = @_; | 
| 413 | 1 |  |  |  |  | 5 | @$content = &$sub(@$content); | 
| 414 | 1 | 50 |  |  |  | 4 | if ($apply_on_markers) { | 
| 415 | 0 |  |  |  |  | 0 | @$markers = &$sub(@$markers); | 
| 416 |  |  |  |  |  |  | } else { | 
| 417 | 1 |  |  |  |  | 6 | @$markers = (0) x scalar(@$content); | 
| 418 |  |  |  |  |  |  | } | 
| 419 |  |  |  |  |  |  | } | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | sub do_tail { | 
| 422 | 4 |  |  | 4 | 0 | 11 | my ($content, $markers, $modes, $options, $len) = @_; | 
| 423 | 4 | 100 |  |  |  | 10 | $len = 10 unless $len; | 
| 424 | 4 |  |  |  |  | 11 | splice @$content, 0, -$len; | 
| 425 | 4 |  |  |  |  | 18 | splice @$markers, 0, -$len; | 
| 426 |  |  |  |  |  |  | } | 
| 427 |  |  |  |  |  |  |  | 
| 428 |  |  |  |  |  |  | sub do_head { | 
| 429 | 4 |  |  | 4 | 0 | 12 | my ($content, $markers, $modes, $options, $len) = @_; | 
| 430 | 4 | 100 |  |  |  | 12 | $len = 10 unless $len; | 
| 431 | 4 | 100 |  |  |  | 13 | $len = -@$content if $len < -@$content; | 
| 432 | 4 |  |  |  |  | 13 | splice @$content, $len; | 
| 433 | 4 |  |  |  |  | 18 | splice @$markers, $len; | 
| 434 |  |  |  |  |  |  | } | 
| 435 |  |  |  |  |  |  |  | 
| 436 |  |  |  |  |  |  | sub do_delete_marked { | 
| 437 |  |  |  |  |  |  | # negative offset if we're deleting a line before the marker. | 
| 438 | 3 |  |  | 3 | 0 | 19 | my ($content, $markers, $modes, $options, $offset) = @_; | 
| 439 | 3 |  |  |  |  | 26 | my $start = min(max(0, -$offset), $#$content); | 
| 440 | 3 |  |  |  |  | 15 | my $end = max(min($#$content, $#$content + $offset), $#$content); | 
| 441 | 3 |  |  |  |  | 13 | my @markers_temp = @$markers;  # So that we can read it even after an undef. | 
| 442 | 3 |  |  |  |  | 13 | for my $i ($start .. $end) { | 
| 443 | 12 | 100 |  |  |  | 27 | if ($markers_temp[$i]) { | 
| 444 | 6 |  |  |  |  | 14 | undef $content->[$i + $offset]; | 
| 445 | 6 |  |  |  |  | 12 | undef $markers->[$i + $offset]; | 
| 446 |  |  |  |  |  |  | } | 
| 447 |  |  |  |  |  |  | } | 
| 448 | 3 |  |  |  |  | 12 | @$content = grep { defined } @$content; | 
|  | 12 |  |  |  |  | 29 |  | 
| 449 | 3 |  |  |  |  | 10 | @$markers = grep { defined } @$markers; | 
|  | 12 |  |  |  |  | 30 |  | 
| 450 |  |  |  |  |  |  | } | 
| 451 |  |  |  |  |  |  |  | 
| 452 |  |  |  |  |  |  | sub do_insert_marked { | 
| 453 |  |  |  |  |  |  | # negative offset if we're inserting a line before the marker. | 
| 454 | 3 |  |  | 3 | 0 | 12 | my ($content, $markers, $modes, $options, $offset, $line) = @_; | 
| 455 | 3 |  |  |  |  | 14 | my @markers_temp = @$markers; | 
| 456 | 3 |  |  |  |  | 10 | my @content_temp = @$content; | 
| 457 | 3 |  |  |  |  | 7 | my $added = 0; | 
| 458 | 3 |  |  |  |  | 6 | my $wrapped; | 
| 459 | 3 | 50 |  |  |  | 15 | if (not $modes->{quote_regex}) { | 
| 460 | 3 |  |  |  |  | 12 | $wrapped = get_code_in_safe_env("\"${line}\"", $options, | 
| 461 |  |  |  |  |  |  | '--insert-marked'); | 
| 462 |  |  |  |  |  |  | } | 
| 463 | 3 |  |  |  |  | 33 | for my $i (0 .. $#content_temp) { | 
| 464 | 12 | 100 |  |  |  | 73 | next unless $markers_temp[$i]; | 
| 465 | 6 |  |  |  |  | 12 | my $r; | 
| 466 | 6 | 50 |  |  |  | 17 | if ($modes->{quote_regex}) { | 
| 467 | 0 |  |  |  |  | 0 | $r = $line; | 
| 468 |  |  |  |  |  |  | } else { | 
| 469 | 6 |  |  |  |  | 11 | $_ = $content_temp[$i]; | 
| 470 | 6 |  |  |  |  | 24 | $n_setter->set($i + 1); | 
| 471 | 6 |  |  |  |  | 24 | $m_setter->set($markers_temp[$i]); | 
| 472 | 6 |  |  |  |  | 18 | $line =~ s/(?:[^\\]|^)((:?\\\\)*")/\\$1/g; | 
| 473 | 6 |  |  |  |  | 10 | $r = eval { $wrapped->() }; | 
|  | 6 |  |  |  |  | 51 |  | 
| 474 | 6 | 50 |  |  |  | 1197 | next if warn_or_die_if_needed( | 
| 475 |  |  |  |  |  |  | 'String interpolation failed in --insert-marked', $modes); | 
| 476 |  |  |  |  |  |  | # it should not be possible for the result to be undef... | 
| 477 |  |  |  |  |  |  | } | 
| 478 |  |  |  |  |  |  | # We never insert at a negative offset or before the previously added line. | 
| 479 |  |  |  |  |  |  | # Offset = 0 means we're inserting after the current line. | 
| 480 | 6 |  |  |  |  | 14 | my $target = $i + $offset + 1 + $added; | 
| 481 | 6 | 50 |  |  |  | 17 | $target = $added if $target < $added; | 
| 482 | 6 | 50 |  |  |  | 16 | $target = @$content if $target > @$content; | 
| 483 | 6 |  |  |  |  | 8 | ++$added; | 
| 484 | 6 |  |  |  |  | 19 | splice @$content, $target, 0, $r; | 
| 485 | 6 |  |  |  |  | 16 | splice @$markers, $target, 0, 0; | 
| 486 |  |  |  |  |  |  | } | 
| 487 |  |  |  |  |  |  | } | 
| 488 |  |  |  |  |  |  |  | 
| 489 |  |  |  |  |  |  | sub do_set_markers { | 
| 490 | 0 |  |  | 0 | 0 | 0 | my ($content, $markers, $modes, $options, $value) = @_; | 
| 491 | 0 |  |  |  |  | 0 | @$markers = ($value)x scalar(@$content); | 
| 492 |  |  |  |  |  |  | } | 
| 493 |  |  |  |  |  |  |  | 
| 494 |  |  |  |  |  |  | sub do_number_lines { | 
| 495 | 0 |  |  | 0 | 0 | 0 | my ($content, $markers, $modes, $options) = @_; | 
| 496 | 0 |  |  |  |  | 0 | my $line = 0; | 
| 497 | 0 |  |  |  |  | 0 | my $n = int(log(@$content) / log(10)) + 1; | 
| 498 | 0 |  |  |  |  | 0 | map { $_ = sprintf("%${n}d  %s", ++$line, $_) } @$content; | 
|  | 0 |  |  |  |  | 0 |  | 
| 499 |  |  |  |  |  |  | } | 
| 500 |  |  |  |  |  |  |  | 
| 501 |  |  |  |  |  |  | sub do_file_name { | 
| 502 | 2 |  |  | 2 | 0 | 6 | my ($content, $markers, $modes, $options, $replace_all) = @_; | 
| 503 | 2 |  |  |  |  | 4 | my $name; | 
| 504 | 2 | 50 | 0 |  |  | 7 | if (ref($modes->{file_name_ref}) eq 'SCALAR') { | 
|  |  | 0 |  |  |  |  |  | 
| 505 | 2 |  |  |  |  | 4 | $name = ${$modes->{file_name_ref}}; | 
|  | 2 |  |  |  |  | 5 |  | 
| 506 |  |  |  |  |  |  | } elsif (ref($modes->{file_name_ref}) eq 'REF' and | 
| 507 | 0 |  |  |  |  | 0 | ref(${$modes->{file_name_ref}}) eq 'SCALAR') { | 
| 508 | 0 |  |  |  |  | 0 | $name = $${$modes->{file_name_ref}}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 509 |  |  |  |  |  |  | } else { | 
| 510 |  |  |  |  |  |  | die 'INTERNAL ERROR: Invalid input marker: '.Dumper($modes->{file_name_ref}) | 
| 511 | 0 |  |  |  |  | 0 | ."\n"; | 
| 512 |  |  |  |  |  |  | } | 
| 513 | 2 | 50 |  |  |  | 5 | if ($replace_all) { | 
| 514 | 0 |  |  |  |  | 0 | @$content = ($name); | 
| 515 | 0 |  |  |  |  | 0 | @$markers = (0); | 
| 516 |  |  |  |  |  |  | } else { | 
| 517 | 2 |  |  |  |  | 5 | unshift @$content, $name; | 
| 518 | 2 |  |  |  |  | 7 | unshift @$markers, 0; | 
| 519 |  |  |  |  |  |  | } | 
| 520 |  |  |  |  |  |  | } | 
| 521 |  |  |  |  |  |  |  | 
| 522 |  |  |  |  |  |  | sub do_line_count { | 
| 523 | 2 |  |  | 2 | 0 | 7 | my ($content, $markers, $modes, $options) = @_; | 
| 524 | 2 |  |  |  |  | 5 | @$content = (scalar(@$content)); | 
| 525 | 2 |  |  |  |  | 6 | @$markers = (0); | 
| 526 |  |  |  |  |  |  | } | 
| 527 |  |  |  |  |  |  |  | 
| 528 |  |  |  |  |  |  | sub do_cut { | 
| 529 | 5 |  |  | 5 | 0 | 16 | my ($content, $markers, $modes, $options, $spec) = @_; | 
| 530 | 5 |  |  |  |  | 20 | my $re = prepare_re($modes->{input_field}, $modes); | 
| 531 | 5 | 50 |  |  |  | 15 | if ($options->{debug_mode}) { | 
| 532 | 5 |  |  |  |  | 15 | print "Examples of the --cut operation:\n"; | 
| 533 | 5 |  |  |  |  | 44 | my @debug = map {  [split $re] } @$content[0..min(5, $#$content)]; | 
|  | 12 |  |  |  |  | 135 |  | 
| 534 | 5 | 100 |  |  |  | 13 | map { for ((@$_)[@$spec]) { $_ = "-->${_}<--" if $_ } } @debug; | 
|  | 12 |  |  |  |  | 31 |  | 
|  | 24 |  |  |  |  | 91 |  | 
| 535 | 5 |  |  |  |  | 13 | local $, = $modes->{output_field}; | 
| 536 | 5 |  |  |  |  | 22 | local $\ = $options->{output_separator}; | 
| 537 | 5 |  |  |  |  | 10 | map { print @$_ } @debug; | 
|  | 12 |  |  |  |  | 50 |  | 
| 538 |  |  |  |  |  |  | } | 
| 539 |  |  |  |  |  |  | @$content = | 
| 540 |  |  |  |  |  |  | map { | 
| 541 | 5 | 100 |  |  |  | 12 | join $modes->{output_field}, map { $_ ? $_ : '' } (split $re)[@$spec] | 
|  | 12 |  |  |  |  | 92 |  | 
|  | 24 |  |  |  |  | 102 |  | 
| 542 |  |  |  |  |  |  | } @$content; | 
| 543 |  |  |  |  |  |  | } | 
| 544 |  |  |  |  |  |  |  | 
| 545 |  |  |  |  |  |  | sub do_paste { | 
| 546 | 0 |  |  | 0 | 0 | 0 | my ($content, $markers, $modes, $options, $file) = @_; | 
| 547 | 0 |  |  |  |  | 0 | my ($side_content, undef) = read_side_input($file, $options); | 
| 548 | 0 |  |  |  |  | 0 | for my $i (0 .. $#$side_content) { | 
| 549 | 0 |  |  |  |  | 0 | $content->[$i] .= $modes->{output_field}.$side_content->[$i]; | 
| 550 |  |  |  |  |  |  | } | 
| 551 | 0 |  |  |  |  | 0 | for my $i ($#$side_content + 1 .. $#$content) { | 
| 552 | 0 |  |  |  |  | 0 | $content->[$i] .= $modes->{output_field}; | 
| 553 |  |  |  |  |  |  | } | 
| 554 | 0 | 0 |  |  |  | 0 | if (@$content > @$markers) { | 
| 555 | 0 |  |  |  |  | 0 | splice @$markers, scalar(@$markers), 0, (0) x (@$content - @$markers); | 
| 556 |  |  |  |  |  |  | } | 
| 557 |  |  |  |  |  |  | } | 
| 558 |  |  |  |  |  |  |  | 
| 559 |  |  |  |  |  |  | sub do_pivot { | 
| 560 | 6 |  |  | 6 | 0 | 17 | my ($content, $markers, $modes, $options, $action) = @_; | 
| 561 | 6 |  |  |  |  | 10 | my $m = 0; | 
| 562 |  |  |  |  |  |  | # This is unused by the 'pivot' action, but it's not a huge issue. | 
| 563 | 6 |  |  |  |  | 24 | my $re = prepare_re($modes->{input_field}, $modes); | 
| 564 | 6 | 100 |  |  |  | 31 | if ($action eq 'transpose') { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 565 | 2 |  |  |  |  | 4 | $m = 0; | 
| 566 |  |  |  |  |  |  | my @lines = | 
| 567 | 2 |  |  |  |  | 4 | map { my $r = [split $re]; $m = max($m, scalar(@$r)); $r } @$content; | 
|  | 6 |  |  |  |  | 37 |  | 
|  | 6 |  |  |  |  | 18 |  | 
|  | 6 |  |  |  |  | 15 |  | 
| 568 |  |  |  |  |  |  | @$content = | 
| 569 |  |  |  |  |  |  | map { | 
| 570 | 2 |  |  |  |  | 6 | my $c = $_; | 
|  | 5 |  |  |  |  | 7 |  | 
| 571 | 5 |  | 100 |  |  | 10 | join $modes->{output_field}, map { $_->[$c] // '' } @lines; | 
|  | 15 |  |  |  |  | 49 |  | 
| 572 |  |  |  |  |  |  | } 0..($m - 1); | 
| 573 |  |  |  |  |  |  | } elsif ($action eq 'pivot') { | 
| 574 | 3 |  |  |  |  | 6 | $m = 1; | 
| 575 | 3 |  |  |  |  | 16 | @$content = (join $modes->{output_field}, @$content); | 
| 576 |  |  |  |  |  |  | } elsif ($action eq 'anti-pivot') { | 
| 577 | 1 |  |  |  |  | 5 | @$content = map { split $re } @$content; | 
|  | 3 |  |  |  |  | 21 |  | 
| 578 | 1 |  |  |  |  | 6 | $m = @$content; | 
| 579 |  |  |  |  |  |  | } else { | 
| 580 | 0 |  |  |  |  | 0 | die "INTERNAL ERROR: unknown action for the pivot method: ${action}\n"; | 
| 581 |  |  |  |  |  |  | } | 
| 582 | 6 |  |  |  |  | 37 | @$markers = (0) x $m; | 
| 583 |  |  |  |  |  |  | } | 
| 584 |  |  |  |  |  |  |  | 
| 585 |  |  |  |  |  |  | sub do_tee { | 
| 586 | 6 |  |  | 6 | 0 | 16 | my ($content, $markers, $modes, $options, $file_name) = @_; | 
| 587 | 6 |  |  |  |  | 22 | $file_name = maybe_interpolate($file_name, $modes, $options, 'tee'); | 
| 588 |  |  |  |  |  |  | # This missing_final_separator is not really an option, it is added in the | 
| 589 |  |  |  |  |  |  | # modes struct by the 'process' method, specifically for this function. | 
| 590 |  |  |  |  |  |  | write_side_output($file_name, $content, $modes->{missing_final_separator}, | 
| 591 | 6 |  |  |  |  | 27 | $options); | 
| 592 |  |  |  |  |  |  | } | 
| 593 |  |  |  |  |  |  |  | 
| 594 |  |  |  |  |  |  | sub do_shell { | 
| 595 | 6 |  |  | 6 | 0 | 18 | my ($content, $markers, $modes, $options, $command, $arg) = @_; | 
| 596 | 6 | 50 |  |  |  | 16 | die "INTERNAL ERROR: Unexpected command in do_tee: ${command}\n" unless $command eq 'shell'; | 
| 597 | 6 |  |  |  |  | 15 | $arg = maybe_interpolate($arg, $modes, $options, $command); | 
| 598 |  |  |  |  |  |  | { | 
| 599 | 6 |  |  |  |  | 11 | local $SIG{PIPE} = "IGNORE"; | 
|  | 6 |  |  |  |  | 136 |  | 
| 600 | 6 | 50 |  |  |  | 16550 | open(my $pipe, '|-', $arg) or die "FATAL: Cannot execute command given to --${command}: $!\n"; | 
| 601 | 6 |  |  |  |  | 308 | write_handle($pipe, $content, $modes->{missing_final_separator}, $options); | 
| 602 | 6 | 50 |  |  |  | 7791 | close $pipe or die "FATAL: Cannot close pipe for command given to --${command}: $!\n"; | 
| 603 |  |  |  |  |  |  | } | 
| 604 |  |  |  |  |  |  | } | 
| 605 |  |  |  |  |  |  |  | 
| 606 |  |  |  |  |  |  | sub do_eat { | 
| 607 | 1 |  |  | 1 | 0 | 24 | my ($content, $markers, $modes, $options) = @_; | 
| 608 | 1 |  |  |  |  | 20 | @$content = (); | 
| 609 | 1 |  |  |  |  | 37 | @$markers = (); | 
| 610 |  |  |  |  |  |  | } | 
| 611 |  |  |  |  |  |  |  | 
| 612 |  |  |  |  |  |  | 1; |