Skip to content

Commit

Permalink
Merge pull request #176 from tvannoy/python-client
Browse files Browse the repository at this point in the history
Python client stub generator
  • Loading branch information
cinemast committed Dec 4, 2016
2 parents e3c4c4f + 79a93c5 commit 1feb600
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 27 deletions.
3 changes: 3 additions & 0 deletions AUTHORS.md
Expand Up @@ -33,6 +33,9 @@ Alexandre Poirot <alexandre.poirot@gmail.com>
+ adapted build file to generate pkg-config file for this lib.
+ added client and server connectors that use Tcp Sockets on Linux and Windows (uses native socket and thread API on each OS)

Trevor Vannoy <trevor.vannoy@flukecal.com>
+ python client stub generator

Bugfixes (chronological order)
==============================

Expand Down
68 changes: 42 additions & 26 deletions doc/manpage.in
Expand Up @@ -5,15 +5,16 @@
jsonrpcstub \- genearate stubs for the libjson\-rpc\-cpp framework.
.SH SYNOPSIS
.B
jsonrpcstub specfile.json [\-\-cpp\-server=namespace::ClassName]
[\-\-cpp\-server\-file=classqname.h] [\-\-cpp\-client=namespace::ClassName]
[\-\-cpp\-client-file=classname.h] [\-\-js\-client=ClassName]
[\-\-js-client-file=classname.js] [\-h] [\-v] [\-\-version]
jsonrpcstub specfile.json [\-\-cpp\-server=namespace::ClassName]
[\-\-cpp\-server\-file=classqname.h] [\-\-cpp\-client=namespace::ClassName]
[\-\-cpp\-client-file=classname.h] [\-\-js\-client=ClassName]
[\-\-js-client-file=classname.js] [\-\-py\-client=ClassName]
[\-\-py\-client\-file=classname.py] [\-h] [\-v] [\-\-version]
.PP

.SH DESCRIPTION
.PP
jsonrpcstub is a tool to generate C++ and JavaScript classes from a procedure specification file.
jsonrpcstub is a tool to generate C++, JavaScript, and Python classes from a procedure specification file.
.SS SPECIFICATION SYNTAX
.PP
The specifictaion file is a JSON file containing all available JSON\-RPC methods and notifications
Expand All @@ -38,9 +39,10 @@ with their corresponding parameters and return values contained in a top\-level
.fi

.PP
The literal in each \fB"params"\fP and \fB"returns"\fP section defines the corresponding type.
If the \fb"params"\fP contains an array, the parameters are accepted by position,
if it contains an object, they are accepted by name.
The literal in each \fB"params"\fP and \fB"returns"\fP section defines the corresponding type.
If the \fb"params"\fP contains an array, the parameters are accepted by position,
if it contains an object, they are accepted by name.

.SH OPTIONS
.IP \-h
Print usage information.
Expand All @@ -49,34 +51,48 @@ Print verbose information during generation.
.IP \-\-version
Print version info and exit.
.IP \-\-cpp\-server=ClassName
Creates a Abstract Server class. Namespaces can be provided using the :: notation
Creates a Abstract Server class. Namespaces can be provided using the :: notation
(e.g. ns1::ns2::Classname).
.IP \-\-cpp\-server\-file=filename.h
Defines the filename to use when generating the C++ Abstract Server class.
If this is not provided, the lowercase classname is used.
.IP \-\-cpp\-client=ClassName
Creates a C++ client class. Namespaces can be provided using the :: notation
Creates a C++ client class. Namespaces can be provided using the :: notation
(e.g. ns1::ns2::Classname).
.IP \-\-cpp\-client\-file=filename.h
Defines the filename to use when generating the C++ client class.
If this is not provided, the lowercase classname is used.
.IP \-\-js\-client=ClassName
Creates a JavaScript client class. No namespaces are supported in this option.
Creates a JavaScript client class. No namespaces are supported in this option.
.IP \-\-js\-client-file=filename.js
Defines the filename to use when generating the JavaScrip client class.
Defines the filename to use when generating the JavaScript client class.
.IP \-\-py\-client=ClassName
Creates a Python client class. No namespaces are supported in this option.
.IP \-\-py\-client\-file=filename.py
Defines the filename to use when generating the Python client class.
If this is not provided, the lowercase classname is used.

.SH EXAMPLES
.PP
Generate C++ Stubs for Server and Client, the classes will be named AbstractStubServer and StubClient:
.P P
.IP
.B
\& jsonrpcstub spec.json \-\-cpp\-server=AbstractStubServer \-\-cpp\-client=StubClient
\&jsonrpcstub spec.json \-\-cpp\-server=AbstractStubServer \-\-cpp\-client=StubClient
.B
.PP
Generate JavaScript Client class MyRpcClient into file someclient.js:
.IP
.B
\&jsonrpcstub spec.json \-\-js\-client=MyRpcClient \-\-js\-client\-file=someclient.js
.B
.PP
Generate Python client class StubClient, which will be saved into stubclient.py
.IP
.B
\& jsonrpcstub spec.json \-\-js\-client=MyRpcClient \-\-js\-client\-file=someclient.js
\&jsonrpcstub spec.json \-\-py\-client=StubClient
.B
.PP

.SH EXIT STATUS
This command returns 0 if no error occurred. In any other case, it returns 1.
.SH SEE ALSO
Expand All @@ -88,21 +104,21 @@ No known bugs. Please report found bugs as an issue on github or send me an emai

Copyright (C) 2011\-2015 Peter Spiess\-Knafl

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in the
Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in the
Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.

.SH AUTHOR
Expand Down
167 changes: 167 additions & 0 deletions src/stubgenerator/client/pyclientstubgenerator.cpp
@@ -0,0 +1,167 @@
#include "pyclientstubgenerator.h"
#include <algorithm>

#define TEMPLATE_PYTHON_CLIENT_SIGCLASS "class <stubname>(client.Client):"

#define TEMPLATE_PYTHON_CLIENT_SIGCONSTRUCTOR "def __init__(self, connector, version='2.0'):\n super(<stubname>, self).__init__(connector, version)"

#define TEMPLATE_PYTHON_CLIENT_SIGMETHOD "def <methodname>(self<parameters>):"

#define TEMPLATE_NAMED_ASSIGNMENT "parameters[\'<paramname>\'] = <paramname>"
#define TEMPLATE_POSITION_ASSIGNMENT "parameters.append(<paramname>)"

#define TEMPLATE_METHODCALL "result = self.call_method(\'<name>\', parameters)"
#define TEMPLATE_NOTIFICATIONCALL "self.call_notification(\'<name>\', parameters)"

using namespace std;
using namespace jsonrpc;


PythonClientStubGenerator::PythonClientStubGenerator(const string &stubname, std::vector<Procedure> &procedures, std::ostream& outputstream) :
StubGenerator(stubname, procedures, outputstream)
{
}

PythonClientStubGenerator::PythonClientStubGenerator(const string &stubname, std::vector<Procedure> &procedures, const string filename) :
StubGenerator(stubname, procedures, filename)
{
}

void PythonClientStubGenerator::generateStub()
{
this->writeLine("#");
this->writeLine("# This file is generated by jsonrpcstub, DO NOT CHANGE IT MANUALLY!");
this->writeLine("#");
this->writeNewLine();
this->writeLine("#");
this->writeLine("# To use this client, jsonrpc_pyclient must be installed:");
this->writeLine("# pip install jsonrpc_pyclient");
this->writeLine("#");
this->writeNewLine();
this->writeLine("from jsonrpc_pyclient import client");
this->writeNewLine();

this->writeLine(replaceAll(TEMPLATE_PYTHON_CLIENT_SIGCLASS, "<stubname>", this->stubname));
this->increaseIndentation();

this->writeLine(replaceAll(TEMPLATE_PYTHON_CLIENT_SIGCONSTRUCTOR, "<stubname>", this->stubname));
this->writeNewLine();

for (unsigned int i=0; i < procedures.size(); i++)
{
this->generateMethod(procedures[i]);
}

this->decreaseIndentation();
this->writeNewLine();
}

void PythonClientStubGenerator::generateMethod(Procedure &proc)
{
string procsignature = TEMPLATE_PYTHON_CLIENT_SIGMETHOD;
replaceAll2(procsignature, "<methodname>", normalizeString(proc.GetProcedureName()));

// generate parameters string
string params = generateParameterDeclarationList(proc);
replaceAll2(procsignature, "<parameters>", params);

this->writeLine(procsignature);
this->increaseIndentation();

generateAssignments(proc);
this->writeNewLine();
generateProcCall(proc);
this->writeNewLine();

this->decreaseIndentation();
}

string PythonClientStubGenerator::generateParameterDeclarationList(Procedure &proc)
{
stringstream param_string;
parameterNameList_t list = proc.GetParameters();

for (parameterNameList_t::iterator it = list.begin(); it != list.end(); ++it)
{
param_string << ", ";
param_string << it->first;
}

return param_string.str();
}

void PythonClientStubGenerator::generateAssignments(Procedure &proc)
{
string assignment;
parameterNameList_t list = proc.GetParameters();
if(list.size() > 0)
{
parameterDeclaration_t declType = proc.GetParameterDeclarationType();
if (proc.GetParameterDeclarationType() == PARAMS_BY_NAME)
{
this->writeLine("parameters = {}");
}
else if(proc.GetParameterDeclarationType() == PARAMS_BY_POSITION)
{
this->writeLine("parameters = []");
}

for (parameterNameList_t::iterator it = list.begin(); it != list.end(); ++it)
{

if(declType == PARAMS_BY_NAME)
{
assignment = TEMPLATE_NAMED_ASSIGNMENT;
}
else
{
assignment = TEMPLATE_POSITION_ASSIGNMENT;
}
replaceAll2(assignment, "<paramname>", it->first);
this->writeLine(assignment);
}
}
else
{
this->writeLine("parameters = None");
}

}

void PythonClientStubGenerator::generateProcCall(Procedure &proc)
{
string call;
if (proc.GetProcedureType() == RPC_METHOD)
{
call = TEMPLATE_METHODCALL;
this->writeLine(replaceAll(call, "<name>", proc.GetProcedureName()));
this->writeLine("return result");
}
else
{
call = TEMPLATE_NOTIFICATIONCALL;
replaceAll2(call, "<name>", proc.GetProcedureName());
this->writeLine(call);
}

}

string PythonClientStubGenerator::class2Filename(const string &classname)
{
string result = classname;
std::transform(result.begin(), result.end(), result.begin(), ::tolower);
return result + ".py";
}

string PythonClientStubGenerator::normalizeString(const string &text)
{
string result = text;
for(unsigned int i=0; i < text.length(); i++)
{
if (!((text[i] >= 'a' && text[i] <= 'z') || (text[i] >= 'A' && text[i] <= 'Z') || (text[i] >= '0' && text[i] <= '9') || text[i] == '_'))
{
result[i] = '_';
}
}
return result;
}
30 changes: 30 additions & 0 deletions src/stubgenerator/client/pyclientstubgenerator.h
@@ -0,0 +1,30 @@
#ifndef PYTHON_CLIENT_STUB_GENERATOR_H
#define PYTHON_CLIENT_STUB_GENERATOR_H

#include "../stubgenerator.h"

namespace jsonrpc
{
/**
* The stub client this class generates requires jsonrpc_pyclient
* to be installed from pypi.
*/
class PythonClientStubGenerator : public StubGenerator
{
public:


PythonClientStubGenerator(const std::string& stubname, std::vector<Procedure> &procedures, std::ostream& outputstream);
PythonClientStubGenerator(const std::string& stubname, std::vector<Procedure> &procedures, const std::string filename);

virtual void generateStub();

void generateMethod(Procedure& proc);
void generateAssignments(Procedure& proc);
void generateProcCall(Procedure &proc);
std::string generateParameterDeclarationList(Procedure &proc);
static std::string class2Filename(const std::string &classname);
static std::string normalizeString(const std::string &text);
};
}
#endif // PYTHON_CLIENT_STUB_GENERATOR_H
1 change: 1 addition & 0 deletions src/stubgenerator/stubgenerator.cpp
Expand Up @@ -18,6 +18,7 @@
#include "server/cppserverstubgenerator.h"
#include "client/cppclientstubgenerator.h"
#include "client/jsclientstubgenerator.h"
#include "client/pyclientstubgenerator.h"

using namespace std;
using namespace jsonrpc;
Expand Down
19 changes: 18 additions & 1 deletion src/stubgenerator/stubgeneratorfactory.cpp
Expand Up @@ -15,6 +15,7 @@
#include "helper/cpphelper.h"
#include "client/cppclientstubgenerator.h"
#include "client/jsclientstubgenerator.h"
#include "client/pyclientstubgenerator.h"
#include "server/cppserverstubgenerator.h"

using namespace jsonrpc;
Expand All @@ -32,10 +33,13 @@ bool StubGeneratorFactory::createStubGenerators(int argc, char **argv, vector<Pr
struct arg_str *cppclientfile = arg_str0(NULL, "cpp-client-file", "<filename.h>", "name of the C++ client stub file");
struct arg_str *jsclient = arg_str0(NULL, "js-client", "<classname>", "name of the JavaScript client stub class");
struct arg_str *jsclientfile = arg_str0(NULL, "js-client-file", "<filename.js>", "name of the JavaScript client stub file");
struct arg_str *pyclient = arg_str0(NULL, "py-client", "<classname>", "name of the Python client stub class");
struct arg_str *pyclientfile = arg_str0(NULL, "py-client-file", "<filename.py>", "name of the Python client stub file");



struct arg_end *end = arg_end(20);
void* argtable[] = {inputfile, help, version, verbose, cppserver, cppserverfile, cppclient, cppclientfile, jsclient, jsclientfile,end};
void* argtable[] = {inputfile, help, version, verbose, cppserver, cppserverfile, cppclient, cppclientfile, jsclient, jsclientfile, pyclient, pyclientfile, end};

if (arg_parse(argc,argv,argtable) > 0)
{
Expand Down Expand Up @@ -121,6 +125,19 @@ bool StubGeneratorFactory::createStubGenerators(int argc, char **argv, vector<Pr
fprintf(stdout, "Generating JavaScript Clientstub to: %s\n", filename.c_str());
stubgenerators.push_back(new JSClientStubGenerator(jsclient->sval[0], procedures, filename));
}

if (pyclient->count > 0)
{
string filename;
if (pyclientfile->count > 0)
filename = pyclientfile->sval[0];
else
filename = PythonClientStubGenerator::class2Filename(pyclient->sval[0]);

if (verbose->count > 0)
fprintf(stdout, "Generating Python Clientstub to: %s\n", filename.c_str());
stubgenerators.push_back(new PythonClientStubGenerator(pyclient->sval[0], procedures, filename));
}
}
catch (const JsonRpcException &ex)
{
Expand Down

0 comments on commit 1feb600

Please sign in to comment.