fpc/utils/fpdoc/mkfpdoc.pp
2021-08-09 16:46:30 +02:00

412 lines
12 KiB
ObjectPascal

unit mkfpdoc;
{$mode objfpc}{$H+}
{$WARN 5024 off : Parameter "$1" not used}
interface
uses
Classes, SysUtils, dglobals, DOM, fpdocxmlopts, dwriter, pscanner, pparser, fpdocproj;
const
DefOSTarget = {$I %FPCTARGETOS%};
DefCPUTarget = {$I %FPCTARGETCPU%};
DefFPCVersion = {$I %FPCVERSION%};
DefFPCDate = {$I %FPCDATE%};
{$IFDEF FPC_BIG_ENDIAN}
DefEndianNess = 'FPC_BIG_ENDIAN';
{$ELSE}
DefEndianNess = 'FPC_LITTLE_ENDIAN';
{$ENDIF}
Type
{ TFPDocCreator }
TFPDocCreator = Class(TComponent)
Private
FBaseDescrDir: String;
FBaseInputDir: String;
FCurPackage : TFPDocPackage;
FExamplesPath: String;
FProcessedUnits : TStrings;
FOnLog: TPasParserLogHandler;
FPParserLogEvents: TPParserLogEvents;
FProject : TFPDocProject;
FProjectMacros: TStrings;
FScannerLogEvents: TPScannerLogEvents;
FVerbose: Boolean;
function GetLogLevels: TFPDocLogLevels;
function GetOptions: TEngineOptions;
function GetPackages: TFPDocPackages;
procedure SetBaseDescrDir(AValue: String);
procedure SetBaseInputDir(AValue: String);
procedure SetExamplesPath(AValue: String);
procedure SetProjectMacros(AValue: TStrings);
Protected
Function FixInputFile(Const AFileName : String) : String;
Function FixDescrFile(Const AFileName : String) : String;
Procedure DoBeforeEmitNote(Sender : TObject; Note : TDomElement; Var EmitNote : Boolean); virtual;
procedure HandleOnParseUnit(Sender: TObject; const AUnitName: String; out AInputFile, OSTarget, CPUTarget: String);
procedure SetVerbose(AValue: Boolean); virtual;
Procedure DoLog(Const Msg : String);
procedure DoLog(Const Fmt : String; Args : Array of Const);
Procedure DoLogSender(Sender : TObject; Const Msg : String);
// Create documetation by specified Writer class
procedure CreateOutput(APackage: TFPDocPackage; Engine: TFPDocEngine); virtual;
Public
Constructor Create(AOwner : TComponent); override;
Destructor Destroy; override;
Procedure CreateDocumentation(APackage : TFPDocPackage; ParseOnly : Boolean); virtual; //Writes out documentation in selected format
Procedure CreateProjectFile(Const AFileName : string); //Writes out project file with the chosen options
Procedure LoadProjectFile(Const AFileName: string);
Property Project : TFPDocProject Read FProject;
Property ScannerLogEvents : TPScannerLogEvents Read FScannerLogEvents Write FScannerLogEvents;
Property ParserLogEvents : TPParserLogEvents Read FPParserLogEvents Write FPParserLogEvents;
Property Verbose : Boolean Read FVerbose Write SetVerbose;
Property OnLog : TPasParserLogHandler Read FOnLog Write FOnLog;
// Easy access
Property Options : TEngineOptions Read GetOptions;
Property Packages : TFPDocPackages Read GetPackages;
// When set, they will be prepended to non-absolute filenames.
Property BaseInputDir : String Read FBaseInputDir Write SetBaseInputDir;
Property BaseDescrDir : String Read FBaseDescrDir Write SetBaseDescrDir;
Property ExamplesPath : String Read FExamplesPath Write SetExamplesPath;
// Macros used when loading the project file
Property ProjectMacros : TStrings Read FProjectMacros Write SetProjectMacros;
end;
implementation
uses fpdocstrs;
{ TFPDocCreator }
procedure TFPDocCreator.SetVerbose(AValue: Boolean);
begin
if FVerbose=AValue then Exit;
FVerbose:=AValue;
if FVerbose then
begin
ScannerLogEvents:=[sleFile];
ParserLogEvents:=[];
Options.InfoUsedFile:= true;
Options.WarnDocumentationEmpty:= true;
Options.WarnXCT:= true;
end
else
begin
ScannerLogEvents:=[];
ParserLogEvents:=[];
end;
end;
procedure TFPDocCreator.DoLog(const Msg: String);
begin
If Assigned(OnLog) then
OnLog(Self,Msg);
end;
procedure TFPDocCreator.DoLog(const Fmt: String; Args: array of const);
begin
DoLog(Format(Fmt,Args));
end;
procedure TFPDocCreator.DoLogSender ( Sender: TObject; const Msg: String ) ;
begin
if Assigned(Sender) then
DoLog(Format('%s - Sender: %s', [Msg, Sender.ClassName]))
else
DoLog(Msg);
end;
procedure TFPDocCreator.HandleOnParseUnit(Sender: TObject;
const AUnitName: String; out AInputFile, OSTarget, CPUTarget: String);
Var
I : Integer;
S,un,opts : String;
begin
AInputFile:='';
OSTarget:='';
CPUTarget:='';
if Assigned(FCurPackage) then
begin
I:=0;
While (AInputFIle='') and (I<FCurPackage.Inputs.Count) do
begin
S:=FCurPackage.Inputs[i];
SplitInputFIleOption(S,UN,Opts);
if CompareText(ChangeFileExt(ExtractFileName(Un),''),AUnitName)=0 then
begin
AInputFile:=FixInputFile(UN)+' '+Opts+' -d'+Options.EndianNess;
OSTarget:=FProject.Options.OSTarget;
CPUTarget:=FProject.Options.CPUTarget;
FProcessedUnits.Add(UN);
end;
Inc(I);
end;
end;
end;
function TFPDocCreator.GetOptions: TEngineOptions;
begin
Result:=FProject.Options;
end;
function TFPDocCreator.GetPackages: TFPDocPackages;
begin
Result:=FProject.Packages;
end;
function TFPDocCreator.FixInputFile(const AFileName: String): String;
begin
Result:=AFileName;
If Result='' then exit;
if (ExtractFileDrive(Result)='') and (Result[1]<>PathDelim) then
Result:=BaseInputDir+Result;
end;
function TFPDocCreator.FixDescrFile(const AFileName: String): String;
begin
Result:=AFileName;
If Result='' then exit;
if (ExtractFileDrive(Result)='') and (Result[1]<>PathDelim) then
Result:=BaseDescrDir+Result;
end;
procedure TFPDocCreator.SetBaseDescrDir(AValue: String);
begin
if FBaseDescrDir=AValue then Exit;
FBaseDescrDir:=AValue;
If FBaseDescrDir<>'' then
FBaseDescrDir:=IncludeTrailingPathDelimiter(FBaseDescrDir);
end;
procedure TFPDocCreator.SetBaseInputDir(AValue: String);
begin
if FBaseInputDir=AValue then Exit;
FBaseInputDir:=AValue;
If FBaseInputDir<>'' then
FBaseInputDir:=IncludeTrailingPathDelimiter(FBaseInputDir);
end;
procedure TFPDocCreator.SetExamplesPath(AValue: String);
begin
if FExamplesPath=AValue then Exit;
FExamplesPath:=AValue;
If FExamplesPath<>'' then
FExamplesPath:=IncludeTrailingPathDelimiter(FExamplesPath);
end;
procedure TFPDocCreator.SetProjectMacros(AValue: TStrings);
begin
if FProjectMacros=AValue then Exit;
FProjectMacros.Assign(AValue);
end;
procedure TFPDocCreator.DoBeforeEmitNote(Sender: TObject; Note: TDomElement;
var EmitNote: Boolean);
begin
EmitNote:=True;
end;
constructor TFPDocCreator.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FProject:=TFPDocProject.Create(Self);
FProject.Options.StopOnParseError:=False;
FProject.Options.CPUTarget:=DefCPUTarget;
FProject.Options.OSTarget:=DefOSTarget;
FProject.Options.EndianNess:=DefEndianNess;
FProcessedUnits:=TStringList.Create;
FProjectMacros:=TStringList.Create;
end;
destructor TFPDocCreator.Destroy;
begin
FreeAndNil(FProcessedUnits);
FreeAndNil(FProject);
FreeAndNil(FProjectMacros);
inherited Destroy;
end;
procedure TFPDocCreator.CreateOutput(APackage: TFPDocPackage;Engine : TFPDocEngine);
Var
WriterClass : TFPDocWriterClass;
Writer : TFPDocWriter;
I : Integer;
Cmd,Arg : String;
begin
// Now is used the specified writer
WriterClass:=GetWriterClass(Options.Backend);
// ALL CONTENT CREATED HERE
Writer:=WriterClass.Create(Engine.Package,Engine);
With Writer do
Try
If FVerbose then
DoLog('Writing documentation');
OnLog:=Self.OnLog;
BeforeEmitNote:=@self.DoBeforeEmitNote;
EmitNotes:=Options.EmitNotes;
If Options.BackendOptions.Count>0 then
for I:=0 to ((Options.BackendOptions.Count-1) div 2) do
begin
Cmd:=Options.BackendOptions[I*2];
Arg:=Options.BackendOptions[I*2+1];
If not InterPretOption(Cmd,Arg) then
DoLog(SCmdLineInvalidOption,[Cmd+'='+Arg]);
end;
// Create documentation by writer
WriteDocumentation();
Finally
Free;
end;
// Output content files
if FVerbose then
DoLog('Content file : '+APackage.ContentFile);
if Length(APackage.ContentFile) > 0 then
Engine.WriteContentFile(APackage.ContentFile);
end;
function TFPDocCreator.GetLogLevels: TFPDocLogLevels;
Procedure DoOpt(doSet : Boolean; aLevel: TFPDocLogLevel);
begin
if DoSet then
Result:=Result+[aLevel];
end;
begin
Result:=[];
DoOpt(Options.WarnNoNode,dleWarnNoNode);
DoOpt(Options.InfoUsedFile,dleWarnUsedFile);
DoOpt(Options.WarnDocumentationEmpty,dleDocumentationEmpty);
DoOpt(Options.WarnXCT,dleXCT);
end;
procedure TFPDocCreator.CreateDocumentation(APackage: TFPDocPackage;
ParseOnly: Boolean);
var
i,j: Integer;
Engine : TFPDocEngine;
Cmd,Arg : String;
WriterClass: TFPDocWriterClass;
eMsg: String;
begin
Cmd:='';
FCurPackage:=APackage;
Engine:=TFPDocEngine.Create;
try
Engine.OnLog:= @DoLogSender;
Engine.ExamplesPath:=Self.ExamplesPath;
// get documentation Writer html, latex, and other
WriterClass:=GetWriterClass(Options.Backend);
For J:=0 to Apackage.Imports.Count-1 do
begin
Arg:=Apackage.Imports[j];
// conversion import FilePathes
WriterClass.SplitImport(Arg,Cmd);
// create tree of imported objects
Engine.ReadContentFile(Arg, Cmd);
end;
for i := 0 to APackage.Descriptions.Count - 1 do
Engine.AddDocFile(FixDescrFile(APackage.Descriptions[i]),Options.donttrim);
// set engine options
Engine.SetPackageName(APackage.Name);
Engine.Output:=APackage.Output;
Engine.OnLog:=Self.OnLog;
Engine.ScannerLogEvents:=Self.ScannerLogEvents;
Engine.ParserLogEvents:=Self.ParserLogEvents;
Engine.HideProtected:=Options.HideProtected;
Engine.HidePrivate:=Not Options.ShowPrivate;
Engine.OnParseUnit:=@HandleOnParseUnit;
Engine.DocLogLevels:=GetLogLevels;
Engine.FalbackSeeAlsoLinks:= Options.FallBackSeeAlsoLinks;
if Length(Options.Language) > 0 then
TranslateDocStrings(Options.Language);
// scan the input source files
for i := 0 to APackage.Inputs.Count - 1 do
try
try
eMsg:='';
// get options from input packages
SplitInputFileOption(APackage.Inputs[i],Cmd,Arg);
arg:=Arg+' -d'+Options.EndianNess;
// make absolute filepath
Cmd:=FixInputFile(Cmd);
if FProcessedUnits.IndexOf(Cmd)=-1 then
begin
FProcessedUnits.Add(Cmd);
// Parce sources for OS Target
//WriteLn(Format('Parsing unit: %s', [ExtractFilenameOnly(Cmd)]));
ParseSource(Engine,Cmd+' '+Arg, Options.OSTarget, Options.CPUTarget,[poUseStreams]); // poSkipDefaultDefs
end;
//else WriteLn(Format('Processed unit: %s', [ExtractFilenameOnly(Cmd)]));
except
on E: EParserError do
begin
eMsg:= Format('Parser error: %s (%d,%d): %s',[E.Filename, E.Row, E.Column, E.Message]);
If Options.StopOnParseError then Raise;
end;
on E: EFileNotFoundError do
begin
eMsg:= Format('Error: file not found - %s', [E.Message]);
If Options.StopOnParseError then Raise;
end;
on E: Exception do
begin
eMsg:= Format('Error: %s', [E.Message]);
If Options.StopOnParseError then Raise;
end;
end; // try except
finally
if eMsg <> '' then
begin
DoLog(eMsg);
If not Options.StopOnParseError then
DoLog('Ignoring error, continuing with next unit (if any).');
end;
end; // try finally
if Not ParseOnly then
begin
Engine.StartDocumenting;
// Create documentation
CreateOutput(APackage,Engine);
end;
finally
FreeAndNil(Engine);
FCurPackage:=Nil;
end;
end;
procedure TFPDocCreator.CreateProjectFile(const AFileName: string);
begin
With TXMLFPDocOptions.Create(Self) do
try
SaveOptionsToFile(FProject,AFileName);
finally
Free;
end;
end;
procedure TFPDocCreator.LoadProjectFile(const AFileName: string);
begin
With TXMLFPDocOptions.Create(self) do
try
if (ProjectMacros.Count>0) then
LoadOptionsFromFile(FProject,AFileName,ProjectMacros)
else
LoadOptionsFromFile(FProject,AFileName,Nil);
finally
Free;
end;
end;
end.