mirror of
				https://gitlab.com/freepascal.org/lazarus/lazarus.git
				synced 2025-10-31 18:31:44 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1496 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
			
		
		
	
	
			1496 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
| unit uManager;
 | |
| (* Manager object for FPDoc GUI, by DoDi
 | |
| Holds configuration and packages.
 | |
| 
 | |
| Packages (shall) contain extended descriptions for:
 | |
| - default OSTarget (FPCDocs: Unix/Linux)
 | |
| - inputs: by OSTarget
 | |
| - directories: project(file), InputDir, DescrDir[by language?]
 | |
| - FPCVersion, LazVersion: variations of inputs
 | |
| - Skeleton and Output options, depending on DocType/Level and Format.
 | |
| Units can be described in multiple XML docs, so that it's possible to
 | |
| have specific parts depending on Laz/FPC version, OSTarget, Language, Widgetset.
 | |
| 
 | |
| This version is decoupled from the fpdoc classes, introduces the classes
 | |
|   TFPDocManager for all packages
 | |
|   TDocPackage for a single package
 | |
|   TFPDocHelper for fpdoc projects
 | |
| *)
 | |
| 
 | |
| (* Currently registered writers:
 | |
| TFPDocWriter in 'dwriter.pp'
 | |
|   template: TTemplateWriter(TFPDocWriter) in 'dw_tmpl.pp'
 | |
|   man:  TMANWriter(TFPDocWriter) in 'dw_man.pp' --> <pkg>.man /unit.
 | |
|   dxml:  TDXMLWriter(TFPDocWriter) in 'dw_dxml.pp'
 | |
|   xml:  TXMLWriter(TFPDocWriter) in 'dw_xml.pp'
 | |
|   html: THTMLWriter(TFPDocWriter) in 'dw_html.pp'
 | |
|     htm:  THTMWriter(THTMLWriter)
 | |
|     chm:  TCHMHTMLWriter(THTMLWriter)
 | |
|   TLinearWriter in 'dwlinear.pp'
 | |
|     template: TTemplateWriter(TLinearWriter) in 'dw_lintmpl.pp'
 | |
|     ipf:  TIPFNewWriter(TLinearWriter) in 'dw_ipflin.pas'
 | |
|     latex:  TLaTeXWriter(TLinearWriter) in 'dw_latex.pp'
 | |
|     rtf:  TRTFWriter(TLinearWriter) in 'dw_linrtf.pp'
 | |
|     txt:  TTXTWriter(TLinearWriter) in 'dw_txt.pp'
 | |
| 
 | |
| TLinearWriter based writers create an single output file for a package:
 | |
|   <path>/pkg .<ext>
 | |
| TFPDocWriter based writers create an file for every module:
 | |
|   <path>/pkg /unit.<ext>
 | |
| 
 | |
| *)
 | |
| {$mode objfpc}{$H+}
 | |
| 
 | |
| {$DEFINE EasyImports} //EasyImports.patch applied?
 | |
| 
 | |
| interface
 | |
| 
 | |
| uses
 | |
|   Classes, SysUtils,
 | |
|   umakeskel, ConfigFile, fpdocproj, dw_HTML;
 | |
| 
 | |
| type
 | |
|   TFPDocHelper = class;
 | |
| 
 | |
|   { TDocPackage }
 | |
| 
 | |
| (* TDocPackage describes a package documentation project.
 | |
| *)
 | |
|   TDocPackage = class
 | |
|   private
 | |
|     FAllDirs: boolean;
 | |
|     FAltDir: string;
 | |
|     FCompOpts: string;
 | |
|     FDescrDir: string;
 | |
|     FDescriptions: TStrings;
 | |
|     FIncludePath: string;
 | |
|     FInputDir: string;
 | |
|     FLazPkg: string;
 | |
|     FLoaded: boolean;
 | |
|     FName: string;
 | |
|     FProjectDir: string;
 | |
|     FProjectFile: string;
 | |
|     FSrcDirs: TStrings;
 | |
|     FRequires: TStrings;
 | |
|     FUnitPath: string;
 | |
|     FUnits: TStrings;
 | |
|     function GetAltDir: string;
 | |
|     procedure SetAllDirs(AValue: boolean);
 | |
|     procedure SetAltDir(AValue: string);
 | |
|     procedure SetCompOpts(AValue: string);
 | |
|     procedure SetDescrDir(AValue: string);
 | |
|     procedure SetDescriptions(AValue: TStrings);
 | |
|     procedure SetIncludePath(AValue: string);
 | |
|     procedure SetInputDir(AValue: string);
 | |
|     procedure SetLazPkg(AValue: string);
 | |
|     procedure SetLoaded(AValue: boolean);
 | |
|     procedure SetName(AValue: string);
 | |
|     procedure SetProjectDir(AValue: string);
 | |
|     procedure SetProjectFile(AValue: string);
 | |
|     procedure SetRequires(AValue: TStrings);
 | |
|     procedure SetUnitPath(AValue: string);
 | |
|     procedure SetUnits(AValue: TStrings);
 | |
|   protected
 | |
|     Config: TConfigFile;
 | |
|     procedure ReadConfig; virtual;
 | |
|   public
 | |
|     constructor Create; virtual;
 | |
|     destructor Destroy; override;
 | |
|     function  IniFileName: string;
 | |
|     function  CreateProject(APrj: TFPDocHelper; const AFile: string): boolean; virtual; //new package project
 | |
|     function  ImportProject(APrj: TFPDocHelper; APkg: TFPDocPackage; const AFile: string): boolean;
 | |
|     procedure UpdateConfig;
 | |
|     procedure EnumUnits(AList: TStrings); virtual;
 | |
|     function  DescrFileName(const AUnit: string): string;
 | |
|     property Name: string read FName write SetName;
 | |
|     property Loaded: boolean read FLoaded write SetLoaded;
 | |
|     property ProjectFile: string read FProjectFile write SetProjectFile; //xml?
 | |
|   //from LazPkg
 | |
|     procedure AddUnit(const AFile: string);
 | |
|     property AllDirs: boolean read FAllDirs write SetAllDirs;
 | |
|     property CompOpts: string read FCompOpts write SetCompOpts;
 | |
|     property LazPkg: string read FLazPkg write SetLazPkg; //LPK name?
 | |
|     property ProjectDir: string read FProjectDir write SetProjectDir;
 | |
|     property DescrDir: string read FDescrDir write SetDescrDir;
 | |
|     property Descriptions: TStrings read FDescriptions write SetDescriptions;
 | |
|     property AltDir: string read GetAltDir write SetAltDir;
 | |
|     property InputDir: string read FInputDir write SetInputDir;
 | |
|     property SrcDirs: TStrings read FSrcDirs;
 | |
|     property Units: TStrings read FUnits write SetUnits;
 | |
|     property Requires: TStrings read FRequires write SetRequires; //only string?
 | |
|     property IncludePath: string read FIncludePath write SetIncludePath; //-Fi
 | |
|     property UnitPath: string read FUnitPath write SetUnitPath; //-Fu
 | |
|   end;
 | |
| 
 | |
|   { TFCLDocPackage }
 | |
| 
 | |
|   TFCLDocPackage = class(TDocPackage)
 | |
|   protected
 | |
|     procedure ReadConfig; override;
 | |
|   public
 | |
|     function  CreateProject(APrj: TFPDocHelper; const AFile: string): boolean; override;
 | |
|   end;
 | |
| 
 | |
|   { TFPDocHelper }
 | |
| 
 | |
| //holds temporary project
 | |
| 
 | |
|   TFPDocHelper = class(TFPDocMaker)
 | |
|   private
 | |
|     FProjectDir: string;
 | |
|     procedure SetProjectDir(AValue: string);
 | |
|   public
 | |
|     InputList, DescrList: TStringList; //still required?
 | |
|   public
 | |
|     constructor Create(AOwner: TComponent); override;
 | |
|     destructor Destroy; override;
 | |
|     function  BeginTest(APkg: TDocPackage): boolean;
 | |
|     function  BeginTest(ADir: string): boolean;
 | |
|     procedure EndTest;
 | |
|     function  CmdToPrj(const AFileName: string): boolean;
 | |
|     function  TestRun(APkg: TDocPackage; AUnit: string): boolean;
 | |
|     function  Update(APkg: TDocPackage; const AUnit: string): boolean;
 | |
|     function  MakeDocs(APkg: TDocPackage; const AUnit: string; AOutput: string): boolean;
 | |
|     property ProjectDir: string read FProjectDir write SetProjectDir;
 | |
|   end;
 | |
| 
 | |
|   TLogHandler = Procedure (Sender : TObject; Const Msg : String) of object;
 | |
| 
 | |
|   { TFPDocManager }
 | |
| 
 | |
| (* Holds configuration and package projects.
 | |
| *)
 | |
|   TFPDocManager = class(TComponent)
 | |
|   private
 | |
|     FExcludedUnits: boolean;
 | |
|     FFpcDir: string;
 | |
|     FFPDocDir: string;
 | |
|     FLazarusDir: string;
 | |
|     FModified: boolean;
 | |
|     FNoParseUnits: TStringList;
 | |
|     FOnChange: TNotifyEvent;
 | |
|     FOnLog: TLogHandler;
 | |
|     FOptions: TCmdOptions;
 | |
|     FPackage: TDocPackage;
 | |
|     FPackages: TStrings;
 | |
|     FProfile: string;
 | |
|     FProfiles: string; //CSV list of profile names
 | |
|     FRootDir: string;
 | |
|     UpdateCount: integer;
 | |
|     function  GetNoParseUnits: TStrings;
 | |
|     procedure SetExcludedUnits(AValue: boolean);
 | |
|     procedure SetFpcDir(AValue: string);
 | |
|     procedure SetFPDocDir(AValue: string);
 | |
|     procedure SetLazarusDir(AValue: string);
 | |
|     procedure SetNoParseUnits(AValue: TStrings);
 | |
|     procedure SetOnChange(AValue: TNotifyEvent);
 | |
|     procedure SetPackage(AValue: TDocPackage);
 | |
|     procedure SetProfile(AValue: string);
 | |
|     procedure SetRootDir(AValue: string);
 | |
|   protected
 | |
|     Helper: TFPDocHelper; //temporary
 | |
|     procedure Changed;
 | |
|     function  BeginTest(const ADir: string): boolean;
 | |
|     procedure EndTest;
 | |
|     function  RegisterPackage(APkg: TDocPackage): integer;
 | |
|     Procedure DoLog(Const Msg : String);
 | |
|   public
 | |
|     Config: TConfigFile; //extend class
 | |
|     constructor Create(AOwner: TComponent); override;
 | |
|     destructor Destroy; override;
 | |
|     procedure BeginUpdate;
 | |
|     procedure EndUpdate;
 | |
|     function  LoadConfig(const ADir: string; Force: boolean = False): boolean;
 | |
|     function  SaveConfig: boolean;
 | |
|     procedure AddProfile(const AName: string);
 | |
|     function  AddProject(const APkg, AFile: string): boolean; //from config
 | |
|     function  CreateProject(const AFileName: string; APkg: TDocPackage): boolean;
 | |
|     function  AddPackage(AName: string): TDocPackage;
 | |
|     function  IsExtended(const APkg: string): string;
 | |
|     function  ImportLpk(const AFile: string): TDocPackage;
 | |
|     procedure ImportProject(APkg: TFPDocPackage; const AFile: string);
 | |
|     function  ImportCmd(const AFile: string): boolean;
 | |
|     procedure UpdatePackage(const AName: string);
 | |
|     function  UpdateFCL(enabled: boolean): boolean;
 | |
|   //actions
 | |
|     function  MakeDoc(APkg: TDocPackage; const AUnit, AOutput: string): boolean;
 | |
|     function  TestRun(APkg: TDocPackage; AUnit: string): boolean;
 | |
|     function  Update(APkg: TDocPackage; const AUnit: string): boolean;
 | |
|   public //published?
 | |
|     property ExcludeUnits: boolean read FExcludedUnits write SetExcludedUnits;
 | |
|     property NoParseUnits: TStrings read GetNoParseUnits write SetNoParseUnits;
 | |
|     property FpcDir: string read FFpcDir write SetFpcDir;
 | |
|     property FpcDocDir: string read FFPDocDir write SetFPDocDir;
 | |
|     property LazarusDir: string read FLazarusDir write SetLazarusDir;
 | |
|     property RootDir: string read FRootDir write SetRootDir;
 | |
|     property Options: TCmdOptions read FOptions;
 | |
|     property Profile: string read FProfile write SetProfile;
 | |
|     property Profiles: string read FProfiles;
 | |
|     property Packages: TStrings read FPackages;
 | |
|     property Package: TDocPackage read FPackage write SetPackage;
 | |
|     property Modified: boolean read FModified; //app
 | |
|     property OnChange: TNotifyEvent read FOnChange write SetOnChange;
 | |
|     Property OnLog : TLogHandler Read FOnLog Write FOnLog;
 | |
|   end;
 | |
| 
 | |
| var
 | |
|   Manager: TFPDocManager = nil; //init by application
 | |
| 
 | |
| function  FixPath(const s: string): string;
 | |
| procedure ListDirs(const ARoot: string; AList: TStrings);
 | |
| procedure ListUnits(const AMask: string; AList: TStrings);
 | |
| function  MatchUnits(const ADir: string; AList: TStrings): integer;
 | |
| 
 | |
| implementation
 | |
| 
 | |
| uses
 | |
|   uLpk, PParser;
 | |
| 
 | |
| const
 | |
|   ConfigName = 'docmgr.ini';
 | |
|   SecProjects = 'projects';
 | |
|   SecGen = 'dirs';
 | |
|   SecDoc = 'project';
 | |
| 
 | |
| function FixPath(const s: string): string;
 | |
| var
 | |
|   c: string;
 | |
| begin
 | |
|   if DirectorySeparator = '/' then
 | |
|     c := '\'
 | |
|   else
 | |
|     c := '/';
 | |
|   Result := StringReplace(s, c, DirectorySeparator, [rfReplaceAll]);
 | |
| end;
 | |
| 
 | |
| procedure ListDirs(const ARoot: string; AList: TStrings);
 | |
| var
 | |
|   Info : TSearchRec;
 | |
|   s: string;
 | |
| begin
 | |
|   if FindFirst (ARoot+'/*',faDirectory,Info)=0 then begin
 | |
|     repeat
 | |
|       if not ((Info.Attr and faDirectory) = faDirectory) then
 | |
|         continue;
 | |
|       s := Info.Name;
 | |
|       if (s[1] <> '.')
 | |
|       and (AList.IndexOf(s) < 0) then //exclude dupes
 | |
|         AList.Add(s); //name only, allow to create relative refs
 | |
|     until FindNext(info)<>0;
 | |
|   end;
 | |
|   FindClose(Info);
 | |
| end;
 | |
| 
 | |
| procedure ListUnits(const AMask: string; AList: TStrings);
 | |
| var
 | |
|   Info : TSearchRec;
 | |
|   s, f: string;
 | |
| begin
 | |
|   if FindFirst (AMask,faArchive,Info)=0 then begin
 | |
|     repeat
 | |
|       s := Info.Name;
 | |
|       if s[1] <> '.' then begin
 | |
|         f := ChangeFileExt(s, ''); //unit name only
 | |
|         if Manager.ExcludeUnits and (Manager.NoParseUnits.IndexOf(f) >= 0) then
 | |
|           continue; //excluded unit!
 | |
|         AList.Add(f);
 | |
|       end;
 | |
|     until FindNext(info)<>0;
 | |
|   end;
 | |
|   FindClose(Info);
 | |
| end;
 | |
| 
 | |
| function MatchUnits(const ADir: string; AList: TStrings): integer;
 | |
| var
 | |
|   Info : TSearchRec;
 | |
|   s, ext: string;
 | |
| begin
 | |
|   Result := -1;
 | |
|   if FindFirst(ADir+DirectorySeparator+'*',faArchive,Info)=0 then begin
 | |
|     repeat
 | |
|       s := Info.Name;
 | |
|       ext := ExtractFileExt(s);
 | |
|       if (ext = '.pas') or (ext = '.pp') then begin
 | |
|         ext := ChangeFileExt(s, '');
 | |
|         Result := AList.IndexOf(ext); //ChangeFileExt(s, '.xml'));
 | |
|         if Result >= 0 then begin
 | |
|           //AList.Delete(Result); //don't search any more
 | |
|           break;
 | |
|         end;
 | |
|       end;
 | |
|     Until FindNext(info)<>0;
 | |
|   end;
 | |
|   FindClose(Info);
 | |
| end;
 | |
| 
 | |
| { TFCLDocPackage }
 | |
| 
 | |
| procedure TFCLDocPackage.ReadConfig;
 | |
| begin
 | |
|   inherited ReadConfig;
 | |
|   if FSrcDirs = nil then
 | |
|     FSrcDirs := TStringList.Create;
 | |
|   Config.ReadSection('SrcDirs', FSrcDirs);
 | |
| end;
 | |
| 
 | |
| function TFCLDocPackage.CreateProject(APrj: TFPDocHelper; const AFile: string
 | |
|   ): boolean;
 | |
| var
 | |
|   i: integer;
 | |
|   s, d, f: string;
 | |
|   dirs, descs: TStringList;
 | |
|   incl, excl: boolean;
 | |
| begin
 | |
| (* Add Lazarus FCL, and explicit or added or all FCL dirs
 | |
| *)
 | |
|   if APrj.Package <> nil then
 | |
|     exit(True); //already configured
 | |
|   Result:=inherited CreateProject(APrj, AFile);
 | |
|   descs := TStringList.Create;
 | |
| //add lazdir
 | |
|   if AltDir <> '' then begin
 | |
|   (* Add inputs for all descrs found in AltDir.
 | |
|     For *MakeSkel* add all units in the selected(!) fcl packages to inputs.
 | |
|     How to distinguish both modes?
 | |
|   *)
 | |
|     s := Manager.LazarusDir + 'docs' + DirectorySeparator + 'xml' + DirectorySeparator + 'fcl';
 | |
|     //APrj.ParseFPDocOption(Format('--descr-dir="%s"', [s])); //todo: add includes
 | |
|     //APrj.AddDirToFileList(descs, s, '*.xml');
 | |
|     ListUnits(s+ DirectorySeparator+ '*.xml', descs); //exclude NoParseUnits
 | |
|     descs.Sorted := True;
 | |
|   end;
 | |
| //scan fcl dirs
 | |
|   dirs := TStringList.Create; //use prepared list?
 | |
|   s := Manager.FFpcDir + 'packages' + DirectorySeparator;
 | |
|   ListDirs(s, dirs);
 | |
| //now match all files in the source dirs
 | |
|   for i := dirs.Count - 1 downto 0 do begin
 | |
|     d := s + dirs[i] + DirectorySeparator + 'src';
 | |
|     if not DirectoryExists(d) then continue; //can this happen?
 | |
|   (* skip explicitly excluded packages, and exclude selected units.
 | |
|   *)
 | |
|     excl := not FAllDirs
 | |
|       and assigned(FSrcDirs)
 | |
|       and (SrcDirs.IndexOfName(dirs[i]) >= 0)
 | |
|       and (SrcDirs.Values[dirs[i]] <= '0');
 | |
|     if excl then begin
 | |
|       //Manager.DoLog('Skipping directory ' + dirs[i]);
 | |
|     end else begin
 | |
|       incl := FAllDirs
 | |
|       or (assigned(FSrcDirs)
 | |
|         and (SrcDirs.IndexOfName(dirs[i]) >= 0)
 | |
|         and (SrcDirs.Values[dirs[i]] > '0'))
 | |
|       or (MatchUnits(d, descs) >= 0); //!!! descs now is empty!
 | |
|       if incl then begin
 | |
|       //add dir
 | |
|         Manager.DoLog('Adding directory ' + dirs[i]);
 | |
|         APrj.ParseFPDocOption(Format('--input-dir="%s"', [d])); //todo: add includes?
 | |
|       end;
 | |
|     end;
 | |
|   end;
 | |
| //exclude explicit units - only for FCL?
 | |
|   if Manager.ExcludeUnits and (Manager.NoParseUnits.Count > 0) then begin
 | |
|   //exclude inputs
 | |
|     for i := APrj.InputList.Count - 1 downto 0 do begin
 | |
|       s := ExtractUnitName(APrj.InputList, i);
 | |
|       if Manager.NoParseUnits.IndexOf(s) >= 0 then //case?
 | |
|         APrj.InputList.Delete(i);
 | |
|     end;
 | |
|   end;
 | |
| //re-create project? The normal project was already created by inherited!
 | |
|   if AFile <> '' then begin
 | |
|     f := ChangeFileExt(AFile, '_ext.xml'); //preserve unmodified project?
 | |
|     APrj.CreateProjectFile(f);
 | |
|   end; // else APrj.CreateProjectFile(Manager.RootDir + 'fcl_ext.xml'); //preserve unmodified project?
 | |
| //finally
 | |
|   dirs.Free;
 | |
|   descs.Free;
 | |
| end;
 | |
| 
 | |
| { TDocPackage }
 | |
| 
 | |
| procedure TDocPackage.SetDescrDir(AValue: string);
 | |
| begin
 | |
|   if FDescrDir=AValue then Exit;
 | |
|   FDescrDir:=AValue;
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.SetCompOpts(AValue: string);
 | |
| begin
 | |
| (* collect all compiler options
 | |
| *)
 | |
|   if FCompOpts=AValue then Exit;
 | |
|   if AValue = '' then exit;
 | |
|   if FCompOpts = '' then
 | |
|     FCompOpts:=AValue
 | |
|   else
 | |
|     FCompOpts:= FCompOpts + ' ' + AValue;
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.SetAltDir(AValue: string);
 | |
| begin
 | |
|   AValue:=FixPath(AValue);
 | |
|   if FAltDir=AValue then Exit;
 | |
|   FAltDir:=AValue;
 | |
| //we must signal config updated
 | |
|   Config.WriteString(SecDoc, 'AltDir', AltDir);
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.SetAllDirs(AValue: boolean);
 | |
| begin
 | |
|   if FAllDirs=AValue then Exit;
 | |
|   FAllDirs:=AValue;
 | |
| end;
 | |
| 
 | |
| function TDocPackage.GetAltDir: string;
 | |
| begin
 | |
| {$IFDEF FCLadds}
 | |
|   Result := FAltDir;
 | |
| {$ELSE}
 | |
|   Result := '';
 | |
| {$ENDIF}
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.SetDescriptions(AValue: TStrings);
 | |
| (* Shall we allow for multiple descriptions? (general + OS specific!?)
 | |
| *)
 | |
| begin
 | |
|   if FDescriptions=AValue then Exit;
 | |
|   if AValue = nil then exit; //clear?
 | |
|   if AValue.Count = 0 then exit;
 | |
|   FDescriptions.Assign(AValue);
 | |
| end;
 | |
| 
 | |
| (* Requires[] only contain package names.
 | |
|   Internal use: Get/Set CommaText
 | |
| *)
 | |
| procedure TDocPackage.SetRequires(AValue: TStrings);
 | |
| 
 | |
|   procedure Import;
 | |
|   var
 | |
|     i: integer;
 | |
|     s: string;
 | |
|   begin
 | |
|     FRequires.Clear; //assume full replace
 | |
|     for i := 0 to AValue.Count - 1 do begin
 | |
|       s := AValue[i]; //<name.xct>,<prefix>
 | |
|       FRequires.Add(ExtractImportName(s));  // + '=' + s);
 | |
|     end;
 | |
|   end;
 | |
| 
 | |
| begin
 | |
|   if FRequires=AValue then Exit;
 | |
|   if AValue = nil then exit;
 | |
|   if AValue.Count = 0 then exit;
 | |
|   Import;
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.SetUnits(AValue: TStrings);
 | |
| 
 | |
|   procedure Import;
 | |
|   var
 | |
|     i: integer;
 | |
|     s: string;
 | |
|   begin
 | |
|     FUnits.Clear; //assume full replace
 | |
|     for i := 0 to AValue.Count - 1 do begin
 | |
|       s := AValue[i]; //filespec
 | |
|       FUnits.Add(ExtractUnitName(AValue, i) + '=' + s);
 | |
|     end;
 | |
|   end;
 | |
| 
 | |
| begin
 | |
|   if FUnits=AValue then Exit;
 | |
|   if AValue = nil then exit;
 | |
|   if AValue.Count = 0 then exit;
 | |
| //import formatted: <unit>=<descr file> (multiple???)
 | |
|   if Pos('=', AValue[0]) > 0 then
 | |
|     FUnits.Assign(AValue) //clears previous content
 | |
|   else //if AValue.Count > 0 then
 | |
|     Import;
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.SetIncludePath(AValue: string);
 | |
| begin
 | |
|   if FIncludePath=AValue then Exit;
 | |
|   FIncludePath:=AValue;
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.SetInputDir(AValue: string);
 | |
| begin
 | |
|   if FInputDir=AValue then Exit;
 | |
|   FInputDir:=AValue;
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.SetLazPkg(AValue: string);
 | |
| begin
 | |
|   if FLazPkg=AValue then Exit;
 | |
|   if AValue = '' then exit;
 | |
|   FLazPkg:=AValue;
 | |
|   FProjectDir := ExtractFilePath(AValue);
 | |
|   //todo: import
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.SetLoaded(AValue: boolean);
 | |
| begin
 | |
|   if FLoaded=AValue then Exit;
 | |
|   FLoaded:=AValue;
 | |
|   if not FLoaded then
 | |
|     exit; //???
 | |
|   if Manager.RegisterPackage(self) < 0 then //now definitely loaded
 | |
|     exit; //really exit?
 | |
|   if Config = nil then
 | |
|     UpdateConfig; //create INI file when loaded
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.SetName(AValue: string);
 | |
| begin
 | |
|   if FName=AValue then Exit;
 | |
|   FName:=AValue;
 | |
|   ReadConfig;
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.SetProjectDir(AValue: string);
 | |
| begin
 | |
|   if FProjectDir=AValue then Exit;
 | |
|   FProjectDir:=AValue;
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.SetProjectFile(AValue: string);
 | |
| begin
 | |
|   if FProjectFile=AValue then Exit;
 | |
|   FProjectFile:=AValue;
 | |
| //really do more?
 | |
|   if FProjectFile = '' then
 | |
|     exit;
 | |
|   ProjectDir:=ExtractFilePath(FProjectFile);
 | |
|   if ExtractFileExt(FProjectFile) <> '.xml' then
 | |
|     ; //really change here???
 | |
|   //import requires fpdocproject - must be created by Manager!
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.SetUnitPath(AValue: string);
 | |
| begin
 | |
|   if FUnitPath=AValue then Exit;
 | |
|   FUnitPath:=AValue;
 | |
| //save to config?
 | |
| end;
 | |
| 
 | |
| constructor TDocPackage.Create;
 | |
| begin
 | |
|   FUnits := TStringList.Create;
 | |
|   FDescriptions := TStringList.Create;
 | |
|   FRequires := TStringList.Create;
 | |
|   //Config requires valid Name -> in SetName
 | |
| end;
 | |
| 
 | |
| destructor TDocPackage.Destroy;
 | |
| begin
 | |
|   FreeAndNil(Config);
 | |
|   FreeAndNil(FUnits);
 | |
|   FreeAndNil(FDescriptions);
 | |
|   FreeAndNil(FRequires);
 | |
|   FreeAndNil(FSrcDirs);
 | |
|   inherited Destroy;
 | |
| end;
 | |
| 
 | |
| (* Create new(?) project.
 | |
| Usage: after LoadLpk, in general for configured project (user options!)
 | |
| (more options to come)
 | |
| *)
 | |
| function TDocPackage.CreateProject(APrj: TFPDocHelper; const AFile: string): boolean;
 | |
| var
 | |
|   s, imp: string;
 | |
|   pkg: TFPDocPackage;
 | |
|   i: integer;
 | |
|   lst: TStringList;
 | |
| begin
 | |
|   Result := APrj.Package <> nil; //already configured?
 | |
|   if Result then
 | |
|     exit;
 | |
|   Result := ProjectDir <> '';
 | |
|   if not Result then
 | |
|     exit; //dir must be known
 | |
| //create pkg
 | |
|   APrj.ParseFPDocOption('--package=' + Name); //selects or creates the pkg
 | |
|   pkg := APrj.SelectedPackage;
 | |
| //add Inputs
 | |
|   //todo: common options? OS options?
 | |
|   for i := 0 to Units.Count - 1 do begin
 | |
|     s := Units.ValueFromIndex[i];
 | |
|     if CompOpts <> '' then
 | |
|       s := s + ' ' + CompOpts;
 | |
|     //add further options?
 | |
|     pkg.Inputs.Add(s);
 | |
|   end;
 | |
| //add Descriptions - either explicit or implicit
 | |
|   if (DescrDir <> '') and (Descriptions.Count = 0) then begin
 | |
|   //first check for existing directory
 | |
|     if not DirectoryExists(DescrDir) then begin
 | |
|       MkDir(DescrDir); //exclude \?
 | |
|     end else if Descriptions.Count = 0 then begin
 | |
|       APrj.ParseFPDocOption('--descr-dir=' + DescrDir); //adds all XML files
 | |
|     end;
 | |
|   end else begin
 | |
|     APrj.DescrDir := DescrDir; //needed by Update
 | |
|     for i := 0 to Descriptions.Count - 1 do begin
 | |
|       s := Descriptions[i];
 | |
|       if Pos('=', s) > 0 then
 | |
|         pkg.Descriptions.Add(Descriptions.ValueFromIndex[i])
 | |
|       else
 | |
|         pkg.Descriptions.Add(s);
 | |
|     end;
 | |
|   end;
 | |
|   if AltDir <> '' then begin
 | |
|   //add descr files
 | |
|     s := Manager.LazarusDir + AltDir;
 | |
|     s := FixPath(s);
 | |
|     if ForceDirectories(s) then begin
 | |
|     //exclude NoParse units
 | |
|       //APrj.ParseFPDocOption(Format('--descr-dir="%s"', [s]));
 | |
|       lst := TStringList.Create;
 | |
|       ListUnits(AltDir + '*.xml', lst); //unit names only
 | |
|     (* add the unit names from lst to the pkg/project
 | |
|     *)
 | |
|       pkg := APrj.SelectedPackage;
 | |
|       for i := 0 to lst.Count - 1 do begin
 | |
|         s := AltDir + lst[i] + '.xml';
 | |
|         pkg.Descriptions.Add(s);
 | |
|       end;
 | |
|     end;
 | |
|   //add source files!?
 | |
|   end;
 | |
| //add Imports
 | |
|   for i := 0 to Requires.Count - 1 do begin
 | |
|     s := Requires[i];
 | |
|   {$IFDEF EasyImports}
 | |
|     imp := Manager.RootDir + s;
 | |
|   {$ELSE}
 | |
|     imp := Manager.RootDir + s + '.xct,../' + s + '/'; //valid for HTML, not for CHM!
 | |
|   {$ENDIF}
 | |
|     APrj.ParseFPDocOption('--import=' + imp);
 | |
|   end;
 | |
| //add options
 | |
|   APrj.Options.Assign(Manager.Options);
 | |
| //debug, looks good here!?
 | |
|   if APrj.Options.Backend = '' then
 | |
|     Manager.DoLog('No format, should be ' + Manager.Options.Backend);
 | |
|   pkg.Output := Manager.RootDir + Name; //???
 | |
|   pkg.ContentFile := Manager.RootDir + Name + '.xct';
 | |
| //now create project file
 | |
|   if AFile <> '' then begin
 | |
|     if ExtractFileExt(AFile) <> '.xml' then
 | |
|       FProjectFile := ExtractFilePath(AFile) + Name + '_prj.xml'
 | |
|     else
 | |
|       FProjectFile := AFile;
 | |
|     APrj.CreateProjectFile(ProjectFile);
 | |
|   end;
 | |
|   Result := True; //assume okay
 | |
| end;
 | |
| 
 | |
| (* Init from TFPDocPackage, into which AFile has been loaded.
 | |
| *)
 | |
| function TDocPackage.ImportProject(APrj: TFPDocHelper; APkg: TFPDocPackage; const AFile: string): boolean;
 | |
| var
 | |
|   s: string;
 | |
| begin
 | |
| //check loaded
 | |
|   Result := Loaded;
 | |
|   if Result then
 | |
|     exit;
 | |
| //init...
 | |
|   s := UnitFile(APkg.Inputs, 0);
 | |
|   if s <> '' then
 | |
|     FUnitPath := ExtractFilePath(s);
 | |
|   s := UnitFile(APkg.Descriptions, 0);
 | |
|   if s <> '' then
 | |
|     FDescrDir := ExtractFilePath(s);
 | |
| //project file - empty if not applicable (multi-package project?!)
 | |
|   if (AFile <> '') and (APrj.Packages.Count = 1) then
 | |
|     ProjectFile := AFile //only if immediately applicable!
 | |
|   else
 | |
|     ProjectDir := ExtractFilePath(AFile);
 | |
| //init lists
 | |
|   Units := APkg.Inputs;
 | |
|   Descriptions := APkg.Descriptions;
 | |
|   Requires := APkg.Imports;
 | |
| //more?
 | |
| //save config!
 | |
|   UpdateConfig;
 | |
| //finish
 | |
|   Result := Loaded;
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.ReadConfig;
 | |
| var
 | |
|   s: string;
 | |
| begin
 | |
|   if Loaded then
 | |
|     exit;
 | |
|   if Config = nil then
 | |
|     Config := TConfigFile.Create(IniFileName);
 | |
| //check config
 | |
|   s := Config.ReadString(SecDoc, 'projectdir', '');
 | |
|   if s = '' then begin
 | |
|     FreeAndNil(Config); //must create and fill later!
 | |
|     exit; //project directory MUST be known
 | |
|   end;
 | |
|   ProjectFile := Config.ReadString(SecDoc, 'projectfile', '');
 | |
|   FInputDir := Config.ReadString(SecDoc, 'inputdir', '');
 | |
|   FCompOpts := Config.ReadString(SecDoc, 'options', '');
 | |
|   FDescrDir := Config.ReadString(SecDoc, 'descrdir', '');
 | |
|   FAltDir := Config.ReadString(SecDoc, 'AltDir', '');
 | |
|   FAllDirs:= Config.ReadBool(SecDoc, 'AllDirs', False);
 | |
|   Requires.CommaText := Config.ReadString(SecDoc, 'requires', '');
 | |
| //units
 | |
|   Config.ReadSection('units', Units);
 | |
|   Config.ReadSection('descrs', Descriptions);
 | |
| //more?
 | |
| //all done
 | |
|   Loaded := True;
 | |
| end;
 | |
| 
 | |
| (* Initialize the package, write global config (+local?)
 | |
| *)
 | |
| procedure TDocPackage.UpdateConfig;
 | |
| begin
 | |
| //create ini file, if not already created
 | |
|   if Config = nil then
 | |
|     Config := TConfigFile.Create(IniFileName); //in document RootDir
 | |
| //general information
 | |
|   Config.WriteString(SecDoc, 'projectdir', ProjectDir);
 | |
|   Config.WriteString(SecDoc, 'projectfile', ProjectFile);
 | |
|   Config.WriteString(SecDoc, 'inputdir', InputDir);
 | |
|   Config.WriteString(SecDoc, 'options', CompOpts);
 | |
|   Config.WriteString(SecDoc, 'descrdir', DescrDir);
 | |
|   Config.WriteString(SecDoc, 'AltDir', FAltDir);
 | |
|   Config.WriteBool(SecDoc, 'AllDirs', AllDirs);
 | |
|   Config.WriteString(SecDoc, 'requires', Requires.CommaText);
 | |
| //units
 | |
|   Config.WriteSectionValues('units', Units);
 | |
|   Config.WriteSectionValues('descrs', Descriptions);
 | |
|   Config.WriteSectionValues('SrcDirs', SrcDirs);
 | |
| //all done
 | |
|   Config.Flush;
 | |
|   Loaded := True;
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.EnumUnits(AList: TStrings);
 | |
| var
 | |
|   i: integer;
 | |
| begin
 | |
| //override to add further units (from AltDir...)
 | |
|   for i := 0 to Units.Count - 1 do
 | |
|     AList.Add(Units.Names[i]);
 | |
| end;
 | |
| 
 | |
| function TDocPackage.DescrFileName(const AUnit: string): string;
 | |
| begin
 | |
| (* [ProjectDir +] DescrDir + AUnit + .xml
 | |
| *)
 | |
|   Result := DescrDir;
 | |
|   if (Result = '') or (Result[1] = '.') then
 | |
|     Result := ProjectDir + Result;
 | |
|   Result := Result + DirectorySeparator + AUnit + '.xml';
 | |
| end;
 | |
| 
 | |
| function TDocPackage.IniFileName: string;
 | |
| begin
 | |
|   Result := Manager.RootDir + Name + '.ini';
 | |
| end;
 | |
| 
 | |
| procedure TDocPackage.AddUnit(const AFile: string);
 | |
| var
 | |
|   s: string;
 | |
| begin
 | |
|   s := ExtractUnitName(AFile);
 | |
|   if s = '' then
 | |
|     Manager.DoLog('No unit: ' + AFile)
 | |
|   else
 | |
|     Units.Add(s + '=' + AFile);
 | |
| end;
 | |
| 
 | |
| { TFPDocManager }
 | |
| 
 | |
| constructor TFPDocManager.Create(AOwner: TComponent);
 | |
| var
 | |
|   lst: TStringList;
 | |
| begin
 | |
|   inherited Create(AOwner);
 | |
|   lst := TStringList.Create;
 | |
|   lst.OwnsObjects := True;
 | |
|   FPackages := lst;
 | |
|   FOptions := TCmdOptions.Create;
 | |
|   FNoParseUnits := TStringList.Create;
 | |
| end;
 | |
| 
 | |
| destructor TFPDocManager.Destroy;
 | |
| begin
 | |
|   SaveConfig;
 | |
|   FreeAndNil(Config);
 | |
|   //FPackages.Clear; //destructor seems NOT to clear/destroy owned object!?
 | |
|   FreeAndNil(FPackages);
 | |
|   FreeAndNil(FOptions);
 | |
|   FreeAndNil(FNoParseUnits);
 | |
|   inherited Destroy;
 | |
| end;
 | |
| 
 | |
| procedure TFPDocManager.SetFPDocDir(AValue: string);
 | |
| begin
 | |
|   if FFPDocDir=AValue then Exit;
 | |
|   FFPDocDir:=AValue;
 | |
|   Config.WriteString(SecGen, 'FpcDocDir', FpcDocDir);
 | |
| end;
 | |
| 
 | |
| procedure TFPDocManager.SetFpcDir(AValue: string);
 | |
| begin
 | |
|   if FFpcDir=AValue then Exit;
 | |
|   FFpcDir:=AValue;
 | |
|   Config.WriteString(SecGen, 'FpcDir', FpcDir);
 | |
| end;
 | |
| 
 | |
| procedure TFPDocManager.SetExcludedUnits(AValue: boolean);
 | |
| begin
 | |
|   if FExcludedUnits=AValue then Exit;
 | |
|   FExcludedUnits:=AValue;
 | |
|   Config.WriteBool(SecGen, 'ExcludeUnits', AValue);
 | |
| end;
 | |
| 
 | |
| procedure TFPDocManager.UpdatePackage(const AName: string);
 | |
| var
 | |
|   pkg: TDocPackage;
 | |
|   i: integer;
 | |
|   s: string;
 | |
| begin
 | |
|   if LazarusDir = '' then exit;
 | |
|   s := {LazarusDir +} 'docs/xml/'+AName;
 | |
|   if not DirectoryExists(FixPath(LazarusDir + s)) then
 | |
|     exit;
 | |
|   i := Packages.IndexOfName(AName);
 | |
|   if i < 0 then
 | |
|     exit;
 | |
|   pkg := Packages.Objects[i] as TDocPackage;
 | |
|   pkg.AltDir := s; //add descriptors when configuring the project/helper
 | |
| end;
 | |
| 
 | |
| function TFPDocManager.UpdateFCL(enabled: boolean): boolean;
 | |
| var
 | |
|   pkg: TFCLDocPackage;
 | |
| begin
 | |
| (* Adding to the FCL requires valid FPC and Lazarus directories (caller checks).
 | |
|   Then laz/docs/xml/fcl/ is added to fpc descr-dirs.
 | |
|   The related units have to be added as input-dirs.
 | |
|   Scan fpc/packages/ for candidates.
 | |
| *)
 | |
| //todo: implement
 | |
|   pkg := AddPackage('fcl') as TFCLDocPackage;
 | |
|   if pkg = nil then
 | |
|     exit(False);
 | |
|   if enabled then
 | |
|     pkg.AltDir := 'docs/xml/fcl'
 | |
|   else
 | |
|     pkg.AltDir := '';
 | |
|   Result := True;
 | |
| end;
 | |
| 
 | |
| procedure TFPDocManager.SetLazarusDir(AValue: string);
 | |
| begin
 | |
|   if FLazarusDir=AValue then Exit;
 | |
|   FLazarusDir:=AValue;
 | |
|   Config.WriteString(SecGen, 'LazarusDir', FLazarusDir);
 | |
| //update RTL and FCL - if exist and Dir exists
 | |
|   UpdatePackage('rtl');
 | |
|   UpdatePackage('fcl');
 | |
| end;
 | |
| 
 | |
| function TFPDocManager.GetNoParseUnits: TStrings;
 | |
| begin
 | |
|   Result := FNoParseUnits;
 | |
| end;
 | |
| 
 | |
| procedure TFPDocManager.SetNoParseUnits(AValue: TStrings);
 | |
| begin
 | |
|   FNoParseUnits.Assign(AValue);
 | |
|   FNoParseUnits.Sorted := True;
 | |
|   Config.WriteSection('NoParseUnits', NoParseUnits);
 | |
| end;
 | |
| 
 | |
| procedure TFPDocManager.SetOnChange(AValue: TNotifyEvent);
 | |
| begin
 | |
|   if FOnChange=AValue then Exit;
 | |
|   FOnChange:=AValue;
 | |
| end;
 | |
| 
 | |
| procedure TFPDocManager.SetPackage(AValue: TDocPackage);
 | |
| begin
 | |
|   if FPackage=AValue then Exit;
 | |
|   FPackage:=AValue;
 | |
| end;
 | |
| 
 | |
| procedure TFPDocManager.SetProfile(AValue: string);
 | |
| begin
 | |
|   if AValue = '' then exit;
 | |
|   if FProfile=AValue then Exit;
 | |
|   if Options.Modified then
 | |
|     Options.SaveConfig(Config, FProfile);
 | |
|   FProfile:=AValue;
 | |
|   if not Config.SectionExists(AValue) then begin
 | |
|     FProfiles := FProfiles + ',' + AValue;
 | |
|     Config.WriteString(SecGen, 'Profiles', FProfiles);
 | |
|   end;
 | |
|   Config.WriteString(SecGen, 'Profile', FProfile);
 | |
|   Options.LoadConfig(Config, Profile);
 | |
| end;
 | |
| 
 | |
| (* Try load config from new dir - this may fail on the first run.
 | |
| *)
 | |
| procedure TFPDocManager.SetRootDir(AValue: string);
 | |
| var
 | |
|   s: string;
 | |
| begin
 | |
|   s := IncludeTrailingPathDelimiter(AValue);
 | |
|   if FRootDir=s then Exit; //prevent recursion
 | |
|   FRootDir:=s;
 | |
| //load config - not here!
 | |
| end;
 | |
| 
 | |
| procedure TFPDocManager.Changed;
 | |
| begin
 | |
|   if not Modified or (UpdateCount > 0) then
 | |
|     exit; //should not be called directly
 | |
|   FModified := False;
 | |
|   if Assigned(OnChange) then
 | |
|     FOnChange(self);
 | |
| end;
 | |
| 
 | |
| function TFPDocManager.BeginTest(const ADir: string): boolean;
 | |
| begin
 | |
|   Helper.Free; //should have been done
 | |
|   Helper := TFPDocHelper.Create(nil);
 | |
|   Helper.OnLog := OnLog;
 | |
|   Result := Helper.BeginTest(ADir);
 | |
|   if Result then
 | |
|     Helper.CmdOptions := Options; //set reference AND propagate!?
 | |
| end;
 | |
| 
 | |
| procedure TFPDocManager.EndTest;
 | |
| begin
 | |
|   SetCurrentDir(ExtractFileDir(RootDir));
 | |
|   FreeAndNil(Helper);
 | |
| end;
 | |
| 
 | |
| procedure TFPDocManager.BeginUpdate;
 | |
| begin
 | |
|   inc(UpdateCount);
 | |
| end;
 | |
| 
 | |
| procedure TFPDocManager.EndUpdate;
 | |
| begin
 | |
|   dec(UpdateCount);
 | |
|   if UpdateCount <= 0 then begin
 | |
|     UpdateCount := 0;
 | |
|     if Modified then
 | |
|       Changed;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| (* Try load config.
 | |
|   Init RootDir (only when config found?)
 | |
|   Try load packages from their INI files
 | |
| *)
 | |
| function TFPDocManager.LoadConfig(const ADir: string; Force: boolean): boolean;
 | |
| var
 | |
|   s, pf, cf: string;
 | |
|   i: integer;
 | |
| begin
 | |
|   s := IncludeTrailingPathDelimiter(ADir);
 | |
|   cf := s + ConfigName;
 | |
|   Result := FileExists(cf);
 | |
|   if not Result and not Force then
 | |
|     exit;
 | |
|   RootDir:=s; //recurse if RootDir changed
 | |
| //sanity check: only one config file!
 | |
|   if assigned(Config) then begin
 | |
|     if (Config.FileName = cf) then
 | |
|       exit(false) //nothing new?
 | |
|     else
 | |
|       Config.Free;
 | |
|     //clear packages???
 | |
|   end;
 | |
|   Config := TConfigFile.Create(cf);
 | |
|   //Config.CacheUpdates := True;
 | |
|   if not Result then
 | |
|     exit; //nothing to read
 | |
| //read directories
 | |
|   FFpcDir := Config.ReadString(SecGen, 'FpcDir', '');
 | |
|   FFPDocDir := Config.ReadString(SecGen, 'FpcDocDir', '');
 | |
|   FLazarusDir:=Config.ReadString(SecGen, 'LazarusDir', '');
 | |
| //read packages
 | |
|   Config.ReadSection(SecProjects, FPackages); //<prj>=<file>
 | |
| //read detailed package information - possibly multiple packages per project!
 | |
|   BeginUpdate;  //turn of app notification!
 | |
|   for i := 0 to Packages.Count - 1 do begin
 | |
|   //read package config (=project file name?)
 | |
|     s := Packages.Names[i];
 | |
|     pf := Packages.ValueFromIndex[i];
 | |
|     if pf <> '' then begin
 | |
|       AddProject(s, pf); //add and load project file, don't update config!
 | |
|       FModified := True; //force app notification
 | |
|     end;
 | |
|   end;
 | |
| //more? (preferences?)
 | |
|   FProfiles:=Config.ReadString(SecGen, 'Profiles', 'default');
 | |
|   FProfile := Config.ReadString(SecGen,'Profile', 'default');
 | |
|   Options.LoadConfig(Config, Profile);
 | |
|   FExcludedUnits := Config.ReadBool(SecGen, 'ExcludeUnits', True);
 | |
|   Config.ReadSection('NoParseUnits', NoParseUnits);
 | |
| //done, nothing modified
 | |
|   EndUpdate;
 | |
| end;
 | |
| 
 | |
| function TFPDocManager.SaveConfig: boolean;
 | |
| begin
 | |
| //Options? assume saved by application?
 | |
|   if Options.Modified then begin
 | |
|     Options.SaveConfig(Config, Profile);
 | |
|   end;
 | |
|   Config.Flush;
 | |
|   Result := True; //for now
 | |
| end;
 | |
| 
 | |
| procedure TFPDocManager.AddProfile(const AName: string);
 | |
| begin
 | |
| //add and select - obsolete!
 | |
|   Profile := AName;
 | |
| end;
 | |
| 
 | |
| (* Add a DocPackage to Packages and INI.
 | |
|   Return package Index.
 | |
|   For exclusive use by Package.SetLoaded!
 | |
| *)
 | |
| function TFPDocManager.RegisterPackage(APkg: TDocPackage): integer;
 | |
| begin
 | |
|   Result := Packages.IndexOfName(APkg.Name);
 | |
|   if Result < 0 then begin
 | |
|   //add package
 | |
|     Result := Packages.AddObject(APkg.Name + '=' + APkg.ProjectFile, APkg);
 | |
|   end else if Packages.Objects[Result] = nil then
 | |
|     Packages.Objects[Result] := APkg;
 | |
|   if APkg.Loaded then begin
 | |
|   //check/create project file?
 | |
|     if APkg.ProjectFile = '' then begin
 | |
|       if APkg.ProjectDir = '' then begin
 | |
|         DoLog('Missing project directory for package ' + APkg.Name);
 | |
|         exit(-1); //???
 | |
|       end;
 | |
|       APkg.ProjectFile := APkg.ProjectDir + APkg.Name; //to be fixed by pkg
 | |
|     end;
 | |
|     if (ExtractFileExt(APkg.ProjectFile) <> '.xml') then begin
 | |
|     //create project file
 | |
|       APkg.ProjectFile := ChangeFileExt(APkg.ProjectFile, '_prj.xml');
 | |
|       CreateProject(APkg.ProjectFile, APkg);
 | |
|     //update Packages[] string
 | |
|       Packages[Result] := APkg.Name + '=' + APkg.ProjectFile;
 | |
|     end;
 | |
|     Config.WriteString(SecProjects, APkg.Name, APkg.ProjectFile);
 | |
|   end;
 | |
|   FModified := True;
 | |
| end;
 | |
| 
 | |
| (* Load FPDoc (XML) project file.
 | |
| Called by
 | |
| - init - not Dirty!
 | |
| *)
 | |
| function TFPDocManager.AddProject(const APkg, AFile: string): boolean;
 | |
| var
 | |
|   pkg: TDocPackage;
 | |
|   i: integer;
 | |
| begin
 | |
| //create DocPackage
 | |
|   pkg := AddPackage(APkg);
 | |
|   if pkg.Loaded then
 | |
|     exit(True); //assume registered!?
 | |
| //check project file
 | |
|   if ExtractFileExt(AFile) <> '.xml' then begin
 | |
|     DoLog('Not a project file: ' + AFile);
 | |
|     Exit(False);
 | |
|   end;
 | |
|   if not FileExists(AFile) then begin
 | |
|     DoLog('Missing project file: ' + AFile);
 | |
|     exit(False);
 | |
|   end;
 | |
| //create helper
 | |
|   BeginTest(AFile);
 | |
|   try
 | |
|   //load the project file into Helper
 | |
|     Helper.LoadProjectFile(AFile);
 | |
|     if Helper.Packages.Count = 1 then begin
 | |
|       Helper.Package := Helper.Packages[0]; //in LoadProject?
 | |
|       Result := pkg.ImportProject(Helper, Helper.Package, AFile);
 | |
|       exit;
 | |
|     end;
 | |
|   //load all packages
 | |
|     for i := 0 to Helper.Packages.Count - 1 do begin
 | |
|       Helper.Package := Helper.Packages[i];
 | |
|       pkg := AddPackage(Helper.Package.Name);
 | |
|       if pkg.Loaded then
 | |
|         continue; //already initialized
 | |
|       pkg.ImportProject(Helper, Helper.Package, '');
 | |
|     end;
 | |
|   finally
 | |
|     EndTest;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| (* Ask DocPackage to create an projectfile.
 | |
|   Overwrite if exists???
 | |
|   AFileName is any file in the project directory, required for CD!
 | |
|   !!! prevent recursive calls, destroying Helper !!!
 | |
| *)
 | |
| function TFPDocManager.CreateProject(const AFileName: string; APkg: TDocPackage
 | |
|   ): boolean;
 | |
| begin
 | |
|   if Helper = nil then begin
 | |
|     BeginTest(AFileName); //CD into project dir
 | |
|     try
 | |
|       Result := APkg.CreateProject(Helper, AFileName);
 | |
|     finally
 | |
|       EndTest;
 | |
|     end;
 | |
|   end else begin
 | |
|   //assume that Helper IS for APkg
 | |
|     Result := APkg.CreateProject(Helper, AFileName);
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| (* Return the named package, create if not found.
 | |
|   Rename: GetPackage?
 | |
| *)
 | |
| function TFPDocManager.AddPackage(AName: string): TDocPackage;
 | |
| var
 | |
|   i: integer;
 | |
| begin
 | |
|   AName := LowerCase(AName);
 | |
|   i := FPackages.IndexOfName(AName);
 | |
|   if i < 0 then
 | |
|     Result := nil
 | |
|   else
 | |
|     Result := FPackages.Objects[i] as TDocPackage;
 | |
|   if Result = nil then begin
 | |
|   {$IFDEF FCLadds}
 | |
|     if AName = 'fcl' then
 | |
|       Result := TFCLDocPackage.Create
 | |
|     else
 | |
|   {$ELSE}
 | |
|   {$ENDIF}
 | |
|       Result := TDocPackage.Create;
 | |
|     Result.Name := AName; //triggers load config --> register
 | |
|     i := FPackages.IndexOfName(AName); //already registered?
 | |
|   end;
 | |
|   if i < 0 then begin
 | |
|   //we MUST create an entry
 | |
|     Packages.AddObject(AName + '=' + Result.ProjectFile, Result);
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| function TFPDocManager.IsExtended(const APkg: string): string;
 | |
| var
 | |
|   pkg: TDocPackage;
 | |
| begin
 | |
| {$IFDEF FCLadds}
 | |
|   pkg := AddPackage(APkg);
 | |
|   if pkg = nil then
 | |
|     Result := ''
 | |
|   else
 | |
|     Result := pkg.AltDir;
 | |
| {$ELSE}
 | |
|   Result := '';
 | |
| {$ENDIF}
 | |
| end;
 | |
| 
 | |
| function TFPDocManager.ImportLpk(const AFile: string): TDocPackage;
 | |
| begin
 | |
|   BeginUpdate;
 | |
| //import the LPK file into? Here: TDocPackage, could be FPDocProject?
 | |
|   Result := uLpk.ImportLpk(AFile);
 | |
|   if Result = nil then
 | |
|     DoLog('Import failed on ' + AFile)
 | |
|   else begin
 | |
|     Result.Loaded := True; //import and write config file
 | |
|   end;
 | |
|   EndUpdate;
 | |
| end;
 | |
| 
 | |
| (* Add the project, just created from cmdline or projectfile
 | |
| *)
 | |
| procedure TFPDocManager.ImportProject(APkg: TFPDocPackage; const AFile: string);
 | |
| var
 | |
|   pkg: TDocPackage;
 | |
| begin
 | |
|   pkg := AddPackage(APkg.Name);
 | |
|   pkg.ImportProject(Helper, APkg, AFile);
 | |
| //update config?
 | |
|   Config.WriteString(SecProjects, pkg.Name, AFile);
 | |
|   FModified := true;
 | |
| //notify app?
 | |
|   //Changed;
 | |
| end;
 | |
| 
 | |
| function TFPDocManager.ImportCmd(const AFile: string): boolean;
 | |
| var
 | |
|   pkg: TDocPackage;
 | |
| begin
 | |
|   Result := False;
 | |
|   BeginTest(AFile); //directory!!!
 | |
|   try
 | |
|     Result := Helper.CmdToPrj(AFile);
 | |
|     if not Result then
 | |
|       exit;
 | |
|     pkg := AddPackage(Helper.SelectedPackage.Name); //create [and register]
 | |
|     pkg.Loaded := False; //force reload
 | |
|     if not pkg.Loaded then begin
 | |
|       Result := pkg.ImportProject(Helper, Helper.Package, AFile);
 | |
|     end;
 | |
|   finally
 | |
|     EndTest;
 | |
|   end;
 | |
|   if Result then
 | |
|     Changed;
 | |
| end;
 | |
| 
 | |
| function TFPDocManager.MakeDoc(APkg: TDocPackage; const AUnit, AOutput: string): boolean;
 | |
| begin
 | |
|   Result := assigned(APkg)
 | |
|   and BeginTest(APkg.ProjectDir)
 | |
|   and APkg.CreateProject(Helper, ''); //only configure, don't create file
 | |
|   if not Result then
 | |
|     exit;
 | |
|   try
 | |
|     Helper.ParseFPDocOption(Format('--output="%s"', [AOutput]));
 | |
|     if Options.Backend = 'chm' then begin
 | |
|       Helper.ParseFPDocOption('--auto-toc');
 | |
|       Helper.ParseFPDocOption('--auto-index');
 | |
|     end;
 | |
|       Helper.ParseFPDocOption('--make-searchable'); //always?
 | |
|     //Result :=
 | |
|     Helper.CreateUnitDocumentation(AUnit, False);
 | |
|   finally
 | |
|     EndTest;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| function TFPDocManager.TestRun(APkg: TDocPackage; AUnit: string): boolean;
 | |
| begin
 | |
|   BeginTest(APkg.ProjectFile);
 | |
|   try
 | |
|     try
 | |
|       Result := Helper.TestRun(APkg, AUnit);
 | |
|     except
 | |
|       on E: EParserError do
 | |
|         DoLog(Format('%s(%d,%d): %s',[e.Filename, e.Row, e.Column, e.Message]));
 | |
|       on E: Exception do
 | |
|         DoLog(E.Message);
 | |
|     end;
 | |
|   finally
 | |
|     EndTest;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| function TFPDocManager.Update(APkg: TDocPackage; const AUnit: string): boolean;
 | |
| begin
 | |
|   Result := assigned(APkg)
 | |
|   and BeginTest(APkg.ProjectFile);
 | |
|   if not Result then
 | |
|     exit;
 | |
|   try
 | |
|     Result := APkg.CreateProject(Helper, ''); //only configure, don't create file
 | |
|     if not Result then
 | |
|       exit;
 | |
|     Result := Helper.Update(APkg, AUnit);
 | |
|   finally
 | |
|     EndTest;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure TFPDocManager.DoLog(const Msg: String);
 | |
| begin
 | |
|   if Assigned(FOnLog) then
 | |
|     FOnLog(self, msg);
 | |
| end;
 | |
| 
 | |
| { TFPDocHelper }
 | |
| 
 | |
| constructor TFPDocHelper.Create(AOwner: TComponent);
 | |
| begin
 | |
|   inherited Create(AOwner);
 | |
|   InputList := TStringList.Create;
 | |
|   DescrList := TStringList.Create;
 | |
| end;
 | |
| 
 | |
| destructor TFPDocHelper.Destroy;
 | |
| begin
 | |
|   FreeAndNil(InputList);
 | |
|   FreeAndNil(DescrList);
 | |
|   inherited Destroy;
 | |
| end;
 | |
| 
 | |
| (* Prepare MakeSkel on temporary FPDocPackage
 | |
| *)
 | |
| function TFPDocHelper.BeginTest(APkg: TDocPackage): boolean;
 | |
| begin
 | |
|   if not assigned(APkg) then
 | |
|     exit(False);
 | |
|   Result := BeginTest(APkg.ProjectFile); //directory would be sufficient!
 | |
|   if not Result then
 | |
|     exit;
 | |
|   APkg.CreateProject(self, ''); //create project file?
 | |
|   Package := Packages.FindPackage(APkg.Name);
 | |
|   //Options?
 | |
| //okay, so far
 | |
|   Result := assigned(Package);
 | |
| end;
 | |
| 
 | |
| procedure TFPDocHelper.EndTest;
 | |
| begin
 | |
| //???
 | |
| end;
 | |
| 
 | |
| function TFPDocHelper.BeginTest(ADir: string): boolean;
 | |
| begin
 | |
|   Result := ADir <> '';
 | |
|   if not Result then
 | |
|     exit;
 | |
| //remember dir!
 | |
|   if ExtractFileExt(ADir) <> '' then //todo: better check for directory!?
 | |
|     ADir := ExtractFileDir(ADir);
 | |
|   ProjectDir:=ADir;
 | |
|   SetCurrentDir(ProjectDir);
 | |
| end;
 | |
| 
 | |
| (* Create a project from an FPDoc commandline.
 | |
|   Do NOT create an project file!(?)
 | |
| *)
 | |
| function TFPDocHelper.CmdToPrj(const AFileName: string): boolean;
 | |
| var
 | |
|   l, w: string;
 | |
|   i: integer;
 | |
| begin
 | |
|   Result := False; //in case of errors
 | |
| //read the commandline
 | |
|   InputList.LoadFromFile(AFileName);
 | |
|   for i := 0 to InputList.Count - 1 do begin
 | |
|     l := InputList[i];
 | |
|     w := GetNextWord(l);
 | |
|     if w = 'fpdoc' then begin //contains!?
 | |
|       Result := True; //so far
 | |
|       break; //fpdoc command found
 | |
|     end;
 | |
|   end;
 | |
|   InputList.Clear;
 | |
|   if not Result then
 | |
|     exit;
 | |
| //parse commandline
 | |
|   while l <> '' do begin
 | |
|     w := GetNextWord(l);
 | |
|     ParseFPDocOption(w);
 | |
|   end;
 | |
|   Result := True;
 | |
| end;
 | |
| 
 | |
| function TFPDocHelper.MakeDocs(APkg: TDocPackage; const AUnit: string;
 | |
|   AOutput: string): boolean;
 | |
| begin
 | |
|   Result := BeginTest(APkg); //configure and select package
 | |
|   if not Result then
 | |
|     exit;
 | |
|   try
 | |
|     ParseFPDocOption(Format('--output="%s"', [AOutput]));
 | |
|     CreateDocumentation(Package, False);
 | |
|   finally
 | |
|     EndTest;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| function TFPDocHelper.TestRun(APkg: TDocPackage; AUnit: string): boolean;
 | |
| begin
 | |
| (* more detailed error handling?
 | |
|   Must CD to the project file directory!?
 | |
| *)
 | |
|   Result := BeginTest(APkg);
 | |
|   if not Result then
 | |
|     exit;
 | |
|   try
 | |
|   //override options for test
 | |
|     ParseFPDocOption('--format=html');
 | |
|     ParseFPDocOption('-v');
 | |
|     ParseFPDocOption('-n');
 | |
|     //verbose?
 | |
|     CreateUnitDocumentation(AUnit, True);
 | |
|   finally
 | |
|     EndTest;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| (* MakeSkel functionality - create skeleton or update file
 | |
|   using temporary Project
 | |
| *)
 | |
| function TFPDocHelper.Update(APkg: TDocPackage; const AUnit: string): boolean;
 | |
| 
 | |
|   function DocumentUnit(const AUnit: string): boolean;
 | |
|   var
 | |
|     OutName, msg: string;
 | |
|   begin
 | |
|     if Manager.NoParseUnits.IndexOf(AUnit) >= 0 then begin
 | |
|       DoLog('NoParse ' + AUnit);
 | |
|       exit(False);
 | |
|     end;
 | |
|     InputList.Clear;
 | |
|     InputList.Add(UnitSpec(AUnit));
 | |
|     DescrList.Clear;
 | |
|     OutName := AUnit + '.xml';
 | |
|     if DescrDir <> '' then
 | |
|       OutName := IncludeTrailingBackslash(DescrDir) + OutName;
 | |
|     CmdOptions.UpdateMode := FileExists(OutName);
 | |
|     if CmdOptions.UpdateMode then begin
 | |
|       DescrList.Add(OutName);
 | |
|       OutName:=Manager.RootDir + 'upd.' + AUnit + '.xml';
 | |
|       DoLog('Update ' + OutName);
 | |
|     end else begin
 | |
|       DoLog('Create ' + OutName);
 | |
|     end;
 | |
|     msg := DocumentPackage(APkg.Name, OutName, InputList, DescrList);
 | |
|     Result := msg = '';
 | |
|     if not Result then
 | |
|       DoLog(msg) //+unit?
 | |
|     else if CmdOptions.UpdateMode then begin
 | |
|       CleanXML(OutName);
 | |
|     end;
 | |
|   end;
 | |
| 
 | |
| var
 | |
|   i: integer;
 | |
|   u: string;
 | |
| begin
 | |
|   Result := BeginTest(APkg);
 | |
|   if not Result then
 | |
|     exit;
 | |
|   if AUnit <> '' then begin
 | |
|     Result := DocumentUnit(AUnit);
 | |
|   end else begin
 | |
|     for i := 0 to Package.Inputs.Count - 1 do begin
 | |
|       u := ExtractUnitName(Package.Inputs, i);
 | |
|       DocumentUnit(u);
 | |
|     end;
 | |
|   end;
 | |
|   EndTest;
 | |
| end;
 | |
| 
 | |
| procedure TFPDocHelper.SetProjectDir(AValue: string);
 | |
| begin
 | |
|   if FProjectDir=AValue then Exit;
 | |
|   FProjectDir:=AValue;
 | |
| end;
 | |
| 
 | |
| end.
 | |
| 
 | 
