{ FPDoc - Free Pascal Documentation Tool Copyright (C) 2000 - 2003 by Areca Systems GmbH / Sebastian Guenther, sg@freepascal.org * Skeleton XML description file generator See the file COPYING, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. } (* --- Version 1.0 --- The TFPDocMaker class shall support the following functionality: - Project generation from a commandline. - FPDoc documentation generation, optionally syntax check only. - MakeSkel skeleton generation or update. Everything else is done in a separate documentation manager. The documentation manager maintains its own projects and creates temporary TFPDocProjects and TFPDocPackages on demand. *) (* Version 0.0 - requires patched FPDoc units! The TFPDocMaker class supports the following functionality: - documentation generation (FPDoc), - for all units in a package - for a selected unit (optionally syntax check only) - project generation - from input and description directories - from a commandline - skeleton generation - for all units in a package - for selected unit (MakeSkel) - documentation sync with source (MakeSkel UpdateMode) - for all units in a package - output into one or more files - for selected unit - skeleton and sync at once *) unit umakeskel; interface {$mode objfpc} {$h+} uses SysUtils, Classes, Gettext, dGlobals, PasTree, PParser,PScanner, mkfpdoc, fpdocproj; resourcestring STitle = 'MakeSkel - FPDoc skeleton XML description file generator'; SVersion = 'Version %s [%s]'; SCopyright = '(c) 2000 - 2003 Areca Systems GmbH / Sebastian Guenther, sg@freepascal.org'; SCmdLineHelp = 'See documentation for usage.'; SCmdLineInvalidOption = 'Ignoring unknown option "%s"'; SNoPackageNameProvided = 'Please specify a package name with --package='; SOutputMustNotBeDescr = 'Output file must be different from description filenames.'; SCreatingNewNode = 'Creating documentation for new node : %s'; SNodeNotReferenced = 'Documentation node "%s" no longer used'; SDone = 'Done.'; //from fpdocxmlopts SErrInvalidRootNode = 'Invalid options root node: Got "%s", expected "docproject"'; SErrNoPackagesNode = 'No "packages" node found in docproject'; type TCmdLineAction = (actionHelp, actionConvert); (* EngineOptions plus MakeSkel options. Used in the commandline parsers, passed to the Engine. Project.Options are ignored by TFDocMaker.(?) *) TCmdOptions = class(TEngineOptions) public WriteDeclaration, UpdateMode, SortNodes, DisableOverride, DisableErrors, DisableSeealso, DisableArguments, DisableProtected, DisablePrivate, DisableFunctionResults: Boolean; EmitClassSeparator: Boolean; end; { TSkelEngine } TSkelEngine = class(TFPDocEngine) Private FEmittedList, FNodeList, FModules : TStringList; FOptions: TCmdOptions; Procedure DoWriteUnReferencedNodes(N : TDocNode; NodePath : String); procedure SetOptions(AValue: TCmdOptions); public Destructor Destroy; override; Function MustWriteElement(El : TPasElement; Full : Boolean) : Boolean; Function WriteElement(Var F : Text; El : TPasElement; ADocNode : TDocNode) : Boolean; function FindModule(const AName: String): TPasModule; override; function CreateElement(AClass: TPTreeElement; const AName: String; AParent: TPasElement; AVisibility :TPasMemberVisibility; const ASourceFilename: String; ASourceLinenumber: Integer): TPasElement; override; procedure WriteUnReferencedNodes; Procedure WriteNodes(Var F : Text; AModule : TPasModule; List : TStrings); Procedure DocumentFile(Var F : Text; Const AFileName,ATarget,ACPU : String); Property NodeList : TStringList Read FNodeList; Property EmittedList : TStringList Read FEmittedList; property Options: TCmdOptions read FOptions write SetOptions; end; THandleOption = function(const Cmd, Arg: string): boolean; TCreatorAction = ( caDefault, caDryRun, caUsage, //explicit or on all errors? caInvalid, caWriteProject ); { TFPDocMaker } (* MakeSkel functionality as a class. *) TFPDocMaker = class(TFPDocCreator) private FDescrDir: string; FInputDir: string; FOnOption: THandleOption; FOptions: TCmdOptions; function GetDescrDir: string; function GetInputDir: string; procedure SetDescrDir(AValue: string); procedure SetInputDir(AValue: string); procedure SetOnOption(AValue: THandleOption); procedure SetOptions(AValue: TCmdOptions); protected FCmdAction: TCreatorAction; FDryRun: boolean; FPackage: TFPDocPackage; FProjectFile: boolean; FWriteProjectFile: string; FTranslated: string; procedure SetCmdAction(AValue: TCreatorAction); procedure SetDryRun(AValue: boolean); procedure SetPackage(AValue: TFPDocPackage); procedure SetWriteProjectFile(AValue: string); function ParseCommon(var Cmd, Arg: string): TCreatorAction; public Function DocumentPackage(Const APackageName,AOutputName: string; InputFiles, DescrFiles : TStrings) : String; procedure CreateUnitDocumentation(APackage: TFPDocPackage; const AUnit: string; ParseOnly: Boolean); public ImportDir: string; SelectedUnit: string; constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure AddDirToFileList(List: TStrings; const ADirName, AMask: String); procedure AddToFileList(List: TStrings; const FileName: String); function UnitSpec(AUnit: string): string; function ImportName(AIndex: integer): string; procedure LogToStdOut(Sender: TObject; const msg: string); procedure LogToStdErr(Sender: TObject; const msg: string); //parsing function ParseFPDocOption(const S: string): TCreatorAction; function ParseUpdateOption(const S: string): TCreatorAction; function CheckSkelOptions: string; function CleanXML(const FileName: string): boolean; {$IFDEF v0} function CreateProject(const AFileName: string; APackage: TFPDocPackage): boolean; virtual; procedure LoadXMLProject(const AFileName: string); function ParseOption(const S: string): TCreatorAction; function Exec: string; {$ELSE} {$ENDIF} function SelectedPackage: TFPDocPackage; property Package: TFPDocPackage read SelectedPackage write SetPackage; property CmdAction: TCreatorAction read FCmdAction write SetCmdAction; property DryRun: boolean read FDryRun write SetDryRun; property ReadProject: boolean read FProjectFile; property WriteProjectFile: string read FWriteProjectFile write SetWriteProjectFile; property OnOption: THandleOption read FOnOption write SetOnOption; property InputDir: string read GetInputDir write SetInputDir; property DescrDir: string read GetDescrDir write SetDescrDir; property Options: TCmdOptions read FOptions write SetOptions; end; {$IFDEF v0} var FCreator: TFPDocMaker; //created by application WriteDeclaration, UpdateMode, SortNodes, DisableOverride, DisableErrors, DisableSeealso, DisableArguments, DisableProtected, DisablePrivate, DisableFunctionResults: Boolean; EmitClassSeparator: Boolean; {$ELSE} {$ENDIF} //Extract next commandline option from a string Function GetNextWord(Var s : string) : String; //Get package name from Imports spec function ExtractImportName(const s: string): string; //Get Unit filename from Inputs or Descriptions function UnitFile(AList: TStrings; AIndex: integer): string; //Get Unit name from Inputs or Descriptions function ExtractUnitName(AList: TStrings; AIndex: integer): string; function ExtractUnitName(s: string): string; implementation uses dom, dWriter; (* Extract (remove!) next commandline option from a string. Handle quoted arguments, but do not unquote. Option may be partially quoted, e.g. -opt="arg with blanks" *) Function GetNextWord(Var s : string) : String; Const WhiteSpace = [' ',#9,#10,#13]; var i,j: integer; quoted: boolean; begin I:=1; quoted := False; While (I<=Length(S)) and (S[i] in WhiteSpace) do Inc(I); J:=I; { While (J<=Length(S)) and (not (S[J] in WhiteSpace)) do Inc(J); } While (J<=Length(S)) do begin if (s[j] = '"') then begin if quoted then break; quoted := True; end else if not quoted and (S[J] in WhiteSpace) then break; Inc(J); end; if (I<=Length(S)) then Result:=Copy(S,I,J-I); Delete(S,1,J); end; function ExtractImportName(const s: string): string; var i: integer; begin Result := s; i := Pos(',', Result); if i > 1 then SetLength(Result, i-1); Result := ChangeFileExt(ExtractFileName(Result), ''); end; function ExtractUnitName(s: string): string; begin Result := ChangeFileExt(ExtractFileName(s), ''); end; (* Unit name from Inputs[i] or Descriptions[i] Package name from Imports? *) function ExtractUnitName(AList: TStrings; AIndex: integer): string; begin Result := UnitFile(AList, AIndex); if Result <> '' then Result := ChangeFileExt(ExtractFileName(Result), ''); end; (* Extract a file reference from Inputs or Descriptions list. Check for existing list and item. *) function UnitFile(AList: TStrings; AIndex: integer): string; var s: string; begin if assigned(AList) and (AIndex < AList.Count) then begin s := AList[AIndex]; while s <> '' do begin Result := GetNextWord(s); if (Result <> '') and (Result[1] <> '-') then exit; //found a non-option end; end; Result := ''; //should never happen! end; type TNodePair = Class(TObject) Private FEl : TPasElement; FNode : TDocNode; Public Constructor Create(AnElement : TPasElement; ADocNode : TDocNode); Property Element : TPasElement Read FEl; Property DocNode : TDocNode Read FNode; end; Constructor TNodePair.Create(AnElement : TPasElement; ADocNode : TDocNode); begin Fel:=Anelement; FNode:=ADocNode; end; function TSkelEngine.FindModule(const AName: String): TPasModule; Var I : Integer; begin Result:=Inherited FindModule(AName); If (Result=Nil) then begin // Create dummy list and search in that. If (FModules=Nil) then begin FModules:=TStringList.Create; FModules.Sorted:=True; end; I:=FModules.IndexOf(AName); IF (I=-1) then begin Result:=TPasModule.Create(AName,Nil); FModules.AddObject(AName,Result); end else Result:=FModules.Objects[i] as TPasModule; end; end; Destructor TSkelEngine.Destroy; Var I : Integer; begin If Assigned(FModules) then begin For I:=0 to FModules.Count-1 do FModules.Objects[i].Free; FreeAndNil(FModules); end; end; Function TSkelEngine.MustWriteElement(El : TPasElement; Full : Boolean) : Boolean; Var ParentVisible:Boolean; PT,PP : TPasElement; begin ParentVisible:=True; If (El is TPasArgument) or (El is TPasResultElement) then begin PT:=El.Parent; // Skip ProcedureType or PasFunctionType If (PT<>Nil) then begin if (PT is TPasProcedureType) or (PT is TPasFunctionType) then PT:=PT.Parent; If (PT<>Nil) and ((PT is TPasProcedure) or (PT is TPasProcedure)) then PP:=PT.Parent else PP:=Nil; If (PP<>Nil) and (PP is TPasClassType) then begin ParentVisible:=((not Options.DisablePrivate or (PT.Visibility<>visPrivate)) and (not Options.DisableProtected or (PT.Visibility<>visProtected))); end; end; end; Result:=Assigned(El.Parent) and (Length(El.Name) > 0) and (ParentVisible and (not Options.DisableArguments or (El.ClassType <> TPasArgument))) and (ParentVisible and (not Options.DisableFunctionResults or (El.ClassType <> TPasResultElement))) and (not Options.DisablePrivate or (el.Visibility<>visPrivate)) and (not Options.DisableProtected or (el.Visibility<>visProtected)); If Result and Full then begin Result:=(Not Assigned(FEmittedList) or (FEmittedList.IndexOf(El.FullName)=-1)); If Options.DisableOverride and (El is TPasProcedure) then Result:=Not TPasProcedure(El).IsOverride; end; end; function TSkelEngine.CreateElement(AClass: TPTreeElement; const AName: String; AParent: TPasElement; AVisibility : TPasMemberVisibility; const ASourceFilename: String; ASourceLinenumber: Integer): TPasElement; Var DN : TDocNode; begin Result := AClass.Create(AName, AParent); Result.Visibility:=AVisibility; if AClass.InheritsFrom(TPasModule) then CurModule := TPasModule(Result); // Track this element If Options.UpdateMode then begin DN:=FindDocNode(Result); If Assigned(DN) then DN.IncRefCount; end else DN:=Nil; // See if we need to write documentation for it If MustWriteElement(Result,False) then FNodeList.AddObject(Result.PathName,TNodePair.Create(Result,DN)); end; Function TSkelEngine.WriteElement(Var F : Text;El : TPasElement; ADocNode : TDocNode) : Boolean; Function WriteOnlyShort(APasElement : TPasElement) : Boolean; begin Result:=(APasElement.ClassType=TPasArgument) or (APasElement.ClassType=TPasResultElement) or (APasElement.ClassType=TPasEnumValue); end; Function IsTypeVarConst(APasElement : TPasElement) : Boolean; begin With APasElement do Result:=(InheritsFrom(TPasType) and not InheritsFrom(TPasClassType)) or (InheritsFrom(TPasResString)) or (InheritsFrom(TPasVariable)); end; Function NeedDeclaration(El : TPasElement) : boolean; begin Result:=IsTypeVarConst(El) or WriteOnlyShort(El) or EL.InheritsFrom(TPasProcedure) end; begin // Check again, this time with full declaration. Result:=MustWriteElement(El,True); If Result and Options.UpdateMode then Result:=(ADocNode=Nil); If Not Result Then Exit; If Options.UpdateMode then DoLog(Format(ScreatingNewNode,[el.PathName])); FEmittedList.Add(El.FullName); // So we don't emit again. WriteLn(f); if Options.EmitClassSeparator and (El.ClassType = TPasClassType) then begin WriteLn(f, ''); WriteLn(f); end; If Not (Options.WriteDeclaration and NeedDeclaration(El)) then Writeln(F,'') else begin Writeln(F,''); end; WriteLn(f,''); WriteLn(f, ''); if Not WriteOnlyShort(El) then begin WriteLn(f, ''); WriteLn(f, ''); if not (Options.DisableErrors or IsTypeVarConst(El)) then begin WriteLn(f, ''); WriteLn(f, ''); end; if not Options.DisableSeealso then begin WriteLn(f, ''); WriteLn(f, ''); end; end; WriteLn(f, ''); end; Procedure TSkelEngine.DoWriteUnReferencedNodes(N : TDocNode; NodePath : String); begin If (N<>Nil) then begin If (NodePath<>'') then NodePath:=NodePath+'.'; DoWriteUnReferencedNodes(N.FirstChild,NodePath+N.Name); While (N<>Nil) do begin if (N.RefCount=0) and (N.Node<>Nil) and (Not N.TopicNode) then DoLog(Format(SNodeNotReferenced,[NodePath+N.Name])); N:=N.NextSibling; end; end; end; procedure TSkelEngine.SetOptions(AValue: TCmdOptions); begin if FOptions=AValue then Exit; FOptions:=AValue; end; procedure TSkelEngine.WriteUnReferencedNodes; begin DoWriteUnReferencedNodes(RootDocNode,''); end; Procedure TSkelEngine.WriteNodes(Var F : Text; AModule : TPasModule; List : TStrings); Var P : TNodePair; I : integer; begin WriteLn(f); WriteLn(f, ''); WriteLn(f); WriteLn(f, ''); if not Options.UpdateMode then begin WriteLn(f, ''); WriteLn(f, ''); WriteLn(f, ''); end; Try For I:=0 to List.Count-1 do begin P:=List.Objects[i] as TNodePair; If (P.Element<>AModule) then WriteElement(F,P.Element,P.DocNode); end; Finally WriteLn(f, ''); WriteLn(f, ' '); WriteLn(f, ''); end; end; Procedure TSkelEngine.DocumentFile(Var F : Text; Const AFileName,ATarget,ACPU : String); Var Module : TPasModule; I : Integer; N : TDocNode; begin FNodeList:=TStringList.Create; Try FEmittedList:=TStringList.Create; FEmittedList.Sorted:=True; try Module:=ParseSource(Self,AFileName,ATarget,ACPU); If Options.UpdateMode then begin N:=FindDocNode(Module); If Assigned(N) then N.IncRefCount; end; If Options.SortNodes then FNodelist.Sorted:=True; WriteNodes(F,Module,FNodeList); If Options.UpdateMode then WriteUnReferencedNodes; Finally FEmittedList.Free; end; Finally For I:=0 to FNodeList.Count-1 do FNodeList.Objects[i].Free; FNodeList.Free; end; end; { --------------------------------------------------------------------- Main program. Document all units. ---------------------------------------------------------------------} { TFPDocMaker } constructor TFPDocMaker.Create(AOwner: TComponent); begin inherited Create(AOwner); FOptions := TCmdOptions.Create; end; destructor TFPDocMaker.Destroy; begin FreeAndNil(FOptions); inherited Destroy; end; function TFPDocMaker.SelectedPackage: TFPDocPackage; begin Result:=FPackage; if (FPackage=Nil) or (FPackage.Name='') then begin DoLog(SNeedPackageName); //Usage(1); - in application end; end; procedure TFPDocMaker.SetOnOption(AValue: THandleOption); begin if FOnOption=AValue then Exit; FOnOption:=AValue; end; procedure TFPDocMaker.SetDescrDir(AValue: string); begin if FDescrDir=AValue then Exit; FDescrDir:=AValue; AddDirToFileList(SelectedPackage.Descriptions, AValue, '*.xml'); end; function TFPDocMaker.GetDescrDir: string; begin if FDescrDir = '' then begin if SelectedPackage.Descriptions.Count > 0 then begin Result := FPackage.Descriptions[0]; FDescrDir := ExtractFilePath(Result); //include separator end; end; Result := FDescrDir; end; function TFPDocMaker.UnitSpec(AUnit: string): string; var i: integer; w: string; begin for i := 0 to SelectedPackage.Inputs.Count - 1 do begin w := ExtractUnitName(FPackage.Inputs, i); if CompareText(w, AUnit) = 0 then begin Result := FPackage.Inputs[i]; exit; end; end; Result := ''; end; function TFPDocMaker.ImportName(AIndex: integer): string; begin Result := ExtractImportName(SelectedPackage.Imports[AIndex]); end; function TFPDocMaker.GetInputDir: string; var W: string; begin if (FInputDir = '') and (SelectedPackage.Inputs.Count > 0) then begin Result := FPackage.Inputs[0]; while Result <> '' do begin w := GetNextWord(Result); if (w <> '') and (w[1] <> '-') then begin FInputDir := ExtractFilePath(W); //include separator break; end; end; end; Result := FInputDir; end; procedure TFPDocMaker.SetInputDir(AValue: string); begin if FInputDir=AValue then Exit; FInputDir:=AValue; AddDirToFileList(SelectedPackage.Inputs, AValue, '*.pp'); AddDirToFileList(SelectedPackage.Inputs, AValue, '*.pas'); end; procedure TFPDocMaker.SetOptions(AValue: TCmdOptions); begin if FOptions=AValue then Exit; FOptions:=AValue; end; (* Check the options, return errors as message strings. *) function TFPDocMaker.CheckSkelOptions: string; Const {$IFDEF Unix} MoFileTemplate = '/usr/local/share/locale/%s/LC_MESSAGES/makeskel.mo'; {$ELSE} MoFileTemplate ='intl/makeskel.%s.mo'; {$ENDIF} Var MOFilename: string; begin Result := ''; //translate strings - only once? If (Options.Language<>FTranslated) then begin MOFilename:=Format(MOFileTemplate,[Options.Language]); if FileExists(MOFilename) then gettext.TranslateResourceStrings(MoFileName) else begin Result := ('NOTE: unable to find translation file ' + MOFilename); exit; end; // Translate internal documentation strings TranslateDocStrings(Options.Language); FTranslated:=Options.Language; end; // Action is to create the XML skeleton if (Package.Name = '') and (CmdAction<>caUsage) then begin Result := (SNoPackageNameProvided); exit; end; if Options.UpdateMode and (SelectedPackage.Descriptions.IndexOf(Package.Output)<>-1) then begin Result := (SOutputMustNotBeDescr); exit; end; end; {$IFDEF v0} function TFPDocMaker.CreateProject(const AFileName: string; APackage: TFPDocPackage): boolean; var f: TXMLPackageProject; begin try f := TXMLPackageProject.Create(nil); try f.SaveOptionsToFile(Project, AFileName, APackage); Result := True; finally f.Free; end; except Result := False; end; end; procedure TFPDocMaker.LoadXMLProject(const AFileName: string); var f: TXMLPackageProject; begin //LoadProjectFile(); f := TXMLPackageProject.Create(self); try f.LoadOptionsFromFile(Project, AFileName); finally f.Free; end; end; {$ELSE} {$ENDIF} procedure TFPDocMaker.SetCmdAction(AValue: TCreatorAction); begin if FCmdAction=AValue then Exit; FCmdAction:=AValue; end; procedure TFPDocMaker.SetDryRun(AValue: boolean); begin if FDryRun=AValue then Exit; FDryRun:=AValue; end; procedure TFPDocMaker.SetPackage(AValue: TFPDocPackage); begin if FPackage=AValue then Exit; FPackage:=AValue; end; procedure TFPDocMaker.SetWriteProjectFile(AValue: string); begin if FWriteProjectFile=AValue then Exit; FWriteProjectFile:=AValue; end; procedure TFPDocMaker.AddDirToFileList(List: TStrings; const ADirName, AMask: String); Var Info : TSearchRec; D : String; begin if (ADirName<>'') and not DirectoryExists(ADirName) then DoLog('Directory '+ADirName+' does not exist') else begin if (ADirName='.') or (ADirName='') then D:='' else D:=IncludeTrailingPathDelimiter(ADirName); If (FindFirst(D+AMask,0,Info)=0) then try Repeat If (Info.Attr and faDirectory)=0 then List.Add(D+Info.name); Until FindNext(Info)<>0; finally FindClose(Info); end; end; end; procedure TFPDocMaker.AddToFileList(List: TStrings; const FileName: String); var f: Text; s: String; begin if Copy(FileName, 1, 1) = '@' then begin AssignFile(f, Copy(FileName, 2, Length(FileName))); Reset(f); while not EOF(f) do begin ReadLn(f, s); List.Add(s); end; Close(f); end else List.Add(FileName); end; function TFPDocMaker.ParseCommon(var Cmd, Arg: string): TCreatorAction; var i: Integer; begin if (Cmd = '-h') or (Cmd = '--help') then begin //Usage(0) CmdAction := caUsage; exit(caUsage); end; {$IFDEF v0} if Cmd = '--makeskel' then Options.CreateSkeleton := True else {$ELSE} {$ENDIF} if Cmd = '--update' then Options.UpdateMode := True else if (Cmd = '-n') or (Cmd = '--dry-run') then begin DryRun:=True; CmdAction := caDryRun; end //project options else if Cmd = '--hide-protected' then Options.HideProtected := True else if Cmd = '--warn-no-node' then Options.WarnNoNode := True else if Cmd = '--show-private' then Options.ShowPrivate := True //DoDi: was False??? else if Cmd = '--stop-on-parser-error' then Options.StopOnParseError := True else if Cmd = '--dont-trim' then Options.DontTrim := True else if Cmd = '--parse-impl' then Options.InterfaceOnly:=false //is default really True??? else begin //split option i := Pos('=', Cmd); if i > 0 then begin Arg := Copy(Cmd, i + 1, Length(Cmd)); SetLength(Cmd, i - 1); if (Arg <> '') and (Arg[1] = '"') then begin //remove quotes Arg := StringReplace(Arg, '"', '', [rfReplaceAll]); end; end else begin SetLength(Arg, 0); exit(caInvalid); //options without values unhandled here! end; //more options Result := caDefault; //assume succ if (Cmd = '--project') or (Cmd='-p') then begin FProjectFile:=True; //means: project loaded WriteProjectFile := Arg; //do *not* normally overwrite! LoadProjectFile(Arg); end else if (Cmd = '--descr') then begin if FileExists(Arg) then AddToFileList(SelectedPackage.Descriptions, Arg) end else if (Cmd = '--descr-dir') then DescrDir:=Arg else if (Cmd = '-i') or (Cmd = '--input') then AddToFileList(SelectedPackage.Inputs, Arg) else if (Cmd = '--input-dir') then InputDir:=Arg else if Cmd = '--package' then begin If FProjectFile then FPackage:=Packages.FindPackage(Arg) else begin if FPackage = nil then FPackage := (Packages.Add) as TFPDocPackage; FPackage.Name:=Arg; end end else if Cmd = '--ostarget' then Options.OSTarget := Arg else if Cmd = '--cputarget' then Options.CPUTarget := Arg else if (Cmd = '-l') or (Cmd = '--lang') then Options.Language := Arg {$IFDEF new} else if (Cmd = '--common-options') then SelectedPackage.CommonOptions:=Arg {$ELSE} {$ENDIF} else if Cmd = '--mo-dir' then Options.modir := Arg else if (Cmd = '-o') or (Cmd = '--output') then SelectedPackage.Output := Arg else if (Cmd = '--unit') then //-u= UpdateMode SelectedUnit:= Arg else if (Cmd = '-v') or (Cmd = '--verbose') then Verbose:=true else if Cmd = '--write-project' then begin CmdAction := caWriteProject; WriteProjectFile:=Arg end //else no match else Result := caInvalid; end; end; function TFPDocMaker.ParseFPDocOption(const S: string): TCreatorAction; //procedure TFPDocAplication.Parseoption(Const S : String); var Cmd, Arg: String; begin Cmd:=S; Arg := ''; //make compiler happy Result := ParseCommon(Cmd, Arg); if Result <> caInvalid then exit; Result := caDefault; //assume succ if Cmd = '--content' then SelectedPackage.ContentFile := Arg else if Cmd = '--import' then SelectedPackage.Imports.Add(Arg) //this should not be a project option else if (Cmd = '-f') or (Cmd = '--format') then begin Arg:=UpperCase(Arg); If FindWriterClass(Arg)=-1 then WriteLn(StdErr, Format(SCmdLineInvalidFormat, [Arg])) else Options.BackEnd:=Arg; end else begin Options.BackendOptions.Add(Cmd); Options.BackendOptions.Add(Arg); end; end; procedure TFPDocMaker.LogToStdOut(Sender: TObject; const msg: string); begin WriteLn(msg); end; procedure TFPDocMaker.LogToStdErr(Sender: TObject; const msg: string); begin WriteLn(stderr, msg); end; (* Write *all* updates into AOutputName (=DescrFile for Create, UpdFile for Update). *) Function TFPDocMaker.DocumentPackage(Const APackageName,AOutputName: string; InputFiles, DescrFiles : TStrings) : String; Var F : Text; I,J : Integer; Engine: TSkelEngine; begin Result:=''; AssignFile(f, AOutputName); Rewrite(f); Try WriteLn(f, ''); WriteLn(f, ''); WriteLn(f, ''); I:=0; While (Result='') And (I'); WriteLn(f, ''); Close(f); end; end; procedure TFPDocMaker.CreateUnitDocumentation(APackage: TFPDocPackage; const AUnit: string; ParseOnly: Boolean); var il: TStringList; spec: string; begin if AUnit <> '' then begin //selected unit only spec := UnitSpec(AUnit); il := TStringList.Create; il.Assign(APackage.Inputs); APackage.Inputs.Clear; APackage.Inputs.Add(spec); try inherited CreateDocumentation(APackage, ParseOnly); finally APackage.Inputs.Assign(il); il.Free; end; end else begin CreateDocumentation(APackage,ParseOnly); end; end; (* Return True and (try) kill file if no " 0 then exit(False); //file not empty end; finally CloseFile(f); end; //nothing found, delete the file if DeleteFile(FileName) then DoLog('File ' + FileName + ' has no elements. Deleted.') else DoLog('File ' + FileName + ' has no elements. Delete failed.'); Result := True; end; function TFPDocMaker.ParseUpdateOption(const s: String): TCreatorAction; var Cmd, Arg: String; begin Cmd:=S; Arg := ''; //make compiler happy Result := ParseCommon(Cmd, Arg); if Result <> caInvalid then exit; Result := caDefault; //assume succ if s = '--disable-arguments' then Options.DisableArguments := True else if s = '--disable-errors' then Options.DisableErrors := True else if s = '--disable-function-results' then Options.DisableFunctionResults := True else if s = '--disable-seealso' then Options.DisableSeealso := True else if s = '--disable-private' then Options.DisablePrivate := True else if s = '--disable-override' then Options.DisableOverride := True else if s = '--disable-protected' then begin Options.DisableProtected := True; Options.DisablePrivate :=True; end else if (s = '--emitclassseparator') or (s='--emit-class-separator') then Options.EmitClassSeparator := True else if (s = '--emit-declaration') then Options.WriteDeclaration := True else if (s = '--sort-nodes') then Options.SortNodes := True else if (Cmd = '-i') or (Cmd = '--input') then AddToFileList(SelectedPackage.Inputs, Arg) else if not assigned(OnOption) or not OnOption(Cmd, Arg) then begin DoLog(SCmdLineInvalidOption, [s]); CmdAction := caInvalid; Result := caInvalid; end; end; {$IFDEF v0} function TFPDocMaker.ParseOption(const S: string): TCreatorAction; begin if Options.CreateSkeleton or Options.UpdateMode then Result := ParseUpdateOption(s) else Result := ParseFPDocOption(s); end; (* An experimental version for executing all functionality. Applications better should use the basic methods, and implement the framework for all handled cases. *) function TFPDocMaker.Exec: string; var Pkg: TFPDocPackage; s, OutputName: string; i: integer; begin if Options.UpdateMode or Options.CreateSkeleton then begin //MakeSkel Result := CheckSkelOptions; if Result <> '' then exit; end else Result := ''; if SelectedUnit <> '' then begin //create fake package Pkg := TFPDocPackage.Create(nil); try Pkg.Name := Package.Name; s := UnitSpec(SelectedUnit); Pkg.Inputs.Add(s); Pkg.Output := Package.Output; //fpdoc OutputName:=DescrDir + SelectedUnit + '.xml'; if Options.UpdateMode then begin if not FileExists(OutputName) then begin Result := 'Not found: ' + OutputName; exit; end; Pkg.Descriptions.Add(OutputName); OutputName := 'upd.' + SelectedUnit + '.xml'; Result := DocumentPackage(Package.Name, OutputName, Pkg.Inputs, Pkg.Descriptions); exit; end; if Options.CreateSkeleton then begin if FileExists(OutputName) then begin Result := 'File already exists: ' + OutputName; exit; end; Result := DocumentPackage(Package.Name, OutputName, Pkg.Inputs, Pkg.Descriptions); end else begin //fpdoc CreateDocumentation(Pkg, DryRun); end; finally Pkg.Free; end; exit; end; //process package if Options.UpdateMode or Options.CreateSkeleton then begin Result := DocumentPackage(Package.Name, Package.Output, Package.Inputs, Package.Descriptions); end else begin //FPDoc //todo: all or single unit? CreateDocumentation(SelectedPackage, DryRun); end; end; {$ELSE} {$ENDIF} end.