File Coverage

blib/lib/App/PTP/Commands.pm
Criterion Covered Total %
statement 440 486 90.5
branch 117 166 70.4
condition 14 18 77.7
subroutine 50 53 94.3
pod 0 34 0.0
total 621 757 82.0


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 25     25   350 use 5.022;
  25         78  
7 25     24   308 use strict;
  24         63  
  24         463  
8 24     20   311 use warnings;
  20         67  
  20         785  
9              
10 20     20   158 no warnings 'experimental::smartmatch';
  20         53  
  20         885  
11 20     20   132 use feature 'switch';
  20         50  
  20         3345  
12              
13 20     20   9040 use App::PTP::Files qw(write_side_output read_side_input write_handle);
  20         52  
  20         1355  
14 20     20   8460 use App::PTP::PerlEnv;
  20         53  
  20         843  
15 20     20   8827 use App::PTP::Util;
  20         50  
  20         670  
16 20     20   125 use Cwd qw(abs_path);
  20         57  
  20         874  
17 20     20   109 use Data::Dumper;
  20         37  
  20         781  
18 20     20   103 use Exporter 'import';
  20         45  
  20         576  
19 20     20   11196 use File::Spec::Functions qw(rel2abs);
  20         19210  
  20         8356  
20 20     20   1788 use List::Util qw(min max);
  20         1762  
  20         5678  
21 20     20   20931 use Safe;
  20         752650  
  20         63773  
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 62 my ($options) = @_;
51 31 100 100     118 if ($safe and $options->{preserve_perl_env} and
      66        
52             $safe->reval('$PerlEnv_LOADED')) {
53 2 50       1020 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         138 $safe = Safe->new();
58 29         29426 $safe->share_from('App::PTP::PerlEnv', \@App::PTP::PerlEnv::EXPORT_OK);
59 29 100       6652 if ($options->{use_safe} > 1) {
60 14         54 $safe->permit_only(qw(:base_core :base_mem :base_loop :base_math :base_orig
61             :load));
62 14         144 $safe->deny(qw(tie untie bless));
63             } else {
64 15         44 $safe->deny_only(qw(:subprocess :ownprocess :others :dangerous));
65             }
66 29 50       237 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 121     121 0 242 my ($options) = @_;
76 121 50 66     340 if ($options->{preserve_perl_env} and $App::PTP::SafeEnv::PerlEnv_LOADED) {
77 2 50       8 print "Skipping reset of perl environment.\n" if $options->{debug_mode};
78 2         5 return;
79             }
80 119 50       383 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 119         304 %App::PTP::SafeEnv:: = ();
86 119         204 eval {
87             package App::PTP::SafeEnv;
88 119         16518 App::PTP::PerlEnv->import(':all');
89 119         445 our $PerlEnv_LOADED = 1; # For some reason the import does not work
90             };
91 119 50       404 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 138     138 0 3030354 undef $safe;
101 138         4637 %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 152     152 0 322 my ($file_name, $options) = @_;
108 152 100       355 if (ref($file_name)) {
109 132         571 $f_setter->set('-');
110 132         354 $F_setter->set('-');
111             } else {
112 20         83 $f_setter->set($file_name);
113 20         538 $F_setter->set(abs_path($file_name));
114             }
115 152 100       430 if ($options->{use_safe} > 0) {
116 31         98 new_safe($options);
117             } else {
118 121         300 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 152     152 0 380 my ($file_name, $pipeline, $options, $content, $missing_final_separator) = @_;
127 152 50       429 if ($options->{debug_mode}) {
128             # For long files, we print only the first and last lines.
129 152         486 my @debug_content = @$content;
130 152         753 my $omit_msg = sprintf "... (%d lines omitted)", (@$content - 8);
131 152 100       438 splice @debug_content, 4, -4, $omit_msg if @$content > 10;
132 152 100       386 if (ref($file_name)) {
133 132         549 print "Processing $${file_name} with content: ".Dumper(\@debug_content);
134             } else {
135 20         103 print "Processing '${file_name}' with content: ".Dumper(\@debug_content);
136             }
137 152 100       9523 print "Has final separator: ".($missing_final_separator ? 'false' : 'true')."\n";
138             }
139 152         478 prepare_perl_env($file_name, $options);
140 152         521 @markers = (0) x scalar(@$content);
141 152         286 my $markers = \@markers;
142 152         352 for my $stage (@$pipeline) {
143 204         1521 my ($command, $code, $modes, @args) = @$stage;
144 204         826 $N_setter->set(scalar(@$content));
145 204         465 $modes->{missing_final_separator} = $missing_final_separator;
146 204         461 $modes->{file_name_ref} = \$_[0]; # this is an alias to the passed value.
147 204 50       605 if ($options->{debug_mode}) {
148 204         382 local $Data::Dumper::Indent = 0;
149             printf "Executing command: %s(%s).\n", $command,
150 204         479 join(', ', map { Dumper($_) } @args);
  308         5569  
151             }
152 204         8469 &$code($content, $markers, $modes, $options, @args);
153             }
154             }
155              
156             sub base_prepare_re {
157 12     12 0 24 my ($re, $modes) = @_;
158 12 50       36 if ($modes->{quote_regex}) {
159 0         0 $re = quotemeta($re);
160             }
161 12 50       34 if (not $modes->{case_sensitive}) {
162 0         0 $re = '(?i)'.$re;
163             }
164 12         28 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 12     12 0 25 my ($re, $modes) = @_;
172 12         33 $re = base_prepare_re($re, $modes);
173 12         34 my $r;
174 12 50       36 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 12         186 $r = qr/$re/s;
189             }
190 12         42 return $r;
191             }
192              
193             sub quote_for_re {
194 75     75 0 134 my ($text, $modes) = @_;
195 75 100       174 if ($modes->{quote_regex}) {
196 7         22 return quotemeta($text);
197             } else {
198             # We quote just the '{' or '}' characters.
199 68         311 return $text =~ s/(\{|\})/\\$1/gr;
200             }
201             }
202              
203             sub prepare_re2 {
204 56     56 0 117 my ($re, $modes) = @_;
205 56         128 $re = quote_for_re($re, $modes);
206 56 100       183 if (not $modes->{case_sensitive}) {
207 1         3 $re = '(?i)'.$re;
208             }
209 56         97 my $use_statement = '';
210 56 50       149 if ($modes->{regex_engine} ne 'perl') {
211 0         0 $use_statement = "use re::engine::$modes->{regex_engine};";
212             }
213 56         195 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 34 my ($str, $modes, $options, $command) = @_;
219 12 100       35 if (not $modes->{quote_regex}) {
220 6         61 $str = eval_in_safe_env("<<\"PTP_EOF_WORD\"\n${str}\nPTP_EOF_WORD\n", $options);
221 6 50       24 die "FATAL: Cannot eval string for --${command}: ${@}\n" if $@;
222 6         16 chomp($str);
223             }
224 12         26 return $str;
225             }
226              
227             sub do_grep {
228 37     37 0 118 my ($content, $markers, $modes, $options, $re) = @_;
229 37         92 my ($use_stmt, $quoted_re) = prepare_re2($re, $modes);
230 37 50       148 print "\$re = ${quoted_re}\n" if $options->{debug_mode};
231 37         135 my $wrapped = get_code_in_safe_env(
232             "{; ${use_stmt} undef \$_ unless m ${quoted_re} }", $options, '--grep');
233 37         114 $. = 0;
234 37         91 map { $m_setter->set(\$markers->[$.]);
  218         15102  
235 218         730 $n_setter->set($.++);
236 218         3159 $wrapped->() } @$content;
237             # This code is duplicated from do_perl:
238 37         4725 for my $i (0 .. $#$content) {
239 218 100       450 if (not defined $content->[$i]) {
    50          
240 141         213 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         120 @$content = grep { defined } @$content;
  218         400  
246 37         79 @$markers = grep { defined } @$markers;
  218         764  
247             }
248              
249             sub do_substitute {
250 19     19 0 57 my ($content, $markers, $modes, $options, $re, $subst) = @_;
251 19 50       54 if ($options->{debug_mode}) {
252 19         60 print "Before: \$re = ${re}; \$subst = ${subst}\n";
253             }
254 19         44 my ($use_stmt, $quoted_re) = prepare_re2($re, $modes);
255 19         48 my $quoted_subst = quote_for_re($subst, $modes);
256 19 100       56 my $g = $modes->{global_match} ? 'g' : '';
257 19 50       44 if ($options->{debug_mode}) {
258 19         51 print "After: \$re = ${quoted_re}; \$subst = ${quoted_subst}\n";
259             }
260 19         67 my $wrapped = get_code_in_safe_env(
261             "; ${use_stmt} s ${quoted_re}{${quoted_subst}}${g}", $options,
262             '--substitute');
263 19         55 $. = 0;
264 19         52 map { $m_setter->set(\$markers->[$.]);
  73         4393  
265 73         247 $n_setter->set($.++);
266 73         1139 $wrapped->() } @$content;
267             }
268              
269             sub warn_or_die_if_needed {
270 224     224 0 453 my ($text, $modes) = @_;
271 224 100       935 return 0 unless $@;
272 3         9 chomp($@);
273 3 50       11 if ($modes->{fatal_error}) {
274 0         0 die "FATAL: ${text}: ${@}\n";
275             } else {
276 3         17 print "WARNING: ${text}: ${@}\n";
277             }
278 3         9 return 1;
279             }
280              
281             sub eval_in_safe_env {
282 153     153 0 315 my ($code, $options) = @_;
283 153 50       412 if ($options->{debug_mode} > 1) {
284 0         0 print "Evaluating the following code: ${code}\n";
285             }
286 153 100       372 if ($options->{use_safe} > 0) {
287 61         179 return $safe->reval($code);
288             } else {
289 92     8   8410 return eval("package App::PTP::SafeEnv;
  8     8   63  
  8     8   16  
  8         288  
  8         52  
  8         16  
  8         621  
  8         69  
  8         18  
  8         227  
  8         42  
  8         55  
  8         618  
  7         62  
  7         20  
  7         223  
  7         39  
  7         15  
  7         431  
  7         66  
  7         26  
  7         252  
  7         38  
  7         16  
  7         632  
  4         33  
  4         21  
  4         108  
  4         25  
  4         9  
  4         323  
  4         32  
  4         9  
  4         164  
  4         23  
  4         8  
  4         281  
  4         33  
  4         9  
  4         176  
  4         24  
  4         56  
  4         391  
  4         86  
  4         11  
  4         122  
  4         23  
  4         8  
  4         314  
  4         28  
  4         11  
  4         139  
  4         22  
  4         10  
  4         315  
  4         31  
  4         9  
  4         107  
  4         23  
  4         12  
  4         298  
  4         32  
  4         9  
  4         105  
  4         20  
  4         9  
  4         297  
  3         22  
  3         6  
  3         85  
  3         16  
  3         5  
  3         151  
  3         37  
  3         7  
  3         83  
  3         24  
  3         7  
  3         257  
  9         75  
  9         43  
  9         276  
  9         96  
  9         33  
  9         621  
  1         12  
  1         3  
  1         23  
  1         8  
  1         2  
  1         71  
  1         7  
  1         7  
  1         28  
290             no strict;
291             no warnings;
292             ${code}");
293             }
294             }
295              
296             sub get_code_in_safe_env {
297 123     123 0 299 my ($code, $options, $cmd) = @_;
298 123         338 my $wrapped_code = eval_in_safe_env("sub { ${code} }", $options);
299 123 50       28948 die "FATAL: Cannot wrap code for ${cmd}: ${@}" if $@;
300 123         284 return $wrapped_code;
301             }
302              
303             sub do_perl {
304 62     62 0 190 my ($content, $markers, $modes, $options, $cmd, $code) = @_;
305 62         159 $. = 0;
306 62         320 my $scmd = '-'.($cmd =~ s/^(..)/-$1/r); # --perl or -n.
307 62         165 my $wrapped_code = get_code_in_safe_env($code, $options, $scmd);
308             my @result = map {
309 62         146 $m_setter->set(\$markers->[$.]);
  209         868  
310 209         661 $n_setter->set(++$.);
311 209         341 my $input = $_;
312             # Among other things, this ensures that the code is always executed in
313             # a scalar context.
314 209         285 my $r = eval { $wrapped_code->() };
  209         2527  
315             # We can't use return as we're not in a sub.
316 209 100       28656 if (warn_or_die_if_needed("Perl code failed in ${scmd}", $modes)) {
317 3         5 given ($cmd) {
318 3         12 1 when 'filter';
319 2         7 $input when 'n';
320 1         4 $markers[$.] when 'mark-line';
321             }
322             } else {
323 206         644 $r;
324             }
325             } @$content;
326              
327 62         203 $n_setter->set(undef);
328 62         186 $m_setter->set(\undef);
329              
330 62 100       223 if ($cmd eq 'perl') {
    100          
    100          
    50          
331             # Do nothing with the result.
332             } elsif ($cmd eq 'n') {
333 30         100 @$content = @result;
334             } elsif ($cmd eq 'filter') {
335 6         62 for my $i (0 .. $#$content) {
336 23 100 100     102 if (!$result[$i] xor $modes->{inverse_match}) {
337 12         26 undef $content->[$i];
338             }
339             }
340             } elsif ($cmd eq 'mark-line') {
341 7         23 @$markers = @result;
342             } else {
343 0         0 die "FATAL: Invalid command received for perl operation ($cmd).\n";
344             }
345              
346 62         180 for my $i (0 .. $#$content) {
347 209 100       510 if (not defined $content->[$i]) {
    100          
348 12         25 undef $markers->[$i];
349             } elsif (not defined $markers->[$i]) {
350             # We don't want undef here, as we will filter on it.
351             # Note that the $m and @m variables shared with the environment try to
352             # prevent the user from setting undef in them. But we don't trust that
353             # Too much.
354 6         22 $markers->[$i] = '';
355             }
356             }
357 62         122 @$content = grep { defined } @$content;
  209         437  
358 62         109 @$markers = grep { defined } @$markers;
  209         1026  
359             }
360              
361             sub do_execute {
362 21     21 0 77 my ($content, $markers, $modes, $options, $cmd, $code) = @_;
363 21 100       71 $code = "use $code;" if $cmd eq 'M';
364 21         65 eval_in_safe_env($code, $options);
365 21 100       23763 if ($@) {
366 2         23 chomp($@);
367 2         20 my $scmd = '-'.($cmd =~ s/^(..)/-$1/r); # --execute or -M.
368 2         41 die "FATAL: Perl code failed in ${scmd}: ${@}\n";
369             }
370             }
371              
372             sub do_load {
373 3     3 0 12 my ($content, $markers, $modes, $options, $file) = @_;
374             # do can open relative paths, but in that case it looks them up in the @INC
375             # directory, which we want to avoid.
376             # We don't use abs_path here to not die (just yet) if the file does not exist.
377 3         13 my $abs_path = rel2abs($file);
378 3 50       179 print "Loading file: '$abs_path'\n" if $options->{debug_mode};
379 3 50       15 if (not defined eval_in_safe_env("do '${abs_path}';", $options)) {
380 0 0       0 if ($@) {
    0          
381 0         0 die "FATAL: Perl code failed in --load: ${@}\n";
382             } elsif ($!) {
383 0         0 die "FATAL: Cannot load file '$file' for --load: $!\n";
384             }
385             }
386             }
387              
388             sub do_sort {
389 10     10 0 79 my ($content, $markers, $modes, $options) = @_;
390 10 50       56 if (ref($modes->{comparator}) eq 'CODE') {
    100          
391             # This branch is no longer used.
392 0         0 @$content = sort { $modes->{comparator}() } @$content;
  0         0  
393             } elsif (ref($modes->{comparator}) eq 'SCALAR') {
394 8 100       17 if (${$modes->{comparator}} eq 'default') {
  8 100       31  
    50          
395 6         45 @$content = sort @$content;
396 2         14 } elsif (${$modes->{comparator}} eq 'numeric') {
397 20     20   208 no warnings "numeric";
  20         42  
  20         1739  
398 1         6 @$content = sort { $a <=> $b } @$content;
  5         18  
399 1         6 } elsif (${$modes->{comparator}} eq 'locale') {
400 20     20   8885 use locale;
  20         10444  
  20         110  
401 1         8 @$content = sort { $a cmp $b } @$content;
  21         36  
402             } else {
403             die sprintf "INTERNAL ERROR: Invalid comparator (%s)\n",
404 0         0 ${$modes->{comparator}};
  0         0  
405             }
406             } else {
407             die sprintf "INTERNAL ERROR: Invalid comparator type (%s)\n",
408 2 50       7 Dumper($modes->{comparator}) if ref $modes->{comparator};
409 2         6 my $cmp = $modes->{comparator};
410 2         8 my $sort = get_code_in_safe_env("sort { $cmp } \@_", $options,
411             'custom comparator');
412 2         69 @$content = $sort->(@$content);
413             }
414 10         58 @$markers = (0) x scalar(@$content);
415             }
416              
417             sub do_list_op {
418 6     6 0 30 my ($content, $markers, $modes, $options, $sub, $apply_on_markers) = @_;
419 6 50       43 if ($apply_on_markers eq 'none') {
    50          
    50          
420 0         0 @$content = &$sub(@$content);
421 0         0 @$markers = (0) x scalar(@$content);
422             } elsif ($apply_on_markers eq 'same') {
423 0         0 @$content = &$sub(@$content);
424 0         0 @$markers = &$sub(@$markers);
425             } elsif ($apply_on_markers eq 'together' ) {
426 6         26 &$sub($content, $markers);
427             } else {
428 0         0 die "INTERNAL ERROR: Invalid value for \$apply_on_markers passed to do_list_op: $apply_on_markers\n";
429             }
430             }
431              
432             sub do_tail {
433 4     4 0 11 my ($content, $markers, $modes, $options, $len) = @_;
434 4 100       11 $len = 10 unless $len;
435 4         12 splice @$content, 0, -$len;
436 4         18 splice @$markers, 0, -$len;
437             }
438              
439             sub do_head {
440 4     4 0 10 my ($content, $markers, $modes, $options, $len) = @_;
441 4 100       24 $len = 10 unless $len;
442 4 100       15 $len = -@$content if $len < -@$content;
443 4         10 splice @$content, $len;
444 4         16 splice @$markers, $len;
445             }
446              
447             sub do_delete_marked {
448             # negative offset if we're deleting a line before the marker.
449 3     3 0 14 my ($content, $markers, $modes, $options, $offset) = @_;
450 3         27 my $start = min(max(0, -$offset), $#$content);
451 3         17 my $end = max(min($#$content, $#$content + $offset), $#$content);
452 3         9 my @markers_temp = @$markers; # So that we can read it even after an undef.
453 3         9 for my $i ($start .. $end) {
454 12 100       27 if ($markers_temp[$i]) {
455 6         14 undef $content->[$i + $offset];
456 6         10 undef $markers->[$i + $offset];
457             }
458             }
459 3         12 @$content = grep { defined } @$content;
  12         27  
460 3         7 @$markers = grep { defined } @$markers;
  12         29  
461             }
462              
463             sub do_insert_marked {
464             # negative offset if we're inserting a line before the marker.
465 3     3 0 22 my ($content, $markers, $modes, $options, $offset, $line) = @_;
466 3         9 my @markers_temp = @$markers;
467 3         9 my @content_temp = @$content;
468 3         5 my $added = 0;
469 3         7 my $wrapped;
470 3 50       13 if (not $modes->{quote_regex}) {
471 3         39 $wrapped = get_code_in_safe_env("\"${line}\"", $options,
472             '--insert-marked');
473             }
474 3         16 for my $i (0 .. $#content_temp) {
475 12 100       76 next unless $markers_temp[$i];
476 6         13 my $r;
477 6 50       16 if ($modes->{quote_regex}) {
478 0         0 $r = $line;
479             } else {
480 6         15 $_ = $content_temp[$i];
481 6         25 $n_setter->set($i + 1);
482 6         22 $m_setter->set($markers_temp[$i]);
483 6         18 $line =~ s/(?:[^\\]|^)((:?\\\\)*")/\\$1/g;
484 6         10 $r = eval { $wrapped->() };
  6         56  
485 6 50       1192 next if warn_or_die_if_needed(
486             'String interpolation failed in --insert-marked', $modes);
487             # it should not be possible for the result to be undef...
488             }
489             # We never insert at a negative offset or before the previously added line.
490             # Offset = 0 means we're inserting after the current line.
491 6         15 my $target = $i + $offset + 1 + $added;
492 6 50       48 $target = $added if $target < $added;
493 6 50       20 $target = @$content if $target > @$content;
494 6         8 ++$added;
495 6         19 splice @$content, $target, 0, $r;
496 6         12 splice @$markers, $target, 0, 0;
497             }
498             }
499              
500             sub do_set_markers {
501 0     0 0 0 my ($content, $markers, $modes, $options, $value) = @_;
502 0         0 @$markers = ($value)x scalar(@$content);
503             }
504              
505             sub do_number_lines {
506 0     0 0 0 my ($content, $markers, $modes, $options) = @_;
507 0         0 my $line = 0;
508 0         0 my $n = int(log(@$content) / log(10)) + 1;
509 0         0 map { $_ = sprintf("%${n}d %s", ++$line, $_) } @$content;
  0         0  
510             }
511              
512             sub do_file_name {
513 5     5 0 16 my ($content, $markers, $modes, $options, $replace_all) = @_;
514 5         8 my $name;
515 5 100 33     31 if (ref($modes->{file_name_ref}) eq 'SCALAR') {
    50          
516 2         3 $name = ${$modes->{file_name_ref}};
  2         17  
517             } elsif (ref($modes->{file_name_ref}) eq 'REF' and
518 3         14 ref(${$modes->{file_name_ref}}) eq 'SCALAR') {
519 3         4 $name = $${$modes->{file_name_ref}};
  3         7  
520             } else {
521             die 'INTERNAL ERROR: Invalid input marker: '.Dumper($modes->{file_name_ref})
522 0         0 ."\n";
523             }
524 5 100       17 if ($replace_all) {
525             # Does nothing to empty file.
526 2 100       13 if (@$content) {
527 1         4 @$content = ($name);
528 1         6 @$markers = (0);
529             }
530             } else {
531 3         9 unshift @$content, $name;
532 3         12 unshift @$markers, 0;
533             }
534             }
535              
536             sub do_line_count {
537 2     2 0 14 my ($content, $markers, $modes, $options) = @_;
538 2         6 @$content = (scalar(@$content));
539 2         6 @$markers = (0);
540             }
541              
542             sub do_cut {
543 5     5 0 28 my ($content, $markers, $modes, $options, $spec) = @_;
544 5         17 my $re = prepare_re($modes->{input_field}, $modes);
545 5 50       16 if ($options->{debug_mode}) {
546 5         23 print "Examples of the --cut operation:\n";
547 5         39 my @debug = map { [split $re] } @$content[0..min(5, $#$content)];
  12         95  
548 5 100       16 map { for ((@$_)[@$spec]) { $_ = "-->${_}<--" if $_ } } @debug;
  12         32  
  24         74  
549 5         17 local $, = $modes->{output_field};
550 5         21 local $\ = $options->{output_separator};
551 5         13 map { print @$_ } @debug;
  12         45  
552             }
553             @$content =
554             map {
555 5 100       14 join $modes->{output_field}, map { $_ ? $_ : '' } (split $re)[@$spec]
  12         85  
  24         108  
556             } @$content;
557             }
558              
559             sub do_paste {
560 0     0 0 0 my ($content, $markers, $modes, $options, $file) = @_;
561 0         0 my ($side_content, undef) = read_side_input($file, $options);
562 0         0 for my $i (0 .. $#$side_content) {
563 0         0 $content->[$i] .= $modes->{output_field}.$side_content->[$i];
564             }
565 0         0 for my $i ($#$side_content + 1 .. $#$content) {
566 0         0 $content->[$i] .= $modes->{output_field};
567             }
568 0 0       0 if (@$content > @$markers) {
569 0         0 splice @$markers, scalar(@$markers), 0, (0) x (@$content - @$markers);
570             }
571             }
572              
573             sub do_pivot {
574 7     7 0 24 my ($content, $markers, $modes, $options, $action) = @_;
575 7         15 my $m = 0;
576             # This is unused by the 'pivot' action, but it's not a huge issue.
577 7         23 my $re = prepare_re($modes->{input_field}, $modes);
578 7 100       41 if ($action eq 'transpose') {
    100          
    50          
579 2         5 $m = 0;
580             my @lines =
581 2         5 map { my $r = [split $re]; $m = max($m, scalar(@$r)); $r } @$content;
  6         35  
  6         17  
  6         15  
582             @$content =
583             map {
584 2         6 my $c = $_;
  5         7  
585 5   100     10 join $modes->{output_field}, map { $_->[$c] // '' } @lines;
  15         50  
586             } 0..($m - 1);
587             } elsif ($action eq 'pivot') {
588 4         18 $m = 1;
589 4         26 @$content = (join $modes->{output_field}, @$content);
590             } elsif ($action eq 'anti-pivot') {
591 1         3 @$content = map { split $re } @$content;
  3         24  
592 1         3 $m = @$content;
593             } else {
594 0         0 die "INTERNAL ERROR: unknown action for the pivot method: ${action}\n";
595             }
596 7         55 @$markers = (0) x $m;
597             }
598              
599             sub do_tee {
600 6     6 0 18 my ($content, $markers, $modes, $options, $file_name) = @_;
601 6         14 $file_name = maybe_interpolate($file_name, $modes, $options, 'tee');
602             # This missing_final_separator is not really an option, it is added in the
603             # modes struct by the 'process' method, specifically for this function.
604             write_side_output($file_name, $content, $modes->{missing_final_separator},
605 6         21 $options);
606             }
607              
608             sub do_shell {
609 6     6 0 20 my ($content, $markers, $modes, $options, $command, $arg) = @_;
610 6 50       17 die "INTERNAL ERROR: Unexpected command in do_tee: ${command}\n" unless $command eq 'shell';
611 6         56 $arg = maybe_interpolate($arg, $modes, $options, $command);
612             {
613 6         12 local $SIG{PIPE} = "IGNORE";
  6         125  
614 6 50       14695 open(my $pipe, '|-', $arg) or die "FATAL: Cannot execute command given to --${command}: $!\n";
615 6         320 write_handle($pipe, $content, $modes->{missing_final_separator}, $options);
616             # When run by CPAN testers, this fails sometime for unknown reason. So this
617             # is only a warning and not a fatal error.
618 6 50       10704 close $pipe or print "WARNING: Cannot close pipe for command given to --${command}: $!\n";
619             }
620             }
621              
622             sub do_eat {
623 1     1 0 21 my ($content, $markers, $modes, $options) = @_;
624 1         21 @$content = ();
625 1         30 @$markers = ();
626             }
627              
628             1;