2
0

unitdiff.pp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  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 aPasElement.InheritsFrom(TPasUnresolvedTypeRef)) and
  69. (not aPasElement.InheritsFrom(TPasUnresolvedUnitRef)) and
  70. (not aPasElement.InheritsFrom(TPasUsesUnit)) and
  71. (not DisableArguments or ((APasElement.ClassType <> TPasArgument) and (not (aParent is TPasArgument)))) and
  72. (not DisableFunctionResults or (APasElement.ClassType <> TPasResultElement)) and
  73. (not DisablePrivate or (AVisibility<>visPrivate)) and
  74. (not DisableProtected or (AVisibility<>visProtected));
  75. end;
  76. begin
  77. Result := AClass.Create(AName, AParent);
  78. if AClass.InheritsFrom(TPasModule) then
  79. CurModule := TPasModule(Result)
  80. else if ExamineThisNode(Result) then
  81. Flist.AddObject(Result.FullName,Result);
  82. end;
  83. Procedure Usage;
  84. begin
  85. Writeln('Usage : ',ExtractFileName(Paramstr(0)),' [options] file1 file2');
  86. Writeln('Where [options] is one or more of :');
  87. Writeln(' -h or --help Emit help.');
  88. Writeln(' --disable-arguments Do not check function arguments.');
  89. Writeln(' --disable-private Do not check class private fields.');
  90. Writeln(' --disable-protected Do not check class protected fields.');
  91. Writeln(' --disable-result Do not check Function results.');
  92. Writeln(' --input=cmdline Input file to create skeleton for. Specify twice, once for each file.');
  93. Writeln(' Use options as for compiler.');
  94. Writeln(' --lang=language Use selected language.');
  95. Writeln(' --list List identifiers instead of making a diff');
  96. Writeln(' --output=filename Send output to file.');
  97. Writeln(' --sparse Sparse list/diff (skip type identification)');
  98. end;
  99. function setinput(const cmd : string) : Boolean;
  100. begin
  101. Result:=True;
  102. if (InputFile1='') then
  103. InputFile1:=Cmd
  104. else if (InputFile2='') then
  105. InputFile2:=Cmd
  106. else
  107. Result:=false;
  108. end;
  109. procedure ParseOption(const s: String);
  110. var
  111. i: Integer;
  112. Cmd, Arg: String;
  113. begin
  114. if (s = '-h') or (s = '--help') then
  115. CmdLineAction := actionHelp
  116. else if s = '--disable-arguments' then
  117. DisableArguments := True
  118. else if s = '--disable-result' then
  119. DisableFunctionResults:=True
  120. else if s = '--disable-private' then
  121. DisablePrivate := True
  122. else if s = '--sparse' then
  123. SparseList := True
  124. else if s = '--list' then
  125. cmdLineAction := ActionList
  126. else if s = '--disable-protected' then
  127. begin
  128. DisableProtected := True;
  129. DisablePrivate :=True;
  130. end
  131. else
  132. begin
  133. i := Pos('=', s);
  134. if i > 0 then
  135. begin
  136. Cmd := Copy(s, 1, i - 1);
  137. Arg := Copy(s, i + 1, Length(s));
  138. end
  139. else
  140. begin
  141. Cmd := s;
  142. SetLength(Arg, 0);
  143. end;
  144. if (Cmd = '-l') or (Cmd = '--lang') then
  145. DocLang := Arg
  146. else if (Cmd = '-o') or (Cmd = '--output') then
  147. OutputName := Arg
  148. else if (Cmd = '-i') or (Cmd = '--input') then
  149. begin
  150. if not SetInput(Arg) then
  151. WriteLn(StdErr, Format(SCmdLineInvalidOption, [s]));
  152. end
  153. else
  154. if (length(cmd)>0) and (cmd[1]='-') then
  155. WriteLn(StdErr, Format(SCmdLineInvalidOption, [s]))
  156. else if not SetInput(cmd) then
  157. WriteLn(StdErr, Format(SCmdLineInvalidOption, [s]));
  158. end;
  159. end;
  160. procedure ParseCommandLine;
  161. Const
  162. {$IFDEF Unix}
  163. MoFileTemplate = '/usr/local/share/locale/%s/LC_MESSAGES/makeskel.mo';
  164. {$ELSE}
  165. MoFileTemplate ='intl/makeskel.%s.mo';
  166. {$ENDIF}
  167. var
  168. MOFilename: string;
  169. i: Integer;
  170. begin
  171. CmdLineAction := actionDiff;
  172. DocLang:='';
  173. SparseList:=False;
  174. for i := 1 to ParamCount do
  175. ParseOption(ParamStr(i));
  176. If (DocLang<>'') then
  177. begin
  178. MOFilename:=Format(MOFileTemplate,[DocLang]);
  179. if FileExists(MOFilename) then
  180. gettext.TranslateResourceStrings(MoFileName)
  181. else
  182. writeln('NOTE: unable to find translation file ',MOFilename);
  183. // Translate internal documentation strings
  184. TranslateDocStrings(DocLang);
  185. end;
  186. if (cmdLineAction<>ActionHelp) then
  187. begin
  188. if (InputFile1='') and (InputFile2='') then
  189. begin
  190. Writeln(StdErr,SErrNoInputFile);
  191. cmdLineAction := actionHelp;
  192. end
  193. else if (InputFile2='') and (CmdLineAction<>ActionList) then
  194. begin
  195. Writeln(StdErr,SWarnAssumingList);
  196. CmdLineAction:=ActionList;
  197. end;
  198. end;
  199. end;
  200. Function GetTypeDescription(El : TPasElement) : String;
  201. begin
  202. If Assigned(El) then
  203. Result:=El.ElementTypeName
  204. else
  205. Result:='(unknown)';
  206. end;
  207. Procedure ListIdentifiers(Fn : String; List : TStrings);
  208. Var
  209. I : Integer;
  210. begin
  211. Writeln(f,Format(SIdentifiersIn,[FN]));
  212. For I:=0 to List.Count-1 do
  213. begin
  214. If Not SparseList then
  215. Write(F,GetTypeDescription(TPasElement(List.Objects[i])),' : ');
  216. Writeln(F,List[i]);
  217. end;
  218. end;
  219. Procedure WriteExtra(FN,Id : String; El: TPaselement);
  220. begin
  221. If SparseList then
  222. Writeln(F,Format(SExtraIdentifier,[FN,ID]))
  223. else
  224. Writeln(F,Format(SExtraTypedIdentifier,[FN,GetTypeDescription(El),ID]));
  225. end;
  226. Procedure DoExtra(FN : String; L : TStrings);
  227. Var
  228. I,Len : Integer;
  229. S : String;
  230. begin
  231. I:=0;
  232. While (I<L.Count) do
  233. begin
  234. WriteExtra(FN,L[I],TPasElement(L.Objects[I]));
  235. // Delete possible subelements.
  236. S:=L[I]+'.';
  237. Len:=Length(S);
  238. While (I+1<L.Count) and (CompareText(Copy(L[I+1],1,Len),S)=0) do
  239. L.Delete(I+1);
  240. Inc(I);
  241. end;
  242. end;
  243. Procedure DiffIdentifiers(List1,List2 : TStrings);
  244. Var
  245. L1,L2 : TStrings;
  246. I,J : Integer;
  247. begin
  248. L1:=List1;
  249. L2:=List2;
  250. If List2.Count>List1.Count then
  251. begin
  252. L1:=List2;
  253. L2:=List1;
  254. end;
  255. // Remove all common elements.
  256. For I:=L1.Count-1 downto 0 do
  257. begin
  258. J:=L2.IndexOf(L1[i]);
  259. If (J<>-1) then
  260. begin
  261. L1.Delete(I);
  262. L2.Delete(J);
  263. end;
  264. end;
  265. If (List1.Count=0) and (List2.Count=0) then
  266. Writeln(F,SIdenticalUnits)
  267. else
  268. begin
  269. DoExtra(InputFile1,List1);
  270. DoExtra(InputFile2,List2);
  271. end;
  272. end;
  273. begin
  274. ParseCommandLine;
  275. if CmdLineAction = actionHelp then
  276. Usage
  277. else
  278. begin
  279. Assign(f, OutputName);
  280. Rewrite(f);
  281. Try
  282. Engine1:=TSkelEngine.Create;
  283. Try
  284. try
  285. Engine1.SetPackageName('diff'); // do not localize
  286. ParseSource(Engine1, InputFile1, OSTarget, CPUTarget);
  287. Engine1.FList.Sorted:=True;
  288. if (InputFile2<>'') then
  289. begin
  290. Engine2:=TSkelEngine.Create;
  291. Try
  292. Engine2.SetPackageName('diff'); // do not localize
  293. ParseSource(Engine2, InputFile2, OSTarget, CPUTarget);
  294. Engine2.FList.Sorted:=True;
  295. If cmdLineAction=ActionList then
  296. begin
  297. ListIdentifiers(InputFile1,Engine1.FList);
  298. ListIdentifiers(InputFile2,Engine2.FList);
  299. end
  300. else
  301. DiffIdentifiers(Engine1.Flist,Engine2.Flist);
  302. finally
  303. Engine2.Free;
  304. end;
  305. end
  306. else
  307. ListIdentifiers(InputFile1,Engine1.FList);
  308. except
  309. on e: eparsererror do
  310. writeln(format('%s(%d,%d): Error: %s',[e.Filename,e.Row,e.Column,e.Message]));
  311. end;
  312. Finally
  313. Engine1.Free;
  314. end;
  315. Finally
  316. Close(f);
  317. end;
  318. end;
  319. end.