restemplate/restemplate.pas

367 lines
8.1 KiB
Plaintext

{
This file is part of restemplate.
Copyright (C) 2015 Andreas Schneider
restemplate is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
sqlvision 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. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with restemplate. If not, see <http://www.gnu.org/licenses/>.
}
program restemplate;
{$mode objfpc}
{.$define use_synapse}
{$define use_fclweb}
uses
SysUtils, Classes, strutils, IniFiles,
{$ifdef use_synapse}httpsend, ssl_openssl,{$endif}
{$ifdef use_fclweb}fphttpclient,{$endif}
JTemplate,
fpjson, jsonparser,
DOM, XMLRead, XMLWrite;
var
data: TextFile;
line: String;
parser: TJTemplateParser;
{$ifdef use_synapse}http: THTTPSend;{$endif}
{$ifdef use_fclweb}http: TFPHTTPClient;{$endif}
method, url: String;
content: TStringList;
commandMode: Boolean;
configDir, templateDir: String;
templateFile, templateName: String;
sessionIni: TIniFile;
beautify: Boolean;
type
TContentType = (ctOther, ctJSON, ctXML);
procedure CmdAskUser(AName: String);
var
value, default: String;
begin
Write(AName);
default := sessionIni.ReadString(templateName, AName, '');
if default <> '' then
Write(' [', default, ']: ')
else
Write(': ');
ReadLn(value);
if value = '' then
value := default;
parser.Fields.Add(AName, value);
sessionIni.WriteString(templateName, AName, value);
end;
procedure CmdHeader(AHeader: String);
var
i: Integer;
name, value: String;
begin
parser.Content := AHeader;
parser.Replace;
AHeader := parser.Content;
{$ifdef use_synapse}
http.Headers.Add(AHeader);
{$endif}
{$ifdef use_fclweb}
i := 1;
while (i < Length(AHeader)) and (AHeader[i] <> ':') do
Inc(i);
name := Trim(Copy(AHeader, 1, i - 1));
value := Trim(Copy(AHeader, i + 1, Length(AHeader)));
http.AddHeader(name, value);
{$endif}
end;
function IdentifyContentType(AString: String; out ContentType: TContentType): Boolean;
begin
if Pos('application/json', AString) > 0 then
ContentType := ctJSON
else if Pos('application/xml', AString) > 0 then
ContentType := ctXML
else
ContentType := ctOther;
Result := (ContentType <> ctOther);
end;
procedure ProcessCall(AURL: String);
var
s: String;
{$ifdef use_fclweb}
request, response: TStream;
{$endif}
jsonParser: TJSONParser;
jsonData: TJSONData;
contentType: TContentType;
xmlDoc: TXMLDocument;
begin
parser.Content := AURL;
parser.Replace;
AURL := parser.Content;
writeln('Calling ', AURL);
{$ifdef use_synapse}
if content.Count > 0 then
begin
// Variable replacement
parser.Content := content.Text;
parser.Replace;
content.Text .= parser.Content;
content.SaveToStream(http.Document);
end;
if http.HTTPMethod(method, AURL) then
begin
writeln;
writeln('Status: ', http.ResultCode);
writeln;
writeln('Headers:');
for s in http.Headers do
writeln(' ', s);
writeln;
content.LoadFromStream(http.Document);
writeln(content.Text);
end else
begin
ExitCode := 2;
writeln;
writeln('FAILED! Last Socket Error: ', http.Sock.SocksLastError);
end;
{$endif}
{$ifdef use_fclweb}
response := TMemoryStream.Create;
request := nil;
if content.Count > 0 then
begin
request := TMemoryStream.Create;
// Variable replacement
parser.Content := content.Text;
parser.Replace;
content.Text := parser.Content;
content.SaveToStream(request);
request.Position := 0;
http.RequestBody := request;
end;
try
http.HTTPMethod(method, AURL, response, []);
except
on E: Exception do
begin
writeln;
writeln('Failed! ', E.Message);
Halt(1);
end;
end;
writeln;
writeln('Status: ', http.ResponseStatusCode, ' (', http.ResponseStatusText, ')');
writeln;
writeln('Headers:');
for s in http.ResponseHeaders do
writeln(' ', s);
writeln;
response.Position := 0;
if beautify and IdentifyContentType(http.GetHeader(http.ResponseHeaders, 'Content-Type'), contentType) then
begin
case contentType of
ctJSON:
begin
jsonParser := TJSONParser.Create(response);
jsonData := jsonParser.Parse;
writeln(jsonData.FormatJSON);
jsonData.Free;
jsonParser.Free;
end;
ctXML:
begin
ReadXMLFile(xmlDoc, response);
response.Size := 0;
WriteXMLFile(xmlDoc, response);
response.Position := 0;
content.LoadFromStream(response);
writeln(content.Text);
end;
end;
end else
begin
content.LoadFromStream(response);
writeln(content.Text);
end;
response.Free;
request.Free;
{$endif}
end;
procedure CmdBasicAuth(AData: String);
var
separator: Char;
i: Integer;
begin
separator := AData[1];
i := 2;
while (i < Length(AData)) and (AData[i] <> separator) do
Inc(i);
http.UserName := Copy(AData, 2, i - 2);
http.Password := Copy(AData, i + 1, Length(AData));
end;
function ProcessCommand(ALine: String): Boolean;
begin
Result := False;
if AnsiStartsStr('Ask ', ALine) then
begin
Result := True;
CmdAskUser(Copy(ALine, 5, Length(ALine)));
end else
if AnsiStartsStr('Header ', ALine) then
begin
Result := True;
CmdHeader(Copy(ALine, 8, Length(ALine)));
end else
if AnsiStartsStr('Method ', ALine) then
begin
Result := True;
method := Copy(ALine, 8, Length(ALine));
end else
// We don't include the space here, since we also interpret this following
// char as separator for username and password! (In case the username itself
// contains a space.)
if AnsiStartsStr('BasicAuth', ALine) then
begin
Result := True;
CmdBasicAuth(Copy(ALine, 10, Length(ALine)));
end else
if ALine = 'Beautify' then
begin
Result := True;
beautify := True;
end else
if AnsiStartsStr('Call ', ALine) then
begin
Result := True;
url := Copy(ALine, 6, Length(ALine));
end;
end;
procedure ListProfiles;
var
sr: TSearchRec;
begin
Writeln('Known profiles:');
if FindFirst(templateDir + '*.rest', faAnyFile, sr) = 0 then
begin
repeat
writeln(' ', Copy(sr.Name, 1, Length(sr.Name) - 5));
until FindNext(sr) <> 0;
end;
end;
begin
configDir := GetAppConfigDir(False);
templateDir := configDir + 'templates' + PathDelim;
templateFile := templateDir + ParamStr(1) + '.rest';
if ParamCount <> 1 then
begin
Writeln('Usage: ', ExtractFileName(ParamStr(0)), ' <profile or file>');
Writeln;
ListProfiles;
Halt(0);
end;
if FileExists(ParamStr(1)) then
begin
AssignFile(data, ParamStr(1));
templateName := ExtractFileName(ParamStr(1));
if AnsiEndsStr('.rest', templateName) then
templateName := Copy(templateName, 1, Length(templateName) - 5);
end else
if FileExists(templateFile) then
begin
AssignFile(data, templateFile);
templateName := ParamStr(1);
end else
begin
writeln('Template not found!');
Halt(1);
end;
Reset(data);
sessionIni := TIniFile.Create(configDir + 'session.ini');
parser := TJTemplateParser.Create;
content := TStringList.Create;
beautify := False; //By default, we don't want to manipulate output.
{$ifdef use_synapse}
http := THTTPSend.Create;
{$endif}
{$ifdef use_fclweb}
http := TFPHttpClient.Create(nil);
{$endif}
commandMode := True;
try
while not EOF(data) do
begin
ReadLn(data, line);
if commandMode and (line <> '') and (line[1] <> '#') and
not ProcessCommand(line) then
begin
commandMode := False;
end;
if not commandMode then
content.Add(line);
end;
if url <> '' then
ProcessCall(url);
finally
sessionIni.Free;
parser.Free;
http.Free;
content.Free;
CloseFile(data);
end;
end.