File Coverage

blib/lib/App/watchdiff.pm
Criterion Covered Total %
statement 52 131 39.6
branch 1 52 1.9
condition 1 21 4.7
subroutine 19 28 67.8
pod 0 7 0.0
total 73 239 30.5


line stmt bran cond sub pod time code
1             package App::watchdiff;
2              
3             ##
4             ## watchdiff: watch difference
5             ##
6             ## Copyright 2014- Kazumasa Utashiro
7             ##
8             ## Original version on Feb 15 2014
9             ##
10              
11 1     1   329692 use v5.14;
  1         4  
12 1     1   5 use warnings;
  1         2  
  1         64  
13              
14 1     1   427 use open ":std" => ":encoding(utf8)";
  1         1587  
  1         5  
15 1     1   1756 use Fcntl;
  1         2  
  1         280  
16 1     1   991 use Pod::Usage;
  1         57968  
  1         156  
17 1     1   717 use Data::Dumper;
  1         8901  
  1         126  
18              
19 1     1   11 use List::Util qw(pairmap);
  1         2  
  1         61  
20              
21 1     1   505 use App::sdif;
  1         3  
  1         64  
22             my $version = $App::sdif::VERSION;
23              
24             my $app;
25              
26 1     1   586 use Getopt::EX::Hashed 'has'; {
  1         8555  
  1         7  
27              
28             Getopt::EX::Hashed->configure(DEFAULT => [ is => 'rw' ]);
29              
30             has help => ' h ' ;
31             has version => ' ' ;
32             has debug => ' d ' ;
33             has unit => ' by :s ' ;
34             has diff => ' =s ' ;
35             has exec => ' e =s@ ' , default => [] ;
36             has refresh => ' r :1 ' , default => 1 ;
37             has interval => ' i =i ' , default => 2 ;
38             has count => ' c =i ' , default => 1000 ;
39             has clear => ' ! ' , default => 1 ;
40             has silent => ' s ! ' , default => 0 ;
41             has mark => ' M ! ' , default => 0 ;
42             has verbose => ' V ! ' , default => undef ;
43             has old => ' O ! ' , default => 0 ;
44             has date => ' D ! ' , default => 1 ;
45             has newline => ' N ! ' , default => 1 ;
46             has context => ' C :2 ' , default => 999, alias => 'U';
47             has scroll => ' S ' , default => 1 ;
48             has colormap => ' cm =s@ ' , default => [] ;
49             has plain => ' p ' ,
50             action => sub {
51             $_->date = $_->newline = 0;
52             };
53              
54             has '+help' => action => sub {
55             pod2usage
56             -verbose => 99,
57             -sections => [ qw(SYNOPSIS VERSION) ];
58             };
59              
60             has '+version' => action => sub {
61             print "$version\n";
62             exit;
63             };
64              
65 1     1   382 } no Getopt::EX::Hashed;
  1         2  
  1         4  
66              
67             my %colormap = qw(
68             APPEND K/544
69             DELETE K/544
70             OCHANGE K/445
71             NCHANGE K/445
72             OTEXT K/455E
73             NTEXT K/554E
74             );
75              
76 1     1   748 use Term::ANSIColor::Concise qw(ansi_code csi_code);
  1         66960  
  1         213  
77             my %termcap = pairmap { $a => ansi_code($b) }
78             qw(
79             home {CUP}
80             clear {CUP}{ED2}
81             el {EL}
82             ed {ED}
83             decsc {DECSC}
84             decrc {DECRC}
85             );
86              
87             sub run {
88 0     0 0 0 $app = my $opt = shift;
89 0         0 local @ARGV = @_;
90              
91 1     1   651 use Getopt::EX::Long;
  1         43780  
  1         212  
92 0         0 Getopt::Long::Configure(qw(bundling require_order));
93 0 0       0 $opt->getopt or usage({status => 1});
94              
95 0 0 0     0 if ($opt->context and $opt->context < 100) {
96 0   0     0 $opt->verbose //= 1;
97             }
98              
99 1     1   9 use Getopt::EX::Colormap;
  1         2  
  1         471  
100             my $cm = Getopt::EX::Colormap
101             ->new(HASH => \%colormap)
102 0         0 ->load_params(@{$opt->colormap});
  0         0  
103              
104 0 0       0 if (@ARGV) {
105 0         0 push @{$opt->exec}, [ @ARGV ];
  0         0  
106             } else {
107 0 0       0 @{$opt->exec} or pod2usage();
  0         0  
108             }
109              
110 0         0 setup_terminal();
111 0     0   0 $SIG{INT} = sub { exit };
  0         0  
112 0         0 $opt->do_loop();
113             }
114              
115             END {
116 1     1   2927 reset_terminal();
117             }
118              
119             sub control_scroll {
120 0     0 0 0 my $opt = shift;
121 0 0 0     0 $opt->scroll && $opt->date && $opt->refresh == 1;
122             }
123              
124             sub setup_terminal {
125 0 0 0 0 0 0 if ($app and $app->control_scroll) {
126 0         0 STDOUT->printflush(csi_code(STBM => 3, 999));
127             }
128             }
129              
130             sub reset_terminal {
131 1 50 33 1 0 43 if ($app and $app->control_scroll) {
132             STDOUT->printflush($termcap{decsc},
133             csi_code(STBM =>),
134 0           $termcap{decrc});
135             }
136             }
137              
138             sub do_loop {
139 0     0 0   my $opt = shift;
140              
141 1     1   867 use Command::Run;
  1         28419  
  1         153  
142 0           my $old = Command::Run->new(@{$opt->exec->[0]});
  0            
143 0           my $new = Command::Run->new(@{$opt->exec->[0]});
  0            
144              
145             my @default_diff = (
146             qw(cdif --no-unknown),
147 0           map { ('--cm', "$_=$colormap{$_}") } sort keys %colormap
  0            
148             );
149              
150 0           my @diffcmd = do {
151 0 0         if ($opt->diff) {
152 1     1   12 use Text::ParseWords;
  1         2  
  1         856  
153 0           shellwords $opt->diff;
154             } else {
155             ( @default_diff,
156 0 0         map { ref $_->[1] eq 'CODE' ? $_->[1]->() : $_->[1] }
157 0           grep { $_->[0] }
158 0     0     [ defined $opt->unit => sub { '--unit=' . $opt->unit } ],
159 0     0     [ defined $opt->context => sub { '-U' . $opt->context } ],
  0            
160             [ ! $opt->verbose => '--no-command' ],
161             [ ! $opt->mark => '--no-mark' ],
162             [ ! $opt->old => '--no-old' ],
163             );
164             }
165             };
166              
167 0 0         print $termcap{clear} if $opt->refresh;
168 0           my $count = 0;
169 0           my $refresh_count = 0;
170 0           while (1) {
171 0           $old->rewind;
172 0           $new->update;
173 0   0       my $data = execute(@diffcmd, $old->path, $new->path) // die "diff: $!\n";
174 0 0         if ($data eq '') {
175 0 0         if ($opt->silent) {
176 0           flush($new->date, "\r");
177 0           next;
178             }
179 0           $data = $new->data;
180 0 0         $data =~ s/^/ /mg if $opt->mark;
181             }
182 0 0         $data .= "\n" if $opt->newline;
183 0 0         if ($opt->refresh) {
184 0           $data =~ s/^/$termcap{el}/mg;
185 0 0         if ($refresh_count++ % $opt->refresh == 0) {
186 0           print $termcap{clear};
187             }
188             }
189 0 0         print $new->date, "\n\n" if $opt->date;
190 0           print $data;
191 0 0 0       if ($opt->refresh and $opt->clear) {
192 0           flush($termcap{ed});
193             }
194             } continue {
195 0 0         last if ++$count == $opt->count;
196 0           ($old, $new) = ($new, $old);
197 0           sleep $opt->interval;
198             }
199              
200 0 0         flush($termcap{el}) if $opt->refresh;
201 0           return 0;
202             }
203              
204             sub flush {
205 1     1   9 use IO::Handle;
  1         3  
  1         116  
206 0 0   0 0   state $stdout = IO::Handle->new->fdopen(fileno(STDOUT), "w") or die;
207 0           $stdout->printflush(@_);
208             }
209              
210             sub execute {
211 1     1   7 use IO::File;
  1         1  
  1         410  
212 0   0 0 0   my $pid = (my $fh = IO::File->new)->open('-|') // die "open: $@\n";
213 0 0         if ($pid == 0) {
214 0 0         open STDERR, ">&STDOUT" or die "dup: $!";
215 0           close STDIN;
216 0 0         exec @_ or warn "$_[0]: $!\n";
217 0           exit 3;
218             }
219 0           binmode $fh, ':encoding(utf8)';
220 0           my $result = do { local $/; <$fh> };
  0            
  0            
221 0           for my $child (wait) {
222 0 0         $child != $pid and die "child = $child, pid = $pid";
223             }
224 0 0         ($? >> 8) == 3 ? undef : $result;
225             }
226              
227             ######################################################################
228              
229             =pod
230              
231             =head1 NAME
232              
233             watchdiff - repeat command and watch differences
234              
235             =head1 VERSION
236              
237             Version 4.44
238              
239             =head1 DESCRIPTION
240              
241             Document is included in the executable. Use `man watchdiff` or
242             `perldoc watchdiff`.
243              
244             =head1 AUTHOR
245              
246             Kazumasa Utashiro
247              
248             L
249              
250             =head1 LICENSE
251              
252             Copyright 2014- Kazumasa Utashiro
253              
254             This library is free software; you can redistribute it and/or modify
255             it under the same terms as Perl itself.
256              
257             =head1 SEE ALSO
258              
259             L, L, L
260              
261             =cut