namespacetool.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  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. if p>1 then
  354. begin
  355. aOpts[I]:=Copy(Opt,1,P-1);
  356. Opt:=Trim(Copy(Opt,P+1));
  357. inc(I);
  358. end;
  359. until (Opt='');
  360. SetLength(aOpts,I);
  361. end;
  362. P:=Pos('=',FN);
  363. if P<>0 then
  364. begin
  365. aRule:=Copy(FN,P+1);
  366. FN:=Copy(FN,1,P-1);
  367. end;
  368. aFileName:=FN;
  369. // Use previous rule ?
  370. aDir:=ExtractFilePath(FN);
  371. if aDir=aLastDir then
  372. begin
  373. if (aRule='') then
  374. aRule:=aLastRule;
  375. if Length(aOpts)=0 then
  376. aOpts:=aLastOpts;
  377. end;
  378. aLastDir:=aDir;
  379. aLastRule:=aRule;
  380. aLastOpts:=aOpts;
  381. end;
  382. function TNamespaceTool.GetPackageDir(const aFileName : string) : string;
  383. Var
  384. P : Integer;
  385. begin
  386. Result:='';
  387. if aFileName='' then
  388. exit;
  389. P:=Pos('/',aFileName,2);
  390. if P=0 then
  391. exit;
  392. Result:=Copy(aFileName,1,P);
  393. If Result[1]='/' then
  394. Delete(Result,1,1);
  395. end;
  396. procedure TNamespaceTool.HandleFileList(const aFileName : String);
  397. begin
  398. DoHandleFileList(aFileName);
  399. if FWritePrefixes and Update then
  400. begin
  401. DoMsg('Updating known prefixes file: %s ',[PrefixesFileName]);
  402. if not FDryRun then
  403. FKnownPrefixes.SaveToFile(FPrefixesFileName);
  404. end;
  405. end;
  406. procedure TNamespaceTool.DoHandleFileList(const aFileName : String);
  407. Var
  408. List,Done : TStringList;
  409. aLine,FN,FNDir, LastPackageDir,aRule : String;
  410. aOpts : TStringDynArray;
  411. begin
  412. aOpts:=[];
  413. Done:=Nil;
  414. LastPackageDir:='';
  415. List:=TStringList.Create;
  416. try
  417. Done:=TStringList.Create;
  418. if (not FRestart) and fileExists(FDoneFileName) then
  419. Done.LoadFromFile(FDoneFileName);
  420. List.LoadFromFile(aFileName);
  421. For aLine in List do
  422. begin
  423. // Lines have 3 parts
  424. // FileName=Rule;Compile Options
  425. SplitLine(aLine,FN,aRule,aOpts);
  426. FNDir:=GetPackageDir(FN);
  427. if (LastPackageDir<>FNDir) then
  428. begin
  429. if (LastPackageDir<>'') and (FFPMakeNameSpaceFile<>'') then
  430. WritePackageNameSpaceFile(LastPackageDir,List);
  431. LastPackageDir:=FNDir;
  432. end;
  433. if Done.indexOf(FN)=-1 then
  434. begin
  435. try
  436. HandleFile(FN,aRule,aOpts);
  437. Done.Add(FN);
  438. except
  439. On E : Exception do
  440. DoMsg('Error %s while handling file %s : %s',[E.ClassName,FN,E.Message],etError);
  441. end;
  442. end;
  443. end;
  444. if (LastPackageDir<>'') and (FFPMakeNameSpaceFile<>'') then
  445. WritePackageNameSpaceFile(LastPackageDir,List);
  446. finally
  447. Done.SaveToFile(FDoneFileName);
  448. List.Free;
  449. end;
  450. end;
  451. procedure TNamespaceTool.CreateKnown(const aFileName: String);
  452. Var
  453. List,Done : TStringList;
  454. aRule,aLine,FN,aUnit,aNewUnit : String;
  455. aOpts : TStringDynArray;
  456. begin
  457. Done:=Nil;
  458. FLastDir:='';
  459. FLastRule:='';
  460. aOpts:=[];
  461. if FPrefixesFileName='' then
  462. FPrefixesFileName:=ChangeFileExt(aFileName,'.map');
  463. List:=TStringList.Create;
  464. try
  465. Done:=TStringList.Create;
  466. if FileExists(FPrefixesFileName) then
  467. Done.LoadFromFile(FPrefixesFileName);
  468. List.LoadFromFile(aFileName);
  469. // Lines have 3 parts
  470. // FileName=Rule;Compile Options
  471. For aLine in List do
  472. begin
  473. SplitLine(aLine,FN,aRule,aOpts);
  474. aUnit:=ChangeFileExt(ExtractFileName(FN),'');
  475. aNewUnit:=ChangeFileExt(ExtractFileName(TPrefixer.ApplyRule(FN,aUnit,aRule,FCasedFiles)),'');
  476. Done.Values[aUnit]:='*'+aNewUnit;
  477. end;
  478. if FDryRun then
  479. begin
  480. for aLine in Done do
  481. DoMsg(aLine)
  482. end
  483. else
  484. Done.SaveToFile(FPrefixesFileName);
  485. finally
  486. Done.SaveToFile('done.tmp');
  487. Done.Free;
  488. List.Free;
  489. end;
  490. end;
  491. end.