]> git.lyx.org Git - lyx.git/blob - po/diff_po.pl
25685d830979c90f73030a3adf5abfd1a289e4f3
[lyx.git] / po / diff_po.pl
1 #! /usr/bin/env perl
2 # -*- mode: perl; -*-
3 #
4 # file diff_po.pl
5 # script to compare changes between translation files before merging them
6 #
7 # Examples of usage:
8 # ./diff_po.pl cs.po.old cs.po
9 # svn diff -r38367 --diff-cmd ./diff_po.pl cs.po
10 # git difftool --extcmd=./diff_po.pl sk.po
11 # ./diff_po.pl -r HEAD~100 cs.po        #fetch git revision and compare
12 # ./diff_po.pl -r39229 cs.po            #fetch svn revision and compare
13 #
14 # This file is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public
16 # License as published by the Free Software Foundation; either
17 # version 2 of the License, or (at your option) any later version.
18 #
19 # This software is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22 # General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public
25 # License along with this software; if not, write to the Free Software
26 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27 #
28 # Copyright (c) 2010-2013 Kornel Benko, kornel@lyx.org
29 #
30 # TODO:
31 # 1.) Search for good correlations of deleted <==> inserted string
32 #     using Text::Levenshtein or Algorithm::Diff
33
34 BEGIN {
35     use File::Spec;
36     my $p = File::Spec->rel2abs( __FILE__ );
37     $p =~ s/[\/\\]?[^\/\\]+$//;
38     unshift(@INC, "$p");
39 }
40
41 use strict;
42 use parsePoLine;
43 use Term::ANSIColor qw(:constants);
44 use File::Temp;
45 use Cwd;
46
47 my ($status, $foundline, $msgid, $msgstr, $fuzzy);
48
49 my %Messages = ();              # Used for original po-file
50 my %newMessages = ();           # new po-file
51 my %Untranslated = ();          # inside new po-file
52 my %Fuzzy = ();                 # inside new po-file
53 my $result = 0;                 # exit value
54 my $printlines = 1;
55 my @names = ();
56
57 # Check first, if called as standalone program for git
58 if ($ARGV[0] =~ /^-r(.*)/) {
59   my $rev = $1;
60   shift(@ARGV);
61   if ($rev eq "") {
62     $rev = shift(@ARGV);
63   }
64   for my $argf (@ARGV) {
65     my $baseargf;
66     my $filedir;
67     if ($argf =~ /^(.*)\/([^\/]+)$/) {
68       $baseargf = $2;
69       $filedir = $1;
70       chdir($filedir);  # set working directory for the repo-command
71     }
72     else {
73       $baseargf = $argf;
74       $filedir = ".";
75     }
76     $filedir = getcwd();
77     my ($repo, $level) = &searchRepo($filedir);
78     my $relargf = $baseargf;    # argf relative to the top-most repo directory
79     my $topdir;
80     if (defined($level)) {
81       my $abspathpo = $filedir; # directory of the po-file
82       $topdir = $abspathpo;
83       #print "Level = $level, abs path = $abspathpo\n";
84       while ($level > 0) {
85         $topdir =~ s/\/([^\/]+)$//;
86         $relargf = "$1/$relargf";
87         $level--;
88         #print "Level = $level, topdir = $topdir, rel path = $relargf\n";
89       }
90       chdir($topdir);
91     }
92     else {
93       print "Could not find the repo-type\n";
94       exit(-1);
95     }
96     #check po-file
97     &check_po_file_readable($baseargf, $relargf);
98     if ($repo eq ".git") {
99       my @args = ();
100       my $tmpfile = File::Temp->new();
101       $rev = &getrev($repo, $rev);
102       push(@args, "-L", $argf . "    (" . $rev . ")");
103       push(@args, "-L", $argf . "    (local copy)");
104       print "git show $rev:$relargf\n";
105       open(FI, "git show $rev:$relargf|");
106       $tmpfile->unlink_on_destroy( 1 );
107       while(my $l = <FI>) {
108         print $tmpfile $l;
109       }
110       close(FI);
111       $tmpfile->seek( 0, SEEK_END );            # Flush()
112       push(@args, $tmpfile->filename, $argf);
113       print "===================================================================\n";
114       &diff_po(@args);
115     }
116     elsif ($repo eq ".svn") {
117       # program svnversion needed here
118       $rev = &getrev($repo, $rev);
119       # call it again indirectly
120       my @cmd = ("svn", "diff", "-r$rev", "--diff-cmd", $0, $relargf);
121       print "cmd = " . join(' ', @cmd) . "\n";
122       system(@cmd);
123     }
124     elsif ($repo eq ".hg") {
125       # for this to work, one has to edit ~/.hgrc
126       # Insert there
127       #     [extensions]
128       #     hgext.extdiff =
129       #
130       $rev = &getrev($repo, $rev);
131       my @cmd = ("hg", "extdiff", "-r", "$rev", "-p", $0, $relargf);
132       print "cmd = " . join(' ', @cmd) . "\n";
133       system(@cmd);
134     }
135   }
136 }
137 else {
138   &diff_po(@ARGV);
139 }
140
141 exit($result);
142 #########################################################
143
144 # This routine builds n-th parent-path
145 # E.g. &buildParentDir("abc", 1) --> "abc/.."
146 #      &buildParentDir("abc", 4) --> "abc/../../../.."
147 sub buildParentDir($$)
148 {
149   my ($dir, $par) = @_;
150   if ($par > 0) {
151     return &buildParentDir("$dir/..", $par-1);
152   }
153   else {
154     return $dir;
155   }
156 }
157
158 # Tries up to 10 parent levels to find the repo-type
159 # Returns the repo-type
160 sub searchRepo($)
161 {
162   my ($dir) = @_;
163   for my $parent ( 0 .. 10 ) {
164     my $f = &buildParentDir($dir, $parent);
165     for my $s (".git", ".svn", ".hg") {
166       if (-d "$f/$s") {
167         #print "Found repo on level $parent\n";
168         return ($s, $parent);
169       }
170     }
171   }
172   return("");   # not found
173 }
174
175 sub diff_po($$)
176 {
177   my @args = @_;
178   %Messages = ();
179   %newMessages = ();
180   %Untranslated = ();
181   %Fuzzy = ();
182   @names = ();
183   while(defined($args[0])) {
184     last if ($args[0] !~ /^\-/);
185     my $param = shift(@args);
186     if ($param eq "-L") {
187       my $name = shift(@args);
188       push(@names, $name);
189     }
190   }
191   if (! defined($names[0])) {
192     push(@names, "original");
193   }
194   if (! defined($names[1])) {
195     push(@names, "new");
196   }
197
198   if (@args != 2) {
199     die("names = \"", join('" "', @names) . "\"... args = \"" . join('" "', @args) . "\" Expected exactly 2 parameters");
200   }
201
202   &check_po_file_readable($names[0], $args[0]);
203   &check_po_file_readable($names[1], $args[1]);
204
205   &parse_po_file($args[0], \%Messages);
206   &parse_po_file($args[1], \%newMessages);
207
208   my @MsgKeys = &getLineSortedKeys(\%newMessages);
209
210   print RED "<<< \"$names[0]\"\n", RESET;
211   print GREEN ">>> \"$names[1]\"\n", RESET;
212   for my $k (@MsgKeys) {
213     if ($newMessages{$k}->{msgstr} eq "") {
214       # this is still untranslated string
215       $Untranslated{$newMessages{$k}->{line}} = $k;
216     }
217     elsif ($newMessages{$k}->{fuzzy}) {
218       #fuzzy string
219       $Fuzzy{$newMessages{$k}->{line}} = $k;
220     }
221     if (exists($Messages{$k})) {
222       &printIfDiff($k, $Messages{$k}, $newMessages{$k});
223       delete($Messages{$k});
224       delete($newMessages{$k});
225     }
226   }
227
228   if (0) {
229     @MsgKeys = sort keys %Messages, keys %newMessages;
230     for my $k (@MsgKeys) {
231       if (defined($Messages{$k})) {
232         $result |= 8;
233         print "deleted message\n";
234         print "< line = " . $Messages{$k}->{line} . "\n" if ($printlines);
235         print RED "< fuzzy = " . $Messages{$k}->{fuzzy} . "\n", RESET;
236         print RED "< msgid = \"$k\"\n", RESET;
237         print RED "< msgstr = \"" . $Messages{$k}->{msgstr} . "\"\n", RESET;
238       }
239       if (defined($newMessages{$k})) {
240         $result |= 16;
241         print "new message\n";
242         print "> line = " . $newMessages{$k}->{line} . "\n" if ($printlines);
243         print GREEN "> fuzzy = " . $newMessages{$k}->{fuzzy} . "\n", RESET;
244         print GREEN "> msgid = \"$k\"\n", RESET;
245         print GREEN "> msgstr = \"" . $newMessages{$k}->{msgstr} . "\"\n", RESET;
246       }
247     }
248   }
249   else {
250     @MsgKeys = &getLineSortedKeys(\%Messages);
251     for my $k (@MsgKeys) {
252       $result |= 8;
253       print "deleted message\n";
254       print "< line = " . $Messages{$k}->{line} . "\n" if ($printlines);
255       print RED "< fuzzy = " . $Messages{$k}->{fuzzy} . "\n", RESET;
256       print RED "< msgid = \"$k\"\n", RESET;
257       print RED "< msgstr = \"" . $Messages{$k}->{msgstr} . "\"\n", RESET;
258     }
259
260     @MsgKeys = &getLineSortedKeys(\%newMessages);
261     for my $k (@MsgKeys) {
262       $result |= 16;
263       print "new message\n";
264       print "> line = " . $newMessages{$k}->{line} . "\n" if ($printlines);
265       print GREEN "> fuzzy = " . $newMessages{$k}->{fuzzy} . "\n", RESET;
266       print GREEN "> msgid = \"$k\"\n", RESET;
267       print GREEN "> msgstr = \"" . $newMessages{$k}->{msgstr} . "\"\n", RESET;
268     }
269   }
270   &printExtraMessages("fuzzy", \%Fuzzy, \@names);
271   &printExtraMessages("untranslated", \%Untranslated, \@names);
272 }
273
274 sub check_po_file_readable($$)
275 {
276   my ($spec, $filename) = @_;
277
278   if (! -e $filename ) {
279     die("$spec po file does not exist");
280   }
281   if ( ! -f $filename ) {
282     die("$spec po file is not regular");
283   }
284   if ( ! -r $filename ) {
285     die("$spec po file is not readable");
286   }
287 }
288
289 # Diff of one corresponding entry
290 sub printDiff($$$$)
291 {
292   my ($k, $nk, $rM, $rnM) = @_;
293   print "diffline = " . $rM->{line} . "," . $rnM->{line} . "\n" if ($printlines);
294   print "  msgid = \"$k\"\n";
295   if ($rM->{fuzzy} eq $rnM->{fuzzy}) {
296     print "  fuzzy = " . $rM->{fuzzy} . "\n" if ($printlines);
297   }
298   else {
299     print RED "< fuzzy = " . $rM->{fuzzy} . "\n", RESET;
300   }
301   print RED "< msgstr = " . $rM->{msgstr} . "\n", RESET;
302   if ($k ne $nk) {
303     print GREEN "> msgid = \"$nk\"\n", RESET;
304   }
305   if ($rM->{fuzzy} ne $rnM->{fuzzy}) {
306     print GREEN "> fuzzy = " . $rnM->{fuzzy} . "\n", RESET;
307   }
308   print GREEN "> msgstr = " . $rnM->{msgstr} . "\n", RESET;
309   print "\n";
310 }
311
312 sub printIfDiff($$$)
313 {
314   my ($k, $rM, $rnM) = @_;
315   my $doprint = 0;
316   $doprint = 1 if ($rM->{fuzzy} != $rnM->{fuzzy});
317   $doprint = 1 if ($rM->{msgstr} ne $rnM->{msgstr});
318   if ($doprint) {
319     $result |= 4;
320     &printDiff($k, $k, $rM, $rnM);
321   }
322 }
323
324 sub printExtraMessages($$$)
325 {
326   my ($type, $rExtra, $rNames) = @_;
327   #print "file1 = $rNames->[0], file2 = $rNames->[1]\n";
328   my @UntranslatedKeys = sort { $a <=> $b;} keys %{$rExtra};
329
330   if (@UntranslatedKeys > 0) {
331     print "Still " . 0 + @UntranslatedKeys . " $type messages found in $rNames->[0]\n";
332     for my $l (@UntranslatedKeys) {
333       print "> line $l: \"" . $rExtra->{$l} . "\"\n";
334     }
335   }
336 }
337
338 #
339 # get repository dependent revision representation
340 sub getrev($$)
341 {
342   my ($repo, $rev) = @_;
343   my $revnum;
344
345   if ($rev eq "HEAD") {
346     $revnum = 0;
347   }
348   else {
349     return $rev if ($rev !~ /^(-|HEAD[-~])(\d+)$/);
350     $revnum = $2;
351   }
352   if ($repo eq ".hg") {
353     return "-$revnum";
354   }
355   elsif ($repo eq ".git") {
356     return("HEAD~$revnum");
357   }
358   elsif ($repo eq ".svn") {
359     if (open(VI, "svnversion |")) {
360       while (my $r1 = <VI>) {
361         chomp($r1);
362         if ($r1 =~ /^((\d+):)?(\d+)M?$/) {
363           $rev = $3-$revnum;
364         }
365       }
366       close(VI);
367     }
368     return $rev;
369   }
370   return $rev;
371 }