File Coverage

blib/lib/App/diff2vba.pm
Criterion Covered Total %
statement 50 173 28.9
branch 0 48 0.0
condition 0 12 0.0
subroutine 17 34 50.0
pod 1 14 7.1
total 68 281 24.2


line stmt bran cond sub pod time code
1             use 5.014;
2 1     1   577 use warnings;
  1         3  
3 1     1   4  
  1         1  
  1         30  
4             our $VERSION = "0.99";
5              
6             use utf8;
7 1     1   463 use Encode;
  1         11  
  1         4  
8 1     1   448 use Data::Dumper;
  1         8526  
  1         54  
9 1     1   470 {
  1         4950  
  1         48  
10             no warnings 'redefine';
11 1     1   6 *Data::Dumper::qquote = sub { qq["${\(shift)}"] };
  1         2  
  1         65  
12 0     0     $Data::Dumper::Useperl = 1;
  0            
13             }
14             use open IO => 'utf8', ':std';
15 1     1   394 use Pod::Usage;
  1         951  
  1         4  
16 1     1   506 use Data::Section::Simple qw(get_data_section);
  1         30685  
  1         97  
17 1     1   357 use List::Util qw(max);
  1         446  
  1         50  
18 1     1   6 use List::MoreUtils qw(pairwise);
  1         1  
  1         46  
19 1     1   457 use App::diff2vba::Util;
  1         10985  
  1         5  
20 1     1   1170 use App::sdif::Util qw(read_unified_2);
  1         2  
  1         27  
21 1     1   368  
  1         2644  
  1         56  
22             use Getopt::EX::Hashed 1.03; {
23 1     1   442  
  1         5357  
  1         4  
24             Getopt::EX::Hashed->configure( DEFAULT => [ is => 'ro' ] );
25              
26             has debug => " " ;
27             has verbose => " v ! " , default => 1;
28             has format => " =s " , default => 'dumb';
29             has subname => " =s " , default => 'Patch';
30             has maxlen => " =i " , default => 250;
31             has adjust => " =i " , default => 2;
32             has identical => " ! " ;
33             has reverse => " ! " ;
34             has help => " " ;
35             has version => " " ;
36              
37             has '+help' => sub {
38             pod2usage
39             -verbose => 99,
40             -sections => [ qw(SYNOPSIS VERSION) ];
41             };
42              
43             has '+version' => sub {
44             print "Version: $VERSION\n";
45             exit;
46             };
47              
48             Getopt::EX::Hashed->configure( DEFAULT => [ is => 'rw' ] );
49              
50             has SCRIPT => ;
51             has TABLE => ;
52             has QUOTES => default => { '“' => "Chr(&H8167)", '”' => "Chr(&H8168)" };
53             has QUOTES_RE => ;
54              
55             } no Getopt::EX::Hashed;
56 1     1   264  
  1         2  
  1         4  
57             my $app = shift;
58             local @ARGV = map { utf8::is_utf8($_) ? $_ : decode('utf8', $_) } @_;
59 0     0 0    
60 0 0         use Getopt::EX::Long qw(GetOptions Configure ExConfigure);
  0            
61             ExConfigure BASECLASS => [ __PACKAGE__, "Getopt::EX" ];
62 1     1   457 Configure qw(bundling no_getopt_compat);
  1         29201  
  1         676  
63 0           $app->getopt || pod2usage();
64 0            
65 0 0         $app->initialize;
66              
67 0           for my $file (@ARGV ? @ARGV : '-') {
68             print $app->reset->load($file)->vba->script;
69 0 0         }
70 0            
71             return 0;
72             }
73 0            
74             my $app = shift;
75             my $file = shift;
76              
77 0     0 0   open my $fh, $file or die "$file: $!\n";
78 0            
79             $app->TABLE(my $fromto = []);
80 0 0         while (<$fh>) {
81             #
82 0           # diff --combined (generic)
83 0           #
84             if (m{^
85             (?<command>
86             (?<mark> \@{2,} ) [ ]
87 0 0         (?<lines> (?: [-+]\d+(?:,\d+)? [ ] ){2,} )
88             \g{mark}
89             (?s:.*)
90             )
91             }x) {
92             my($command, $lines) = @+{qw(command lines)};
93             my $column = length $+{mark};
94             my @lines = map {
95 0           $_ eq ' ' ? 1 : int $_
96 0           } $lines =~ /\d+(?|,(\d+)|( ))/g;
97              
98 0 0         warn $_ if $app->debug;
  0            
99              
100             next if @lines != $column;
101 0 0         next if $column != 2;
102              
103 0 0         push @$fromto, $app->read_diff($fh, @lines);
104 0 0         }
105             }
106 0           $app;
107             }
108              
109 0           (my $app = shift)
110             ->prologue()
111             ->substitute()
112             ->epilogue();
113 0     0 0   }
114              
115             my $app = shift;
116             if (my $name = $app->subname) {
117             $app->append(text => "Sub $name()\n");
118             }
119             $app->append(section => "setup.vba");
120 0     0 0   $app;
121 0 0         }
122 0            
123             my $app = shift;
124 0           if (my $name = $app->subname) {
125 0           $app->append(text => "End Sub\n");
126             }
127             $app;
128             }
129 0     0 0    
130 0 0         my $app = shift;
131 0           my $template = sprintf "subst_%s.vba", $app->format;
132             my $max = $app->maxlen;
133 0            
134             my @fromto = do {
135             if ($app->reverse) {
136             map { [ $_->[1], $_->[0] ] } @{$app->TABLE};
137 0     0 0   } else {
138 0           @{$app->TABLE};
139 0           }
140             };
141 0           for my $i (0 .. $#fromto) {
142 0 0         my $fromto = $fromto[$i];
143 0           use integer;
  0            
  0            
144             chomp @$fromto;
145 0           my($from, $to) = @$fromto;
  0            
146             my $longer = max map { length } $from, $to;
147             my $count = ($longer + $max - 1) / $max;
148 0           my @from = split_string($from, $count);
149 0           my @to = split_string($to, $count);
150 1     1   8 adjust_border(\@from, \@to, $app->adjust) if $app->adjust;
  1         1  
  1         5  
151 0           for my $j (0 .. $#from) {
152 0           next if !$app->identical and $from[$j] eq $to[$j];
153 0           $app->append(text => sprintf "' # %d-%d\n", $i + 1, $j + 1);
  0            
154 0           $app->append(section => $template,
155 0           { TARGET => $app->string_literal($from[$j]),
156 0           REPLACEMENT => $app->string_literal($to[$j]) });
157 0 0         }
158 0           }
159 0 0 0       $app;
160 0           }
161 0            
162             my $app = shift;
163             my $chrs = join '', keys %{$app->QUOTES};
164             $app->QUOTES_RE(qr/[\Q$chrs\E]/);
165             $app;
166 0           }
167              
168             my $app = shift;
169             my $what = shift // 'text';
170 0     0 0   if ($what eq 'text') {
171 0           push @{$app->SCRIPT}, @_;
  0            
172 0           } elsif ($what eq 'section') {
173 0           push @{$app->SCRIPT}, $app->section(@_);
174             } else { die }
175             $app;
176             }
177 0     0 0    
178 0   0       my $app = shift;
179 0 0         join "\n", @{$app->SCRIPT};
    0          
180 0           }
  0            
181              
182 0           (my $app = shift)->SCRIPT([]);
  0            
183 0           $app;
184 0           }
185              
186             my $app = shift;
187             my $section = shift;
188 0     0 0   my $replace = shift // {};
189 0           local $_ = get_data_section($section);
  0            
190             do { s/\A\n*//; s/\n\K\n*\z// };
191             for my $name (keys %$replace) {
192             s/\b(\Q$name\E)\b/$replace->{$1}/ge;
193 0     0 1   }
194 0           $_;
195             }
196              
197             my $app = shift;
198 0     0 0   my($fh, @lines) = @_;
199 0           my @diff = read_unified_2 $fh, @lines;
200 0   0       my @out;
201 0           while (my($c, $o, $n) = splice(@diff, 0, 3)) {
202 0           @$o > 0 and @$o == @$n or next;
  0            
  0            
203 0           s/^[\t +-]// for @$c, @$o, @$n;
204 0           push @out, pairwise { [ $a, $b ] } @$o, @$n;
  0            
205             }
206 0           @out;
207             }
208              
209             my $app = shift;
210 0     0 0   my $quotes = $app->QUOTES;
211 0           my $chrs_re = $app->QUOTES_RE;
212 0           join(' & ',
213 0           map { $quotes->{$_} || sprintf('"%s"', s/\"/\"\"/gr) }
214 0           map { split /($chrs_re)/ } @_);
215 0 0 0       }
216 0            
217 0     0     ######################################################################
  0            
218              
219 0           my($a, $b, $max) = @_;
220             $max //= 2;
221             return if @$a < 1;
222             return if $max == 0;
223 0     0 0   for my $i (1 .. $#{$a}) {
224 0           next if substr($a->[$i-1], -($max+1)) eq substr($b->[$i-1], -($max+1));
225 0           for my $shift (reverse 1 .. $max) {
226             _adjust($a, $b, $i, $shift) and last;
227 0 0         _adjust($b, $a, $i, $shift) and last;
228 0           }
  0            
229             }
230             }
231              
232             my($a, $b, $i, $len) = @_;
233             if (substr($a->[$i-1], -($len + 1), $len + 1) eq
234 0     0 0   substr($b->[$i-1], -1, 1) . substr($b->[$i], 0, $len)) {
235 0   0       $b->[$i-1] .= substr($b->[$i], 0, $len, '');
236 0 0         return 1;
237 0 0         }
238 0           return 0;
  0            
239 0 0         }
240 0            
241 0 0         1;
242 0 0          
243             =encoding utf-8
244              
245             =head1 NAME
246              
247             App::diff2vba - generate VBA patch script from diff output
248 0     0      
249 0 0         =head1 VERSION
250              
251 0           Version 0.99
252 0            
253             =head1 SYNOPSIS
254 0            
255             greple -Mmsdoc -Msubst \
256             --all-sample-dict --diff some.docx | diff2vba > patch.vba
257              
258             =head1 DESCRIPTION
259              
260             B<diff2vba> is a command to generate VBA patch script from diff output.
261              
262             Read document in script file for detail.
263              
264             =head1 AUTHOR
265              
266             Kazumasa Utashiro
267              
268             =head1 LICENSE
269              
270             Copyright 2021 Kazumasa Utashiro.
271              
272             This library is free software; you can redistribute it and/or modify
273             it under the same terms as Perl itself.
274              
275             =cut
276              
277              
278             @@ setup.vba
279              
280             With Selection.Find
281             .MatchCase = True
282             .MatchByte = True
283             .IgnoreSpace = False
284             .IgnorePunct = False
285             End With
286              
287             Options.AutoFormatAsYouTypeReplaceQuotes = False
288              
289             @@ subst_dumb.vba
290              
291             With Selection.Find
292             .Text = TARGET
293             .Replacement.Text = REPLACEMENT
294             .Execute Replace:=wdReplaceOne
295             End With
296             Selection.Collapse Direction:=wdCollapseEnd
297              
298             @@ subst_dumb2.vba
299              
300             With Selection.Find
301             .Text = TARGET
302             if .Execute Then
303             Selection.Range.Text = REPLACEMENT
304             End If
305             End With
306