{$mode objfpc} {$h+} { $DEFINE USESYNAPSE} {$IFDEF VER2_6} {$DEFINE USESYNAPSE} {$ENDIF} program googleapiconv; uses custapp, classes, sysutils, fpjson, jsonparser, fpwebclient, {$IFDEF USESYNAPSE} ssl_openssl, synapsewebclient, {$ELSE} fphttpwebclient, {$ENDIF} googlediscoverytopas, googleservice, restbase, pascodegen, restcodegen; Const BaseDiscoveryURL = 'https://www.googleapis.com/discovery/v1/apis/'; Type { TGoogleAPIConverter } { TAPIEntry } TAPIEntry = Class(TCollectionItem) private FAPIIcon: String; FAPIName: String; FAPIUnitName: String; FFileName: String; Public Property APIName : String Read FAPIName Write FAPIName; Property FileName : String Read FFileName Write FFileName; Property APIUnitName : String Read FAPIUnitName Write FAPIUnitName; Property APIIcon : String Read FAPIIcon Write FAPIIcon; end; { TAPIEntries } TAPIEntries = Class(TCollection) private function GetE(AIndex : Integer): TAPIEntry; Public Function AddEntry : TAPIEntry; Property Entries [AIndex : Integer] : TAPIEntry Read GetE; default; end; TGoogleAPIConverter = CLass(TCustomApplication) private FDownloadOnly: Boolean; FKeepJSON: Boolean; FUnitPrefix: String; FVerbose: Boolean; procedure ConversionLog(Sender: TObject; LogType: TCodegenLogType; const Msg: String); procedure CreateFPMake(FileName: String; L: TAPIEntries); procedure DoAll(LocalFile, URL, OFN: String; AllVersions: Boolean); Procedure DoConversion(JS: TStream; AEntry : TAPIEntry) ; procedure DownloadIcon(APIEntry: TAPIentry); function GetList(LocalFile, URL: String; AllVersions: Boolean): TJSONObject; Function HttpGetJSON(Const URL : String; Response : TStream) : Boolean; procedure RegisterUnit(FileName: String; L: TAPIEntries); procedure Usage(Msg: String); Public Constructor Create(AOwner: TComponent); override; Destructor Destroy; override; Procedure DoRun; override; Property KeepJSON : Boolean Read FKeepJSON Write FKeepJSON; Property Verbose : Boolean Read FVerbose Write FVerbose; Property DownloadOnly : Boolean Read FDownloadOnly Write FDownloadOnly; Property UnitPrefix : String Read FUnitPrefix Write FUnitPrefix; end; { TAPIEntries } function TAPIEntries.GetE(AIndex : Integer): TAPIEntry; begin Result:=Items[AIndex] as TAPIEntry; end; function TAPIEntries.AddEntry: TAPIEntry; begin Result:=Add as TAPIEntry; end; Constructor TGoogleAPIConverter.Create(AOwner: TComponent); begin inherited Create(AOwner); StopOnException:=True; TDiscoveryJSONToPas.RegisterAllObjects; UnitPrefix:='google'; end; Destructor TGoogleAPIConverter.Destroy; begin inherited Destroy; end; Function TGoogleAPIConverter.HttpGetJSON(Const URL: String; Response: TStream ): Boolean; Var Webclient : TAbstractWebClient; Req: TWebClientRequest; Resp : TWebClientResponse; begin Result:=True; Req:=Nil; Resp:=Nil; {$IFDEF USESYNAPSE} WebClient:=TSynapseWebClient.Create(Self); {$ELSE} WebClient:=TFPHTTPWebClient.Create(Self); {$ENDIF} try Req:=WebClient.CreateRequest; Req.ResponseContent:=Response; ConversionLog(Self,cltInfo,'Downloading: '+URL); Resp:=WebClient.ExecuteRequest('GET',URL,Req); Result:=(Resp<>Nil); finally Resp.Free; Req.Free; WebClient.Free; end; end; procedure TGoogleAPIConverter.Usage(Msg : String); begin If (Msg<>'') then Writeln('Error : ',Msg); Writeln('Usage : ',ExeName,' [options] [inputfile] [outputfile]'); Writeln('Where options is one of: '); Writeln('-a --all Download and generate code for all preferred services.'); Writeln('-A --All Download and generate code for all services.'); Writeln('if one of these options is given, then:'); Writeln(' a) The service name will be appended to the output filename.'); Writeln(' b) The --input will be used as a json which lists the services.'); Writeln('-b --baseclass=classname Class name to use as parent class for all classes.'); Writeln('-b --baseclass=classname Class name to use as parent class for all classes.'); Writeln('-m --fpmake=filename Generate fpmake program.'); Writeln('-e --extraunits=units comma separated list of units to add to uses clause.'); Writeln('-h --help this message'); Writeln('-i --input=file input filename (overrides non-option inputfile)'); Writeln('-I --icons Download service icon (size 16)'); Writeln('-L --license=licensetext Set license text to be added to the top of the unit.'); Writeln(' Use @filename to load license text from filename'); Writeln('-o --output=file output filename (overrides non-option outputfile)'); Writeln(' Default is to use input filename with extension .pp'); Writeln('-p --classprefix=prefix Prefix to use in class names for all classes.'); Writeln('-r --resourcesuffix=suffix Suffix to use for resource names. Default is Resource.'); Writeln('-R --register=unit Register unit for Lazarus.'); Writeln('-t --timestamp Add timestamp to generated unit.'); Writeln('-u --url=URL URL to download the REST description from.'); Writeln('-v --serviceversion=v Service version to download the REST description for.'); Writeln('-V --verbose Write some diagnostic messages'); Writeln('-k --keepjson Keep the downloaded JSON files'); Writeln('-d --onlydownload Just download the files, do not actually convert.'); Writeln(' Only effective if -k or --keepjson is also specified.'); Writeln('-f --unitprefix Prefix for generated unit names. Default is "google"'); Writeln('If the outputfilename is empty and cannot be determined, an error is returned'); Halt(Ord(Msg<>'')); end; function TGoogleAPIConverter.GetList(LocalFile, URL: String; AllVersions: Boolean): TJSONObject; Var D : TJSONData; S : TStream; begin if (LocalFile<>'') then S:=TFileStream.Create(LocalFile,fmOpenRead) else begin S:=TMemoryStream.Create; if (URL='') then URL:=BaseDiscoveryURL; If not AllVersions then URL:=URL+'?preferred=true'; HTTPGetJSON(URL,S); S.Position:=0; end; try D:=GetJSON(S); if Not (D is TJSONObject) then begin D.Free; Raise Exception.CreateFmt('Source is not a valid JSON description',[LocalFile+URL]); end; Result:=D as TJSONObject; finally S.Free; end; end; procedure TGoogleAPIConverter.RegisterUnit(FileName :String; L : TAPIEntries); Var I : Integer; UN,N,V : String; begin UN:=ChangeFileext(ExtractFileName(FileName),''); With TStringList.Create do try Add(Format('unit %s;',[un])); Add(''); Add('interface'); Add(''); Add('{$mode objfpc}{$h+}'); Add(''); Add('uses sysutils,classes;'); Add(''); Add('procedure register;'); Add(''); Add('implementation'); Add(''); Add('uses'); if Hasoption('I','icon') then Add(' lazres,'); Add(' restbase,'); Add(' googleservice,'); Add(' googlebase,'); Add(' googleclient,'); For I:=0 to L.Count-1 do begin N:=L[i].APIUnitName; if I0 then IFN:=NonOpts[0]; if NonOpts.Count>1 then OFN:=NonOpts[1]; finally O.Free; NonOpts.Free; end; FVerbose:=HasOption('V','verbose'); FKeepJSON:=HasOption('k','keepjson'); if HasOption('f','unitprefix') then UnitPrefix:=GetOptionValue('f','unitprefix'); If FKeepJSON Then FDownLoadOnly:=HasOption('d','onlydownload'); if (S<>'') or HasOption('h','help') then Usage(S); DoAllServices:=HasOption('a','all') or HasOption('A','All'); if HasOption('i','input') then IFN:=GetOptionValue('i','input'); if HasOption('o','output') then OFN:=GetOptionValue('o','output'); if HasOption('u','url') then URL:=GetOptionValue('u','url') else if hasOption('s','service') then begin URL:=BaseDiscoveryURL+getOptionValue('s','service'); if (pos('/',getOptionValue('s','service'))= 0) and HasOption('v','serviceversion') then URL:=URL+getOptionValue('v','serviceversion'); if (URL[Length(URL)]<>'/') then URL:=URL+'/'; URL:=URL+'rest'; end; if (not DoAllServices) and (IFN='') and (URL='') then Usage('Need an input filename or URL'); if (OFN='') then if (IFN<>'') then OFN:=ChangeFileExt(IFN,'.pp') else if getOptionValue('s','service')<>'' then OFN:=UnitPrefix+getOptionValue('s','service')+'.pp'; if (OFN='') and Not DoAllServices then Usage('Need an output filename'); if DoAllServices then DoAll(IFN,URL,OFN,HasOption('A','All')) else begin APIEntry:=Nil; if (IFN='') and (URL<>'') then begin JS:=TMemoryStream.Create; if not HttpGetJSON(URL,JS) then Raise Exception.Create('Could not download from URL: '+URL); if KeepJSON then With TFIleStream.Create(ChangeFileExt(OFN,'.json'),fmCreate) do try CopyFrom(JS,0); finally Free; end; JS.POsition:=0; end else JS:=TFileStream.Create(IFN,fmOpenRead or fmShareDenyWrite); try if not DownLoadOnly then APIEntry:=TAPIEntry.Create(Nil); try APIEntry.FileName:=OFN; DoConversion(JS,APIEntry); if HasOption('I','icon') then DownloadIcon(APIEntry); finally APIEntry.Free; end; finally JS.Free; end; end; Terminate; end; procedure TGoogleAPIConverter.DownloadIcon(APIEntry : TAPIentry); Var FN : String; FS : TFileStream; begin if (APIEntry.APIIcon<>'') then begin FN:=ExtractFilePath(APIEntry.FileName)+APIEntry.APIName+ExtractFileExt(APIEntry.APIIcon); FS:=TFileStream.Create(FN,fmCreate); try if HasOption('V','verbose') then Writeln(Format('Downloading icon %s to %s',[APIEntry.APIIcon,FN])); HttpGetJSON(APIEntry.APIIcon,FS); finally FS.Free; end; end; end; Procedure TGoogleAPIConverter.DoConversion(JS: TStream; AEntry: TAPIEntry); Var L: String; O : TGoogleIcons; begin With TDiscoveryJSONToPas.Create(Nil) do try L:=GetOptionValue('L','license'); if (L<>'') then begin if (L[1]<>'@') then LicenseText.Text:=L else begin Delete(L,1,1); LicenseText.LoadFromFile(L); end; end; OnLog:=@ConversionLog; ExtraUnits:=GetOptionValue('e','extraunits'); if HasOption('b','baseclass') then BaseClassName:=GetOptionValue('b','baseclass'); if HasOption('p','classprefix') then ClassPrefix:=GetOptionValue('p','classprefix'); if HasOption('r','resourcesuffix') then ResourceSuffix:=GetOptionValue('r','resourcesuffix'); AddTimeStamp:=HasOption('t','timestamp'); LoadFromStream(JS); AEntry.APIUnitName:=ChangeFileExt(ExtractFileName(AEntry.FileName),''); AEntry.APIName:=APIClassName; O:=Description.icons; if Assigned(O) then AEntry.APIIcon:=O.x16; SaveToFile(AEntry.FileName); finally Free; end; end; Var Application : TGoogleAPIConverter; begin {$if declared(Heaptrc)} printleakedblock:=true; printfaultyblock:=true; add_tail:=true; SetHeapTraceOutput('heaptrc.txt'); {$endif} Application:=TGoogleAPIConverter.Create(Nil); Application.Initialize; Application.Run; end.