unitdiff.pp 9.0 KB

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