File Coverage

blib/lib/App/Greple/tee.pm
Criterion Covered Total %
statement 20 62 32.2
branch 0 16 0.0
condition 0 4 0.0
subroutine 7 13 53.8
pod 0 5 0.0
total 27 100 27.0


line stmt bran cond sub pod time code
1             =encoding utf-8
2              
3             =head1 NAME
4              
5             App::Greple::tee - module to replace matched text by the external command result
6              
7             =head1 SYNOPSIS
8              
9             greple -Mtee command -- ...
10              
11             =head1 DESCRIPTION
12              
13             Greple's B<-Mtee> module sends matched text part to the given filter
14             command, and replace them by the command result. The idea is derived
15             from the command called B. It is like bypassing partial data to
16             the external filter command.
17              
18             Filter command follows module declaration (C<-Mtee>) and terminates by
19             two dashes (C<-->). For example, next command call command C
20             command with C arguments for the matched word in the data.
21              
22             greple -Mtee tr a-z A-Z -- '\w+' ...
23              
24             Above command convert all matched words from lower-case to upper-case.
25             Actually this example itself is not so useful because B can do
26             the same thing more effectively with B<--cm> option.
27              
28             By default, the command is executed as a single process, and all
29             matched data is sent to it mixed together. If the matched text does
30             not end with newline, it is added before and removed after. Data are
31             mapped line by line, so the number of lines of input and output data
32             must be identical.
33              
34             Using B<--discrete> option, individual command is called for each
35             matched part. You can tell the difference by following commands.
36              
37             greple -Mtee cat -n -- copyright LICENSE
38             greple -Mtee cat -n -- copyright LICENSE --discrete
39              
40             Lines of input and output data do not have to be identical when used
41             with B<--discrete> option.
42              
43             =head1 OPTIONS
44              
45             =over 7
46              
47             =item B<--discrete>
48              
49             Invoke new command individually for every matched part.
50              
51             =back
52              
53             =head1 WHY DO NOT USE TEIP
54              
55             First of all, whenever you can do it with the B command, use
56             it. It is an excellent tool and much faster than B.
57              
58             Because B is designed to process document files, it has many
59             features that are appropriate for it, such as match area controls. It
60             might be worth using B to take advantage of those features.
61              
62             Also, B cannot handle multiple lines of data as a single unit,
63             while B can execute individual commands on a data chunk
64             consisting of multiple lines.
65              
66             =head1 EXAMPLE
67              
68             Next command will find text blocks inside L style document
69             included in Perl module file.
70              
71             greple --inside '^=(?s:.*?)(^=cut|\z)' --re '^(\w.+\n)+' tee.pm
72              
73             You can translate them by DeepL service by executing the above command
74             convined with B<-Mtee> module which calls B command like this:
75              
76             greple -Mtee deepl text --to JA - -- --discrete ...
77              
78             Because B works better for single line input, you can change
79             command part as this:
80              
81             sh -c 'perl -00pE "s/\s+/ /g" | deepl text --to JA -'
82              
83             The dedicated module L is more effective
84             for this purpose, though. In fact, the implementation hint of B
85             module came from B module.
86              
87             =head1 EXAMPLE 2
88              
89             Next command will find some indented part in LICENSE document.
90              
91             greple --re '^[ ]{2}[a-z][)] .+\n([ ]{5}.+\n)*' -C LICENSE
92              
93             a) distribute a Standard Version of the executables and library files,
94             together with instructions (in the manual page or equivalent) on where to
95             get the Standard Version.
96            
97             b) accompany the distribution with the machine-readable source of the Package
98             with your modifications.
99            
100             You can reformat this part by using B module with B
101             command:
102              
103             greple -Mtee ansifold -rsw40 --prefix ' ' -- --discrete --re ...
104              
105             a) distribute a Standard Version of
106             the executables and library files,
107             together with instructions (in the
108             manual page or equivalent) on where
109             to get the Standard Version.
110            
111             b) accompany the distribution with the
112             machine-readable source of the
113             Package with your modifications.
114            
115             =head1 INSTALL
116              
117             =head2 CPANMINUS
118              
119             $ cpanm App::Greple::tee
120              
121             =head1 SEE ALSO
122              
123             L, L
124              
125             L
126              
127             L, L
128              
129             L
130              
131             L
132              
133             =head1 AUTHOR
134              
135             Kazumasa Utashiro
136              
137             =head1 LICENSE
138              
139             Copyright © 2023 Kazumasa Utashiro.
140              
141             This library is free software; you can redistribute it and/or modify
142             it under the same terms as Perl itself.
143              
144             =cut
145              
146             package App::Greple::tee;
147              
148             our $VERSION = "0.04";
149              
150 1     1   758 use v5.14;
  1         3  
151 1     1   5 use warnings;
  1         2  
  1         25  
152 1     1   6 use Carp;
  1         2  
  1         60  
153 1     1   6 use List::Util qw(sum first);
  1         3  
  1         106  
154 1     1   496 use Text::ParseWords qw(shellwords);
  1         1265  
  1         56  
155 1     1   432 use App::cdif::Command;
  1         25190  
  1         35  
156 1     1   8 use Data::Dumper;
  1         2  
  1         793  
157              
158             our $command;
159             our $blockmatch;
160             our $discrete;
161              
162             my @jammed;
163             my($mod, $argv);
164              
165             sub initialize {
166 0     0 0   ($mod, $argv) = @_;
167 0 0   0     if (defined (my $i = first { $argv->[$_] eq '--' } 0 .. $#{$argv})) {
  0            
  0            
168 0 0         if (my @command = splice @$argv, 0, $i) {
169 0           $command = \@command;
170             }
171 0           shift @$argv;
172             }
173             }
174              
175             sub call {
176 0     0 0   my $data = shift;
177 0   0       $command // return $data;
178 0           state $exec = App::cdif::Command->new;
179 0 0         if (ref $command ne 'ARRAY') {
180 0           $command = [ shellwords $command ];
181             }
182 0           $exec->command($command)->setstdin($data)->update->data;
183             }
184              
185             sub jammed_call {
186 0     0 0   my @need_nl = grep { $_[$_] !~ /\n\z/ } 0 .. $#_;
  0            
187 0           my @from = @_;
188 0           $from[$_] .= "\n" for @need_nl;
189 0           my @lines = map { int tr/\n/\n/ } @from;
  0            
190 0           my $from = join '', @from;
191 0           my $out = call $from;
192 0           my @out = $out =~ /.*\n/g;
193 0 0         if (@out < sum @lines) {
194 0           die "Unexpected response from command:\n\n$out\n";
195             }
196 0           my @to = map { join '', splice @out, 0, $_ } @lines;
  0            
197 0           $to[$_] =~ s/\n\z// for @need_nl;
198 0           return @to;
199             }
200              
201             sub postgrep {
202 0     0 0   my $grep = shift;
203 0           @jammed = my @block = ();
204 0 0         if ($blockmatch) {
205             $grep->{RESULT} = [
206             [ [ 0, length ],
207             map {
208 0           [ $_->[0][0], $_->[0][1], 0, $grep->{callback} ]
  0            
209             } $grep->result
210             ] ];
211             }
212 0 0         return if $discrete;
213 0           for my $r ($grep->result) {
214 0           my($b, @match) = @$r;
215 0           for my $m (@match) {
216 0           push @block, $grep->cut(@$m);
217             }
218             }
219 0 0         @jammed = jammed_call @block if @block;
220             }
221              
222             sub callback {
223 0 0   0 0   if ($discrete) {
224 0           call { @_ }->{match};
225             }
226             else {
227 0   0       shift @jammed // die;
228             }
229             }
230              
231             1;
232              
233             __DATA__