mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-05 15:47:54 +02:00
530 lines
14 KiB
ObjectPascal
530 lines
14 KiB
ObjectPascal
{
|
|
This program is part of the Free Pascal run time library.
|
|
Copyright (c) 1998-2002 by Peter Vreman
|
|
|
|
Show the differences between two .msg files
|
|
|
|
See the file COPYING.FPC, included in this distribution,
|
|
for details about the copyright.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
**********************************************************************}
|
|
|
|
{ May be we need to compare a prefixes of option_help_pages too?
|
|
Currently this is not performed }
|
|
|
|
Program messagedif;
|
|
|
|
{$h+} {Huge strings}
|
|
|
|
Uses
|
|
Strings;
|
|
|
|
Type
|
|
TEnum = String;
|
|
TText = String;
|
|
|
|
PMsg = ^TMsg;
|
|
TMsg = Record
|
|
Line, ctxt, cnb : Longint;
|
|
enum : TEnum;
|
|
text : TText;
|
|
comment : pchar;
|
|
Next,Prev : PMsg;
|
|
FileNext,
|
|
Equivalent : PMsg;
|
|
end;
|
|
Var
|
|
OrgFileName,DiffFileName : String;
|
|
OrgRoot,DiffRoot : PMsg;
|
|
OrgFirst,DiffFirst : PMsg;
|
|
Last : PMsg;
|
|
|
|
const
|
|
NewFileName = 'new.msg';
|
|
Is_interactive : boolean = false;
|
|
Auto_verbosity : boolean = false;
|
|
|
|
|
|
Procedure GetTranslation( p : PMsg);
|
|
var
|
|
s : string;
|
|
i,j,k : longint;
|
|
begin
|
|
i:=pos('_',p^.text);
|
|
if i>0 then
|
|
for j:=i+1 to Length(p^.text) do
|
|
if p^.text[j]='_' then
|
|
begin
|
|
i:=j;
|
|
break;
|
|
end;
|
|
if (i>0) and (i<=15) then
|
|
Writeln(P^.Enum,' type "',copy(p^.text,1,i-1),'" "',copy(p^.text,i+1,255),'"')
|
|
else
|
|
Writeln(P^.enum,' "',p^.text,'"');
|
|
Writeln('Type translated error message in,');
|
|
Writeln('Press return to keep it unchanged, or "q" to finish interactive mode');
|
|
Readln(s);
|
|
if s='' then
|
|
exit;
|
|
if s='q' then
|
|
begin
|
|
Is_interactive:=false;
|
|
exit;
|
|
end;
|
|
j:=pos('_',s);
|
|
if j>0 then
|
|
for k:=j+1 to Length(s) do
|
|
if s[j]='_' then
|
|
begin
|
|
j:=k;
|
|
break;
|
|
end;
|
|
if (j>0) then
|
|
begin
|
|
if copy(p^.text,1,i)<>copy(s,1,j) then
|
|
Writeln('Warning : different verbosity !!');
|
|
p^.text:=s;
|
|
end
|
|
else
|
|
p^.text:=copy(p^.text,1,i)+s;
|
|
end;
|
|
|
|
Function NewMsg (Var RM : PMsg; L : Longint; Const E : TEnum;Const T : TText;C : pchar;NbLn,TxtLn : longint) : PMsg;
|
|
|
|
Var
|
|
P,R : PMsg;
|
|
|
|
begin
|
|
New(P);
|
|
with P^ do
|
|
begin
|
|
Line:=L;
|
|
Text:=T;
|
|
enum:=E;
|
|
comment:=c;
|
|
cnb:=NbLn;
|
|
ctxt:=TxtLn;
|
|
next:=Nil;
|
|
prev:=Nil;
|
|
filenext:=nil;
|
|
equivalent:=nil;
|
|
if assigned(last) then
|
|
last^.FileNext:=P;
|
|
last:=P;
|
|
end;
|
|
R:=RM;
|
|
While (R<>Nil) and (UpCase(R^.enum)>UpCase(P^.Enum)) do
|
|
begin
|
|
P^.Prev:=R;
|
|
R:=R^.next;
|
|
end;
|
|
if assigned(R) and (UpCase(R^.Enum)=UpCase(P^.Enum)) then
|
|
Writeln('Error ',R^.Enum,' duplicate');
|
|
P^.Next:=R;
|
|
If R<>Nil then
|
|
R^.Prev:=P;
|
|
If P^.Prev<>Nil then
|
|
P^.Prev^.Next:=P
|
|
else
|
|
RM:=P;
|
|
NewMsg:=P;
|
|
end;
|
|
|
|
Procedure PrintList(const name : string;R : PMsg);
|
|
var
|
|
P : PMsg;
|
|
f : text;
|
|
begin
|
|
P:=R;
|
|
Assign(f,name);
|
|
Rewrite(f);
|
|
while assigned(P) do
|
|
begin
|
|
Writeln(f,UpCase(P^.Enum));
|
|
P:=P^.Next;
|
|
end;
|
|
Close(f);
|
|
end;
|
|
|
|
Procedure Usage;
|
|
|
|
begin
|
|
Writeln('Usage : msgdif [options] <org-file> <dif-file>');
|
|
Writeln('Options:');
|
|
Writeln(' -i allow to enter translated messages interactively');
|
|
Writeln(' -y1 use <org-file> verbosity (do not query acknowledge)');
|
|
Writeln('');
|
|
Writeln('Generates "',NewFileName,'" that contain the messages from <dif-file>');
|
|
Writeln('with a new messages from <org-file>');
|
|
Writeln('');
|
|
Writeln('Example:');
|
|
Writeln(' msgdif errore.msg errorr.msg');
|
|
halt(1)
|
|
end;
|
|
|
|
Procedure ProcessOptions;
|
|
var
|
|
i,count : longint;
|
|
begin
|
|
Is_interactive:=false;
|
|
Auto_verbosity:=false;
|
|
|
|
count:=paramcount; i:=1;
|
|
while (count>0) and (Paramstr(i)[1]='-') do
|
|
case UpCase(Paramstr(i)[2]) of
|
|
'I': begin
|
|
Is_interactive:=true;
|
|
dec(count); Inc(i);
|
|
end;
|
|
'Y': case Paramstr(i)[3] of
|
|
'1': begin
|
|
Auto_verbosity:=true;
|
|
dec(count); Inc(i);
|
|
end;
|
|
else
|
|
Writeln ('Error: unknown option ', Paramstr(i));
|
|
Usage;
|
|
end;
|
|
else
|
|
Writeln ('Error: unknown option ', Paramstr(i));
|
|
Usage;
|
|
end;
|
|
If Count<>2 then begin
|
|
Writeln ('Error: there must be exactly two message files');
|
|
Usage;
|
|
end;
|
|
|
|
OrgfileName:=Paramstr(i);
|
|
DiffFileName:=Paramstr(i+1);
|
|
if (OrgFileName=NewFileName) or (DiffFileName=NewFileName) then
|
|
begin
|
|
Writeln('The file names must be different from ',NewFileName);
|
|
Halt(1);
|
|
end;
|
|
end;
|
|
|
|
Procedure ProcessFile (FileName : String; Var Root,First : PMsg);
|
|
|
|
Const
|
|
ArrayLength = 65500;
|
|
Var F : Text;
|
|
S,prevS : String;
|
|
J,LineNo,Count,NbLn,TxtLn : Longint;
|
|
chararray : array[0..ArrayLength] of char;
|
|
currentindex : longint;
|
|
c : pchar;
|
|
multiline : boolean;
|
|
begin
|
|
Assign(F,FileName);
|
|
Reset(F);
|
|
Write ('Processing: ',Filename,'...');
|
|
LineNo:=0;
|
|
NbLn:=0;
|
|
TxtLn:=0;
|
|
Count:=0;
|
|
currentindex:=0;
|
|
Root:=Nil;
|
|
First:=nil;
|
|
Last:=nil;
|
|
PrevS:='';
|
|
multiline:=false;
|
|
While not eof(f) do
|
|
begin
|
|
Readln(F,S);
|
|
Inc(LineNo);
|
|
If multiline then
|
|
begin
|
|
PrevS:=PrevS+#10+S; Inc(TxtLn);
|
|
if (Length(S)<>0) and (S[1]=']') then
|
|
multiline:=false;
|
|
end
|
|
else
|
|
if (length(S)>0) and Not (S[1] in ['%','#']) Then
|
|
begin
|
|
J:=Pos('=',S);
|
|
If j<1 then
|
|
writeln (Filename,'(',LineNo,') : Invalid entry')
|
|
else
|
|
begin
|
|
chararray[currentindex]:=#0;
|
|
c:=strnew(@chararray);
|
|
if PrevS<>'' then
|
|
NewMsg(Root,LineNo,Copy(PrevS,1,Pos('=',PrevS)-1),
|
|
Copy(PrevS,Pos('=',PrevS)+1,Length(PrevS)),c,NbLn,TxtLn)
|
|
else
|
|
StrDispose(c);
|
|
currentindex:=0;
|
|
NbLn:=0; TxtLn:=0;
|
|
PrevS:=S; Inc(TxtLn);
|
|
if S[j+7]='[' then multiline:=true;
|
|
if First=nil then
|
|
First:=Root;
|
|
Inc(Count);
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
if currentindex+length(s)+1>ArrayLength then
|
|
Writeln('Comment too long : over ',ArrayLength,' chars')
|
|
else
|
|
begin
|
|
strpcopy(@chararray[currentindex],s+#10);
|
|
inc(currentindex,length(s)+1);
|
|
inc(NbLn);
|
|
end;
|
|
end;
|
|
end;
|
|
chararray[currentindex]:=#0;
|
|
c:=strnew(@chararray);
|
|
if PrevS<>'' then
|
|
NewMsg(Root,LineNo,Copy(PrevS,1,Pos('=',PrevS)-1),
|
|
Copy(PrevS,Pos('=',PrevS)+1,Length(PrevS)),c,NbLn,TxtLn);
|
|
Writeln (' Done. Read ',LineNo,' lines, got ',Count,' constants.');
|
|
Close(f);
|
|
end;
|
|
|
|
Procedure ShowDiff (POrg,PDiff : PMsg);
|
|
|
|
Var
|
|
count,orgcount,diffcount : longint;
|
|
|
|
Procedure NotFound (Org : Boolean; P : PMsg);
|
|
|
|
begin
|
|
With P^ do
|
|
If Org Then
|
|
Writeln ('Not found in ',DiffFileName,' : ',Enum,' ',OrgFileName,'(',Line,')')
|
|
else
|
|
Writeln ('Extra in ',DiffFileName,'(',line,') : ',enum);
|
|
if org then
|
|
inc(orgcount)
|
|
else
|
|
inc(diffcount);
|
|
end;
|
|
|
|
begin
|
|
orgcount:=0;
|
|
diffcount:=0;
|
|
count:=0;
|
|
While (Porg<>Nil) and (PDiff<>Nil) do
|
|
begin
|
|
// Writeln (POrg^.enum,'<=>',PDiff^.Enum);
|
|
If UpCase(Porg^.Enum)>UpCase(PDiff^.Enum) then
|
|
begin
|
|
NotFound (True,Porg);
|
|
POrg:=POrg^.Next
|
|
end
|
|
else If UpCase(POrg^.enum)=UpCase(PDiff^.Enum) then
|
|
begin
|
|
inc(count);
|
|
POrg^.Equivalent:=PDiff;
|
|
PDiff^.Equivalent:=POrg;
|
|
POrg:=POrg^.Next;
|
|
PDiff:=PDiff^.Next;
|
|
end
|
|
else
|
|
begin
|
|
NotFound (False,PDiff);
|
|
PDiff:=PDiff^.Next
|
|
end;
|
|
end;
|
|
While POrg<>Nil do
|
|
begin
|
|
NotFound(True,Porg);
|
|
POrg:=pOrg^.Next;
|
|
end;
|
|
While PDiff<>Nil do
|
|
begin
|
|
NotFound(False,PDiff);
|
|
PDiff:=PDiff^.Next;
|
|
end;
|
|
Writeln(count,' messages found in common to both files');
|
|
Writeln(orgcount,' messages only in ',OrgFileName);
|
|
Writeln(diffcount,' messages only in ',DiffFileName);
|
|
end;
|
|
|
|
type TArgSet = set of 0..31;
|
|
|
|
function MsgToSet(const Msg, FileName: string; var R: TArgSet): Boolean;
|
|
var
|
|
i, j,l, num : integer;
|
|
code : word;
|
|
begin
|
|
R:=[];
|
|
MsgToSet:=false;
|
|
for i:=1 to Length(Msg) do
|
|
if Msg[i]='$' then
|
|
begin
|
|
j:=i+1; l:=length(msg)+1;
|
|
while (j<l) and (Msg[j] in ['0'..'9']) do Inc(j);
|
|
if j > (i+1) then
|
|
begin
|
|
val(copy(Msg,i+1,j-i-1),num,code);
|
|
if num > high(TArgSet) then begin
|
|
WriteLn('Error in ', FileName,': ', Msg);
|
|
WriteLn(' number at position ', i);
|
|
WriteLn(' must be LE ', high(TArgSet));
|
|
Exit;
|
|
end;
|
|
R:=R+[num];
|
|
end;
|
|
end;
|
|
MsgToSet:=true;
|
|
end;
|
|
|
|
|
|
procedure CheckParm(const s1, s2: string);
|
|
var
|
|
R1, R2: TArgSet;
|
|
begin
|
|
if MsgToSet(s1,OrgFileName, R1) <> true then Exit;
|
|
if MsgToSet(s2,DiffFileName,R2) <> true then Exit;
|
|
if R1<>R2 then begin
|
|
WriteLn('Error: set of arguments is different');
|
|
WriteLn(' ',s1);
|
|
WriteLn(' ',s2);
|
|
end;
|
|
end;
|
|
|
|
procedure WriteReorderedFile(FileName : string;orgnext,diffnext : PMsg);
|
|
var t,t2,t3 : text;
|
|
i,ntcount : longint;
|
|
j : integer;
|
|
s,s2,s3 : string;
|
|
is_msg : boolean;
|
|
nextdiffkept : pmsg;
|
|
begin
|
|
ntcount:=0;
|
|
Assign(t,FileName);
|
|
Rewrite(t);
|
|
Writeln(t,'%%% Reordering of ',DiffFileName,' respective to ',OrgFileName);
|
|
Writeln(t,'%%% Contains all comments from ',DiffFileName);
|
|
Assign(t2,DiffFileName);
|
|
Reset(t2);
|
|
Assign(t3,OrgFileName);
|
|
Reset(t3);
|
|
i:=2;
|
|
s:='';s3:='';
|
|
nextdiffkept:=diffnext;
|
|
while assigned(nextdiffkept) and (nextdiffkept^.equivalent=nil) do
|
|
nextdiffkept:=nextdiffkept^.filenext;
|
|
{ First write the header of diff }
|
|
repeat
|
|
Readln(t2,s);
|
|
is_msg:=(pos('=',s)>1) and (s[1]<>'%') and (s[1]<>'#');
|
|
if not is_msg then
|
|
begin
|
|
Writeln(t,s);
|
|
inc(i);
|
|
end;
|
|
until is_msg;
|
|
{ Write all messages in Org order }
|
|
while assigned(orgnext) do
|
|
begin
|
|
if not assigned(orgnext^.equivalent) then
|
|
begin
|
|
{ Insert a new error msg with the english comments }
|
|
Writeln('New error ',orgnext^.enum,' added');
|
|
If Is_interactive then
|
|
GetTranslation(orgnext);
|
|
Writeln(t,orgnext^.enum,'=',orgnext^.text);
|
|
inc(i,orgnext^.ctxt);
|
|
Write(t,orgnext^.comment);
|
|
inc(i,orgnext^.cnb);
|
|
end
|
|
else
|
|
begin
|
|
inc(i);
|
|
if orgnext^.text=orgnext^.equivalent^.text then
|
|
begin
|
|
Writeln(FileName,'(',i,') ',orgnext^.enum,' not translated');
|
|
If Is_interactive then
|
|
GetTranslation(orgnext^.equivalent);
|
|
if orgnext^.text=orgnext^.equivalent^.text then
|
|
inc(ntcount);
|
|
end;
|
|
s2:=orgnext^.text;
|
|
j:=pos('_',copy(s2,7,20)) + 6;
|
|
s2:=upcase(copy(s2,1,j));
|
|
s3:=orgnext^.equivalent^.text;
|
|
j:=pos('_',copy(s3,7,20)) + 6;
|
|
s3:=upcase(copy(s3,1,j));
|
|
{ that are the conditions in verbose unit }
|
|
if (length(s3)<12) and (s2<>s3) then
|
|
begin
|
|
Writeln('Warning: different options for ',orgnext^.enum);
|
|
Writeln(' ',orgnext^.text);
|
|
Writeln(' ',orgnext^.equivalent^.text);
|
|
s:='N';
|
|
if Auto_verbosity then
|
|
s:='Y'
|
|
else
|
|
If Is_interactive then
|
|
begin
|
|
Write('Use ',s2,' verbosity ? [y/n] ');
|
|
Readln(s);
|
|
end;
|
|
if UpCase(s[1])='Y' then
|
|
begin
|
|
orgnext^.equivalent^.text:=s2+copy(orgnext^.equivalent^.text,
|
|
length(s3)+1,Length(orgnext^.equivalent^.text));
|
|
WriteLn(' Using ', s2);
|
|
end;
|
|
end;
|
|
|
|
CheckParm(orgnext^.text, orgnext^.equivalent^.text);
|
|
|
|
Writeln(t,orgnext^.enum,'=',orgnext^.equivalent^.text);
|
|
Dec(i); Inc(i,orgnext^.equivalent^.ctxt);
|
|
if assigned(orgnext^.equivalent^.comment) and
|
|
(strlen(orgnext^.equivalent^.comment)>0) then
|
|
begin
|
|
Write(t,orgnext^.equivalent^.comment);
|
|
inc(i,orgnext^.equivalent^.cnb);
|
|
end
|
|
else if assigned(orgnext^.comment) and
|
|
(strlen(orgnext^.comment)>0) then
|
|
begin
|
|
Writeln('Comment from ',OrgFileName,' for enum ',orgnext^.enum,' added');
|
|
Write(t,orgnext^.comment);
|
|
inc(i,orgnext^.cnb);
|
|
end;
|
|
end;
|
|
orgnext:=orgnext^.filenext;
|
|
end;
|
|
|
|
while assigned(diffnext) do
|
|
begin
|
|
if not assigned(diffnext^.Equivalent) then
|
|
begin
|
|
{ Skip removed enum in errore.msg}
|
|
{ maybe a renaming of an enum !}
|
|
Writeln(diffnext^.enum,' commented out');
|
|
Writeln(t,'%%% ',diffnext^.enum,'=',diffnext^.text);
|
|
inc(i,diffnext^.ctxt);
|
|
Write(t,diffnext^.comment);
|
|
inc(i,diffnext^.cnb);
|
|
end;
|
|
diffnext:=diffnext^.filenext;
|
|
end;
|
|
Close(t);
|
|
Close(t2);
|
|
Close(t3);
|
|
Writeln(ntcount,' not translated items found');
|
|
end;
|
|
|
|
begin
|
|
ProcessOptions;
|
|
ProcessFile(OrgFileName,orgroot,orgfirst);
|
|
ProcessFile(DiffFileName,diffRoot,difffirst);
|
|
PrintList('org.lst',OrgRoot);
|
|
PrintList('diff.lst',DiffRoot);
|
|
ShowDiff (OrgRoot,DiffRoot);
|
|
WriteReorderedFile(NewFileName,orgfirst,difffirst);
|
|
end.
|