unitdiff.pp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  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(' -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.');
  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) then
  168. begin
  169. if (InputFile1='') and (InputFile2='') then
  170. begin
  171. Writeln(StdErr,SErrNoInputFile);
  172. cmdLineAction := actionHelp;
  173. end
  174. else if (InputFile2='') and (CmdLineAction<>ActionList) then
  175. begin
  176. Writeln(StdErr,SWarnAssumingList);
  177. CmdLineAction:=ActionList;
  178. end;
  179. end;
  180. end;
  181. Function GetTypeDescription(El : TPasElement) : String;
  182. begin
  183. If Assigned(El) then
  184. Result:=El.ElementTypeName
  185. else
  186. Result:='(unknown)';
  187. end;
  188. Procedure ListIdentifiers(Fn : String; List : TStrings);
  189. Var
  190. I : Integer;
  191. begin
  192. Writeln(f,Format(SIdentifiersIn,[FN]));
  193. For I:=0 to List.Count-1 do
  194. begin
  195. If Not SparseList then
  196. Write(GetTypeDescription(TPasElement(List.Objects[i])),' : ');
  197. Writeln(List[i]);
  198. end;
  199. end;
  200. Procedure WriteExtra(FN,Id : String; El: TPaselement);
  201. begin
  202. If SparseList then
  203. Writeln(F,Format(SExtraIdentifier,[FN,ID]))
  204. else
  205. Writeln(F,Format(SExtraTypedIdentifier,[FN,GetTypeDescription(El),ID]));
  206. end;
  207. Procedure DoExtra(FN : String; L : TStrings);
  208. Var
  209. I,Len : Integer;
  210. S : String;
  211. begin
  212. I:=0;
  213. While (I<L.Count) do
  214. begin
  215. WriteExtra(FN,L[I],TPasElement(L.Objects[I]));
  216. // Delete possible subelements.
  217. S:=L[I]+'.';
  218. Len:=Length(S);
  219. While (I+1<L.Count) and (CompareText(Copy(L[I+1],1,Len),S)=0) do
  220. L.Delete(I+1);
  221. Inc(I);
  222. end;
  223. end;
  224. Procedure DiffIdentifiers(List1,List2 : TStrings);
  225. Var
  226. L1,L2 : TStrings;
  227. I,J : Integer;
  228. begin
  229. L1:=List1;
  230. L2:=List2;
  231. If List2.Count>List1.Count then
  232. begin
  233. L1:=List2;
  234. L2:=List1;
  235. end;
  236. // Remove all common elements.
  237. For I:=L1.Count-1 downto 0 do
  238. begin
  239. J:=L2.IndexOf(L1[i]);
  240. If (J<>-1) then
  241. begin
  242. L1.Delete(I);
  243. L2.Delete(J);
  244. end;
  245. end;
  246. If (List1.Count=0) and (List2.Count=0) then
  247. Writeln(F,SIdenticalUnits)
  248. else
  249. begin
  250. DoExtra(InputFile1,List1);
  251. DoExtra(InputFile2,List2);
  252. end;
  253. end;
  254. begin
  255. ParseCommandLine;
  256. if CmdLineAction = actionHelp then
  257. Usage
  258. else
  259. begin
  260. Assign(f, OutputName);
  261. Rewrite(f);
  262. Try
  263. Engine1:=TSkelEngine.Create;
  264. Try
  265. try
  266. Engine1.SetPackageName('diff'); // do not localize
  267. ParseSource(Engine1, InputFile1, OSTarget, CPUTarget);
  268. Engine1.FList.Sorted:=True;
  269. if (InputFile2<>'') then
  270. begin
  271. Engine2:=TSkelEngine.Create;
  272. Try
  273. Engine2.SetPackageName('diff'); // do not localize
  274. ParseSource(Engine2, InputFile2, OSTarget, CPUTarget);
  275. Engine2.FList.Sorted:=True;
  276. If cmdLineAction=ActionList then
  277. begin
  278. ListIdentifiers(InputFile1,Engine1.FList);
  279. ListIdentifiers(InputFile2,Engine2.FList);
  280. end
  281. else
  282. DiffIdentifiers(Engine1.Flist,Engine2.Flist);
  283. finally
  284. Engine2.Free;
  285. end;
  286. end
  287. else
  288. ListIdentifiers(InputFile1,Engine1.FList);
  289. except
  290. on e: eparsererror do
  291. writeln(format('%s(%d,%d): Error: %s',[e.Filename,e.Row,e.Column,e.Message]));
  292. end;
  293. Finally
  294. Engine1.Free;
  295. end;
  296. Finally
  297. Close(f);
  298. end;
  299. end;
  300. end.