mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-05 10:57:55 +02:00
509 lines
13 KiB
ObjectPascal
509 lines
13 KiB
ObjectPascal
unit ProjectTemplates;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, IniFiles,
|
|
// LazUtils
|
|
FileUtil, LazFileUtils,
|
|
// ProjectTemplates
|
|
ptstrconst;
|
|
|
|
type
|
|
|
|
{ TProjectTemplates }
|
|
|
|
TProjectTemplate = Class(TCollectionItem)
|
|
private
|
|
FAuthor: String;
|
|
FDescription: String;
|
|
FDirectory: String;
|
|
FExclude: String;
|
|
FName: String;
|
|
FProjectFile: String;
|
|
FRecurse: Boolean;
|
|
FFiles : TStrings;
|
|
FVariables: TStrings;
|
|
function DefaultFileSubstitutes(AFileName: String): string;
|
|
function GetFileCount: Integer;
|
|
function GetFileName(FileIndex : Integer): String;
|
|
procedure SetVariables(const AValue: TStrings);
|
|
procedure GetFileList(Const Dir : String);
|
|
Protected
|
|
Function SpecialFile(Const AName : String) : Boolean;
|
|
procedure InitFromDir(Const DirName : String);
|
|
procedure CopyAndSubstituteDir(Const SrcDir,DestDir : String; Values : TStrings);
|
|
procedure CopyAndSubstituteFile(Const SrcFN,DestFN : String; Values : Tstrings);
|
|
Public
|
|
constructor Create(ACollection: TCollection); override;
|
|
Destructor Destroy; override;
|
|
Procedure CreateProject(Const ProjectDir : String; Values : TStrings);
|
|
Procedure CreateFile(FileIndex : Integer; Source,Values : TStrings);
|
|
Procedure CreateFile(Const FileName: String; Source,Values : TStrings);
|
|
Procedure CreateProjectDirs(Const BaseDir : String; Values : TStrings);
|
|
Function TargetFileName(FN : String; Values : TStrings) : String;
|
|
Function TargetFileName(I : Integer; Values : TStrings) : String;
|
|
Property FileCount : Integer read GetFileCount;
|
|
Property FileNames[FileIndex : Integer] : String Read GetFileName;
|
|
published
|
|
Property Name : String Read FName;
|
|
Property Directory : String Read FDirectory;
|
|
Property Description : String Read FDescription Write FDescription;
|
|
Property Variables : TStrings Read FVariables Write SetVariables;
|
|
Property ProjectFile : String Read FProjectFile;
|
|
Property Author : String Read FAuthor;
|
|
Property Recurse : Boolean Read FRecurse;
|
|
Property Exclude : String Read FExclude;
|
|
end;
|
|
|
|
{ TProjectTemplates }
|
|
|
|
TProjectTemplates = class(TCollection)
|
|
private
|
|
FTemplateDir: String;
|
|
function GetTemplate(Index : Integer): TProjectTemplate;
|
|
function GetTemplateName(Index : Integer): String;
|
|
procedure SetTemplate(Index : Integer; const AValue: TProjectTemplate);
|
|
public
|
|
Constructor Create(Const ATemplateDir : String);
|
|
Procedure Initialize(Const ATemplateDir : String);
|
|
Procedure CreateProject(Const ProjectName, ProjectDir : String; Variables : TStrings);
|
|
Function IndexOfProject(Const ProjectName : String) : Integer;
|
|
Function ProjectTemplateByName(Const ProjectName : String) : TProjectTemplate;
|
|
Property TemplateDir : String Read FTemplateDir;
|
|
Property Names [Index : Integer] : String Read GetTemplateName;
|
|
Property Templates[Index : Integer] : TProjectTemplate Read GetTemplate Write SetTemplate;default;
|
|
end;
|
|
|
|
ETemplateError=Class(Exception);
|
|
|
|
Const
|
|
// Section & Key names for ini file.
|
|
SProject = 'Project';
|
|
SVariables = 'Variables';
|
|
KeyName = 'Name';
|
|
KeyAuthor = 'Author';
|
|
KeyDescription = 'Description';
|
|
KeyRecurse = 'Recurse';
|
|
KeyExclude = 'Exclude';
|
|
KeyProjectFile = 'ProjectFile';
|
|
varprefixstr = '__'; // subtitution pattern is "__varname__"
|
|
varpostfixstr = '__';
|
|
|
|
Function SubstituteString(Const S : String; Variables : TStrings): String;
|
|
Function SimpleFileCopy(Const Source,Dest : String) : Boolean;
|
|
|
|
implementation
|
|
|
|
{ Auxiliary function }
|
|
|
|
Function SubstituteString(Const S : String; Variables : TStrings): String;
|
|
|
|
Var
|
|
T : String;
|
|
P : Integer;
|
|
|
|
begin
|
|
T:=S;
|
|
Result:='';
|
|
Repeat
|
|
P:=Pos(varprefixstr,T);
|
|
|
|
If (P=0) then
|
|
begin
|
|
Result:=Result+T;
|
|
T:='';
|
|
end
|
|
else
|
|
begin
|
|
Result:=Result+Copy(T,1,P-1);
|
|
Delete(T,1,P+1);
|
|
P:=Pos(varpostfixstr,T);
|
|
If (P=0) then
|
|
begin
|
|
Result:=Result+varprefixstr+T;
|
|
T:='';
|
|
end
|
|
else
|
|
begin
|
|
Result:=Result+Variables.Values[Copy(T,1,P-1)];
|
|
Delete(T,1,P+1);
|
|
end;
|
|
end;
|
|
until (T='');
|
|
end;
|
|
|
|
Function SimpleFileCopy(Const Source,Dest : String) : Boolean;
|
|
Var
|
|
F1,F2 : TFileStream;
|
|
begin
|
|
Result:=False;
|
|
try
|
|
F1:=TFileStream.Create(Source,fmOpenRead);
|
|
try
|
|
F2:=TFileStream.Create(Dest,fmCreate);
|
|
try
|
|
F2.CopyFrom(F1,0);
|
|
finally
|
|
F2.Free;
|
|
end;
|
|
finally
|
|
F1.Free;
|
|
end;
|
|
Result:=True;
|
|
except
|
|
//
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TProjectTemplates }
|
|
|
|
function TProjectTemplates.GetTemplateName(Index : Integer): String;
|
|
begin
|
|
Result:=GetTemplate(Index).Name;
|
|
end;
|
|
|
|
|
|
function TProjectTemplates.GetTemplate(Index : Integer): TProjectTemplate;
|
|
begin
|
|
Result:=Items[Index] as TProjectTemplate
|
|
end;
|
|
|
|
|
|
procedure TProjectTemplates.SetTemplate(Index : Integer;
|
|
const AValue: TProjectTemplate);
|
|
begin
|
|
Items[Index]:=AValue;
|
|
end;
|
|
|
|
|
|
constructor TProjectTemplates.Create(const ATemplateDir: String);
|
|
begin
|
|
Inherited Create(TProjectTemplate);
|
|
Initialize(ATemplateDir);
|
|
end;
|
|
|
|
|
|
function TProjectTemplates.IndexOfProject(const ProjectName: String): Integer;
|
|
|
|
begin
|
|
Result:=Count-1;
|
|
While (Result>=0) and (CompareText(GetTemplate(Result).Name,ProjectName)<>0) do
|
|
Dec(Result)
|
|
end;
|
|
|
|
|
|
function TProjectTemplates.ProjectTemplateByName(const ProjectName: String): TProjectTemplate;
|
|
|
|
Var
|
|
Index : Integer;
|
|
|
|
begin
|
|
Index:=IndexOfProject(ProjectName);
|
|
If (Index=-1) then
|
|
Raise ETemplateError.CreateFmt(SErrNoSuchTemplate,[ProjectName]);
|
|
Result:=GetTemplate(Index);
|
|
end;
|
|
|
|
|
|
procedure TProjectTemplates.Initialize(const ATemplateDir: String);
|
|
|
|
Var
|
|
Info : TSearchRec;
|
|
D : String;
|
|
|
|
begin
|
|
Clear;
|
|
FTemplateDir:=IncludeTrailingPathDelimiter(ATemplateDir);
|
|
D:=FTemplateDir;
|
|
try
|
|
If FindFirstUTF8(D+GetAllFilesMask,faDirectory,Info)=0 then
|
|
Repeat
|
|
If ((Info.Attr and faDirectory)<>0)
|
|
and not ((Info.Name='.') or (Info.Name='..') or (Info.Name='')) then
|
|
With Add as TProjectTemplate do
|
|
begin
|
|
InitFromDir(D+Info.Name);
|
|
if (name='') or (directory='') // skip invalid template folders
|
|
then delete(count-1); // this prevents IDE hanging
|
|
end;
|
|
Until FindNextUTF8(Info)<>0;
|
|
finally
|
|
FindCloseUTF8(Info);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TProjectTemplates.CreateProject(const ProjectName, ProjectDir: String; Variables : Tstrings);
|
|
|
|
Var
|
|
T : TProjectTemplate;
|
|
|
|
begin
|
|
T:=ProjectTemplateByName(ProjectName);
|
|
T.CreateProject(ProjectDir,Variables);
|
|
end;
|
|
|
|
{ TProjectTemplate }
|
|
|
|
constructor TProjectTemplate.Create(ACollection: TCollection);
|
|
begin
|
|
inherited Create(ACollection);
|
|
FVariables:=TStringList.Create;
|
|
FFiles:=TStringList.Create;
|
|
FProjectFile:='';
|
|
end;
|
|
|
|
|
|
destructor TProjectTemplate.Destroy;
|
|
begin
|
|
FreeAndNil(FVariables);
|
|
FreeAndNil(FFiles);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
|
|
procedure TProjectTemplate.SetVariables(const AValue: TStrings);
|
|
begin
|
|
FVariables.Assign(AValue);
|
|
end;
|
|
|
|
function TProjectTemplate.GetFileName(FileIndex : Integer): String;
|
|
begin
|
|
Result:=FFiles[FileIndex];
|
|
end;
|
|
|
|
function TProjectTemplate.GetFileCount: Integer;
|
|
begin
|
|
Result:=FFiles.Count;
|
|
end;
|
|
|
|
|
|
procedure TProjectTemplate.InitFromDir(const DirName: String);
|
|
|
|
Var
|
|
L : TStringList;
|
|
FN : String;
|
|
sr: TSearchRec;
|
|
|
|
begin
|
|
FDirectory:=IncludeTrailingPathDelimiter(DirName);
|
|
L:=TStringList.Create;
|
|
Try
|
|
FN:=FDirectory+'project.ini';
|
|
If FileExistsUTF8(FN) then
|
|
begin
|
|
With TMemInifile.Create(FN) do
|
|
try
|
|
FProjectFile:=ReadString(SProject,KeyProjectFile,FProjectFile);
|
|
If Not FileExists(FDirectory+FProjectFile+'.lpi') then
|
|
begin
|
|
If (FindFirstUTF8(FDirectory+'*.lpi',0,sr)=0)
|
|
and FileExistsUTF8(FDirectory+sr.Name) then
|
|
FProjectFile:=ExtractFileNameOnly(sr.Name);
|
|
FindClose(sr);
|
|
end;
|
|
FName:=ReadString(SProject,KeyName,DirName);
|
|
FAuthor:=ReadString(SProject,KeyAuthor,'');
|
|
FDescription:=ReadString(SProject,KeyDescription,'');
|
|
FRecurse:=ReadBool(SProject,KeyRecurse,False);
|
|
FExclude:=ReadString(SProject,KeyExclude,'');
|
|
If (FExclude<>'') then
|
|
FExclude:=FExclude+',';
|
|
// Don't change ico and res files
|
|
If pos('.ico,',FExclude)<=0 then
|
|
FExclude:=FExclude+'.ico,';
|
|
If pos('.res,',FExclude)<=0 then
|
|
FExclude:=FExclude+'.res,';
|
|
ReadSectionValues(SVariables,FVariables);
|
|
Finally
|
|
Free;
|
|
end;
|
|
end;
|
|
FN:=Directory+'description.txt';
|
|
If FileExistsUTF8(FN) then
|
|
begin
|
|
L.LoadFromFile(FN);
|
|
FDescription:=L.Text;
|
|
end;
|
|
GetFileList(FDirectory);
|
|
Finally
|
|
L.Free;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TProjectTemplate.CreateFile(FileIndex: Integer; Source, Values: TStrings);
|
|
begin
|
|
CreateFile(FileNames[FileIndex],Source,Values);
|
|
end;
|
|
|
|
procedure TProjectTemplate.CreateFile(const FileName: String; Source,
|
|
Values: TStrings);
|
|
|
|
Var
|
|
F : Text;
|
|
Line : String;
|
|
|
|
begin
|
|
AssignFile(F,FileName);
|
|
Reset(F);
|
|
Try
|
|
While not EOF(F) do
|
|
begin
|
|
ReadLn(F,Line);
|
|
Source.Add(SubstituteString(Line,Values));
|
|
end;
|
|
Finally
|
|
CloseFile(F);
|
|
end;
|
|
end;
|
|
|
|
procedure TProjectTemplate.CreateProjectDirs(const BaseDir: String; Values : TStrings);
|
|
|
|
Var
|
|
RFN : String;
|
|
I : Integer;
|
|
|
|
begin
|
|
If not ForceDirectoriesUTF8(BaseDir) then
|
|
Raise ETemplateError.CreateFmt(SErrCouldNotCreateDir,[BaseDir]);
|
|
For I:=0 to FileCount-1 do
|
|
begin
|
|
RFN:=ExtractRelativePath(Directory,FileNames[i]);
|
|
RFN:=SubstituteString(ExtractFilePath(RFN),Values);
|
|
If (RFN<>'') Then
|
|
If not ForceDirectoriesUTF8(BaseDir+RFN) then
|
|
Raise ETemplateError.CreateFmt(SErrCouldNotCreateDir,[BaseDir+RFN]);
|
|
end;
|
|
end;
|
|
|
|
function TProjectTemplate.DefaultFileSubstitutes(AFileName : String) : string;
|
|
|
|
begin
|
|
Result:=AFileName;
|
|
If SameFileName(ChangeFileExt(ExtractFileName(Result),''),ProjectFile) then
|
|
Result:=ExtractFilePath(Result)+VarPrefixStr+'ProjName'+varpostfixstr+ExtractFileExt(Result);
|
|
end;
|
|
|
|
function TProjectTemplate.TargetFileName(FN: String; Values: TStrings): String;
|
|
|
|
begin
|
|
Result:=ExtractRelativePath(Directory,FN);
|
|
Result:=DefaultFileSubstitutes(Result);
|
|
Result:=SubstituteString(Result,Values);
|
|
end;
|
|
|
|
function TProjectTemplate.TargetFileName(I: Integer; Values: TStrings): String;
|
|
begin
|
|
Result:=TargetFileName(FileNames[I],Values);
|
|
end;
|
|
|
|
procedure TProjectTemplate.CopyAndSubstituteFile(Const SrcFN,DestFN : String; Values : Tstrings);
|
|
|
|
Var
|
|
L : TStringList;
|
|
|
|
begin
|
|
If pos(ExtractFileExt(SrcFN)+',',Exclude)<>0 then
|
|
begin
|
|
If not SimpleFileCopy(SrcFN,DestFN) then
|
|
Raise ETemplateError.CreateFmt(SErrFailedToCopyFile,[SrcFN,DestFN]);
|
|
end
|
|
else
|
|
begin
|
|
L:=TStringList.Create;
|
|
try
|
|
CreateFile(SrcFN,L,Values);
|
|
L.SaveToFile(DestFN);
|
|
Finally
|
|
L.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TProjectTemplate.GetFileList(Const Dir : String);
|
|
|
|
Var
|
|
Info : TSearchRec;
|
|
|
|
begin
|
|
If FindFirstUTF8(Dir+GetAllFilesMask,0,Info)=0 then
|
|
try
|
|
repeat
|
|
if Not SpecialFile(info.name) then
|
|
FFiles.Add(Dir+Info.Name);
|
|
Until (FindNextUTF8(Info)<>0);
|
|
finally
|
|
FindCloseUTF8(Info);
|
|
end;
|
|
if Recurse then
|
|
If (FindFirstUTF8(Dir+GetAllFilesMask,0,Info)=0) then
|
|
try
|
|
repeat
|
|
if ((Info.attr and faDirectory)<>0) and
|
|
(Info.Name<>'.') and (info.Name<>'..') and (Info.Name<>'') then
|
|
GetFileList(Dir+Info.Name+PathSeparator);
|
|
until FindNextUTF8(Info)<>0;
|
|
finally
|
|
FindCloseUTF8(Info);
|
|
end;
|
|
end;
|
|
|
|
function TProjectTemplate.SpecialFile(const AName: String): Boolean;
|
|
begin
|
|
Result:=SameFileName(AName,'description.txt') or SameFileName(AName,'project.ini');
|
|
end;
|
|
|
|
|
|
procedure TProjectTemplate.CopyAndSubstituteDir(Const SrcDir,DestDir : String; Values: Tstrings);
|
|
|
|
Var
|
|
D1,D2 : String;
|
|
Info : TSearchRec;
|
|
N : String;
|
|
|
|
begin
|
|
D1:=IncludeTrailingPathDelimiter(SrcDir);
|
|
D2:=IncludeTrailingPathDelimiter(DestDir);
|
|
If not ForceDirectoriesUTF8(D2) then
|
|
Raise ETemplateError.CreateFmt(SErrCouldNotCreateDir,[D2]);
|
|
If FindFirstUTF8(D1+GetAllFilesMask,0,Info)=0 then
|
|
try
|
|
repeat
|
|
N:=Info.Name;
|
|
if Not SpecialFile(N) then
|
|
begin
|
|
// Anything that has projectfile as a name, is also substituted
|
|
N:=DefaultFileSubstitutes(N);
|
|
CopyAndSubstituteFile(D1+Info.Name,D2+SubstituteString(N,Values),Values);
|
|
end;
|
|
Until (FindNextUTF8(Info)<>0);
|
|
finally
|
|
FindCloseUTF8(Info);
|
|
end;
|
|
if Recurse then
|
|
If (FindFirstUTF8(D1+GetAllFilesMask,0,Info)<>0) then
|
|
try
|
|
repeat
|
|
if ((Info.attr and faDirectory)<>0) and
|
|
(Info.Name<>'.') and (info.Name<>'..') and (Info.Name<>'')
|
|
then
|
|
CopyAndSubstituteDir(D1+Info.Name,D2+SubstituteString(Info.Name,Values),Values);
|
|
until FindNextUTF8(Info)<>0;
|
|
finally
|
|
FindCloseUTF8(Info);
|
|
end;
|
|
end;
|
|
|
|
procedure TProjectTemplate.CreateProject(const ProjectDir: String;
|
|
Values: TStrings);
|
|
begin
|
|
CopyAndSubstituteDir(Directory,ProjectDir,Values);
|
|
end;
|
|
|
|
end.
|