File Coverage

blib/lib/App/PTP/Commands.pm
Criterion Covered Total %
statement 415 462 89.8
branch 105 152 69.0
condition 13 18 72.2
subroutine 47 50 94.0
pod 0 31 0.0
total 580 713 81.3


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