googleapiconv.pp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. {$mode objfpc}
  2. {$h+}
  3. { $DEFINE USESYNAPSE}
  4. program googleapiconv;
  5. uses
  6. custapp, classes, sysutils, fpjson, jsonparser, fpwebclient,
  7. fphttpwebclient, opensslsockets,
  8. googlediscoverytopas, googleservice, restbase, pascodegen, restcodegen;
  9. Const
  10. ProgramVersionNumber = '0.6';
  11. // BaseDiscoveryURL = 'https://www.googleapis.com/discovery/v1/apis/';
  12. BaseDiscoveryURL = 'https://discovery.googleapis.com/discovery/v1/apis/';
  13. Type
  14. { TGoogleAPIConverter }
  15. { TAPIEntry }
  16. TAPIEntry = Class(TCollectionItem)
  17. private
  18. FAPIIcon: String;
  19. FAPIName: String;
  20. FAPIUnitName: String;
  21. FFileName: String;
  22. Public
  23. Property APIName : String Read FAPIName Write FAPIName;
  24. Property FileName : String Read FFileName Write FFileName;
  25. Property APIUnitName : String Read FAPIUnitName Write FAPIUnitName;
  26. Property APIIcon : String Read FAPIIcon Write FAPIIcon;
  27. end;
  28. { TAPIEntries }
  29. TAPIEntries = Class(TCollection)
  30. private
  31. function GetE(AIndex : Integer): TAPIEntry;
  32. Public
  33. Function AddEntry : TAPIEntry;
  34. Property Entries [AIndex : Integer] : TAPIEntry Read GetE; default;
  35. end;
  36. TGoogleAPIConverter = CLass(TCustomApplication)
  37. private
  38. FDownloadOnly: Boolean;
  39. FKeepJSON: Boolean;
  40. FUnitPrefix: String;
  41. FVerbose: Boolean;
  42. procedure ConversionLog(Sender: TObject; LogType: TCodegenLogType; const Msg: String);
  43. procedure CreateFPMake(FileName: String; L: TAPIEntries);
  44. procedure DoAll(LocalFile, URL, OFN: String; AllVersions: Boolean);
  45. Procedure DoConversion(JS: TStream; AEntry : TAPIEntry) ;
  46. procedure DownloadIcon(APIEntry: TAPIentry);
  47. function GetList(LocalFile, URL: String; AllVersions: Boolean): TJSONObject;
  48. Function HttpGetJSON(Const URL : String; Response : TStream) : Boolean;
  49. procedure RegisterUnit(FileName: String; L: TAPIEntries);
  50. procedure Usage(Msg: String);
  51. Public
  52. Constructor Create(AOwner: TComponent); override;
  53. Destructor Destroy; override;
  54. Procedure DoRun; override;
  55. Property KeepJSON : Boolean Read FKeepJSON Write FKeepJSON;
  56. Property Verbose : Boolean Read FVerbose Write FVerbose;
  57. Property DownloadOnly : Boolean Read FDownloadOnly Write FDownloadOnly;
  58. Property UnitPrefix : String Read FUnitPrefix Write FUnitPrefix;
  59. end;
  60. { TAPIEntries }
  61. function TAPIEntries.GetE(AIndex : Integer): TAPIEntry;
  62. begin
  63. Result:=Items[AIndex] as TAPIEntry;
  64. end;
  65. function TAPIEntries.AddEntry: TAPIEntry;
  66. begin
  67. Result:=Add as TAPIEntry;
  68. end;
  69. Constructor TGoogleAPIConverter.Create(AOwner: TComponent);
  70. begin
  71. inherited Create(AOwner);
  72. StopOnException:=True;
  73. TDiscoveryJSONToPas.RegisterAllObjects;
  74. UnitPrefix:='google';
  75. end;
  76. Destructor TGoogleAPIConverter.Destroy;
  77. begin
  78. inherited Destroy;
  79. end;
  80. Function TGoogleAPIConverter.HttpGetJSON(Const URL: String; Response: TStream
  81. ): Boolean;
  82. Var
  83. Webclient : TAbstractWebClient;
  84. Req: TWebClientRequest;
  85. Resp : TWebClientResponse;
  86. begin
  87. Result:=True;
  88. Req:=Nil;
  89. Resp:=Nil;
  90. WebClient:=TFPHTTPWebClient.Create(Self);
  91. try
  92. Req:=WebClient.CreateRequest;
  93. Req.ResponseContent:=Response;
  94. ConversionLog(Self,cltInfo,'Downloading: '+URL);
  95. Resp:=WebClient.ExecuteRequest('GET',URL,Req);
  96. Result:=(Resp<>Nil);
  97. finally
  98. Resp.Free;
  99. Req.Free;
  100. WebClient.Free;
  101. end;
  102. end;
  103. procedure TGoogleAPIConverter.Usage(Msg : String);
  104. begin
  105. If (Msg<>'') then
  106. Writeln('Error : ',Msg);
  107. Writeln('Usage : ',ExeName,' [options] [inputfile] [outputfile]');
  108. Writeln('Where options is one of: ');
  109. Writeln('-a --all Download and generate code for all preferred services.');
  110. Writeln('-A --All Download and generate code for all services.');
  111. Writeln('if one of these options is given, then:');
  112. Writeln(' a) The service name will be appended to the output filename.');
  113. Writeln(' b) The --input will be used as a json which lists the services.');
  114. Writeln('-b --baseclass=classname Class name to use as parent class for all classes.');
  115. Writeln('-d --onlydownload Just download the files, do not actually convert.');
  116. Writeln(' Only effective if -k or --keepjson is also specified.');
  117. Writeln('-e --extraunits=units comma separated list of units to add to uses clause.');
  118. Writeln('-f --unitprefix Prefix for generated unit names. Default is "google"');
  119. Writeln('-h --help this message');
  120. Writeln('-i --input=file input filename (overrides non-option inputfile)');
  121. Writeln('-I --icon Download service icon (size 16)');
  122. Writeln('-k --keepjson Keep the downloaded JSON files');
  123. Writeln('-L --license=licensetext Set license text to be added to the top of the unit.');
  124. Writeln(' Use @filename to load license text from filename');
  125. Writeln('-m --fpmake=filename Generate fpmake program.');
  126. Writeln('-o --output=file output filename (overrides non-option outputfile)');
  127. Writeln(' Default is to use input filename with extension .pp');
  128. Writeln('-p --classprefix=prefix Prefix to use in class names for all classes.');
  129. Writeln('-r --resourcesuffix=suffix Suffix to use for resource names. Default is Resource.');
  130. Writeln('-R --register=unit Register unit for Lazarus.');
  131. Writeln('-s --service=servicename Service name to download the REST description for.');
  132. Writeln('-t --timestamp Add timestamp to generated unit.');
  133. Writeln('-u --url=URL URL to download the REST description from.');
  134. Writeln('-v --serviceversion=v Service version to download the REST description for.');
  135. Writeln('-V --verbose Write some diagnostic messages');
  136. Writeln(' --version Show version number and exit');
  137. Writeln('If the outputfilename is empty and cannot be determined, an error is returned');
  138. Halt(Ord(Msg<>''));
  139. end;
  140. function TGoogleAPIConverter.GetList(LocalFile, URL: String;
  141. AllVersions: Boolean): TJSONObject;
  142. Var
  143. D : TJSONData;
  144. S : TStream;
  145. begin
  146. if (LocalFile<>'') then
  147. S:=TFileStream.Create(LocalFile,fmOpenRead)
  148. else
  149. begin
  150. S:=TMemoryStream.Create;
  151. if (URL='') then
  152. URL:=BaseDiscoveryURL;
  153. If not AllVersions then
  154. URL:=URL+'?preferred=true';
  155. HTTPGetJSON(URL,S);
  156. S.Position:=0;
  157. end;
  158. try
  159. D:=GetJSON(S);
  160. if Not (D is TJSONObject) then
  161. begin
  162. D.Free;
  163. Raise Exception.CreateFmt('Source is not a valid JSON description',[LocalFile+URL]);
  164. end;
  165. Result:=D as TJSONObject;
  166. finally
  167. S.Free;
  168. end;
  169. end;
  170. procedure TGoogleAPIConverter.RegisterUnit(FileName :String; L : TAPIEntries);
  171. Var
  172. I : Integer;
  173. UN,N : String;
  174. begin
  175. UN:=ChangeFileext(ExtractFileName(FileName),'');
  176. With TStringList.Create do
  177. try
  178. Add(Format('unit %s;',[un]));
  179. Add('');
  180. Add('interface');
  181. Add('');
  182. Add('{$mode objfpc}{$h+}');
  183. Add('');
  184. Add('uses sysutils,classes;');
  185. Add('');
  186. Add('procedure register;');
  187. Add('');
  188. Add('implementation');
  189. Add('');
  190. Add('uses');
  191. if Hasoption('I','icon') then
  192. Add(' lazres,');
  193. Add(' restbase,');
  194. Add(' googleservice,');
  195. Add(' googlebase,');
  196. Add(' googleclient,');
  197. For I:=0 to L.Count-1 do
  198. begin
  199. N:=L[i].APIUnitName;
  200. if I<L.Count-1 then
  201. Add(' '+N+',')
  202. else
  203. Add(' '+N+';')
  204. end;
  205. Add('');
  206. Add('');
  207. Add('procedure register;');
  208. Add('');
  209. Add('begin');
  210. if Hasoption('I','icon') then
  211. Add('{$i '+un+'.inc}');
  212. Add(' RegisterComponents(''Google API'',[');
  213. Add(' TGoogleClient,');
  214. For I:=0 to L.Count-1 do
  215. begin
  216. N:=L[i].APIName;
  217. if I<L.Count-1 then
  218. Add(' '+N+',')
  219. else
  220. Add(' '+N)
  221. end;
  222. Add(' ]);');
  223. Add('end;');
  224. Add('');
  225. Add('end.');
  226. SaveToFile(FileName);
  227. finally
  228. Free;
  229. end;
  230. end;
  231. procedure TGoogleAPIConverter.ConversionLog(Sender: TObject;
  232. LogType: TCodegenLogType; const Msg: String);
  233. begin
  234. if Verbose then
  235. Writeln(StdErr,Msg);
  236. end;
  237. procedure TGoogleAPIConverter.CreateFPMake(FileName :String; L : TAPIEntries);
  238. Var
  239. I : Integer;
  240. N : String;
  241. begin
  242. With TStringList.Create do
  243. try
  244. Add('program fpmake;');
  245. Add('');
  246. Add('{$mode objfpc}{$h+}');
  247. Add('');
  248. Add('uses sysutils,classes, fpmkunit;');
  249. Add('');
  250. Add('');
  251. Add('function StdDep(T : TTarget) : TTarget;');
  252. Add('begin');
  253. Add(' T.Dependencies.AddUnit(''googlebase'');');
  254. Add(' T.Dependencies.AddUnit(''googleservice'');');
  255. Add(' Result:=T;');
  256. Add('end;');
  257. Add('');
  258. Add('Procedure AddGoogle;');
  259. Add('');
  260. Add('Var');
  261. Add(' P : TPackage;');
  262. Add(' T : TTarget;');
  263. Add('');
  264. Add('begin');
  265. Add(' With Installer do');
  266. Add(' begin');
  267. Add(' P:=AddPackage(''googleapis'');');
  268. Add(' P.ShortName:=''googleap'';');
  269. Add(' T:=P.Targets.AddUnit(''googlebase.pp'');');
  270. Add(' T:=P.Targets.AddUnit(''googleclient.pp'');');
  271. Add(' T:=P.Targets.AddUnit(''googleservice.pp'');');
  272. Add(' T.Dependencies.AddUnit(''googleclient'');');
  273. Add(' T.Dependencies.AddUnit(''googlebase'');');
  274. For I:=0 to L.Count-1 do
  275. begin
  276. N:=L[i].APIUnitName;
  277. Add(Format(' T:=StdDep(P.Targets.AddUnit(''%s''));',[ExtractFileName(N)]));
  278. end;
  279. Add(' end;');
  280. Add('end;');
  281. Add('');
  282. Add('{$ifndef ALLPACKAGES}');
  283. Add('begin');
  284. Add(' AddGoogle;');
  285. Add(' Installer.Run;');
  286. Add('end.');
  287. Add('{$endif ALLPACKAGES}');
  288. Add('');
  289. Add('procedure register;');
  290. Add('');
  291. Add('begin');
  292. SaveToFile(FileName);
  293. finally
  294. Free;
  295. end;
  296. end;
  297. procedure TGoogleAPIConverter.DoAll(LocalFile, URL, OFN : String; AllVersions : Boolean);
  298. Var
  299. D,O : TJSONObject;
  300. RS : TStringStream;
  301. A : TJSONArray;
  302. S : TJSONEnum;
  303. LFN,RU,E : String;
  304. UL : TAPIEntries;
  305. U : TAPIEntry;
  306. I : Integer;
  307. begin
  308. E:=ExtractFileExt(OFN);
  309. if (E='') then
  310. E:='.pp';
  311. UL:=Nil;
  312. D:=GetList(LocalFile,URL,ALlVersions);
  313. try
  314. UL:=TAPIEntries.Create(TAPIEntry);
  315. A:=D.Get('items',TJSONArray(Nil));
  316. For S in A do
  317. begin
  318. O:=S.Value as TJSONObject;
  319. if AllVersions or O.Get('preferred',false) then
  320. begin
  321. RU:=O.get('discoveryRestUrl');
  322. LFN:=UnitPrefix+O.get('name');
  323. if AllVersions then
  324. LFN:=LFN+'_'+StringReplace(O.get('version'),'.','',[rfReplaceAll]);
  325. if (OFN='') then
  326. LFN:=LFN+E
  327. else
  328. LFN:=ChangeFileExt(OFN,LFN+E);
  329. RS:=TStringStream.Create('');
  330. try
  331. if not HttpGetJSON(RU,RS) then
  332. Raise Exception.Create('Could not download rest description from URL: '+RU);
  333. if KeepJSON then
  334. With TFIleStream.Create(ChangeFileExt(LFN,'.json'),fmCreate) do
  335. try
  336. CopyFrom(RS,0);
  337. finally
  338. Free;
  339. end;
  340. ConversionLog(Self,cltInfo,'Saving file: '+ChangeFileExt(LFN,'.json'));
  341. RS.Position:=0;
  342. U:=UL.AddEntry;
  343. U.FileName:=LFN;
  344. if not DownloadOnly then
  345. DoConversion(RS,U);
  346. finally
  347. RS.Free;
  348. end;
  349. end;
  350. end;
  351. if not DownloadOnly then
  352. begin
  353. if HasOption('R','register') then
  354. RegisterUnit(GetOptionValue('R','register'),UL);
  355. if HasOption('m','fpmake') then
  356. CreateFpMake(GetOptionValue('m','fpmake'),UL);
  357. end;
  358. if HasOption('I','icon') then
  359. For I:=0 to UL.Count-1 do
  360. DownloadIcon(UL[i]); //this isn't working with --onlydownload and --all
  361. //the icon URL is not known until after DoConversion
  362. { #todo : fix download icon}
  363. finally
  364. UL.Free;
  365. D.Free;
  366. end;
  367. end;
  368. Procedure TGoogleAPIConverter.DoRun;
  369. Const
  370. MyO : Array[1..21] of ansistring
  371. = ('help','input:','output:','extraunits:','baseclass:','classprefix:',
  372. 'url:','service:','serviceversion:','resourcesuffix:','license:',
  373. 'All','all','register','icon','fpmake:','timestamp','verbose','keepjson',
  374. 'onlydownload','unitprefix');
  375. Var
  376. O,NonOpts : TStrings;
  377. URL, S, IFN, OFN : AnsiString;
  378. JS : TStream;
  379. DoAllServices : Boolean;
  380. APIEntry : TAPIEntry;
  381. begin
  382. if (ParamCount=1) and HasOption('version') then
  383. begin
  384. WriteLn(ExtractFileName(Self.ExeName),' ', ProgramVersionNumber);
  385. Terminate;
  386. EXIT;
  387. end;
  388. JS:=Nil;
  389. O:=Nil;
  390. NonOpts:=TStringList.Create;
  391. try
  392. O:=TStringList.Create;
  393. For S in MyO do O.Add(S);
  394. S:=Checkoptions('hi:o:e:b:p:u:s:v:r:L:aAR:Im:tVkdf',O,TStrings(Nil),NonOpts,True);
  395. if NonOpts.Count>0 then
  396. IFN:=NonOpts[0];
  397. if NonOpts.Count>1 then
  398. OFN:=NonOpts[1];
  399. finally
  400. O.Free;
  401. NonOpts.Free;
  402. end;
  403. FVerbose:=HasOption('V','verbose');
  404. FKeepJSON:=HasOption('k','keepjson');
  405. if HasOption('f','unitprefix') then
  406. UnitPrefix:=GetOptionValue('f','unitprefix');
  407. If FKeepJSON Then
  408. FDownLoadOnly:=HasOption('d','onlydownload');
  409. if (S<>'') or HasOption('h','help') then
  410. Usage(S);
  411. DoAllServices:=HasOption('a','all') or HasOption('A','All');
  412. if HasOption('i','input') then
  413. IFN:=GetOptionValue('i','input');
  414. if HasOption('o','output') then
  415. OFN:=GetOptionValue('o','output');
  416. if HasOption('u','url') then
  417. URL:=GetOptionValue('u','url')
  418. else if hasOption('s','service') then
  419. begin
  420. URL:=BaseDiscoveryURL+getOptionValue('s','service');
  421. if (pos('/',getOptionValue('s','service'))= 0) and
  422. HasOption('v','serviceversion') then
  423. URL:=URL+getOptionValue('v','serviceversion');
  424. if (URL[Length(URL)]<>'/') then
  425. URL:=URL+'/';
  426. URL:=URL+'rest';
  427. end;
  428. if (not DoAllServices) and (IFN='') and (URL='') then
  429. Usage('Need an input filename or URL');
  430. if (OFN='') then
  431. if (IFN<>'') then
  432. OFN:=ChangeFileExt(IFN,'.pp')
  433. else if getOptionValue('s','service')<>'' then
  434. OFN:=UnitPrefix+getOptionValue('s','service')+'.pp';
  435. if (OFN='') and Not DoAllServices then
  436. Usage('Need an output filename');
  437. if DoAllServices then
  438. DoAll(IFN,URL,OFN,HasOption('A','All'))
  439. else
  440. begin
  441. APIEntry:=Nil;
  442. if (IFN='') and (URL<>'') then
  443. begin
  444. JS:=TMemoryStream.Create;
  445. if not HttpGetJSON(URL,JS) then
  446. Raise Exception.Create('Could not download from URL: '+URL);
  447. if KeepJSON then
  448. With TFIleStream.Create(ChangeFileExt(OFN,'.json'),fmCreate) do
  449. try
  450. CopyFrom(JS,0);
  451. finally
  452. Free;
  453. end;
  454. ConversionLog(Self,cltInfo,'Saving file: '+ChangeFileExt(OFN,'.json'));
  455. JS.POsition:=0;
  456. end
  457. else
  458. JS:=TFileStream.Create(IFN,fmOpenRead or fmShareDenyWrite);
  459. try
  460. if not DownLoadOnly then
  461. APIEntry:=TAPIEntry.Create(Nil);
  462. try
  463. APIEntry.FileName:=OFN;
  464. DoConversion(JS,APIEntry);
  465. if HasOption('I','icon') then
  466. DownloadIcon(APIEntry);
  467. finally
  468. APIEntry.Free;
  469. end;
  470. finally
  471. JS.Free;
  472. end;
  473. end;
  474. Terminate;
  475. end;
  476. procedure TGoogleAPIConverter.DownloadIcon(APIEntry : TAPIentry);
  477. Var
  478. FN : String;
  479. FS : TFileStream;
  480. begin
  481. if (APIEntry.APIIcon<>'') then
  482. begin
  483. FN:=ExtractFilePath(APIEntry.FileName)+APIEntry.APIName+ExtractFileExt(APIEntry.APIIcon);
  484. FS:=TFileStream.Create(FN,fmCreate);
  485. try
  486. if HasOption('V','verbose') then
  487. Writeln(Format('Downloading icon %s to %s',[APIEntry.APIIcon,FN]));
  488. HttpGetJSON(APIEntry.APIIcon,FS);
  489. finally
  490. FS.Free;
  491. end;
  492. end;
  493. end;
  494. Procedure TGoogleAPIConverter.DoConversion(JS: TStream; AEntry: TAPIEntry);
  495. Var
  496. L: String;
  497. O : TGoogleIcons;
  498. DiscoveryJSONToPas: TDiscoveryJSONToPas;
  499. begin
  500. DiscoveryJSONToPas := TDiscoveryJSONToPas.Create(Nil);
  501. try
  502. L:=GetOptionValue('L','license');
  503. if (L<>'') then
  504. begin
  505. if (L[1]<>'@') then
  506. DiscoveryJSONToPas.LicenseText.Text:=L
  507. else
  508. begin
  509. Delete(L,1,1);
  510. DiscoveryJSONToPas.LicenseText.LoadFromFile(L);
  511. end;
  512. end;
  513. DiscoveryJSONToPas.OnLog := @ConversionLog;
  514. DiscoveryJSONToPas.ExtraUnits:=GetOptionValue('e','extraunits');
  515. if HasOption('b','baseclass') then
  516. DiscoveryJSONToPas.BaseClassName:=GetOptionValue('b','baseclass');
  517. if HasOption('p','classprefix') then
  518. DiscoveryJSONToPas.ClassPrefix:=GetOptionValue('p','classprefix');
  519. if HasOption('r','resourcesuffix') then
  520. DiscoveryJSONToPas.ResourceSuffix:=GetOptionValue('r','resourcesuffix');
  521. DiscoveryJSONToPas.AddTimeStamp:=HasOption('t','timestamp');
  522. DiscoveryJSONToPas.LoadFromStream(JS);
  523. AEntry.APIUnitName:=ChangeFileExt(ExtractFileName(AEntry.FileName),'');
  524. AEntry.APIName:=DiscoveryJSONToPas.APIClassName;
  525. O:=DiscoveryJSONToPas.Description.icons;
  526. if Assigned(O) then
  527. AEntry.APIIcon:=O.x16;
  528. DiscoveryJSONToPas.OutputUnitName := AEntry.APIUnitName;
  529. ConversionLog(Self,cltInfo,Format('Converting service "%s" to unit: %s',[AEntry.APIUnitName,AEntry.FileName]));
  530. DiscoveryJSONToPas.Execute;
  531. DiscoveryJSONToPas.SaveToFile(AEntry.FileName);
  532. finally
  533. DiscoveryJSONToPas.Free;
  534. end;
  535. end;
  536. Var
  537. Application : TGoogleAPIConverter;
  538. begin
  539. {$if declared(Heaptrc)}
  540. printleakedblock:=true;
  541. printfaultyblock:=true;
  542. add_tail:=true;
  543. SetHeapTraceOutput('heaptrc.txt');
  544. {$endif}
  545. Application:=TGoogleAPIConverter.Create(Nil);
  546. Application.Initialize;
  547. Application.Run;
  548. FreeAndNil(Application); //gets rid of memory leak and makes Heaptrc happy
  549. end.