namespacetool.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. unit namespacetool;
  2. {$mode ObjFPC}{$H+}
  3. interface
  4. uses
  5. Classes, SysUtils, types, prefixer;
  6. Const
  7. DefaultSubdir = 'namespaced';
  8. DefaultDoneList = 'done.lst';
  9. type
  10. { TNamespaceCreation }
  11. TSubDirMode = (
  12. sdmAppend, // append dirmap result to subdir
  13. sdmReplace // replace directory part with result of dirmap
  14. );
  15. TNamespaceToolLogEvent = procedure(Sender : TObject; EventType : TEventType; Const Msg : String) of object;
  16. TChangeFPMakeResult = (cmrFailed,cmrAlreadyDone,cmrOK);
  17. { TNamespaceTool }
  18. TNamespaceTool = class(TComponent)
  19. Private
  20. FDoneFileName : string;
  21. FDirMapFileName: string;
  22. FOnLog: TNamespaceToolLogEvent;
  23. FPrefixesFileName: string;
  24. FDefaultPrefix: string;
  25. FFPMakeNameSpaceFile : String;
  26. FSubDir : String;
  27. FCasedFiles,
  28. FUpdate,
  29. FDryRun,
  30. FWritePrefixes,
  31. FBackup: Boolean;
  32. FSubdirMode: TSubDirMode;
  33. FFPMakeMap : TStrings;
  34. FDirmap : TStrings;
  35. FKnownPrefixes : TStrings;
  36. FRestart : Boolean;
  37. FLastOpts: TStringDynArray;
  38. FLastRule,
  39. FLastDir : String;
  40. FForcedExt : String;
  41. procedure DoPrefixLog(Sender: TObject; aType: TEventType; const aMsg: String
  42. );
  43. procedure SetForcedExt(AValue: String);
  44. procedure SetSubdir(AValue: String);
  45. Protected
  46. procedure DoMsg(const aFmt: String; const aArgs: array of const;
  47. EventType: TEventType=etInfo); overload;
  48. procedure DoMsg(const aMessage: String; EventType: TEventType=etInfo); overload;
  49. // Add code to initialize namespace to fpmake in filename.
  50. function AddNamespaceNameToFpMake(const aFileName: string): TChangeFPMakeResult;
  51. // add file to FPMake namespaces file
  52. procedure AddToFPMakeMap(const aSrcFileName, aDestFileName: string);
  53. // Create directory if not dryrun
  54. procedure CreateDestDir(const aDestDir: string);
  55. // Actual HandleFileList
  56. procedure DoHandleFileList(const aFileName: String);
  57. // Return name of package dir from filename (first level of dir tree).
  58. function GetPackageDir(const aFileName: string): string;
  59. // Return unit name from file name.
  60. function GetUnitNameFromFile(aFile: String): string;
  61. // Split line into
  62. procedure SplitLine(aLine: String; out aFileName, aRule: String;
  63. var aOpts: TStringDynArray);
  64. // Write FPMake Namespaces file.
  65. procedure WritePackageNameSpaceFile(aDir: String; aList: TStrings; DoClear: Boolean=True);
  66. Public
  67. class procedure SplitRuleLine(aLine: String; out aFileName, aRule: String;
  68. var AlastDir, aLastRule: String; var aOpts, aLastOpts: TStringDynArray);
  69. Public
  70. Constructor Create(aOwner : TComponent); override;
  71. Destructor Destroy; override;
  72. // Initialize (load config files)
  73. Procedure Init;
  74. // Actual actions
  75. // Apply rule to a single unit file
  76. procedure HandleFile(const aFileName: String; aRule: string; aOptions: array of String);
  77. // Load file list and call handlefile for each
  78. procedure HandleFileList(const aFileName: String);
  79. // Create a 'known prefixes' file with the names of the files
  80. procedure CreateKnown(const aFileName: String);
  81. Property OnLog : TNamespaceToolLogEvent Read FOnLog Write FOnLog;
  82. Property ForcedExt : String Read FForcedExt Write SetForcedExt;
  83. Property DirMapFileName : String Read FDirMapFileName Write FDirMapFileName;
  84. Property PrefixesFileName : String Read FPrefixesFileName Write FPrefixesFileName;
  85. Property DefaultPrefix : String Read FDefaultPrefix Write FDefaultPrefix;
  86. Property Subdir : String Read FSubdir Write SetSubdir;
  87. Property SubdirMode : TSubDirMode Read FSubdirMode Write FSubdirMode;
  88. Property Backup : Boolean Read FBackup Write FBackup;
  89. Property Update : Boolean Read FUpdate Write FUpdate;
  90. Property DryRun : Boolean Read FDryRun Write FDryRun;
  91. Property Restart : Boolean Read FRestart Write FRestart;
  92. Property CasedFiles : Boolean Read FCasedFiles Write FCasedFiles;
  93. Property FPMakeNameSpaceFile : String Read FFPMakeNameSpaceFile Write FFPMakeNameSpaceFile;
  94. Property KnownPrefixes : TStrings Read FKnownPrefixes;
  95. Property DirMap : Tstrings Read FDirmap;
  96. end;
  97. implementation
  98. procedure TNamespaceTool.CreateDestDir(const aDestDir : string);
  99. begin
  100. if not DirectoryExists(aDestDir) then
  101. begin
  102. DoMsg('Creating destination directory: %s',[aDestDir]);
  103. if not FDryRun then
  104. if not ForceDirectories(aDestDir) then
  105. Raise Exception.Create('Could not create destination directory '+aDestDir);
  106. end;
  107. end;
  108. procedure TNamespaceTool.DoMsg(const aFmt: String; const aArgs: array of const; EventType : TEventType = etInfo);
  109. begin
  110. DoMsg(Format(aFmt,aArgs),EventType);
  111. end;
  112. procedure TNamespaceTool.DoMsg(const aMessage: String; EventType : TEventType = etInfo);
  113. begin
  114. if assigned(OnLog) then
  115. OnLog(Self,EventType, aMessage);
  116. end;
  117. procedure TNamespaceTool.AddToFPMakeMap(const aSrcFileName,aDestFileName : string);
  118. Var
  119. Src,Dest,aDir,aRule : String;
  120. begin
  121. Src:=aSrcFileName;
  122. Dest:=aDestFileName;
  123. // Strip package dir
  124. aDir:=GetPackageDir(aSrcFileName);
  125. if Pos(aDir,Src)=1 then
  126. Delete(Src,1,Length(aDir));
  127. if Pos(aDir,Dest)=1 then
  128. Delete(Dest,1,Length(aDir));
  129. // Map file itself.
  130. FFPMakeMap.Values[Src]:=Dest;
  131. aDir:=ExtractFilePath(Src);
  132. // Map source directory to namespaced
  133. aRule:='{s*:'+aDir+'}';
  134. FFPMakeMap.Values[aRule]:=ExtractFilePath(Dest);
  135. // Add original to include directory
  136. aRule:='{i+:'+aDir+'}';
  137. if FFPMakeMap.IndexOf(aRule)=-1 then
  138. FFPMakeMap.Add(aRule);
  139. end;
  140. function TNamespaceTool.GetUnitNameFromFile(aFile : String) : string;
  141. begin
  142. Result:=ExtractFileName(ChangeFileExt(aFile,''))
  143. end;
  144. procedure TNamespaceTool.SetForcedExt(AValue: String);
  145. begin
  146. if FForcedExt=AValue then Exit;
  147. if (aValue<>'') and (aValue[1]<>'.') then
  148. aValue:='.'+aValue;
  149. FForcedExt:=AValue;
  150. end;
  151. procedure TNamespaceTool.DoPrefixLog(Sender: TObject; aType: TEventType;
  152. const aMsg: String);
  153. begin
  154. DoMsg(aMsg,aType);
  155. end;
  156. procedure TNamespaceTool.SetSubdir(AValue: String);
  157. begin
  158. if FSubdir=AValue then Exit;
  159. FSubdir:=AValue;
  160. if FSubDir<>'' then
  161. FSubDir:=IncludeTrailingPathDelimiter(FSubDir);
  162. end;
  163. procedure TNamespaceTool.HandleFile(const aFileName: String; aRule : string; aOptions: array of String);
  164. Var
  165. aNewUnitName,aNewUnitFile,Ext,SrcDir,aUnitName,DestDir,aDummy,DestFN : String;
  166. P : TPrefixer;
  167. NeedUpdate : Boolean;
  168. Idx : Integer;
  169. begin
  170. NeedUpdate:=False;
  171. Ext:=FForcedExt;
  172. if Ext='' then
  173. Ext:=ExtractFileExt(aFileName);
  174. // Construct File name
  175. aUnitName:=GetUnitNameFromFile(aFilename);
  176. // Construct destination dir.
  177. SrcDir:=ExtractFilePath(aFileName);
  178. DestDir:=FDirMap.Values[aUnitName];
  179. if DestDir='' then
  180. DestDir:=FDirMap.Values[ExcludeTrailingBackslash(SrcDir)];
  181. if DestDir='' then
  182. DestDir:=SrcDir;
  183. case SubDirMode of
  184. sdmAppend : DestDir:=FSubDir+DestDir;
  185. sdmReplace : ; // do nothing
  186. end;
  187. DestDir:=IncludeTrailingPathDelimiter(DestDir);
  188. // No rule, see if there is a filename rule in known prefixes
  189. if aRule='' then
  190. begin
  191. Idx:=FKnownPrefixes.IndexOfName(aUnitName);
  192. if Idx<>-1 then
  193. FKnownPrefixes.GetNameValue(Idx,aDummy,aRule);
  194. end;
  195. aNewUnitFile:=TPrefixer.ApplyRule(aFileName,aDummy,aRule,FCasedFiles and (aRule<>''));
  196. aNewUnitName:=GetUnitNameFromFile(aNewUnitFile);
  197. if SameText(aNewUnitName,aUnitName) then
  198. begin
  199. DoMsg('Rule for %s does not result in different unit name, skipping.',[aFileName],etWarning);
  200. exit;
  201. end;
  202. DestFN:=DestDir+aNewUnitName+Ext;
  203. // Add new file to FPMake map.
  204. AddToFPMakeMap(aFileName,DestFN);
  205. if FileExists(DestFN) then
  206. DoMsg('File %s already exists, skipping generation',[DestFN]);
  207. // Create directory.
  208. CreateDestDir(DestDir);
  209. DoMsg('Converting %s to %s',[aFileName,DestFN]);
  210. if not FDryRun then
  211. begin
  212. P:=TPrefixer.Create(Self);
  213. try
  214. P.OnLog:=@DoPrefixLog;
  215. P.UnitFileMode:=fmInclude;
  216. P.IncludeUnitNameMode:=inmIfndef;
  217. P.FileName:=aFileName;
  218. P.NameSpace:=TPrefixer.ExtractPrefix(aRule);
  219. P.KnownNameSpaces.AddStrings(FKnownPrefixes);
  220. P.SkipDestFileName:=FileExists(DestFN);
  221. P.DestFileName:=DestFN;
  222. P.CreateBackups:=FBackup;
  223. P.CasedFileNames:=FCasedFiles;
  224. P.Params.AddStrings(aOptions);
  225. P.Params.Add('-Fi'+ExtractFilePath(aFileName));
  226. P.Execute;
  227. finally
  228. P.Free;
  229. end;
  230. end;
  231. If NeedUpdate then
  232. begin
  233. FKnownPrefixes.Values[aUnitName]:='*'+aNewUnitName;
  234. FWritePrefixes:=True;
  235. end;
  236. end;
  237. Function TNamespaceTool.AddNamespaceNameToFpMake(const aFileName : string) : TChangeFPMakeResult;
  238. const
  239. namespacelist = 'namespaces.lst';
  240. Var
  241. aFile : TStringList;
  242. I : Integer;
  243. aLine : string;
  244. begin
  245. Result:=cmrFailed;
  246. aFile:=TStringList.Create;
  247. try
  248. aFile.LoadFromFile(aFileName);
  249. i:=aFile.Count-1;
  250. while (I>=0) and (Result=cmrFailed) do
  251. begin
  252. if Pos('p.namespacemap',LowerCase(aFile[i]))>0 then
  253. result:=cmrAlreadyDone;
  254. Dec(I);
  255. end;
  256. i:=aFile.Count-1;
  257. while (I>=0) and (Result=cmrFailed) do
  258. begin
  259. aLine:=aFile[i];
  260. if pos('{$ifndef ALLPACKAGES}',aLine)>0 then
  261. if Pos('run',Lowercase(aFile[i+1]))>0 then
  262. begin
  263. aFile.Insert(I,'');
  264. aFile.Insert(I,Format(' P.NamespaceMap:=''%s'';',[namespacelist]));
  265. aFile.Insert(I,'');
  266. Result:=cmrOK;
  267. end;
  268. Dec(I);
  269. end;
  270. if Result=cmrOK then
  271. aFile.SaveToFile(aFileName);
  272. finally
  273. aFile.Free;
  274. end;
  275. end;
  276. procedure TNamespaceTool.WritePackageNameSpaceFile(aDir : String; aList : TStrings; DoClear : Boolean = True);
  277. Var
  278. FN : String;
  279. begin
  280. if aDir<>'' then
  281. aDir:=IncludeTrailingPathDelimiter(aDir);
  282. if (FFPMakeNameSpaceFile='') or (FFPMakeMap.Count=0) then
  283. exit;
  284. FN:=aDir+FFPMakeNameSpaceFile;
  285. DoMsg('Writing fpmake map file to %s, writing %d rules',[FN,FFPMakeMap.Count]);
  286. FFPMakeMap.SaveToFile(FN);
  287. if DoClear then
  288. FFPMakeMap.Clear;
  289. if FileExists(aDir+'fpmake.pp') then
  290. Case AddNamespaceNameToFpMake(aDir+'fpmake.pp') of
  291. cmrFailed : DoMsg('Failed to set NamespaceMap to file "%s"',[FN],etError);
  292. cmrAlreadyDone : DoMsg('NamespaceMap already set in "%s"',[FN],etWarning);
  293. cmrOK : DoMsg('Added NamespaceMap to file "%s"',[FN],etInfo);
  294. end
  295. end;
  296. constructor TNamespaceTool.Create(aOwner: TComponent);
  297. begin
  298. inherited Create(aOwner);
  299. FDirmap:=TStringList.Create;
  300. FKnownPrefixes:=TStringList.Create;
  301. FFPMakeMap:=TStringList.Create;
  302. FDoneFileName:=DefaultDoneList;
  303. end;
  304. destructor TNamespaceTool.Destroy;
  305. begin
  306. FreeAndNil(FDirmap);
  307. FreeAndNil(FKnownPrefixes);
  308. FreeAndNil(FFPMakeMap);
  309. inherited Destroy;
  310. end;
  311. procedure TNamespaceTool.Init;
  312. begin
  313. if (PrefixesFileName<>'') then
  314. begin
  315. KnownPrefixes.LoadFromFile(PrefixesFileName);
  316. DoMsg('Load of %s results in %d known prefixes',[PrefixesFileName,KnownPrefixes.Count]);
  317. end;
  318. if (DirMapFileName<>'') then
  319. begin
  320. Dirmap.LoadFromFile(DirMapFileName);
  321. DoMsg('Load of %s results in %d directory mappings',[DirMapFileName,DirMap.Count]);
  322. end;
  323. end;
  324. procedure TNamespaceTool.SplitLine(aLine: String; out aFileName, aRule: String;
  325. var aOpts: TStringDynArray);
  326. begin
  327. SplitRuleLine(aLine,aFileName,aRule,FLastDir,FLastRule,aOpts,FLastOpts);
  328. end;
  329. Class procedure TNamespaceTool.SplitRuleLine(aLine: String; out aFileName, aRule: String; var AlastDir, aLastRule : String; var aOpts, aLastOpts: TStringDynArray);
  330. var
  331. I,P : Integer;
  332. aDir,FN,Opt : String;
  333. begin
  334. aRule:='';
  335. aFileName:='';
  336. aOpts:=[];
  337. P:=Pos(';',aLine);
  338. if P=0 then
  339. begin
  340. FN:=aLine;
  341. SetLength(aOpts,0);
  342. end
  343. else
  344. begin
  345. FN:=Copy(aLine,1,P-1);
  346. Opt:=Trim(Copy(aLine,P+1));
  347. SetLength(aOpts,Length(Opt));
  348. I:=0;
  349. Repeat
  350. P:=Pos(' ',Opt);
  351. if P=0 then
  352. P:=Length(Opt)+1;
  353. aOpts[I]:=Copy(Opt,1,P-1);
  354. Opt:=Trim(Copy(Opt,P+1));
  355. inc(I);
  356. until (Opt='');
  357. SetLength(aOpts,I);
  358. end;
  359. P:=Pos('=',FN);
  360. if P<>0 then
  361. begin
  362. aRule:=Copy(FN,P+1);
  363. FN:=Copy(FN,1,P-1);
  364. end;
  365. aFileName:=FN;
  366. // Use previous rule ?
  367. aDir:=ExtractFilePath(FN);
  368. if aDir=aLastDir then
  369. begin
  370. if (aRule='') then
  371. aRule:=aLastRule;
  372. if Length(aOpts)=0 then
  373. aOpts:=aLastOpts;
  374. end;
  375. aLastDir:=aDir;
  376. aLastRule:=aRule;
  377. aLastOpts:=aOpts;
  378. end;
  379. function TNamespaceTool.GetPackageDir(const aFileName : string) : string;
  380. Var
  381. P : Integer;
  382. begin
  383. Result:='';
  384. if aFileName='' then
  385. exit;
  386. P:=Pos('/',aFileName,2);
  387. if P=0 then
  388. exit;
  389. Result:=Copy(aFileName,1,P);
  390. If Result[1]='/' then
  391. Delete(Result,1,1);
  392. end;
  393. procedure TNamespaceTool.HandleFileList(const aFileName : String);
  394. begin
  395. DoHandleFileList(aFileName);
  396. if FWritePrefixes and Update then
  397. begin
  398. DoMsg('Updating known prefixes file: %s ',[PrefixesFileName]);
  399. if not FDryRun then
  400. FKnownPrefixes.SaveToFile(FPrefixesFileName);
  401. end;
  402. end;
  403. procedure TNamespaceTool.DoHandleFileList(const aFileName : String);
  404. Var
  405. List,Done : TStringList;
  406. aLine,FN,FNDir, LastPackageDir,aRule : String;
  407. aOpts : TStringDynArray;
  408. begin
  409. aOpts:=[];
  410. Done:=Nil;
  411. LastPackageDir:='';
  412. List:=TStringList.Create;
  413. try
  414. Done:=TStringList.Create;
  415. if (not FRestart) and fileExists(FDoneFileName) then
  416. Done.LoadFromFile(FDoneFileName);
  417. List.LoadFromFile(aFileName);
  418. For aLine in List do
  419. begin
  420. // Lines have 3 parts
  421. // FileName=Rule;Compile Options
  422. SplitLine(aLine,FN,aRule,aOpts);
  423. FNDir:=GetPackageDir(FN);
  424. if (LastPackageDir<>FNDir) then
  425. begin
  426. if (LastPackageDir<>'') and (FFPMakeNameSpaceFile<>'') then
  427. WritePackageNameSpaceFile(LastPackageDir,List);
  428. LastPackageDir:=FNDir;
  429. end;
  430. if Done.indexOf(FN)=-1 then
  431. begin
  432. try
  433. HandleFile(FN,aRule,aOpts);
  434. Done.Add(FN);
  435. except
  436. On E : Exception do
  437. DoMsg('Error %s while handling file %s : %s',[E.ClassName,FN,E.Message],etError);
  438. end;
  439. end;
  440. end;
  441. if (LastPackageDir<>'') and (FFPMakeNameSpaceFile<>'') then
  442. WritePackageNameSpaceFile(LastPackageDir,List);
  443. finally
  444. Done.SaveToFile(FDoneFileName);
  445. List.Free;
  446. end;
  447. end;
  448. procedure TNamespaceTool.CreateKnown(const aFileName: String);
  449. Var
  450. List,Done : TStringList;
  451. aRule,aLine,FN,aUnit,aNewUnit : String;
  452. aOpts : TStringDynArray;
  453. begin
  454. Done:=Nil;
  455. FLastDir:='';
  456. FLastRule:='';
  457. aOpts:=[];
  458. if FPrefixesFileName='' then
  459. FPrefixesFileName:=ChangeFileExt(aFileName,'.map');
  460. List:=TStringList.Create;
  461. try
  462. Done:=TStringList.Create;
  463. if FileExists(FPrefixesFileName) then
  464. Done.LoadFromFile(FPrefixesFileName);
  465. List.LoadFromFile(aFileName);
  466. // Lines have 3 parts
  467. // FileName=Rule;Compile Options
  468. For aLine in List do
  469. begin
  470. SplitLine(aLine,FN,aRule,aOpts);
  471. aUnit:=ChangeFileExt(ExtractFileName(FN),'');
  472. aNewUnit:=ChangeFileExt(ExtractFileName(TPrefixer.ApplyRule(FN,aUnit,aRule,FCasedFiles)),'');
  473. Done.Values[aUnit]:='*'+aNewUnit;
  474. end;
  475. if FDryRun then
  476. begin
  477. for aLine in Done do
  478. DoMsg(aLine)
  479. end
  480. else
  481. Done.SaveToFile(FPrefixesFileName);
  482. finally
  483. Done.SaveToFile('done.tmp');
  484. Done.Free;
  485. List.Free;
  486. end;
  487. end;
  488. end.