unitdiff.pp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. {
  2. UnitDiff Copyright (C) 2004 by the Free Pascal team
  3. Show differences between unit interfaces.
  4. See the file COPYING, included in this distribution,
  5. for details about the copyright.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  9. }
  10. {$mode objfpc}
  11. {$h+}
  12. program unitdiff;
  13. uses
  14. SysUtils, Classes, Gettext,
  15. dGlobals, PasTree, PParser,PScanner;
  16. resourcestring
  17. SIdentifiersIn = 'Identifiers in file "%s"';
  18. SCmdLineInvalidOption = 'Ignoring unknown option "%s"';
  19. SErrNoInputFile = 'No input file specified';
  20. SWarnAssumingList = 'Only one input file specified. Assuming --list option.';
  21. SExtraIdentifier = 'Extra identifier in file "%s" : Name: %s';
  22. SExtraTypedIdentifier = 'Extra identifier in file "%s" : Type %s, Name: %s';
  23. SIdenticalUnits = 'Unit interfaces are identical.';
  24. type
  25. TCmdLineAction = (actionHelp, actionDiff,ActionList);
  26. TSkelEngine = class(TFPDocEngine)
  27. public
  28. FList: TStringList;
  29. Constructor Create;
  30. Destructor Destroy;override;
  31. function CreateElement(AClass: TPTreeElement; const AName: String;
  32. AParent: TPasElement; AVisibility :TPasMemberVisibility;
  33. const ASourceFilename: String; ASourceLinenumber: Integer): TPasElement; override;
  34. end;
  35. Constructor TSkelEngine.Create;
  36. begin
  37. Inherited Create;
  38. FList:=TStringList.Create;
  39. end;
  40. Destructor TSkelEngine.Destroy;
  41. begin
  42. FreeAndNil(FList);
  43. Inherited;
  44. end;
  45. const
  46. CmdLineAction: TCmdLineAction = actionDiff;
  47. OSTarget: String = {$I %FPCTARGETOS%};
  48. CPUTarget: String = {$I %FPCTARGETCPU%};
  49. var
  50. InputFile1,
  51. InputFile2 : String;
  52. DocLang: String;
  53. Engine1,
  54. Engine2: TSkelEngine;
  55. SparseList,
  56. DisableArguments,
  57. DisableProtected,
  58. DisablePrivate,
  59. DisableFunctionResults: Boolean;
  60. OutputName: String;
  61. f: Text;
  62. function TSkelEngine.CreateElement(AClass: TPTreeElement; const AName: String;
  63. AParent: TPasElement; AVisibility : TPasMemberVisibility;
  64. const ASourceFilename: String; ASourceLinenumber: Integer): TPasElement;
  65. Function ExamineThisNode(APasElement : TPasElement) : Boolean;
  66. begin
  67. Result:=Assigned(AParent) and (Length(AName) > 0) and
  68. (not DisableArguments or (APasElement.ClassType <> TPasArgument)) and
  69. (not DisableFunctionResults or (APasElement.ClassType <> TPasResultElement)) and
  70. (not DisablePrivate or (AVisibility<>visPrivate)) and
  71. (not DisableProtected or (AVisibility<>visProtected));
  72. end;
  73. begin
  74. Result := AClass.Create(AName, AParent);
  75. if AClass.InheritsFrom(TPasModule) then
  76. CurModule := TPasModule(Result)
  77. else if ExamineThisNode(Result) then
  78. Flist.AddObject(Result.FullName,Result);
  79. end;
  80. Procedure Usage;
  81. begin
  82. Writeln('Usage : ',ExtractFileName(Paramstr(0)),' [options] file1 file2');
  83. Writeln('Where [options] is one or more of :');
  84. Writeln(' --disable-arguments Do not check function arguments.');
  85. Writeln(' --disable-private Do not check class private fields.');
  86. Writeln(' --disable-protected Do not check class protected fields.');
  87. Writeln(' --help Emit help.');
  88. Writeln(' --input=cmdline Input file to create skeleton for.');
  89. Writeln(' Use options are as for compiler.');
  90. Writeln(' --lang=language Use selected language.');
  91. Writeln(' --list List identifiers instead of making a diff');
  92. Writeln(' --output=filename Send output to file.');
  93. Writeln(' --sparse Sparse list/diff (skip type identification)');
  94. end;
  95. procedure ParseOption(const s: String);
  96. var
  97. i: Integer;
  98. Cmd, Arg: String;
  99. begin
  100. if (s = '-h') or (s = '--help') then
  101. CmdLineAction := actionHelp
  102. else if s = '--disable-arguments' then
  103. DisableArguments := True
  104. else if s = '--disable-private' then
  105. DisablePrivate := True
  106. else if s = '--sparse' then
  107. SparseList := True
  108. else if s = '--disable-protected' then
  109. begin
  110. DisableProtected := True;
  111. DisablePrivate :=True;
  112. end
  113. else
  114. begin
  115. i := Pos('=', s);
  116. if i > 0 then
  117. begin
  118. Cmd := Copy(s, 1, i - 1);
  119. Arg := Copy(s, i + 1, Length(s));
  120. end
  121. else
  122. begin
  123. Cmd := s;
  124. SetLength(Arg, 0);
  125. end;
  126. if (Cmd = '-l') or (Cmd = '--lang') then
  127. DocLang := Arg
  128. else if (Cmd = '-o') or (Cmd = '--output') then
  129. OutputName := Arg
  130. else
  131. if (length(cmd)>0) and (cmd[1]='-') then
  132. WriteLn(StdErr, Format(SCmdLineInvalidOption, [s]))
  133. else if (InputFile1='') then
  134. InputFile1:=Cmd
  135. else if (InputFile2='') then
  136. InputFile2:=Cmd
  137. else
  138. WriteLn(StdErr, Format(SCmdLineInvalidOption, [s]));
  139. end;
  140. end;
  141. procedure ParseCommandLine;
  142. Const
  143. {$IFDEF Unix}
  144. MoFileTemplate = '/usr/local/share/locale/%s/LC_MESSAGES/makeskel.mo';
  145. {$ELSE}
  146. MoFileTemplate ='intl/makeskel.%s.mo';
  147. {$ENDIF}
  148. var
  149. MOFilename: string;
  150. i: Integer;
  151. begin
  152. CmdLineAction := actionDiff;
  153. DocLang:='';
  154. SparseList:=False;
  155. for i := 1 to ParamCount do
  156. ParseOption(ParamStr(i));
  157. If (DocLang<>'') then
  158. begin
  159. MOFilename:=Format(MOFileTemplate,[DocLang]);
  160. if FileExists(MOFilename) then
  161. gettext.TranslateResourceStrings(MoFileName)
  162. else
  163. writeln('NOTE: unable to find translation file ',MOFilename);
  164. // Translate internal documentation strings
  165. TranslateDocStrings(DocLang);
  166. end;
  167. if (cmdLineAction<>ActionHelp) and (InputFile1='') and (InputFile2='') then
  168. begin
  169. Writeln(StdErr,SErrNoInputFile);
  170. cmdLineAction := actionHelp;
  171. end else if (InputFile2='') and (CmdLineAction<>ActionList) then
  172. begin
  173. Writeln(StdErr,SWarnAssumingList);
  174. CmdLineAction:=ActionList;
  175. end;
  176. end;
  177. Function GetTypeDescription(El : TPasElement) : String;
  178. begin
  179. If Assigned(El) then
  180. Result:=El.ElementTypeName
  181. else
  182. Result:='(unknown)';
  183. end;
  184. Procedure ListIdentifiers(Fn : String; List : TStrings);
  185. Var
  186. I : Integer;
  187. begin
  188. Writeln(f,Format(SIdentifiersIn,[FN]));
  189. For I:=0 to List.Count-1 do
  190. begin
  191. If Not SparseList then
  192. Write(GetTypeDescription(TPasElement(List.Objects[i])),' : ');
  193. Writeln(List[i]);
  194. end;
  195. end;
  196. Procedure WriteExtra(FN,Id : String; El: TPaselement);
  197. begin
  198. If SparseList then
  199. Writeln(F,Format(SExtraIdentifier,[FN,ID]))
  200. else
  201. Writeln(F,Format(SExtraTypedIdentifier,[FN,GetTypeDescription(El),ID]));
  202. end;
  203. Procedure DoExtra(FN : String; L : TStrings);
  204. Var
  205. I,Len : Integer;
  206. S : String;
  207. begin
  208. I:=0;
  209. While (I<L.Count) do
  210. begin
  211. WriteExtra(FN,L[I],TPasElement(L.Objects[I]));
  212. // Delete possible subelements.
  213. S:=L[I]+'.';
  214. Len:=Length(S);
  215. While (I+1<L.Count) and (CompareText(Copy(L[I+1],1,Len),S)=0) do
  216. L.Delete(I+1);
  217. Inc(I);
  218. end;
  219. end;
  220. Procedure DiffIdentifiers(List1,List2 : TStrings);
  221. Var
  222. L1,L2 : TStrings;
  223. I,J : Integer;
  224. begin
  225. L1:=List1;
  226. L2:=List2;
  227. If List2.Count>List1.Count then
  228. begin
  229. L1:=List2;
  230. L2:=List1;
  231. end;
  232. // Remove all common elements.
  233. For I:=L1.Count-1 downto 0 do
  234. begin
  235. J:=L2.IndexOf(L1[i]);
  236. If (J<>-1) then
  237. begin
  238. L1.Delete(I);
  239. L2.Delete(J);
  240. end;
  241. end;
  242. If (List1.Count=0) and (List2.Count=0) then
  243. Writeln(F,SIdenticalUnits)
  244. else
  245. begin
  246. DoExtra(InputFile1,List1);
  247. DoExtra(InputFile2,List2);
  248. end;
  249. end;
  250. begin
  251. ParseCommandLine;
  252. if CmdLineAction = actionHelp then
  253. Usage
  254. else
  255. begin
  256. Assign(f, OutputName);
  257. Rewrite(f);
  258. Try
  259. Engine1:=TSkelEngine.Create;
  260. Try
  261. try
  262. Engine1.SetPackageName('diff'); // do not localize
  263. ParseSource(Engine1, InputFile1, OSTarget, CPUTarget);
  264. Engine1.FList.Sorted:=True;
  265. if (InputFile2<>'') then
  266. begin
  267. Engine2:=TSkelEngine.Create;
  268. Try
  269. Engine2.SetPackageName('diff'); // do not localize
  270. ParseSource(Engine2, InputFile2, OSTarget, CPUTarget);
  271. Engine2.FList.Sorted:=True;
  272. If cmdLineAction=ActionList then
  273. begin
  274. ListIdentifiers(InputFile1,Engine1.FList);
  275. ListIdentifiers(InputFile2,Engine2.FList);
  276. end
  277. else
  278. DiffIdentifiers(Engine1.Flist,Engine2.Flist);
  279. finally
  280. Engine2.Free;
  281. end;
  282. end
  283. else
  284. ListIdentifiers(InputFile1,Engine1.FList);
  285. except
  286. on e: eparsererror do
  287. writeln(format('%s(%d,%d): Error: %s',[e.Filename,e.Row,e.Column,e.Message]));
  288. end;
  289. Finally
  290. Engine1.Free;
  291. end;
  292. Finally
  293. Close(f);
  294. end;
  295. end;
  296. end.