fpc/utils/dotutils/namespacetool.pas
2023-12-03 13:14:22 +01:00

552 lines
15 KiB
ObjectPascal

unit namespacetool;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, types, prefixer;
Const
DefaultSubdir = 'namespaced';
DefaultDoneList = 'done.lst';
type
{ TNamespaceCreation }
TSubDirMode = (
sdmAppend, // append dirmap result to subdir
sdmReplace // replace directory part with result of dirmap
);
TNamespaceToolLogEvent = procedure(Sender : TObject; EventType : TEventType; Const Msg : String) of object;
TChangeFPMakeResult = (cmrFailed,cmrAlreadyDone,cmrOK);
{ TNamespaceTool }
TNamespaceTool = class(TComponent)
Private
FDoneFileName : string;
FDirMapFileName: string;
FOnLog: TNamespaceToolLogEvent;
FPrefixesFileName: string;
FDefaultPrefix: string;
FFPMakeNameSpaceFile : String;
FSubDir : String;
FCasedFiles,
FUpdate,
FDryRun,
FWritePrefixes,
FBackup: Boolean;
FSubdirMode: TSubDirMode;
FFPMakeMap : TStrings;
FDirmap : TStrings;
FKnownPrefixes : TStrings;
FRestart : Boolean;
FLastOpts: TStringDynArray;
FLastRule,
FLastDir : String;
FForcedExt : String;
procedure DoPrefixLog(Sender: TObject; aType: TEventType; const aMsg: String
);
procedure SetForcedExt(AValue: String);
procedure SetSubdir(AValue: String);
Protected
procedure DoMsg(const aFmt: String; const aArgs: array of const;
EventType: TEventType=etInfo); overload;
procedure DoMsg(const aMessage: String; EventType: TEventType=etInfo); overload;
// Add code to initialize namespace to fpmake in filename.
function AddNamespaceNameToFpMake(const aFileName: string): TChangeFPMakeResult;
// add file to FPMake namespaces file
procedure AddToFPMakeMap(const aSrcFileName, aDestFileName: string);
// Create directory if not dryrun
procedure CreateDestDir(const aDestDir: string);
// Actual HandleFileList
procedure DoHandleFileList(const aFileName: String);
// Return name of package dir from filename (first level of dir tree).
function GetPackageDir(const aFileName: string): string;
// Return unit name from file name.
function GetUnitNameFromFile(aFile: String): string;
// Split line into
procedure SplitLine(aLine: String; out aFileName, aRule: String;
var aOpts: TStringDynArray);
// Write FPMake Namespaces file.
procedure WritePackageNameSpaceFile(aDir: String; aList: TStrings; DoClear: Boolean=True);
Public
class procedure SplitRuleLine(aLine: String; out aFileName, aRule: String;
var AlastDir, aLastRule: String; var aOpts, aLastOpts: TStringDynArray);
Public
Constructor Create(aOwner : TComponent); override;
Destructor Destroy; override;
// Initialize (load config files)
Procedure Init;
// Actual actions
// Apply rule to a single unit file
procedure HandleFile(const aFileName: String; aRule: string; aOptions: array of String);
// Load file list and call handlefile for each
procedure HandleFileList(const aFileName: String);
// Create a 'known prefixes' file with the names of the files
procedure CreateKnown(const aFileName: String);
Property OnLog : TNamespaceToolLogEvent Read FOnLog Write FOnLog;
Property ForcedExt : String Read FForcedExt Write SetForcedExt;
Property DirMapFileName : String Read FDirMapFileName Write FDirMapFileName;
Property PrefixesFileName : String Read FPrefixesFileName Write FPrefixesFileName;
Property DefaultPrefix : String Read FDefaultPrefix Write FDefaultPrefix;
Property Subdir : String Read FSubdir Write SetSubdir;
Property SubdirMode : TSubDirMode Read FSubdirMode Write FSubdirMode;
Property Backup : Boolean Read FBackup Write FBackup;
Property Update : Boolean Read FUpdate Write FUpdate;
Property DryRun : Boolean Read FDryRun Write FDryRun;
Property Restart : Boolean Read FRestart Write FRestart;
Property CasedFiles : Boolean Read FCasedFiles Write FCasedFiles;
Property FPMakeNameSpaceFile : String Read FFPMakeNameSpaceFile Write FFPMakeNameSpaceFile;
Property KnownPrefixes : TStrings Read FKnownPrefixes;
Property DirMap : Tstrings Read FDirmap;
end;
implementation
procedure TNamespaceTool.CreateDestDir(const aDestDir : string);
begin
if not DirectoryExists(aDestDir) then
begin
DoMsg('Creating destination directory: %s',[aDestDir]);
if not FDryRun then
if not ForceDirectories(aDestDir) then
Raise Exception.Create('Could not create destination directory '+aDestDir);
end;
end;
procedure TNamespaceTool.DoMsg(const aFmt: String; const aArgs: array of const; EventType : TEventType = etInfo);
begin
DoMsg(Format(aFmt,aArgs),EventType);
end;
procedure TNamespaceTool.DoMsg(const aMessage: String; EventType : TEventType = etInfo);
begin
if assigned(OnLog) then
OnLog(Self,EventType, aMessage);
end;
procedure TNamespaceTool.AddToFPMakeMap(const aSrcFileName,aDestFileName : string);
Var
Src,Dest,aDir,aRule : String;
begin
Src:=aSrcFileName;
Dest:=aDestFileName;
// Strip package dir
aDir:=GetPackageDir(aSrcFileName);
if Pos(aDir,Src)=1 then
Delete(Src,1,Length(aDir));
if Pos(aDir,Dest)=1 then
Delete(Dest,1,Length(aDir));
// Map file itself.
FFPMakeMap.Values[Src]:=Dest;
aDir:=ExtractFilePath(Src);
// Map source directory to namespaced
aRule:='{s*:'+aDir+'}';
FFPMakeMap.Values[aRule]:=ExtractFilePath(Dest);
// Add original to include directory
aRule:='{i+:'+aDir+'}';
if FFPMakeMap.IndexOf(aRule)=-1 then
FFPMakeMap.Add(aRule);
end;
function TNamespaceTool.GetUnitNameFromFile(aFile : String) : string;
begin
Result:=ExtractFileName(ChangeFileExt(aFile,''))
end;
procedure TNamespaceTool.SetForcedExt(AValue: String);
begin
if FForcedExt=AValue then Exit;
if (aValue<>'') and (aValue[1]<>'.') then
aValue:='.'+aValue;
FForcedExt:=AValue;
end;
procedure TNamespaceTool.DoPrefixLog(Sender: TObject; aType: TEventType;
const aMsg: String);
begin
DoMsg(aMsg,aType);
end;
procedure TNamespaceTool.SetSubdir(AValue: String);
begin
if FSubdir=AValue then Exit;
FSubdir:=AValue;
if FSubDir<>'' then
FSubDir:=IncludeTrailingPathDelimiter(FSubDir);
end;
procedure TNamespaceTool.HandleFile(const aFileName: String; aRule : string; aOptions: array of String);
Var
aNewUnitName,aNewUnitFile,Ext,SrcDir,aUnitName,DestDir,aDummy,DestFN : String;
P : TPrefixer;
NeedUpdate : Boolean;
Idx : Integer;
begin
NeedUpdate:=False;
Ext:=FForcedExt;
if Ext='' then
Ext:=ExtractFileExt(aFileName);
// Construct File name
aUnitName:=GetUnitNameFromFile(aFilename);
// Construct destination dir.
SrcDir:=ExtractFilePath(aFileName);
DestDir:=FDirMap.Values[aUnitName];
if DestDir='' then
DestDir:=FDirMap.Values[ExcludeTrailingBackslash(SrcDir)];
if DestDir='' then
DestDir:=SrcDir;
case SubDirMode of
sdmAppend : DestDir:=FSubDir+DestDir;
sdmReplace : ; // do nothing
end;
DestDir:=IncludeTrailingPathDelimiter(DestDir);
// No rule, see if there is a filename rule in known prefixes
if aRule='' then
begin
Idx:=FKnownPrefixes.IndexOfName(aUnitName);
if Idx<>-1 then
FKnownPrefixes.GetNameValue(Idx,aDummy,aRule);
end;
aNewUnitFile:=TPrefixer.ApplyRule(aFileName,aDummy,aRule,FCasedFiles and (aRule<>''));
aNewUnitName:=GetUnitNameFromFile(aNewUnitFile);
if SameText(aNewUnitName,aUnitName) then
begin
DoMsg('Rule for %s does not result in different unit name, skipping.',[aFileName],etWarning);
exit;
end;
DestFN:=DestDir+aNewUnitName+Ext;
// Add new file to FPMake map.
AddToFPMakeMap(aFileName,DestFN);
if FileExists(DestFN) then
DoMsg('File %s already exists, skipping generation',[DestFN]);
// Create directory.
CreateDestDir(DestDir);
DoMsg('Converting %s to %s',[aFileName,DestFN]);
if not FDryRun then
begin
P:=TPrefixer.Create(Self);
try
P.OnLog:=@DoPrefixLog;
P.UnitFileMode:=fmInclude;
P.IncludeUnitNameMode:=inmIfndef;
P.FileName:=aFileName;
P.NameSpace:=TPrefixer.ExtractPrefix(aRule);
P.KnownNameSpaces.AddStrings(FKnownPrefixes);
P.SkipDestFileName:=FileExists(DestFN);
P.DestFileName:=DestFN;
P.CreateBackups:=FBackup;
P.CasedFileNames:=FCasedFiles;
P.Params.AddStrings(aOptions);
P.Params.Add('-Fi'+ExtractFilePath(aFileName));
P.Execute;
finally
P.Free;
end;
end;
If NeedUpdate then
begin
FKnownPrefixes.Values[aUnitName]:='*'+aNewUnitName;
FWritePrefixes:=True;
end;
end;
Function TNamespaceTool.AddNamespaceNameToFpMake(const aFileName : string) : TChangeFPMakeResult;
const
namespacelist = 'namespaces.lst';
Var
aFile : TStringList;
I : Integer;
aLine : string;
begin
Result:=cmrFailed;
aFile:=TStringList.Create;
try
aFile.LoadFromFile(aFileName);
i:=aFile.Count-1;
while (I>=0) and (Result=cmrFailed) do
begin
if Pos('p.namespacemap',LowerCase(aFile[i]))>0 then
result:=cmrAlreadyDone;
Dec(I);
end;
i:=aFile.Count-1;
while (I>=0) and (Result=cmrFailed) do
begin
aLine:=aFile[i];
if pos('{$ifndef ALLPACKAGES}',aLine)>0 then
if Pos('run',Lowercase(aFile[i+1]))>0 then
begin
aFile.Insert(I,'');
aFile.Insert(I,Format(' P.NamespaceMap:=''%s'';',[namespacelist]));
aFile.Insert(I,'');
Result:=cmrOK;
end;
Dec(I);
end;
if Result=cmrOK then
aFile.SaveToFile(aFileName);
finally
aFile.Free;
end;
end;
procedure TNamespaceTool.WritePackageNameSpaceFile(aDir : String; aList : TStrings; DoClear : Boolean = True);
Var
FN : String;
begin
if aDir<>'' then
aDir:=IncludeTrailingPathDelimiter(aDir);
if (FFPMakeNameSpaceFile='') or (FFPMakeMap.Count=0) then
exit;
FN:=aDir+FFPMakeNameSpaceFile;
DoMsg('Writing fpmake map file to %s, writing %d rules',[FN,FFPMakeMap.Count]);
FFPMakeMap.SaveToFile(FN);
if DoClear then
FFPMakeMap.Clear;
if FileExists(aDir+'fpmake.pp') then
Case AddNamespaceNameToFpMake(aDir+'fpmake.pp') of
cmrFailed : DoMsg('Failed to set NamespaceMap to file "%s"',[FN],etError);
cmrAlreadyDone : DoMsg('NamespaceMap already set in "%s"',[FN],etWarning);
cmrOK : DoMsg('Added NamespaceMap to file "%s"',[FN],etInfo);
end
end;
constructor TNamespaceTool.Create(aOwner: TComponent);
begin
inherited Create(aOwner);
FDirmap:=TStringList.Create;
FKnownPrefixes:=TStringList.Create;
FFPMakeMap:=TStringList.Create;
FDoneFileName:=DefaultDoneList;
end;
destructor TNamespaceTool.Destroy;
begin
FreeAndNil(FDirmap);
FreeAndNil(FKnownPrefixes);
FreeAndNil(FFPMakeMap);
inherited Destroy;
end;
procedure TNamespaceTool.Init;
begin
if (PrefixesFileName<>'') then
begin
KnownPrefixes.LoadFromFile(PrefixesFileName);
DoMsg('Load of %s results in %d known prefixes',[PrefixesFileName,KnownPrefixes.Count]);
end;
if (DirMapFileName<>'') then
begin
Dirmap.LoadFromFile(DirMapFileName);
DoMsg('Load of %s results in %d directory mappings',[DirMapFileName,DirMap.Count]);
end;
end;
procedure TNamespaceTool.SplitLine(aLine: String; out aFileName, aRule: String;
var aOpts: TStringDynArray);
begin
SplitRuleLine(aLine,aFileName,aRule,FLastDir,FLastRule,aOpts,FLastOpts);
end;
Class procedure TNamespaceTool.SplitRuleLine(aLine: String; out aFileName, aRule: String; var AlastDir, aLastRule : String; var aOpts, aLastOpts: TStringDynArray);
var
I,P : Integer;
aDir,FN,Opt : String;
begin
aRule:='';
aFileName:='';
aOpts:=[];
P:=Pos(';',aLine);
if P=0 then
begin
FN:=aLine;
SetLength(aOpts,0);
end
else
begin
FN:=Copy(aLine,1,P-1);
Opt:=Trim(Copy(aLine,P+1));
SetLength(aOpts,Length(Opt));
I:=0;
Repeat
P:=Pos(' ',Opt);
if P=0 then
P:=Length(Opt)+1;
if p>1 then
begin
aOpts[I]:=Copy(Opt,1,P-1);
Opt:=Trim(Copy(Opt,P+1));
inc(I);
end;
until (Opt='');
SetLength(aOpts,I);
end;
P:=Pos('=',FN);
if P<>0 then
begin
aRule:=Copy(FN,P+1);
FN:=Copy(FN,1,P-1);
end;
aFileName:=FN;
// Use previous rule ?
aDir:=ExtractFilePath(FN);
if aDir=aLastDir then
begin
if (aRule='') then
aRule:=aLastRule;
if Length(aOpts)=0 then
aOpts:=aLastOpts;
end;
aLastDir:=aDir;
aLastRule:=aRule;
aLastOpts:=aOpts;
end;
function TNamespaceTool.GetPackageDir(const aFileName : string) : string;
Var
P : Integer;
begin
Result:='';
if aFileName='' then
exit;
P:=Pos('/',aFileName,2);
if P=0 then
exit;
Result:=Copy(aFileName,1,P);
If Result[1]='/' then
Delete(Result,1,1);
end;
procedure TNamespaceTool.HandleFileList(const aFileName : String);
begin
DoHandleFileList(aFileName);
if FWritePrefixes and Update then
begin
DoMsg('Updating known prefixes file: %s ',[PrefixesFileName]);
if not FDryRun then
FKnownPrefixes.SaveToFile(FPrefixesFileName);
end;
end;
procedure TNamespaceTool.DoHandleFileList(const aFileName : String);
Var
List,Done : TStringList;
aLine,FN,FNDir, LastPackageDir,aRule : String;
aOpts : TStringDynArray;
begin
aOpts:=[];
Done:=Nil;
LastPackageDir:='';
List:=TStringList.Create;
try
Done:=TStringList.Create;
if (not FRestart) and fileExists(FDoneFileName) then
Done.LoadFromFile(FDoneFileName);
List.LoadFromFile(aFileName);
For aLine in List do
begin
// Lines have 3 parts
// FileName=Rule;Compile Options
SplitLine(aLine,FN,aRule,aOpts);
FNDir:=GetPackageDir(FN);
if (LastPackageDir<>FNDir) then
begin
if (LastPackageDir<>'') and (FFPMakeNameSpaceFile<>'') then
WritePackageNameSpaceFile(LastPackageDir,List);
LastPackageDir:=FNDir;
end;
if Done.indexOf(FN)=-1 then
begin
try
HandleFile(FN,aRule,aOpts);
Done.Add(FN);
except
On E : Exception do
DoMsg('Error %s while handling file %s : %s',[E.ClassName,FN,E.Message],etError);
end;
end;
end;
if (LastPackageDir<>'') and (FFPMakeNameSpaceFile<>'') then
WritePackageNameSpaceFile(LastPackageDir,List);
finally
Done.SaveToFile(FDoneFileName);
List.Free;
end;
end;
procedure TNamespaceTool.CreateKnown(const aFileName: String);
Var
List,Done : TStringList;
aRule,aLine,FN,aUnit,aNewUnit : String;
aOpts : TStringDynArray;
begin
Done:=Nil;
FLastDir:='';
FLastRule:='';
aOpts:=[];
if FPrefixesFileName='' then
FPrefixesFileName:=ChangeFileExt(aFileName,'.map');
List:=TStringList.Create;
try
Done:=TStringList.Create;
if FileExists(FPrefixesFileName) then
Done.LoadFromFile(FPrefixesFileName);
List.LoadFromFile(aFileName);
// Lines have 3 parts
// FileName=Rule;Compile Options
For aLine in List do
begin
SplitLine(aLine,FN,aRule,aOpts);
aUnit:=ChangeFileExt(ExtractFileName(FN),'');
aNewUnit:=ChangeFileExt(ExtractFileName(TPrefixer.ApplyRule(FN,aUnit,aRule,FCasedFiles)),'');
Done.Values[aUnit]:='*'+aNewUnit;
end;
if FDryRun then
begin
for aLine in Done do
DoMsg(aLine)
end
else
Done.SaveToFile(FPrefixesFileName);
finally
Done.SaveToFile('done.tmp');
Done.Free;
List.Free;
end;
end;
end.