{ 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 license. 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 --- uMakeSkel merges the FPDoc and MakeSkel functionality, for use in applications. The TFPDocMaker class supports the following functionality: - Project generation from a commandline, lpk or lpi file. - FPDoc documentation generation, optionally syntax check only. - MakeSkel skeleton generation or update. - Processing of single units or entire packages. - Added and extended commandline options. Everything else is done in a separate documentation manager. The documentation manager maintains its own projects and creates temporary TFPDocProjects and TFPDocPackages on demand. *) unit umakeskel; interface {$mode objfpc} {$h+} {$IF FPC_FULLVERSION<20701} {$ERROR requires FPC 2.7.1 at least} {$ENDIF} uses SysUtils, Classes, Gettext, dGlobals, PasTree, PParser,PScanner, ConfigFile, 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 } TCmdOptions = class(TEngineOptions) public WriteDeclaration, UpdateMode, SortNodes, DisableOverride, DisableErrors, DisableSeealso, DisableArguments, DisableProtected, DisablePrivate, DisableFunctionResults: Boolean; EmitClassSeparator: Boolean; Verbose, Modified: boolean; procedure Assign(Source: TPersistent); override; procedure LoadConfig(cf: TConfigFile; AProfile: string); procedure SaveConfig(cf: TConfigFile; AProfile: string); procedure BackendToPairs(Dest: TStrings); procedure BackendFromPairs(Source: TStrings); 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(const AUnit: string; ParseOnly: Boolean); public 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; function SelectedPackage: TFPDocPackage; //property Package: TFPDocPackage read SelectedPackage write SetPackage; property Package: TFPDocPackage read FPackage write SetPackage; //without message 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 CmdOptions: TCmdOptions read FOptions write SetOptions; end; //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; { TCmdOptions } procedure TCmdOptions.Assign(Source: TPersistent); var s: TCmdOptions absolute Source; begin inherited Assign(Source); //writes to the local copy! if Source is TCmdOptions then begin WriteDeclaration := s.WriteDeclaration; DisableOverride := s.DisableOverride; DisableErrors:=s.DisableErrors; DisableSeealso:=s.DisableSeealso; DisableArguments:=s.DisableArguments; DisableFunctionResults := s.DisableFunctionResults; ShowPrivate := s.ShowPrivate; DisableProtected:=s.DisableProtected; SortNodes := s.SortNodes; Verbose:=s.Verbose; end; end; const SecOpts = 'default'; procedure TCmdOptions.LoadConfig(cf: TConfigFile; AProfile: string); var s, sec: string; begin //MakeSkel WriteDeclaration := cf.ReadBool(SecOpts, 'WriteDeclaration', True); DisableOverride := cf.ReadBool(SecOpts, 'DisableOverride', False); DisableErrors := cf.ReadBool(SecOpts, 'DisableErrors', False); DisableSeealso := cf.ReadBool(SecOpts, 'DisableSeealso', False); DisableArguments := cf.ReadBool(SecOpts, 'DisableArguments', False); DisableFunctionResults := cf.ReadBool(SecOpts, 'DisableFunctionResults', False); ShowPrivate := cf.ReadBool(SecOpts, 'ShowPrivate', True); DisableProtected := cf.ReadBool(SecOpts, 'DisableProtected', False); SortNodes := cf.ReadBool(SecOpts, 'SortNodes', False); //Engine StopOnParseError := cf.ReadBool(SecOpts, 'StopOnParseError', False); WarnNoNode := cf.ReadBool(SecOpts, 'WarnNoNode', True); InterfaceOnly := cf.ReadBool(SecOpts, 'InterfaceOnly', True); if AProfile = '' then AProfile := SecOpts; OSTarget := cf.ReadString(AProfile, 'OSTarget', DefOSTarget); CPUTarget := cf.ReadString(AProfile, 'CPUTarget', DefCPUTarget); Language := cf.ReadString(AProfile, 'Language', ''); Backend := cf.ReadString(AProfile, 'Backend', 'html'); MoDir := cf.ReadString(AProfile, 'MoDir', ''); HideProtected := cf.ReadBool(AProfile, 'HideProtected', False); ShowPrivate := cf.ReadBool(AProfile, 'ShowPrivate', False); DontTrim := cf.ReadBool(AProfile, 'DontTrim', False); //Backend s := cf.ReadString(AProfile, 'BackendOptions', ''); BackendOptions.CommaText := s; //finally Modified := False; end; procedure TCmdOptions.SaveConfig(cf: TConfigFile; AProfile: string); begin //MakeSkel cf.WriteBool(SecOpts, 'WriteDeclaration', WriteDeclaration); cf.WriteBool(SecOpts, 'DisableOverride', DisableOverride); cf.WriteBool(SecOpts, 'DisableErrors', DisableErrors); cf.WriteBool(SecOpts, 'DisableSeealso', DisableSeealso); cf.WriteBool(SecOpts, 'DisableArguments', DisableArguments); cf.WriteBool(SecOpts, 'DisableFunctionResults', DisableFunctionResults); cf.WriteBool(SecOpts, 'DisablePrivate', DisablePrivate); cf.WriteBool(SecOpts, 'DisableProtected', DisableProtected); cf.WriteBool(SecOpts, 'SortNodes', SortNodes); //Engine cf.WriteBool(SecOpts, 'StopOnParseError', StopOnParseError); cf.WriteBool(SecOpts, 'WarnNoNode', WarnNoNode); cf.WriteBool(SecOpts, 'DontTrim', DontTrim); if AProfile = '' then AProfile := SecOpts; cf.WriteString(AProfile, 'OSTarget', OSTarget); cf.WriteString(AProfile, 'CPUTarget', CPUTarget); cf.WriteString(AProfile, 'Language', Language); cf.WriteString(AProfile, 'Backend', Backend); cf.WriteString(AProfile, 'MoDir', MoDir); cf.WriteBool(AProfile, 'HideProtected', HideProtected); cf.WriteBool(AProfile, 'ShowPrivate', ShowPrivate); cf.WriteBool(AProfile, 'InterfaceOnly', InterfaceOnly); //Backend if BackendOptions.Count > 0 then cf.WriteString(AProfile, 'BackendOptions', BackendOptions.CommaText); //finally Modified := False; end; procedure TCmdOptions.BackendToPairs(Dest: TStrings); var i, n: integer; begin Dest.Clear; n := BackendOptions.Count div 2; if n = 0 then exit; Dest.Capacity := n; for i := 0 to n-1 do begin Dest.Add(BackendOptions[i*2] + '=' + BackendOptions[i*2 + 1]); end; end; procedure TCmdOptions.BackendFromPairs(Source: TStrings); var i: integer; begin BackendOptions.Clear; BackendOptions.Capacity:=Source.Count * 2; for i := 0 to Source.Count - 1 do begin BackendOptions.Add(Source.Names[i]); BackendOptions.Add(Source.ValueFromIndex[i]); end; Modified := True; //todo: only if really changed? 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; FModules.OwnsObjects := True; //auto destroy 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 {$IFDEF old} If Assigned(FModules) then begin For I:=0 to FModules.Count-1 do FModules.Objects[i].Free; FreeAndNil(FModules); end; {$ELSE} FreeAndNil(FModules); {$ENDIF} 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, True); //use streams 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.Assign(AValue); //the local MakeSkel options Options.Assign(AValue); //the FPDoc Engine options Verbose := AValue.Verbose; //not in Options 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 CmdOptions.UpdateMode and (SelectedPackage.Descriptions.IndexOf(Package.Output)<>-1) then begin Result := (SOutputMustNotBeDescr); exit; end; end; 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, opts : String; i: integer; begin i := Pos(',', ADirName); if i > 0 then begin opts := ' ' + Copy(ADirName, i+1, Length(ADirName)); D := Copy(ADirName, 1, i-1); end else begin D := ADirName; opts := ''; end; if (D<>'') and not DirectoryExists(D) then DoLog('Directory '+D+' does not exist') else begin if (D='.') then D:='' else D:=IncludeTrailingPathDelimiter(D); If (FindFirst(D+AMask,0,Info)=0) then try Repeat If (Info.Attr and faDirectory)=0 then List.Add(D+Info.name + opts); Until FindNext(Info)<>0; finally FindClose(Info); end; end; end; procedure TFPDocMaker.AddToFileList(List: TStrings; const FileName: String); var f: Text; s, opts: String; i: integer; begin i := Pos(',', FileName); if i > 0 then begin opts := ' ' + Copy(FileName, i+1, Length(FileName)); s := Copy(FileName, 1, i-1); end else begin s := FileName; opts := ''; end; if s[1] = '@' then begin AssignFile(f, Copy(s, 2, Length(s))); Reset(f); while not EOF(f) do begin ReadLn(f, s); List.Add(s + opts); end; Close(f); end else List.Add(s + opts); 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; if Cmd = '--update' then CmdOptions.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 else if Cmd = '--mo-dir' then Options.modir := Arg else if (Cmd = '-o') or (Cmd = '--output') then SelectedPackage.Output := 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; 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 = '-t') or (Cmd = '--emit-notes') then Options.EmitNotes := True else 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); if Result <> '' then begin DeleteFile(AOutputName); //remove invalid file end; end; end; procedure TFPDocMaker.CreateUnitDocumentation(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(Package.Inputs); Package.Inputs.Clear; Package.Inputs.Add(spec); try inherited CreateDocumentation(Package, ParseOnly); finally Package.Inputs.Assign(il); il.Free; end; end else begin CreateDocumentation(Package,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 CmdOptions.DisableArguments := True else if s = '--disable-errors' then CmdOptions.DisableErrors := True else if s = '--disable-function-results' then CmdOptions.DisableFunctionResults := True else if s = '--disable-seealso' then CmdOptions.DisableSeealso := True else if s = '--disable-private' then CmdOptions.DisablePrivate := True else if s = '--disable-override' then CmdOptions.DisableOverride := True else if s = '--disable-protected' then begin CmdOptions.DisableProtected := True; CmdOptions.DisablePrivate :=True; end else if (s = '--emitclassseparator') or (s='--emit-class-separator') then CmdOptions.EmitClassSeparator := True else if (s = '--emit-declaration') then CmdOptions.WriteDeclaration := True else if (s = '--sort-nodes') then CmdOptions.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; end.