复现已有算法

This commit is contained in:
cloud
2026-07-14 15:43:18 +08:00
parent abebd2a683
commit 50b8111fd9
860 changed files with 182250 additions and 18 deletions
+89
View File
@@ -0,0 +1,89 @@
set(ctl_sources
cmd.cc
create.cc
create_controller.cc
create_filter.cc
create_model.cc
create_plugin.cc
create_project.cc
create_view.cc
help.cc
main.cc
press.cc
version.cc)
add_executable(_drogon_ctl
main.cc
cmd.cc
create.cc
create_view.cc)
target_link_libraries(_drogon_ctl ${PROJECT_NAME})
if (WIN32 AND BUILD_SHARED_LIBS)
set(DROGON_FILE $<TARGET_FILE:drogon>)
if (USE_SUBMODULE)
set(TRANTOR_FILE $<TARGET_FILE:trantor>)
else()
set(TRANTOR_FILE $<TARGET_FILE:Trantor::Trantor>)
endif()
add_custom_command(TARGET _drogon_ctl POST_BUILD
COMMAND ${CMAKE_COMMAND}
-DCTL_FILE=${DROGON_FILE}
-DINSTALL_BIN_DIR=$<TARGET_FILE_DIR:_drogon_ctl>
-P
${CMAKE_CURRENT_SOURCE_DIR}/CopyDlls.cmake)
add_custom_command(TARGET _drogon_ctl POST_BUILD
COMMAND ${CMAKE_COMMAND}
-DCTL_FILE=${TRANTOR_FILE}
-DINSTALL_BIN_DIR=$<TARGET_FILE_DIR:_drogon_ctl>
-P
${CMAKE_CURRENT_SOURCE_DIR}/CopyDlls.cmake)
endif()
file(GLOB SCP_LIST ${CMAKE_CURRENT_SOURCE_DIR}/templates/*.csp)
foreach(cspFile ${SCP_LIST})
message(STATUS "cspFile:" ${cspFile})
get_filename_component(classname ${cspFile} NAME_WE)
message(STATUS "view classname:" ${classname})
add_custom_command(OUTPUT ${classname}.h ${classname}.cc
COMMAND ${DROGON_CTL}
ARGS
create
view
${cspFile}
DEPENDS ${cspFile}
VERBATIM)
set(TEMPL_SRC ${TEMPL_SRC} ${classname}.cc)
endforeach()
add_executable(drogon_ctl ${ctl_sources} ${TEMPL_SRC})
target_link_libraries(drogon_ctl PRIVATE ${PROJECT_NAME})
target_include_directories(drogon_ctl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
add_dependencies(drogon_ctl _drogon_ctl)
if(WIN32)
target_link_libraries(drogon_ctl PRIVATE ws2_32 rpcrt4 iphlpapi)
endif(WIN32)
if(APPLE)
target_link_libraries(drogon_ctl PRIVATE resolv)
endif()
message(STATUS "bin:" ${INSTALL_BIN_DIR})
install(TARGETS _drogon_ctl RUNTIME DESTINATION ${INSTALL_BIN_DIR})
install(TARGETS drogon_ctl RUNTIME DESTINATION ${INSTALL_BIN_DIR})
if(WIN32)
set(CTL_FILE $<TARGET_FILE:drogon_ctl>)
add_custom_command(TARGET drogon_ctl POST_BUILD
COMMAND ${CMAKE_COMMAND}
-DCTL_FILE=${CTL_FILE}
-DINSTALL_BIN_DIR=${INSTALL_BIN_DIR}
-DRENAME_EXE=ON
-P
${CMAKE_CURRENT_SOURCE_DIR}/CopyDlls.cmake)
else(WIN32)
install(CODE "execute_process( \
COMMAND ${CMAKE_COMMAND} -E create_symlink \
./drogon_ctl \
./dg_ctl \
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/dg_ctl"
DESTINATION ${INSTALL_BIN_DIR})
endif(WIN32)
set(ctl_targets _drogon_ctl drogon_ctl)
set_property(TARGET ${ctl_targets} PROPERTY CXX_STANDARD ${DROGON_CXX_STANDARD})
set_property(TARGET ${ctl_targets} PROPERTY CXX_STANDARD_REQUIRED ON)
set_property(TARGET ${ctl_targets} PROPERTY CXX_EXTENSIONS OFF)
+44
View File
@@ -0,0 +1,44 @@
/**
*
* CommandHandler.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/DrObject.h>
#include <vector>
#include <string>
class CommandHandler : public virtual drogon::DrObjectBase
{
public:
virtual void handleCommand(std::vector<std::string> &parameters) = 0;
virtual bool isTopCommand()
{
return false;
}
virtual std::string script()
{
return "";
}
virtual std::string detail()
{
return "";
}
virtual ~CommandHandler()
{
}
};
+8
View File
@@ -0,0 +1,8 @@
make_directory("${INSTALL_BIN_DIR}")
get_filename_component(CTL_PATH ${CTL_FILE} DIRECTORY)
file(GLOB DLL_FILES ${CTL_PATH}/*.dll)
file(COPY ${DLL_FILES} DESTINATION ${INSTALL_BIN_DIR})
file(COPY ${CTL_FILE} DESTINATION ${INSTALL_BIN_DIR})
if (RENAME_EXE)
file(RENAME ${INSTALL_BIN_DIR}/drogon_ctl.exe ${INSTALL_BIN_DIR}/dg_ctl.exe)
endif()
+56
View File
@@ -0,0 +1,56 @@
/**
*
* cmd.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "cmd.h"
#include "CommandHandler.h"
#include <drogon/DrClassMap.h>
#include <memory>
using namespace drogon;
void exeCommand(std::vector<std::string> &parameters)
{
if (parameters.empty())
{
std::cout << "incomplete command!use help command to get usage!"
<< std::endl;
return;
}
std::string command = parameters[0];
std::string handlerName = std::string("drogon_ctl::").append(command);
parameters.erase(parameters.begin());
// new command handler to do cmd
auto obj = std::shared_ptr<DrObjectBase>(
drogon::DrClassMap::newObject(handlerName));
if (obj)
{
auto ctl = std::dynamic_pointer_cast<CommandHandler>(obj);
if (ctl)
{
ctl->handleCommand(parameters);
}
else
{
std::cout << "command not found!use help command to get usage!"
<< std::endl;
}
}
else
{
std::cout << "command error!use help command to get usage!"
<< std::endl;
}
}
+21
View File
@@ -0,0 +1,21 @@
/**
*
* cmd.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <vector>
#include <string>
#include <iostream>
#define ARGS_ERROR_STR "args error!use help command to get usage!"
void exeCommand(std::vector<std::string> &parameters);
+57
View File
@@ -0,0 +1,57 @@
/**
*
* @file create.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "create.h"
#include "cmd.h"
#include <drogon/DrClassMap.h>
#include <iostream>
#include <memory>
using namespace drogon_ctl;
std::string create::detail()
{
return "Use create command to create some source files of drogon webapp\n\n"
"Usage:drogon_ctl create <view|controller|filter|project|model> "
"[-options] <object name>\n\n"
"drogon_ctl create view <csp file name> [-o <output path>] [-n "
"<namespace>] [--path-to-namespace] //create HttpView source files "
"from csp files, namespace is prefixed of path-to-namespace\n\n"
"drogon_ctl create controller [-s] <[namespace::]class_name> //"
"create HttpSimpleController source files\n\n"
"drogon_ctl create controller -h <[namespace::]class_name> //"
"create HttpController source files\n\n"
"drogon_ctl create controller -w <[namespace::]class_name> //"
"create WebSocketController source files\n\n"
"drogon_ctl create controller -r <[namespace::]class_name> "
"[--resource=...]//"
"create restful controller source files\n\n"
"drogon_ctl create filter <[namespace::]class_name> //"
"create a filter named class_name\n\n"
"drogon_ctl create plugin <[namespace::]class_name> //"
"create a plugin named class_name\n\n"
"drogon_ctl create project <project_name> //"
"create a project named project_name\n\n"
"drogon_ctl create model <model_path> [-o <output path>] "
"[--table=<table_name>] [-f]//"
"create model classes in model_path\n";
}
void create::handleCommand(std::vector<std::string> &parameters)
{
// std::cout<<"create!"<<std::endl;
auto createObjName = parameters[0];
createObjName = std::string("create_") + createObjName;
parameters[0] = createObjName;
exeCommand(parameters);
}
+41
View File
@@ -0,0 +1,41 @@
/**
*
* create.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/DrObject.h>
#include "CommandHandler.h"
using namespace drogon;
namespace drogon_ctl
{
class create : public DrObject<create>, public CommandHandler
{
public:
void handleCommand(std::vector<std::string> &parameters) override;
std::string script() override
{
return "create some source files(Use 'drogon_ctl help create' for more "
"information)";
}
bool isTopCommand() override
{
return true;
}
std::string detail() override;
};
} // namespace drogon_ctl
+471
View File
@@ -0,0 +1,471 @@
/**
*
* create_controller.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "create_controller.h"
#include "cmd.h"
#include <drogon/DrTemplateBase.h>
#include <drogon/utils/Utilities.h>
#include <iostream>
#include <fstream>
#include <regex>
using namespace drogon_ctl;
void create_controller::handleCommand(std::vector<std::string> &parameters)
{
// std::cout<<"create!"<<std::endl;
ControllerType type = Simple;
for (auto iter = parameters.begin(); iter != parameters.end(); ++iter)
{
if ((*iter)[0] == '-')
{
if (*iter == "-s" || (*iter == "--simple"))
{
parameters.erase(iter);
break;
}
else if (*iter == "-a" || *iter == "-h" || *iter == "--http")
{
type = Http;
parameters.erase(iter);
break;
}
else if (*iter == "-w" || *iter == "--websocket")
{
type = WebSocket;
parameters.erase(iter);
break;
}
else if (*iter == "-r" || *iter == "--restful")
{
type = Restful;
parameters.erase(iter);
break;
}
else
{
std::cout << ARGS_ERROR_STR << std::endl;
return;
}
}
}
if (type != Restful)
createController(parameters, type);
else
{
std::string resource;
for (auto iter = parameters.begin(); iter != parameters.end(); ++iter)
{
if ((*iter).find("--resource=") == 0)
{
resource = (*iter).substr(strlen("--resource="));
parameters.erase(iter);
break;
}
if ((*iter)[0] == '-')
{
std::cerr << "Error parameter for '" << (*iter) << "'"
<< std::endl;
exit(1);
}
}
if (parameters.size() > 1)
{
std::cerr << "Too many parameters" << std::endl;
exit(1);
}
auto className = parameters[0];
createARestfulController(className, resource);
}
}
void create_controller::newSimpleControllerHeaderFile(
std::ofstream &file,
const std::string &className)
{
file << "#pragma once\n";
file << "\n";
file << "#include <drogon/HttpSimpleController.h>\n";
file << "\n";
file << "using namespace drogon;\n";
file << "\n";
std::string class_name = className;
std::string namepace_path = "/";
auto pos = class_name.find("::");
size_t namespaceCount = 0;
while (pos != std::string::npos)
{
++namespaceCount;
auto namespaceName = class_name.substr(0, pos);
class_name = class_name.substr(pos + 2);
file << "namespace " << namespaceName << "\n";
namepace_path.append(namespaceName).append("/");
file << "{\n";
pos = class_name.find("::");
}
file << "class " << class_name << " : public drogon::HttpSimpleController<"
<< class_name << ">\n";
file << "{\n";
file << " public:\n";
file << " void asyncHandleHttpRequest(const HttpRequestPtr& "
"req, std::function<void (const HttpResponsePtr &)> &&callback) "
"override;\n";
file << " PATH_LIST_BEGIN\n";
file << " // list path definitions here;\n";
file << " "
"// PATH_ADD(\"/"
"path\", \"filter1\", \"filter2\", HttpMethod1, HttpMethod2...);\n";
file << " PATH_LIST_END\n";
file << "};\n";
while (namespaceCount > 0)
{
--namespaceCount;
file << "}\n";
}
}
void create_controller::newSimpleControllerSourceFile(
std::ofstream &file,
const std::string &className,
const std::string &filename)
{
file << "#include \"" << filename << ".h\"\n";
file << "\n";
auto pos = className.rfind("::");
auto class_name = className;
if (pos != std::string::npos)
{
auto namespacename = className.substr(0, pos);
file << "using namespace " << namespacename << ";\n";
file << "\n";
class_name = className.substr(pos + 2);
}
file << "void " << class_name
<< "::asyncHandleHttpRequest(const HttpRequestPtr& req, "
"std::function<void (const HttpResponsePtr &)> &&callback)\n";
file << "{\n";
file << " // write your application logic here\n";
file << "}\n";
}
void create_controller::newWebsockControllerHeaderFile(
std::ofstream &file,
const std::string &className)
{
file << "#pragma once\n";
file << "\n";
file << "#include <drogon/WebSocketController.h>\n";
file << "\n";
file << "using namespace drogon;\n";
file << "\n";
std::string class_name = className;
std::string namepace_path = "/";
auto pos = class_name.find("::");
size_t namespaceCount = 0;
while (pos != std::string::npos)
{
++namespaceCount;
auto namespaceName = class_name.substr(0, pos);
class_name = class_name.substr(pos + 2);
file << "namespace " << namespaceName << "\n";
namepace_path.append(namespaceName).append("/");
file << "{\n";
pos = class_name.find("::");
}
file << "class " << class_name << " : public drogon::WebSocketController<"
<< class_name << ">\n";
file << "{\n";
file << " public:\n";
file << " void handleNewMessage(const WebSocketConnectionPtr&,\n";
file << " std::string &&,\n";
file << " const WebSocketMessageType &) "
"override;\n";
file << " void handleNewConnection(const HttpRequestPtr &,\n";
file << " const "
"WebSocketConnectionPtr&) override;\n";
file << " void handleConnectionClosed(const "
"WebSocketConnectionPtr&) override;\n";
file << " WS_PATH_LIST_BEGIN\n";
file << " // list path definitions here;\n";
file << " // WS_PATH_ADD(\"/path\", \"filter1\", \"filter2\", ...);\n";
file << " WS_PATH_LIST_END\n";
file << "};\n";
while (namespaceCount > 0)
{
--namespaceCount;
file << "}\n";
}
}
void create_controller::newWebsockControllerSourceFile(
std::ofstream &file,
const std::string &className,
const std::string &filename)
{
file << "#include \"" << filename << ".h\"\n";
file << "\n";
auto pos = className.rfind("::");
auto class_name = className;
if (pos != std::string::npos)
{
auto namespacename = className.substr(0, pos);
file << "using namespace " << namespacename << ";\n";
file << "\n";
class_name = className.substr(pos + 2);
}
file << "void " << class_name
<< "::handleNewMessage(const WebSocketConnectionPtr& wsConnPtr, "
"std::string &&message, const WebSocketMessageType &type)\n";
file << "{\n";
file << " // write your application logic here\n";
file << "}\n";
file << "\n";
file << "void " << class_name
<< "::handleNewConnection(const HttpRequestPtr &req, const "
"WebSocketConnectionPtr& wsConnPtr)\n";
file << "{\n";
file << " // write your application logic here\n";
file << "}\n";
file << "\n";
file << "void " << class_name
<< "::handleConnectionClosed(const WebSocketConnectionPtr& "
"wsConnPtr)\n";
file << "{\n";
file << " // write your application logic here\n";
file << "}\n";
}
void create_controller::newHttpControllerHeaderFile(
std::ofstream &file,
const std::string &className)
{
file << "#pragma once\n";
file << "\n";
file << "#include <drogon/HttpController.h>\n";
file << "\n";
file << "using namespace drogon;\n";
file << "\n";
std::string class_name = className;
std::string namepace_path = "/";
auto pos = class_name.find("::");
size_t namespaceCount = 0;
while (pos != std::string::npos)
{
++namespaceCount;
auto namespaceName = class_name.substr(0, pos);
class_name = class_name.substr(pos + 2);
file << "namespace " << namespaceName << "\n";
namepace_path.append(namespaceName).append("/");
file << "{\n";
pos = class_name.find("::");
}
file << "class " << class_name << " : public drogon::HttpController<"
<< class_name << ">\n";
file << "{\n";
file << " public:\n";
file << " METHOD_LIST_BEGIN\n";
file << " // use METHOD_ADD to add your custom processing function "
"here;\n";
file << " // METHOD_ADD(" << class_name
<< "::get, \"/{2}/{1}\", Get);"
" // path is "
<< namepace_path << class_name << "/{arg2}/{arg1}\n";
file << " // METHOD_ADD(" << class_name
<< "::your_method_name, \"/{1}/{2}/list\", Get);"
" // path is "
<< namepace_path << class_name << "/{arg1}/{arg2}/list\n";
file << " // ADD_METHOD_TO(" << class_name
<< "::your_method_name, \"/absolute/path/{1}/{2}/list\", Get);"
" // path is /absolute/path/{arg1}/{arg2}/list\n";
file << "\n";
file << " METHOD_LIST_END\n";
file << " // your declaration of processing function maybe like this:\n";
file << " // void get(const HttpRequestPtr& req, "
"std::function<void (const HttpResponsePtr &)> &&callback, int "
"p1, std::string p2);\n";
file << " // void your_method_name(const HttpRequestPtr& req, "
"std::function<void (const HttpResponsePtr &)> &&callback, double "
"p1, int p2) const;\n";
file << "};\n";
while (namespaceCount > 0)
{
--namespaceCount;
file << "}\n";
}
}
void create_controller::newHttpControllerSourceFile(
std::ofstream &file,
const std::string &className,
const std::string &filename)
{
file << "#include \"" << filename << ".h\"\n";
file << "\n";
auto pos = className.rfind("::");
auto class_name = className;
if (pos != std::string::npos)
{
auto namespacename = className.substr(0, pos);
file << "using namespace " << namespacename << ";\n";
file << "\n";
class_name = className.substr(pos + 2);
}
file << "// Add definition of your processing function here\n";
}
void create_controller::createController(std::vector<std::string> &httpClasses,
ControllerType type)
{
for (auto iter = httpClasses.begin(); iter != httpClasses.end(); ++iter)
{
if ((*iter)[0] == '-')
{
std::cout << ARGS_ERROR_STR << std::endl;
return;
}
}
for (auto const &className : httpClasses)
{
createController(className, type);
}
}
void create_controller::createController(const std::string &className,
ControllerType type)
{
std::regex regex("::");
std::string ctlName =
std::regex_replace(className, regex, std::string("_"));
std::string headFileName = ctlName + ".h";
std::string sourceFilename = ctlName + ".cc";
{
std::ifstream iHeadFile(headFileName.c_str(), std::ifstream::in);
std::ifstream iSourceFile(sourceFilename.c_str(), std::ifstream::in);
if (iHeadFile || iSourceFile)
{
std::cout << "The file you want to create already exists, "
"overwrite it(y/n)?"
<< std::endl;
auto in = getchar();
(void)getchar(); // get the return key
if (in != 'Y' && in != 'y')
{
std::cout << "Abort!" << std::endl;
exit(0);
}
}
}
std::ofstream oHeadFile(headFileName.c_str(), std::ofstream::out);
std::ofstream oSourceFile(sourceFilename.c_str(), std::ofstream::out);
if (!oHeadFile || !oSourceFile)
{
perror("");
exit(1);
}
if (type == Http)
{
std::cout << "Create a http controller: " << className << std::endl;
newHttpControllerHeaderFile(oHeadFile, className);
newHttpControllerSourceFile(oSourceFile, className, ctlName);
}
else if (type == Simple)
{
std::cout << "Create a http simple controller: " << className
<< std::endl;
newSimpleControllerHeaderFile(oHeadFile, className);
newSimpleControllerSourceFile(oSourceFile, className, ctlName);
}
else if (type == WebSocket)
{
std::cout << "Create a websocket controller: " << className
<< std::endl;
newWebsockControllerHeaderFile(oHeadFile, className);
newWebsockControllerSourceFile(oSourceFile, className, ctlName);
}
}
void create_controller::createARestfulController(const std::string &className,
const std::string &resource)
{
std::regex regex("::");
std::string ctlName =
std::regex_replace(className, regex, std::string("_"));
std::string headFileName = ctlName + ".h";
std::string sourceFilename = ctlName + ".cc";
{
std::ifstream iHeadFile(headFileName.c_str(), std::ifstream::in);
std::ifstream iSourceFile(sourceFilename.c_str(), std::ifstream::in);
if (iHeadFile || iSourceFile)
{
std::cout << "The file you want to create already exists, "
"overwrite it(y/n)?"
<< std::endl;
auto in = getchar();
(void)getchar(); // get the return key
if (in != 'Y' && in != 'y')
{
std::cout << "Abort!" << std::endl;
exit(0);
}
}
}
std::ofstream oHeadFile(headFileName.c_str(), std::ofstream::out);
std::ofstream oSourceFile(sourceFilename.c_str(), std::ofstream::out);
if (!oHeadFile || !oSourceFile)
{
perror("");
exit(1);
}
auto v = utils::splitString(className, "::");
drogon::DrTemplateData data;
data.insert("className", v[v.size() - 1]);
v.pop_back();
data.insert("namespaceVector", v);
data.insert("resource", resource);
data.insert("fileName", ctlName);
if (resource.empty())
{
data.insert("ctlCommand",
std::string("drogon_ctl create controller -r ") +
className);
}
else
{
data.insert("ctlCommand",
std::string("drogon_ctl create controller -r ") +
className + " --resource=" + resource);
}
try
{
auto templ = DrTemplateBase::newTemplate("restful_controller_h.csp");
oHeadFile << templ->genText(data);
templ = DrTemplateBase::newTemplate("restful_controller_cc.csp");
oSourceFile << templ->genText(data);
}
catch (const std::exception &err)
{
std::cerr << err.what() << std::endl;
exit(1);
}
std::cout << "Create a http restful API controller: " << className
<< std::endl;
std::cout << "File name: " << ctlName << ".h and " << ctlName << ".cc"
<< std::endl;
}
+68
View File
@@ -0,0 +1,68 @@
/**
*
* create_controller.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/DrObject.h>
#include <drogon/DrTemplateBase.h>
#include "CommandHandler.h"
using namespace drogon;
namespace drogon_ctl
{
class create_controller : public DrObject<create_controller>,
public CommandHandler
{
public:
void handleCommand(std::vector<std::string> &parameters) override;
std::string script() override
{
return "create controller files";
}
protected:
enum ControllerType
{
Simple = 0,
Http,
WebSocket,
Restful
};
void createController(std::vector<std::string> &httpClasses,
ControllerType type);
void createController(const std::string &className, ControllerType type);
void createARestfulController(const std::string &className,
const std::string &resource);
void newSimpleControllerHeaderFile(std::ofstream &file,
const std::string &className);
void newSimpleControllerSourceFile(std::ofstream &file,
const std::string &className,
const std::string &filename);
void newWebsockControllerHeaderFile(std::ofstream &file,
const std::string &className);
void newWebsockControllerSourceFile(std::ofstream &file,
const std::string &className,
const std::string &filename);
void newHttpControllerHeaderFile(std::ofstream &file,
const std::string &className);
void newHttpControllerSourceFile(std::ofstream &file,
const std::string &className,
const std::string &filename);
};
} // namespace drogon_ctl
+118
View File
@@ -0,0 +1,118 @@
/**
*
* create_filter.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "create_filter.h"
#include <drogon/DrTemplateBase.h>
#include <drogon/utils/Utilities.h>
#include <string>
#include <iostream>
#ifndef _WIN32
#include <unistd.h>
#endif
#include <fstream>
#include <regex>
#include <sys/stat.h>
#include <sys/types.h>
using namespace drogon_ctl;
static void createFilterHeaderFile(std::ofstream &file,
const std::string &className,
const std::string &fileName)
{
auto templ = drogon::DrTemplateBase::newTemplate("filter_h");
HttpViewData data;
if (className.find("::") != std::string::npos)
{
auto namespaceVector = utils::splitString(className, "::");
data.insert("className", namespaceVector.back());
namespaceVector.pop_back();
data.insert("namespaceVector", namespaceVector);
}
else
{
data.insert("className", className);
}
data.insert("filename", fileName);
file << templ->genText(data);
}
static void createFilterSourceFile(std::ofstream &file,
const std::string &className,
const std::string &fileName)
{
auto templ = drogon::DrTemplateBase::newTemplate("filter_cc");
HttpViewData data;
if (className.find("::") != std::string::npos)
{
auto pos = className.rfind("::");
data.insert("namespaceString", className.substr(0, pos));
data.insert("className", className.substr(pos + 2));
}
else
{
data.insert("className", className);
}
data.insert("filename", fileName);
file << templ->genText(data);
}
void create_filter::handleCommand(std::vector<std::string> &parameters)
{
if (parameters.size() < 1)
{
std::cout << "Invalid parameters!" << std::endl;
}
for (auto className : parameters)
{
std::regex regex("::");
std::string fileName =
std::regex_replace(className, regex, std::string("_"));
std::string headFileName = fileName + ".h";
std::string sourceFilename = fileName + ".cc";
{
std::ifstream iHeadFile(headFileName.c_str(), std::ifstream::in);
std::ifstream iSourceFile(sourceFilename.c_str(),
std::ifstream::in);
if (iHeadFile || iSourceFile)
{
std::cout << "The file you want to create already exists, "
"overwrite it(y/n)?"
<< std::endl;
auto in = getchar();
(void)getchar(); // get the return key
if (in != 'Y' && in != 'y')
{
std::cout << "Abort!" << std::endl;
exit(0);
}
}
}
std::ofstream oHeadFile(headFileName.c_str(), std::ofstream::out);
std::ofstream oSourceFile(sourceFilename.c_str(), std::ofstream::out);
if (!oHeadFile || !oSourceFile)
{
perror("");
exit(1);
}
std::cout << "create a http filter:" << className << std::endl;
createFilterHeaderFile(oHeadFile, className, fileName);
createFilterSourceFile(oSourceFile, className, fileName);
}
}
+36
View File
@@ -0,0 +1,36 @@
/**
*
* create_filter.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/DrObject.h>
#include "CommandHandler.h"
using namespace drogon;
namespace drogon_ctl
{
class create_filter : public DrObject<create_filter>, public CommandHandler
{
public:
void handleCommand(std::vector<std::string> &parameters) override;
std::string script() override
{
return "create filter class files";
}
protected:
std::string outputPath_{"."};
};
} // namespace drogon_ctl
File diff suppressed because it is too large Load Diff
+434
View File
@@ -0,0 +1,434 @@
/**
*
* create_model.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/config.h>
#include <drogon/DrTemplateBase.h>
#include <json/json.h>
#include <drogon/orm/DbClient.h>
using namespace drogon::orm;
#include <drogon/DrObject.h>
#include "CommandHandler.h"
#include <string>
#include <algorithm>
using namespace drogon;
namespace drogon_ctl
{
struct ColumnInfo
{
std::string colName_;
std::string colValName_;
std::string colTypeName_;
std::string colType_;
std::string colDatabaseType_;
std::string dbType_;
ssize_t colLength_{0};
size_t index_{0};
bool isAutoVal_{false};
bool isPrimaryKey_{false};
bool notNull_{false};
bool hasDefaultVal_{false};
};
inline std::string nameTransform(const std::string &origName, bool isType)
{
auto str = origName;
std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) {
return tolower(c);
});
std::string::size_type startPos = 0;
std::string::size_type pos;
std::string ret;
do
{
pos = str.find("_", startPos);
if (pos == std::string::npos)
{
pos = str.find(".", startPos);
}
if (pos != std::string::npos)
ret += str.substr(startPos, pos - startPos);
else
{
ret += str.substr(startPos);
break;
}
while (str[pos] == '_' || str[pos] == '.')
++pos;
if (str[pos] >= 'a' && str[pos] <= 'z')
str[pos] += ('A' - 'a');
startPos = pos;
} while (1);
if (isType && ret[0] >= 'a' && ret[0] <= 'z')
ret[0] += ('A' - 'a');
return ret;
}
std::string escapeIdentifier(const std::string &identifier,
const std::string &rdbms);
class PivotTable
{
public:
PivotTable() = default;
PivotTable(const Json::Value &json)
: tableName_(json.get("table_name", "").asString())
{
if (tableName_.empty())
{
throw std::runtime_error("table_name can't be empty");
}
originalKey_ = json.get("original_key", "").asString();
if (originalKey_.empty())
{
throw std::runtime_error("original_key can't be empty");
}
targetKey_ = json.get("target_key", "").asString();
if (targetKey_.empty())
{
throw std::runtime_error("target_key can't be empty");
}
}
PivotTable reverse() const
{
PivotTable pivot;
pivot.tableName_ = tableName_;
pivot.originalKey_ = targetKey_;
pivot.targetKey_ = originalKey_;
return pivot;
}
const std::string &tableName() const
{
return tableName_;
}
const std::string &originalKey() const
{
return originalKey_;
}
const std::string &targetKey() const
{
return targetKey_;
}
private:
std::string tableName_;
std::string originalKey_;
std::string targetKey_;
};
class ConvertMethod
{
public:
ConvertMethod(const Json::Value &convert)
{
tableName_ = convert.get("table", "*").asString();
colName_ = convert.get("column", "*").asString();
auto method = convert["method"];
if (method.isNull())
{
throw std::runtime_error("method - object is missing.");
} // endif
if (!method.isObject())
{
throw std::runtime_error("method is not an object.");
} // endif
methodBeforeDbWrite_ = method.get("before_db_write", "").asString();
methodAfterDbRead_ = method.get("after_db_read", "").asString();
auto includeFiles = convert["includes"];
if (includeFiles.isNull())
{
return;
} // endif
if (!includeFiles.isArray())
{
throw std::runtime_error("includes must be an array");
} // endif
for (auto &i : includeFiles)
{
includeFiles_.push_back(i.asString());
} // for
}
ConvertMethod() = default;
bool shouldConvert(const std::string &tableName,
const std::string &colName) const;
const std::string &tableName() const
{
return tableName_;
}
const std::string &colName() const
{
return colName_;
}
const std::string &methodBeforeDbWrite() const
{
return methodBeforeDbWrite_;
}
const std::string &methodAfterDbRead() const
{
return methodAfterDbRead_;
}
const std::vector<std::string> &includeFiles() const
{
return includeFiles_;
}
private:
std::string tableName_{"*"};
std::string colName_{"*"};
std::string methodBeforeDbWrite_;
std::string methodAfterDbRead_;
std::vector<std::string> includeFiles_;
};
class Relationship
{
public:
enum class Type
{
HasOne,
HasMany,
ManyToMany
};
Relationship(const Json::Value &relationship)
{
auto type = relationship.get("type", "has one").asString();
if (type == "has one")
{
type_ = Relationship::Type::HasOne;
}
else if (type == "has many")
{
type_ = Relationship::Type::HasMany;
}
else if (type == "many to many")
{
type_ = Relationship::Type::ManyToMany;
}
else
{
char message[128];
snprintf(message,
sizeof(message),
"Invalid relationship type: %s",
type.data());
throw std::runtime_error(message);
}
originalTableName_ =
relationship.get("original_table_name", "").asString();
if (originalTableName_.empty())
{
throw std::runtime_error("original_table_name can't be empty");
}
originalKey_ = relationship.get("original_key", "").asString();
if (originalKey_.empty())
{
throw std::runtime_error("original_key can't be empty");
}
originalTableAlias_ =
relationship.get("original_table_alias", "").asString();
targetTableName_ = relationship.get("target_table_name", "").asString();
if (targetTableName_.empty())
{
throw std::runtime_error("target_table_name can't be empty");
}
targetKey_ = relationship.get("target_key", "").asString();
if (targetKey_.empty())
{
throw std::runtime_error("target_key can't be empty");
}
targetTableAlias_ =
relationship.get("target_table_alias", "").asString();
enableReverse_ = relationship.get("enable_reverse", false).asBool();
if (type_ == Type::ManyToMany)
{
auto &pivot = relationship["pivot_table"];
if (pivot.isNull())
{
throw std::runtime_error(
"ManyToMany relationship needs a pivot table");
}
pivotTable_ = PivotTable(pivot);
}
}
Relationship() = default;
Relationship reverse() const
{
Relationship r;
if (type_ == Type::HasMany)
{
r.type_ = Type::HasOne;
}
else
{
r.type_ = type_;
}
r.originalTableName_ = targetTableName_;
r.originalTableAlias_ = targetTableAlias_;
r.originalKey_ = targetKey_;
r.targetTableName_ = originalTableName_;
r.targetTableAlias_ = originalTableAlias_;
r.targetKey_ = originalKey_;
r.enableReverse_ = enableReverse_;
r.pivotTable_ = pivotTable_.reverse();
return r;
}
Type type() const
{
return type_;
}
bool enableReverse() const
{
return enableReverse_;
}
const std::string &originalTableName() const
{
return originalTableName_;
}
const std::string &originalTableAlias() const
{
return originalTableAlias_;
}
const std::string &originalKey() const
{
return originalKey_;
}
const std::string &targetTableName() const
{
return targetTableName_;
}
const std::string &targetTableAlias() const
{
return targetTableAlias_;
}
const std::string &targetKey() const
{
return targetKey_;
}
const PivotTable &pivotTable() const
{
return pivotTable_;
}
private:
Type type_{Type::HasOne};
std::string originalTableName_;
std::string originalTableAlias_;
std::string targetTableName_;
std::string targetTableAlias_;
std::string originalKey_;
std::string targetKey_;
bool enableReverse_{false};
PivotTable pivotTable_;
};
class create_model : public DrObject<create_model>, public CommandHandler
{
public:
void handleCommand(std::vector<std::string> &parameters) override;
std::string script() override
{
return "create Model classes files";
}
protected:
void createModel(const std::string &path,
const std::string &singleModelName);
void createModel(const std::string &path,
const Json::Value &config,
const std::string &singleModelName);
#if USE_POSTGRESQL
void createModelClassFromPG(
const std::string &path,
const DbClientPtr &client,
const std::string &tableName,
const std::string &schema,
const Json::Value &restfulApiConfig,
const std::vector<Relationship> &relationships,
const std::vector<ConvertMethod> &convertMethods);
void createModelFromPG(
const std::string &path,
const DbClientPtr &client,
const std::string &schema,
const Json::Value &restfulApiConfig,
std::map<std::string, std::vector<Relationship>> &relationships,
std::map<std::string, std::vector<ConvertMethod>> &convertMethods);
#endif
#if USE_MYSQL
void createModelClassFromMysql(
const std::string &path,
const DbClientPtr &client,
const std::string &tableName,
const Json::Value &restfulApiConfig,
const std::vector<Relationship> &relationships,
const std::vector<ConvertMethod> &convertMethods);
void createModelFromMysql(
const std::string &path,
const DbClientPtr &client,
const Json::Value &restfulApiConfig,
std::map<std::string, std::vector<Relationship>> &relationships,
std::map<std::string, std::vector<ConvertMethod>> &convertMethods);
#endif
#if USE_SQLITE3
void createModelClassFromSqlite3(
const std::string &path,
const DbClientPtr &client,
const std::string &tableName,
const Json::Value &restfulApiConfig,
const std::vector<Relationship> &relationships,
const std::vector<ConvertMethod> &convertMethod);
void createModelFromSqlite3(
const std::string &path,
const DbClientPtr &client,
const Json::Value &restfulApiConfig,
std::map<std::string, std::vector<Relationship>> &relationships,
std::map<std::string, std::vector<ConvertMethod>> &convertMethod);
#endif
void createRestfulAPIController(const DrTemplateData &tableInfo,
const Json::Value &restfulApiConfig);
std::string dbname_;
bool forceOverwrite_{false};
std::string outputPath_;
};
} // namespace drogon_ctl
+118
View File
@@ -0,0 +1,118 @@
/**
*
* create_plugin.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "create_plugin.h"
#include <drogon/DrTemplateBase.h>
#include <drogon/utils/Utilities.h>
#include <string>
#include <iostream>
#ifndef _WIN32
#include <unistd.h>
#endif
#include <fstream>
#include <regex>
#include <sys/stat.h>
#include <sys/types.h>
using namespace drogon_ctl;
static void createPluginHeaderFile(std::ofstream &file,
const std::string &className,
const std::string &fileName)
{
auto templ = drogon::DrTemplateBase::newTemplate("plugin_h");
HttpViewData data;
if (className.find("::") != std::string::npos)
{
auto namespaceVector = utils::splitString(className, "::");
data.insert("className", namespaceVector.back());
namespaceVector.pop_back();
data.insert("namespaceVector", namespaceVector);
}
else
{
data.insert("className", className);
}
data.insert("filename", fileName);
file << templ->genText(data);
}
static void createPluginSourceFile(std::ofstream &file,
const std::string &className,
const std::string &fileName)
{
auto templ = drogon::DrTemplateBase::newTemplate("plugin_cc");
HttpViewData data;
if (className.find("::") != std::string::npos)
{
auto pos = className.rfind("::");
data.insert("namespaceString", className.substr(0, pos));
data.insert("className", className.substr(pos + 2));
}
else
{
data.insert("className", className);
}
data.insert("filename", fileName);
file << templ->genText(data);
}
void create_plugin::handleCommand(std::vector<std::string> &parameters)
{
if (parameters.size() < 1)
{
std::cout << "Invalid parameters!" << std::endl;
}
for (auto className : parameters)
{
std::regex regex("::");
std::string fileName =
std::regex_replace(className, regex, std::string("_"));
std::string headFileName = fileName + ".h";
std::string sourceFilename = fileName + ".cc";
{
std::ifstream iHeadFile(headFileName.c_str(), std::ifstream::in);
std::ifstream iSourceFile(sourceFilename.c_str(),
std::ifstream::in);
if (iHeadFile || iSourceFile)
{
std::cout << "The file you want to create already exists, "
"overwrite it(y/n)?"
<< std::endl;
auto in = getchar();
(void)getchar(); // get the return key
if (in != 'Y' && in != 'y')
{
std::cout << "Abort!" << std::endl;
exit(0);
}
}
}
std::ofstream oHeadFile(headFileName.c_str(), std::ofstream::out);
std::ofstream oSourceFile(sourceFilename.c_str(), std::ofstream::out);
if (!oHeadFile || !oSourceFile)
{
perror("");
exit(1);
}
std::cout << "create a plugin:" << className << std::endl;
createPluginHeaderFile(oHeadFile, className, fileName);
createPluginSourceFile(oSourceFile, className, fileName);
}
}
+36
View File
@@ -0,0 +1,36 @@
/**
*
* create_plugin.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/DrObject.h>
#include "CommandHandler.h"
using namespace drogon;
namespace drogon_ctl
{
class create_plugin : public DrObject<create_plugin>, public CommandHandler
{
public:
void handleCommand(std::vector<std::string> &parameters) override;
std::string script() override
{
return "create plugin class files";
}
protected:
std::string outputPath_{"."};
};
} // namespace drogon_ctl
+143
View File
@@ -0,0 +1,143 @@
/**
*
* create_project.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "create_project.h"
#include <drogon/DrTemplateBase.h>
#include <drogon/utils/Utilities.h>
#include <iostream>
#include <sys/stat.h>
#include <sys/types.h>
#ifndef _WIN32
#include <unistd.h>
#else
#include <io.h>
#include <direct.h>
#endif
#include <fstream>
using namespace drogon_ctl;
void create_project::handleCommand(std::vector<std::string> &parameters)
{
if (parameters.size() < 1)
{
std::cout << "please input project name" << std::endl;
exit(1);
}
auto pName = parameters[0];
createProject(pName);
}
static void newCmakeFile(std::ofstream &cmakeFile,
const std::string &projectName)
{
HttpViewData data;
data.insert("ProjectName", projectName);
auto templ = DrTemplateBase::newTemplate("cmake.csp");
cmakeFile << templ->genText(data);
}
static void newMainFile(std::ofstream &mainFile)
{
auto templ = DrTemplateBase::newTemplate("demoMain");
mainFile << templ->genText();
}
static void newGitIgFile(std::ofstream &gitFile)
{
auto templ = DrTemplateBase::newTemplate("gitignore.csp");
gitFile << templ->genText();
}
static void newConfigJsonFile(std::ofstream &configJsonFile)
{
auto templ = DrTemplateBase::newTemplate("config_json");
configJsonFile << templ->genText();
}
static void newConfigYamlFile(std::ofstream &configYamlFile)
{
auto templ = DrTemplateBase::newTemplate("config_yaml");
configYamlFile << templ->genText();
}
static void newModelConfigFile(std::ofstream &configFile)
{
auto templ = DrTemplateBase::newTemplate("model_json");
configFile << templ->genText();
}
static void newTestMainFile(std::ofstream &mainFile)
{
auto templ = DrTemplateBase::newTemplate("test_main");
mainFile << templ->genText();
}
static void newTestCmakeFile(std::ofstream &testCmakeFile,
const std::string &projectName)
{
HttpViewData data;
data.insert("ProjectName", projectName);
auto templ = DrTemplateBase::newTemplate("test_cmake");
testCmakeFile << templ->genText(data);
}
void create_project::createProject(const std::string &projectName)
{
#ifdef _WIN32
if (_access(projectName.data(), 0) == 0)
#else
if (access(projectName.data(), 0) == 0)
#endif
{
std::cerr
<< "The directory already exists, please use another project name!"
<< std::endl;
exit(1);
}
std::cout << "create a project named " << projectName << std::endl;
drogon::utils::createPath(projectName);
// 1.create CMakeLists.txt
#ifdef _WIN32
auto r = _chdir(projectName.data());
#else
auto r = chdir(projectName.data());
#endif
(void)(r);
std::ofstream cmakeFile("CMakeLists.txt", std::ofstream::out);
newCmakeFile(cmakeFile, projectName);
std::ofstream mainFile("main.cc", std::ofstream::out);
newMainFile(mainFile);
drogon::utils::createPath("views");
drogon::utils::createPath("controllers");
drogon::utils::createPath("filters");
drogon::utils::createPath("plugins");
drogon::utils::createPath("build");
drogon::utils::createPath("models");
drogon::utils::createPath("test");
std::ofstream gitFile(".gitignore", std::ofstream::out);
newGitIgFile(gitFile);
std::ofstream configJsonFile("config.json", std::ofstream::out);
newConfigJsonFile(configJsonFile);
std::ofstream configYamlFile("config.yaml", std::ofstream::out);
newConfigYamlFile(configYamlFile);
std::ofstream modelConfigFile("models/model.json", std::ofstream::out);
newModelConfigFile(modelConfigFile);
std::ofstream testMainFile("test/test_main.cc", std::ofstream::out);
newTestMainFile(testMainFile);
std::ofstream testCmakeFile("test/CMakeLists.txt", std::ofstream::out);
newTestCmakeFile(testCmakeFile, projectName);
}
+36
View File
@@ -0,0 +1,36 @@
/**
*
* create_project.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/DrObject.h>
#include "CommandHandler.h"
using namespace drogon;
namespace drogon_ctl
{
class create_project : public DrObject<create_project>, public CommandHandler
{
public:
void handleCommand(std::vector<std::string> &parameters) override;
std::string script() override
{
return "create a project";
}
protected:
std::string outputPath_{"."};
void createProject(const std::string &projectName);
};
} // namespace drogon_ctl
+554
View File
@@ -0,0 +1,554 @@
/**
*
* @file create_view.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "create_view.h"
#include "cmd.h"
#include <drogon/utils/Utilities.h>
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <regex>
static const std::string cxx_include = "<%inc";
static const std::string cxx_end = "%>";
static const std::string cxx_lang = "<%c++";
static const std::string cxx_view_data = "@@";
static const std::string cxx_output = "$$";
static const std::string cxx_val_start = "[[";
static const std::string cxx_val_end = "]]";
static const std::string sub_view_start = "<%view";
static const std::string sub_view_end = "%>";
using namespace drogon_ctl;
static std::string &replace_all(std::string &str,
const std::string &old_value,
const std::string &new_value)
{
std::string::size_type pos(0);
while (true)
{
// std::cout<<str<<endl;
// std::cout<<"pos="<<pos<<endl;
if ((pos = str.find(old_value, pos)) != std::string::npos)
{
str = str.replace(pos, old_value.length(), new_value);
pos += new_value.length() - old_value.length();
++pos;
}
else
break;
}
return str;
}
static void parseCxxLine(std::ofstream &oSrcFile,
const std::string &line,
const std::string &streamName,
const std::string &viewDataName)
{
if (line.length() > 0)
{
std::string tmp = line;
replace_all(tmp, cxx_output, streamName);
replace_all(tmp, cxx_view_data, viewDataName);
oSrcFile << tmp << "\n";
}
}
static void outputVal(std::ofstream &oSrcFile,
const std::string &streamName,
const std::string &viewDataName,
const std::string &keyName)
{
oSrcFile << "{\n";
oSrcFile << " auto & val=" << viewDataName << "[\"" << keyName
<< "\"];\n";
oSrcFile << " if(val.type()==typeid(const char *)){\n";
oSrcFile << " " << streamName
<< "<<*(std::any_cast<const char *>(&val));\n";
oSrcFile << " }else "
"if(val.type()==typeid(std::string)||val.type()==typeid(const "
"std::string)){\n";
oSrcFile << " " << streamName
<< "<<*(std::any_cast<const std::string>(&val));\n";
oSrcFile << " }\n";
oSrcFile << "}\n";
}
static void outputSubView(std::ofstream &oSrcFile,
const std::string &streamName,
const std::string &viewDataName,
const std::string &keyName)
{
oSrcFile << "{\n";
oSrcFile << " auto templ=DrTemplateBase::newTemplate(\"" << keyName
<< "\");\n";
oSrcFile << " if(templ){\n";
oSrcFile << " " << streamName << "<< templ->genText(" << viewDataName
<< ");\n";
oSrcFile << " }\n";
oSrcFile << "}\n";
}
static void parseLine(std::ofstream &oSrcFile,
std::string &line,
const std::string &streamName,
const std::string &viewDataName,
int &cxx_flag,
int returnFlag = 1)
{
std::string::size_type pos(0);
// std::cout<<line<<"("<<line.length()<<")\n";
if (line.length() > 0 && line[line.length() - 1] == '\r')
{
line.resize(line.length() - 1);
}
if (line.length() == 0)
{
// std::cout<<"blank line!"<<std::endl;
// std::cout<<streamName<<"<<\"\\n\";\n";
if (returnFlag && !cxx_flag)
oSrcFile << streamName << "<<\"\\n\";\n";
return;
}
if (cxx_flag == 0)
{
// find cxx lang begin
if ((pos = line.find(cxx_lang)) != std::string::npos)
{
std::string oldLine = line.substr(0, pos);
if (oldLine.length() > 0)
parseLine(
oSrcFile, oldLine, streamName, viewDataName, cxx_flag, 0);
std::string newLine = line.substr(pos + cxx_lang.length());
cxx_flag = 1;
if (newLine.length() > 0)
parseLine(oSrcFile,
newLine,
streamName,
viewDataName,
cxx_flag,
returnFlag);
}
else
{
if ((pos = line.find(cxx_val_start)) != std::string::npos)
{
std::string oldLine = line.substr(0, pos);
parseLine(
oSrcFile, oldLine, streamName, viewDataName, cxx_flag, 0);
std::string newLine = line.substr(pos + cxx_val_start.length());
if ((pos = newLine.find(cxx_val_end)) != std::string::npos)
{
std::string keyName = newLine.substr(0, pos);
auto iter = keyName.begin();
while (iter != keyName.end() && *iter == ' ')
++iter;
auto iterEnd = iter;
while (iterEnd != keyName.end() && *iterEnd != ' ')
++iterEnd;
keyName = std::string(iter, iterEnd);
outputVal(oSrcFile, streamName, viewDataName, keyName);
std::string tailLine =
newLine.substr(pos + cxx_val_end.length());
parseLine(oSrcFile,
tailLine,
streamName,
viewDataName,
cxx_flag,
returnFlag);
}
else
{
std::cerr << "format err!" << std::endl;
exit(1);
}
}
else if ((pos = line.find(sub_view_start)) != std::string::npos)
{
std::string oldLine = line.substr(0, pos);
parseLine(
oSrcFile, oldLine, streamName, viewDataName, cxx_flag, 0);
std::string newLine =
line.substr(pos + sub_view_start.length());
if ((pos = newLine.find(sub_view_end)) != std::string::npos)
{
std::string keyName = newLine.substr(0, pos);
auto iter = keyName.begin();
while (iter != keyName.end() && *iter == ' ')
++iter;
auto iterEnd = iter;
while (iterEnd != keyName.end() && *iterEnd != ' ')
++iterEnd;
keyName = std::string(iter, iterEnd);
outputSubView(oSrcFile, streamName, viewDataName, keyName);
std::string tailLine =
newLine.substr(pos + sub_view_end.length());
parseLine(oSrcFile,
tailLine,
streamName,
viewDataName,
cxx_flag,
returnFlag);
}
else
{
std::cerr << "format err!" << std::endl;
exit(1);
}
}
else
{
if (line.length() > 0)
{
replace_all(line, "\\", "\\\\");
replace_all(line, "\"", "\\\"");
oSrcFile << "\t" << streamName << " << \"" << line;
}
if (returnFlag)
oSrcFile << "\\n\";\n";
else
oSrcFile << "\";\n";
}
}
}
else
{
if ((pos = line.find(cxx_end)) != std::string::npos)
{
std::string newLine = line.substr(0, pos);
parseCxxLine(oSrcFile, newLine, streamName, viewDataName);
std::string oldLine = line.substr(pos + cxx_end.length());
cxx_flag = 0;
if (oldLine.length() > 0)
parseLine(oSrcFile,
oldLine,
streamName,
viewDataName,
cxx_flag,
returnFlag);
}
else
{
parseCxxLine(oSrcFile, line, streamName, viewDataName);
}
}
}
void create_view::handleCommand(std::vector<std::string> &parameters)
{
for (auto iter = parameters.begin(); iter != parameters.end();)
{
auto &file = *iter;
if (file == "-o" || file == "--output")
{
iter = parameters.erase(iter);
if (iter != parameters.end())
{
outputPath_ = *iter;
iter = parameters.erase(iter);
}
continue;
}
else if (file == "-n" || file == "--namespace")
{
iter = parameters.erase(iter);
if (iter != parameters.end())
{
namespaces_ = utils::splitString(*iter, "::");
iter = parameters.erase(iter);
}
continue;
}
else if (file == "--path-to-namespace")
{
iter = parameters.erase(iter);
pathToNamespaceFlag_ = true;
continue;
}
else if (file[0] == '-')
{
std::cout << ARGS_ERROR_STR << std::endl;
return;
}
++iter;
}
createViewFiles(parameters);
}
void create_view::createViewFiles(std::vector<std::string> &cspFileNames)
{
for (auto const &file : cspFileNames)
{
std::cout << "create view:" << file << std::endl;
if (createViewFile(file) != 0)
exit(1);
}
}
int create_view::createViewFile(const std::string &script_filename)
{
std::cout << "create HttpView Class file by " << script_filename
<< std::endl;
if (pathToNamespaceFlag_)
{
std::string::size_type pos1 = 0, pos2 = 0;
if (script_filename.length() >= 2 && script_filename[0] == '.' &&
(script_filename[1] == '/' || script_filename[1] == '\\'))
{
pos1 = pos2 = 2;
}
else if (script_filename.length() >= 1 &&
(script_filename[0] == '/' || script_filename[0] == '\\'))
{
pos1 = pos2 = 1;
}
while (pos2 < script_filename.length() - 1)
{
if (script_filename[pos2] == '/' || script_filename[pos2] == '\\')
{
if (pos2 > pos1)
{
namespaces_.push_back(
script_filename.substr(pos1, pos2 - pos1));
}
pos1 = ++pos2;
}
else
{
++pos2;
}
}
}
std::string npPrefix;
for (auto &np : namespaces_)
{
npPrefix += np;
npPrefix += "_";
}
std::ifstream infile(script_filename.c_str(), std::ifstream::in);
if (infile)
{
std::string::size_type pos = script_filename.rfind('.');
if (pos != std::string::npos)
{
std::string className = script_filename.substr(0, pos);
if ((pos = className.rfind('/')) != std::string::npos)
{
className = className.substr(pos + 1);
}
std::cout << "className=" << className << std::endl;
std::string headFileName =
outputPath_ + "/" + npPrefix + className + ".h";
std::string sourceFilename =
outputPath_ + "/" + npPrefix + className + ".cc";
std::ofstream oHeadFile(headFileName.c_str(), std::ofstream::out);
std::ofstream oSourceFile(sourceFilename.c_str(),
std::ofstream::out);
if (!oHeadFile || !oSourceFile)
{
std::cerr << "Can't open " << headFileName << " or "
<< sourceFilename << "\n";
return -1;
}
newViewHeaderFile(oHeadFile, className);
newViewSourceFile(oSourceFile, className, npPrefix, infile);
}
else
return -1;
}
else
{
std::cerr << "can't open file " << script_filename << std::endl;
return -1;
}
return 0;
}
void create_view::newViewHeaderFile(std::ofstream &file,
const std::string &className)
{
file << "//this file is generated by program automatically,don't modify "
"it!\n";
file << "#include <drogon/DrTemplate.h>\n";
for (auto &np : namespaces_)
{
file << "namespace " << np << "\n";
file << "{\n";
}
file << "class " << className << ":public drogon::DrTemplate<" << className
<< ">\n";
file << "{\npublic:\n\t" << className << "(){};\n\tvirtual ~" << className
<< "(){};\n\t"
"virtual std::string genText(const drogon::DrTemplateData &) "
"override;\n};\n";
for (std::size_t i = 0; i < namespaces_.size(); ++i)
{
file << "}\n";
}
}
void create_view::newViewSourceFile(std::ofstream &file,
const std::string &className,
const std::string &namespacePrefix,
std::ifstream &infile)
{
file << "//this file is generated by program(drogon_ctl) "
"automatically,don't modify it!\n";
file << "#include \"" << namespacePrefix << className << ".h\"\n";
file << "#include <drogon/utils/OStringStream.h>\n";
file << "#include <drogon/utils/Utilities.h>\n";
file << "#include <string>\n";
file << "#include <map>\n";
file << "#include <vector>\n";
file << "#include <set>\n";
file << "#include <iostream>\n";
file << "#include <unordered_map>\n";
file << "#include <unordered_set>\n";
file << "#include <algorithm>\n";
file << "#include <list>\n";
file << "#include <deque>\n";
file << "#include <queue>\n";
// Find layout tag
std::string layoutName;
std::regex layoutReg("<%layout[ \\t]+(((?!%\\}).)*[^ \\t])[ \\t]*%>");
for (std::string buffer; std::getline(infile, buffer);)
{
std::smatch results;
if (std::regex_search(buffer, results, layoutReg))
{
if (results.size() > 1)
{
layoutName = results[1].str();
break;
}
}
}
infile.clear();
infile.seekg(0, std::ifstream::beg);
bool import_flag{false};
for (std::string buffer; std::getline(infile, buffer);)
{
std::string::size_type pos(0);
if (!import_flag)
{
std::string lowerBuffer = buffer;
std::transform(lowerBuffer.begin(),
lowerBuffer.end(),
lowerBuffer.begin(),
[](unsigned char c) { return tolower(c); });
if ((pos = lowerBuffer.find(cxx_include)) != std::string::npos)
{
// std::cout<<"haha find it!"<<endl;
std::string newLine = buffer.substr(pos + cxx_include.length());
import_flag = true;
if ((pos = newLine.find(cxx_end)) != std::string::npos)
{
newLine = newLine.substr(0, pos);
file << newLine << "\n";
break;
}
else
{
file << newLine << "\n";
}
}
}
else
{
// std::cout<<buffer<<endl;
if ((pos = buffer.find(cxx_end)) != std::string::npos)
{
std::string newLine = buffer.substr(0, pos);
file << newLine << "\n";
break;
}
else
{
// std::cout<<"to source file"<<buffer<<endl;
file << buffer << "\n";
}
}
}
// std::cout<<"import_flag="<<import_flag<<std::endl;
if (!import_flag)
{
infile.clear();
infile.seekg(0, std::ifstream::beg);
}
if (!namespaces_.empty())
{
file << "using namespace ";
for (std::size_t i = 0; i < namespaces_.size(); ++i)
{
if (i != namespaces_.size() - 1)
{
file << namespaces_[i] << "::";
}
else
{
file << namespaces_[i] << ";";
}
}
file << "\n";
}
file << "using namespace drogon;\n";
std::string viewDataName = className + "_view_data";
// virtual std::string genText(const DrTemplateData &)
file << "std::string " << className << "::genText(const DrTemplateData& "
<< viewDataName << ")\n{\n";
// std::string bodyName=className+"_bodystr";
std::string streamName = className + "_tmp_stream";
// oSrcFile <<"\tstd::string "<<bodyName<<";\n";
file << "\tdrogon::OStringStream " << streamName << ";\n";
file << "\tstd::string layoutName{\"" << layoutName << "\"};\n";
int cxx_flag = 0;
for (std::string buffer; std::getline(infile, buffer);)
{
if (buffer.length() > 0)
{
std::smatch results;
if (std::regex_search(buffer, results, layoutReg))
{
if (results.size() > 1)
{
continue;
}
}
std::regex re("\\{%[ \\t]*(((?!%\\}).)*[^ \\t])[ \\t]*%\\}");
buffer = std::regex_replace(buffer, re, "<%c++$$$$<<$1;%>");
}
parseLine(file, buffer, streamName, viewDataName, cxx_flag);
}
file << "if(layoutName.empty())\n{\n";
file << "std::string ret{std::move(" << streamName << ".str())};\n";
file << "return ret;\n}else\n{\n";
file << "auto templ = DrTemplateBase::newTemplate(layoutName);\n";
file << "if(!templ) return \"\";\n";
file << "HttpViewData data = " << viewDataName << ";\n";
file << "auto str = std::move(" << streamName << ".str());\n";
file << "if(!str.empty() && str[str.length()-1] == '\\n') "
"str.resize(str.length()-1);\n";
file << "data[\"\"] = std::move(str);\n";
file << "return templ->genText(data);\n";
file << "}\n}\n";
}
+45
View File
@@ -0,0 +1,45 @@
/**
*
* @file create_view.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/DrObject.h>
#include "CommandHandler.h"
using namespace drogon;
namespace drogon_ctl
{
class create_view : public DrObject<create_view>, public CommandHandler
{
public:
void handleCommand(std::vector<std::string> &parameters) override;
std::string script() override
{
return "create view class files";
}
protected:
std::string outputPath_{"."};
std::vector<std::string> namespaces_;
bool pathToNamespaceFlag_{false};
void createViewFiles(std::vector<std::string> &cspFileNames);
int createViewFile(const std::string &script_filename);
void newViewHeaderFile(std::ofstream &file, const std::string &className);
void newViewSourceFile(std::ofstream &file,
const std::string &className,
const std::string &namespacePrefix,
std::ifstream &infile);
};
} // namespace drogon_ctl
+70
View File
@@ -0,0 +1,70 @@
/**
*
* help.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "help.h"
#include <drogon/DrClassMap.h>
#include <iostream>
#include <memory>
using namespace drogon_ctl;
void help::handleCommand(std::vector<std::string> &parameters)
{
if (parameters.size() == 0)
{
std::cout << "usage: drogon_ctl [-v | --version] [-h | --help] "
"<command> [<args>]"
<< std::endl;
std::cout << "commands list:" << std::endl;
for (auto &className : drogon::DrClassMap::getAllClassName())
{
auto classPtr = std::shared_ptr<DrObjectBase>(
drogon::DrClassMap::newObject(className));
if (classPtr)
{
auto cmdHdlPtr =
std::dynamic_pointer_cast<CommandHandler>(classPtr);
if (cmdHdlPtr)
{
if (!cmdHdlPtr->isTopCommand())
continue;
auto pos = className.rfind("::");
if (pos != std::string::npos)
{
className = className.substr(pos + 2);
}
while (className.length() < 24)
className.append(" ");
std::cout << className << cmdHdlPtr->script() << std::endl;
}
}
}
}
else
{
auto cmd = std::string("drogon_ctl::") + parameters[0];
auto classPtr =
std::shared_ptr<DrObjectBase>(drogon::DrClassMap::newObject(cmd));
if (classPtr)
{
auto cmdHdlPtr =
std::dynamic_pointer_cast<CommandHandler>(classPtr);
if (cmdHdlPtr)
{
if (cmdHdlPtr->isTopCommand())
std::cout << cmdHdlPtr->detail() << std::endl;
}
}
}
}
+38
View File
@@ -0,0 +1,38 @@
/**
*
* help.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/DrObject.h>
#include "CommandHandler.h"
using namespace drogon;
namespace drogon_ctl
{
class help : public DrObject<help>, public CommandHandler
{
public:
void handleCommand(std::vector<std::string> &parameters) override;
std::string script() override
{
return "display this message";
}
bool isTopCommand() override
{
return true;
}
};
} // namespace drogon_ctl
+49
View File
@@ -0,0 +1,49 @@
/**
*
* @file main.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "cmd.h"
#include <string>
#include <vector>
#include <iostream>
int main(int argc, char *argv[])
{
std::vector<std::string> args;
if (argc < 2)
{
args = {"help"};
exeCommand(args);
return 0;
}
for (int i = 1; i < argc; ++i)
{
args.push_back(argv[i]);
}
if (args.size() > 0)
{
auto &arg = args[0];
if (arg == "-h" || arg == "--help")
{
arg = "help";
}
else if (arg == "-v" || arg == "--version")
{
arg = "version";
}
}
exeCommand(args);
return 0;
}
+472
View File
@@ -0,0 +1,472 @@
/**
*
* press.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by the MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "press.h"
#include "cmd.h"
#include <drogon/DrClassMap.h>
#include <iostream>
#include <memory>
#include <iomanip>
#include <cstdlib>
#include <json/json.h>
#include <fstream>
#include <string>
#include <unordered_map>
#ifndef _WIN32
#include <unistd.h>
#endif
using namespace drogon_ctl;
std::string press::detail()
{
return "Use press command to do stress testing\n"
"Usage:drogon_ctl press <options> <url>\n"
" -n num number of requests(default : 1)\n"
" -t num number of threads(default : 1)\n"
" -c num concurrent connections(default : 1)\n"
" -k disable SSL certificate validation(default: enable)\n"
" -f customize http request json file(default: disenable)\n"
" -q no progress indication(default: show)\n\n"
"example: drogon_ctl press -n 10000 -c 100 -t 4 -q "
"http://localhost:8080/index.html -f ./http_request.json\n";
}
void outputErrorAndExit(const std::string_view &err)
{
std::cout << err << std::endl;
exit(1);
}
void press::handleCommand(std::vector<std::string> &parameters)
{
for (auto iter = parameters.begin(); iter != parameters.end(); iter++)
{
auto &param = *iter;
if (param.find("-n") == 0)
{
if (param == "-n")
{
++iter;
if (iter == parameters.end())
{
outputErrorAndExit("No number of requests!");
}
auto &num = *iter;
try
{
numOfRequests_ = std::stoll(num);
}
catch (...)
{
outputErrorAndExit("Invalid number of requests!");
}
continue;
}
else
{
auto num = param.substr(2);
try
{
numOfRequests_ = std::stoll(num);
}
catch (...)
{
outputErrorAndExit("Invalid number of requests!");
}
continue;
}
}
else if (param.find("-t") == 0)
{
if (param == "-t")
{
++iter;
if (iter == parameters.end())
{
outputErrorAndExit("No number of threads!");
}
auto &num = *iter;
try
{
numOfThreads_ = std::stoll(num);
}
catch (...)
{
outputErrorAndExit("Invalid number of threads!");
}
continue;
}
else
{
auto num = param.substr(2);
try
{
numOfThreads_ = std::stoll(num);
}
catch (...)
{
outputErrorAndExit("Invalid number of threads!");
}
continue;
}
}
else if (param.find("-c") == 0)
{
if (param == "-c")
{
++iter;
if (iter == parameters.end())
{
outputErrorAndExit("No number of connections!");
}
auto &num = *iter;
try
{
numOfConnections_ = std::stoll(num);
}
catch (...)
{
outputErrorAndExit("Invalid number of connections!");
}
continue;
}
else
{
auto num = param.substr(2);
try
{
numOfConnections_ = std::stoll(num);
}
catch (...)
{
outputErrorAndExit("Invalid number of connections!");
}
continue;
}
}
else if (param.find("-f") == 0)
{
if (param == "-f")
{
++iter;
if (iter == parameters.end())
{
outputErrorAndExit("No http request json file!");
}
httpRequestJsonFile_ = *iter;
continue;
}
else
{
httpRequestJsonFile_ = param.substr(2);
continue;
}
}
else if (param == "-k")
{
certValidation_ = false;
continue;
}
else if (param == "-q")
{
processIndication_ = false;
}
else if (param[0] != '-')
{
url_ = param;
}
}
// std::cout << "n=" << numOfRequests_ << std::endl;
// std::cout << "t=" << numOfThreads_ << std::endl;
// std::cout << "c=" << numOfConnections_ << std::endl;
// std::cout << "q=" << processIndication_ << std::endl;
// std::cout << "url=" << url_ << std::endl;
if (url_.empty() || url_.compare(0, 4, "http") != 0 ||
(url_.compare(4, 3, "://") != 0 && url_.compare(4, 4, "s://") != 0))
{
outputErrorAndExit("Invalid URL");
}
else
{
auto pos = url_.find("://");
auto posOfPath = url_.find('/', pos + 3);
if (posOfPath == std::string::npos)
{
host_ = url_;
path_ = "/";
}
else
{
host_ = url_.substr(0, posOfPath);
path_ = url_.substr(posOfPath);
}
}
/*
http_request.json
{
"method": "POST",
"header": {
"token": "e2e9d0fe-dd14-4eaf-8ac1-0997730a805d"
},
"body": {
"passwd": "123456",
"account": "10001"
}
}
*/
if (!httpRequestJsonFile_.empty())
{
Json::Value httpRequestJson;
std::ifstream httpRequestFile(httpRequestJsonFile_,
std::ifstream::binary);
if (!httpRequestFile.is_open())
{
outputErrorAndExit(std::string{"No "} + httpRequestJsonFile_);
}
httpRequestFile >> httpRequestJson;
if (!httpRequestJson.isMember("method"))
{
outputErrorAndExit("No contain method");
}
auto methodStr = httpRequestJson["method"].asString();
std::transform(methodStr.begin(),
methodStr.end(),
methodStr.begin(),
::toupper);
auto toHttpMethod = [&]() -> drogon::HttpMethod {
if (methodStr == "GET")
{
return drogon::HttpMethod::Get;
}
else if (methodStr == "POST")
{
return drogon::HttpMethod::Post;
}
else if (methodStr == "HEAD")
{
return drogon::HttpMethod::Head;
}
else if (methodStr == "PUT")
{
return drogon::HttpMethod::Put;
}
else if (methodStr == "DELETE")
{
return drogon::HttpMethod::Delete;
}
else if (methodStr == "OPTIONS")
{
return drogon::HttpMethod::Options;
}
else if (methodStr == "PATCH")
{
return drogon::HttpMethod::Patch;
}
else
{
outputErrorAndExit("invalid method");
}
return drogon::HttpMethod::Get;
};
std::unordered_map<std::string, std::string> header;
if (httpRequestJson.isMember("header"))
{
auto &jsonValue = httpRequestJson["header"];
for (const auto &key : jsonValue.getMemberNames())
{
if (jsonValue[key].isString())
{
header[key] = jsonValue[key].asString();
}
else
{
header[key] = jsonValue[key].toStyledString();
}
}
}
std::string body;
if (httpRequestJson.isMember("body"))
{
Json::FastWriter fastWriter;
body = fastWriter.write(httpRequestJson["body"]);
}
createHttpRequestFunc_ = [this,
method = toHttpMethod(),
body = std::move(body),
header =
std::move(header)]() -> HttpRequestPtr {
auto request = HttpRequest::newHttpRequest();
request->setPath(path_);
request->setMethod(method);
for (const auto &[field, val] : header)
request->addHeader(field, val);
if (!body.empty())
request->setBody(body);
return request;
};
}
// std::cout << "host=" << host_ << std::endl;
// std::cout << "path=" << path_ << std::endl;
doTesting();
}
void press::doTesting()
{
createRequestAndClients();
if (clients_.empty())
{
outputErrorAndExit("No connection!");
}
statistics_.startDate_ = trantor::Date::now();
for (auto &client : clients_)
{
sendRequest(client);
}
loopPool_->wait();
}
void press::createRequestAndClients()
{
loopPool_ = std::make_unique<trantor::EventLoopThreadPool>(numOfThreads_);
loopPool_->start();
for (size_t i = 0; i < numOfConnections_; ++i)
{
auto client = HttpClient::newHttpClient(host_,
loopPool_->getNextLoop(),
false,
certValidation_);
client->enableCookies();
clients_.push_back(client);
}
}
void press::sendRequest(const HttpClientPtr &client)
{
auto numOfRequest = statistics_.numOfRequestsSent_++;
if (numOfRequest >= numOfRequests_)
{
return;
}
HttpRequestPtr request;
if (createHttpRequestFunc_)
{
request = createHttpRequestFunc_();
}
else
{
request = HttpRequest::newHttpRequest();
request->setPath(path_);
request->setMethod(Get);
}
// std::cout << "send!" << std::endl;
client->sendRequest(
request,
[this, client, request](ReqResult r, const HttpResponsePtr &resp) {
size_t goodNum, badNum;
if (r == ReqResult::Ok)
{
// std::cout << "OK" << std::endl;
goodNum = ++statistics_.numOfGoodResponse_;
badNum = statistics_.numOfBadResponse_;
statistics_.bytesRecieved_ += resp->body().length();
auto delay = trantor::Date::now().microSecondsSinceEpoch() -
request->creationDate().microSecondsSinceEpoch();
statistics_.totalDelay_ += delay;
}
else
{
goodNum = statistics_.numOfGoodResponse_;
badNum = ++statistics_.numOfBadResponse_;
if (badNum > numOfRequests_ / 10)
{
outputErrorAndExit("Too many errors");
}
}
if (goodNum + badNum >= numOfRequests_)
{
outputResults();
}
if (r == ReqResult::Ok)
sendRequest(client);
else
{
client->getLoop()->runAfter(1, [this, client]() {
sendRequest(client);
});
}
if (processIndication_)
{
auto rec = goodNum + badNum;
if (rec % 100000 == 0)
{
std::cout << rec << " responses are received" << std::endl
<< std::endl;
}
}
});
}
void press::outputResults()
{
size_t totalSent = 0;
size_t totalRecv = 0;
for (auto &client : clients_)
{
totalSent += client->bytesSent();
totalRecv += client->bytesReceived();
}
auto now = trantor::Date::now();
auto microSecs = now.microSecondsSinceEpoch() -
statistics_.startDate_.microSecondsSinceEpoch();
double seconds = (double)microSecs / 1000000.0;
auto rps = static_cast<size_t>(statistics_.numOfGoodResponse_ / seconds);
std::cout << std::endl;
std::cout << "TOTALS: " << numOfConnections_ << " connect, "
<< numOfRequests_ << " requests, "
<< statistics_.numOfGoodResponse_ << " success, "
<< statistics_.numOfBadResponse_ << " fail" << std::endl;
std::cout << "TRAFFIC: "
<< statistics_.bytesRecieved_ / statistics_.numOfGoodResponse_
<< " avg bytes, "
<< (totalRecv - statistics_.bytesRecieved_) /
statistics_.numOfGoodResponse_
<< " avg overhead, " << statistics_.bytesRecieved_ << " bytes, "
<< totalRecv - statistics_.bytesRecieved_ << " overhead"
<< std::endl;
std::cout << std::setiosflags(std::ios::fixed) << std::setprecision(3)
<< "TIMING: " << seconds << " seconds, " << rps << " rps, "
<< (double)(statistics_.totalDelay_) /
statistics_.numOfGoodResponse_ / 1000
<< " ms avg req time" << std::endl;
std::cout << "SPEED: download " << totalRecv / seconds / 1000
<< " kBps, upload " << totalSent / seconds / 1000 << " kBps"
<< std::endl
<< std::endl;
exit(0);
}
+81
View File
@@ -0,0 +1,81 @@
/**
*
* press.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by the MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "CommandHandler.h"
#include <drogon/DrObject.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/HttpClient.h>
#include <trantor/utils/Date.h>
#include <trantor/net/EventLoopThreadPool.h>
#include <functional>
#include <string>
#include <atomic>
#include <memory>
#include <vector>
using namespace drogon;
namespace drogon_ctl
{
struct Statistics
{
std::atomic_size_t numOfRequestsSent_{0};
std::atomic_size_t bytesRecieved_{0};
std::atomic_size_t numOfGoodResponse_{0};
std::atomic_size_t numOfBadResponse_{0};
std::atomic_size_t totalDelay_{0};
trantor::Date startDate_;
trantor::Date endDate_;
};
class press : public DrObject<press>, public CommandHandler
{
public:
void handleCommand(std::vector<std::string> &parameters) override;
std::string script() override
{
return "Do stress testing(Use 'drogon_ctl help press' for more "
"information)";
}
bool isTopCommand() override
{
return true;
}
std::string detail() override;
private:
size_t numOfThreads_{1};
size_t numOfRequests_{1};
size_t numOfConnections_{1};
std::string httpRequestJsonFile_;
std::function<HttpRequestPtr()> createHttpRequestFunc_;
bool certValidation_{true};
bool processIndication_{true};
std::string url_;
std::string host_;
std::string path_;
void doTesting();
void createRequestAndClients();
void sendRequest(const HttpClientPtr &client);
void outputResults();
std::unique_ptr<trantor::EventLoopThreadPool> loopPool_;
std::vector<HttpClientPtr> clients_;
Statistics statistics_;
};
} // namespace drogon_ctl
+75
View File
@@ -0,0 +1,75 @@
cmake_minimum_required(VERSION 3.5)
project([[ProjectName]] CXX)
include(CheckIncludeFileCXX)
check_include_file_cxx(any HAS_ANY)
check_include_file_cxx(string_view HAS_STRING_VIEW)
check_include_file_cxx(coroutine HAS_COROUTINE)
if (NOT "${CMAKE_CXX_STANDARD}" STREQUAL "")
# Do nothing
elseif (HAS_ANY AND HAS_STRING_VIEW AND HAS_COROUTINE)
set(CMAKE_CXX_STANDARD 20)
elseif (HAS_ANY AND HAS_STRING_VIEW)
set(CMAKE_CXX_STANDARD 17)
else ()
set(CMAKE_CXX_STANDARD 14)
endif ()
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
add_executable(${PROJECT_NAME} main.cc)
# ##############################################################################
# If you include the drogon source code locally in your project, use this method
# to add drogon
# add_subdirectory(drogon)
# target_link_libraries(${PROJECT_NAME} PRIVATE drogon)
#
# and comment out the following lines
find_package(Drogon CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon)
# ##############################################################################
if (CMAKE_CXX_STANDARD LESS 17)
message(FATAL_ERROR "c++17 or higher is required")
elseif (CMAKE_CXX_STANDARD LESS 20)
message(STATUS "use c++17")
else ()
message(STATUS "use c++20")
endif ()
aux_source_directory(controllers CTL_SRC)
aux_source_directory(filters FILTER_SRC)
aux_source_directory(plugins PLUGIN_SRC)
aux_source_directory(models MODEL_SRC)
drogon_create_views(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/views
${CMAKE_CURRENT_BINARY_DIR})
# use the following line to create views with namespaces.
# drogon_create_views(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/views
# ${CMAKE_CURRENT_BINARY_DIR} TRUE)
# use the following line to create views with namespace CHANGE_ME prefixed
# and path namespaces.
# drogon_create_views(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/views
# ${CMAKE_CURRENT_BINARY_DIR} TRUE CHANGE_ME)
target_include_directories(${PROJECT_NAME}
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/models)
target_sources(${PROJECT_NAME}
PRIVATE
${SRC_DIR}
${CTL_SRC}
${FILTER_SRC}
${PLUGIN_SRC}
${MODEL_SRC})
# ##############################################################################
# uncomment the following line for dynamically loading views
# set_property(TARGET ${PROJECT_NAME} PROPERTY ENABLE_EXPORTS ON)
# ##############################################################################
add_subdirectory(test)
@@ -0,0 +1,350 @@
/* This is a JSON format configuration file
*/
{
/*
//ssl:The global SSL settings. "key" and "cert" are the path to the SSL key and certificate. While
// "conf" is an array of 1 or 2-element tuples that supplies file style options for `SSL_CONF_cmd`.
"ssl": {
"cert": "../../trantor/trantor/tests/server.crt",
"key": "../../trantor/trantor/tests/server.key",
"conf": [
//["Options", "-SessionTicket"],
//["Options", "Compression"]
]
},
"listeners": [
{
//address: Ip address,0.0.0.0 by default
"address": "0.0.0.0",
//port: Port number
"port": 80,
//https: If true, use https for security,false by default
"https": false
},
{
"address": "0.0.0.0",
"port": 443,
"https": true,
//cert,key: Cert file path and key file path, empty by default,
//if empty, use the global setting
"cert": "",
"key": "",
//use_old_tls: enable the TLS1.0/1.1, false by default
"use_old_tls": false,
"ssl_conf": [
//["MinProtocol", "TLSv1.3"]
]
}
],
"db_clients": [
{
//name: Name of the client,'default' by default
"name": "default",
//rdbms: Server type, postgresql,mysql or sqlite3, "postgresql" by default
"rdbms": "postgresql",
//filename: Sqlite3 db file name
//"filename":"",
//host: Server address,localhost by default
"host": "127.0.0.1",
//port: Server port, 5432 by default
"port": 5432,
//dbname: Database name
"dbname": "test",
//user: 'postgres' by default
"user": "",
//passwd: '' by default
"passwd": "",
//is_fast: false by default, if it is true, the client is faster but user can't call
//any synchronous interface of it.
"is_fast": false,
//client_encoding: The character set used by the client. it is empty string by default which
//means use the default character set.
//"client_encoding": "",
//number_of_connections: 1 by default, if the 'is_fast' is true, the number is the number of
//connections per IO thread, otherwise it is the total number of all connections.
"number_of_connections": 1,
//timeout: -1.0 by default, in seconds, the timeout for executing a SQL query.
//zero or negative value means no timeout.
"timeout": -1.0,
//auto_batch: this feature is only available for the PostgreSQL driver(version >= 14.0), see
//the wiki for more details.
"auto_batch": false
//connect_options: extra options for the connection. Only works for PostgreSQL now.
//For more information, see https://www.postgresql.org/docs/16/libpq-connect.html#LIBPQ-CONNECT-OPTIONS
//"connect_options": { "statement_timeout": "1s" }
}
],
"redis_clients": [
{
//name: Name of the client,'default' by default
"name": "default",
//host: Server IP, 127.0.0.1 by default
"host": "127.0.0.1",
//port: Server port, 6379 by default
"port": 6379,
//username: '' by default which means 'default' in redis ACL
"username": "",
//passwd: '' by default
"passwd": "",
//db index: 0 by default
"db": 0,
//is_fast: false by default, if it is true, the client is faster but user can't call
//any synchronous interface of it.
"is_fast": false,
//number_of_connections: 1 by default, if the 'is_fast' is true, the number is the number of
//connections per IO thread, otherwise it is the total number of all connections.
"number_of_connections": 1,
//timeout: -1.0 by default, in seconds, the timeout for executing a command.
//zero or negative value means no timeout.
"timeout": -1.0
}
],*/
"app": {
//number_of_threads: The number of IO threads, 1 by default, if the value is set to 0, the number of threads
//is the number of CPU cores
"number_of_threads": 1,
//enable_session: False by default
"enable_session": false,
"session_timeout": 0,
//string value of SameSite attribute of the Set-Cookie HTTP response header
//valid value is either 'Null' (default), 'Lax', 'Strict' or 'None'
"session_same_site" : "Null",
//session_cookie_key: The cookie key of the session, "JSESSIONID" by default
"session_cookie_key": "JSESSIONID",
//session_max_age: The max age of the session cookie, -1 by default
"session_max_age": -1,
//document_root: Root path of HTTP document, default path is ./
"document_root": "./",
//home_page: Set the HTML file of the home page, the default value is "index.html"
//If there isn't any handler registered to the path "/", the home page file in the "document_root" is send to clients as a response
//to the request for "/".
"home_page": "index.html",
//use_implicit_page: enable implicit pages if true, true by default
"use_implicit_page": true,
//implicit_page: Set the file which would the server access in a directory that a user accessed.
//For example, by default, http://localhost/a-directory resolves to http://localhost/a-directory/index.html.
"implicit_page": "index.html",
//static_file_headers: Headers for static files
/*"static_file_headers": [
{
"name": "field-name",
"value": "field-value"
}
],*/
//upload_path: The path to save the uploaded file. "uploads" by default.
//If the path isn't prefixed with /, ./ or ../,
//it is relative path of document_root path
"upload_path": "uploads",
/* file_types:
* HTTP download file types,The file types supported by drogon
* by default are "html", "js", "css", "xml", "xsl", "txt", "svg",
* "ttf", "otf", "woff2", "woff" , "eot", "png", "jpg", "jpeg",
* "gif", "bmp", "ico", "icns", etc. */
"file_types": [
"gif",
"png",
"jpg",
"js",
"css",
"html",
"ico",
"swf",
"xap",
"apk",
"cur",
"xml",
"webp",
"svg"
],
// mime: A dictionary that extends the internal MIME type support. Maps extensions into new MIME types
// note: This option only adds MIME to the sever. `file_types` above have to be set for the server to serve them.
"mime": {
// "text/markdown": "md",
// "text/gemini": ["gmi", "gemini"]
},
//locations: An array of locations of static files for GET requests.
"locations": [
{
//uri_prefix: The URI prefix of the location prefixed with "/", the default value is "" that disables the location.
//"uri_prefix": "/.well-known/acme-challenge/",
//default_content_type: The default content type of the static files without
//an extension. empty string by default.
"default_content_type": "text/plain",
//alias: The location in file system, if it is prefixed with "/", it
//presents an absolute path, otherwise it presents a relative path to
//the document_root path.
//The default value is "" which means use the document root path as the location base path.
"alias": "",
//is_case_sensitive: indicates whether the URI prefix is case sensitive.
"is_case_sensitive": false,
//allow_all: true by default. If it is set to false, only static files with a valid extension can be accessed.
"allow_all": true,
//is_recursive: true by default. If it is set to false, files in sub directories can't be accessed.
"is_recursive": true,
//filters: string array, the filters applied to the location.
"filters": []
}
],
//max_connections: maximum number of connections, 100000 by default
"max_connections": 100000,
//max_connections_per_ip: maximum number of connections per client, 0 by default which means no limit
"max_connections_per_ip": 0,
//Load_dynamic_views: False by default, when set to true, drogon
//compiles and loads dynamically "CSP View Files" in directories defined
//by "dynamic_views_path"
"load_dynamic_views": false,
//dynamic_views_path: If the path isn't prefixed with /, ./ or ../,
//it is relative path of document_root path
"dynamic_views_path": [
"./views"
],
//dynamic_views_output_path: Default by an empty string which means the output path of source
//files is the path where the csp files locate. If the path isn't prefixed with /, it is relative
//path of the current working directory.
"dynamic_views_output_path": "",
//json_parser_stack_limit: 1000 by default, the maximum number of stack depth when reading a json string by the jsoncpp library.
"json_parser_stack_limit": 1000,
//enable_unicode_escaping_in_json: true by default, enable unicode escaping in json.
"enable_unicode_escaping_in_json": true,
//float_precision_in_json: set precision of float number in json.
"float_precision_in_json": {
//precision: 0 by default, 0 means use the default precision of the jsoncpp lib.
"precision": 0,
//precision_type: must be "significant" or "decimal", defaults to "significant" that means
//setting max number of significant digits in string, "decimal" means setting max number of
//digits after "." in string
"precision_type": "significant"
},
//log: Set log output, drogon output logs to stdout by default
"log": {
//use_spdlog: Use spdlog library to log
"use_spdlog": false,
//log_path: Log file path,empty by default,in which case,logs are output to the stdout
//"log_path": "./",
//logfile_base_name: Log file base name,empty by default which means drogon names logfile as
//drogon.log ...
"logfile_base_name": "",
//log_size_limit: 100000000 bytes by default,
//When the log file size reaches "log_size_limit", the log file is switched.
"log_size_limit": 100000000,
//max_files: 0 by default,
//When the number of old log files exceeds "max_files", the oldest file will be deleted. 0 means never delete.
"max_files": 0,
//log_level: "DEBUG" by default,options:"TRACE","DEBUG","INFO","WARN"
//The TRACE level is only valid when built in DEBUG mode.
"log_level": "DEBUG",
//display_local_time: false by default, if true, the log time is displayed in local time
"display_local_time": false
},
//run_as_daemon: False by default
"run_as_daemon": false,
//handle_sig_term: True by default
"handle_sig_term": true,
//relaunch_on_error: False by default, if true, the program will be restart by the parent after exiting;
"relaunch_on_error": false,
//use_sendfile: True by default, if true, the program
//uses sendfile() system-call to send static files to clients;
"use_sendfile": true,
//use_gzip: True by default, use gzip to compress the response body's content;
"use_gzip": true,
//use_brotli: False by default, use brotli to compress the response body's content;
"use_brotli": false,
//static_files_cache_time: 5 (seconds) by default, the time in which the static file response is cached,
//0 means cache forever, the negative value means no cache
"static_files_cache_time": 5,
//simple_controllers_map: Used to configure mapping from path to simple controller
//"simple_controllers_map": [
// {
// "path": "/path/name",
// "controller": "controllerClassName",
// "http_methods": [
// "get",
// "post"
// ],
// "filters": [
// "FilterClassName"
// ]
// }
//],
//idle_connection_timeout: Defaults to 60 seconds, the lifetime
//of the connection without read or write
"idle_connection_timeout": 60,
//server_header_field: Set the 'Server' header field in each response sent by drogon,
//empty string by default with which the 'Server' header field is set to "Server: drogon/version string\r\n"
"server_header_field": "",
//enable_server_header: Set true to force drogon to add a 'Server' header to each HTTP response. The default
//value is true.
"enable_server_header": true,
//enable_date_header: Set true to force drogon to add a 'Date' header to each HTTP response. The default
//value is true.
"enable_date_header": true,
//keepalive_requests: Set the maximum number of requests that can be served through one keep-alive connection.
//After the maximum number of requests are made, the connection is closed.
//The default value of 0 means no limit.
"keepalive_requests": 0,
//pipelining_requests: Set the maximum number of unhandled requests that can be cached in pipelining buffer.
//After the maximum number of requests are made, the connection is closed.
//The default value of 0 means no limit.
"pipelining_requests": 0,
//gzip_static: If it is set to true, when the client requests a static file, drogon first finds the compressed
//file with the extension ".gz" in the same path and send the compressed file to the client.
//The default value of gzip_static is true.
"gzip_static": true,
//br_static: If it is set to true, when the client requests a static file, drogon first finds the compressed
//file with the extension ".br" in the same path and send the compressed file to the client.
//The default value of br_static is true.
"br_static": true,
//client_max_body_size: Set the maximum body size of HTTP requests received by drogon. The default value is "1M".
//One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit.
"client_max_body_size": "1M",
//max_memory_body_size: Set the maximum body size in memory of HTTP requests received by drogon. The default value is "64K" bytes.
//If the body size of a HTTP request exceeds this limit, the body is stored to a temporary file for processing.
//Setting it to "" means no limit.
"client_max_memory_body_size": "64K",
//client_max_websocket_message_size: Set the maximum size of messages sent by WebSocket client. The default value is "128K".
//One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit.
"client_max_websocket_message_size": "128K",
//reuse_port: Defaults to false, users can run multiple processes listening on the same port at the same time.
"reuse_port": false,
// enabled_compressed_request: Defaults to false. If true the server will automatically decompress compressed request bodies.
// Currently only gzip and br are supported. Note: max_memory_body_size and max_body_size applies twice for compressed requests.
// Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request
// will be rejected.
"enabled_compressed_request": false,
// enable_request_stream: Defaults to false. If true the server will enable stream mode for http requests.
// See the wiki for more details.
"enable_request_stream": false,
},
//plugins: Define all plugins running in the application
"plugins": [
{
//name: The class name of the plugin
"name": "drogon::plugin::PromExporter",
//dependencies: Plugins that the plugin depends on. It can be commented out
"dependencies": [],
//config: The configuration of the plugin. This json object is the parameter to initialize the plugin.
//It can be commented out
"config": {
"path": "/metrics"
}
},
{
"name": "drogon::plugin::AccessLogger",
"dependencies": [],
"config": {
"use_spdlog": false,
"log_path": "",
"log_format": "",
"log_file": "access.log",
"log_size_limit": 0,
"use_local_time": true,
"log_index": 0,
// "show_microseconds": true,
// "custom_time_format": "",
// "use_real_ip": false
}
}
],
//custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method.
"custom_config": {}
}
@@ -0,0 +1,313 @@
# This is a YAML format configuration file
# ssl:The global SSL settings. "key" and "cert" are the path to the SSL key and certificate. While
# "conf" is an array of 1 or 2-element tuples that supplies file style options for `SSL_CONF_cmd`.
# ssl:
# cert: ../../trantor/trantor/tests/server.crt
# key: ../../trantor/trantor/tests/server.key
# conf: [
# # [Options, -SessionTicket],
# # [Options, Compression]
# ]
# listeners:
# # address: Ip address,0.0.0.0 by default
# - address: 0.0.0.0
# # port: Port number
# port: 80
# # https: If true, use https for security,false by default
# https: false
# - address: 0.0.0.0
# port: 443
# https: true
# # cert,key: Cert file path and key file path, empty by default,
# # if empty, use the global setting
# cert: ''
# key: ''
# # use_old_tls: enable the TLS1.0/1.1, false by default
# use_old_tls: false
# ssl_conf: [
# # [MinProtocol, TLSv1.3]
# ]
# db_clients:
# # name: Name of the client,'default' by default
# - name: default
# # rdbms: Server type, postgresql,mysql or sqlite3, "postgresql" by default
# rdbms: postgresql
# # filename: Sqlite3 db file name
# # filename: ''
# # host: Server address,localhost by default
# host: 127.0.0.1
# # port: Server port, 5432 by default
# port: 5432
# # dbname: Database name
# dbname: test
# # user: 'postgres' by default
# user: ''
# # passwd: '' by default
# passwd: ''
# # is_fast: false by default, if it is true, the client is faster but user can't call
# # any synchronous interface of it.
# is_fast: false
# # client_encoding: The character set used by the client. it is empty string by default which
# # means use the default character set.
# # client_encoding: ''
# # number_of_connections: 1 by default, if the 'is_fast' is true, the number is the number of
# # connections per IO thread, otherwise it is the total number of all connections.
# number_of_connections: 1
# # timeout: -1 by default, in seconds, the timeout for executing a SQL query.
# # zero or negative value means no timeout.
# timeout: -1
# # auto_batch: this feature is only available for the PostgreSQL driver(version >= 14.0), see
# # the wiki for more details.
# auto_batch: false
# # connect_options: extra options for the connection. Only works for PostgreSQL now.
# # For more information, see https://www.postgresql.org/docs/16/libpq-connect.html#LIBPQ-CONNECT-OPTIONS
# # connect_options:
# # statement_timeout: '1s'
# redis_clients:
# # name: Name of the client,'default' by default
# - name: default
# # host: Server IP, 127.0.0.1 by default
# host: 127.0.0.1
# # port: Server port, 6379 by default
# port: 6379
# # username: '' by default which means 'default' in redis ACL
# username: ''
# # passwd: '' by default
# passwd: ''
# # db index: 0 by default
# db: 0
# # is_fast: false by default, if it is true, the client is faster but user can't call
# # any synchronous interface of it.
# is_fast: false
# # number_of_connections: 1 by default, if the 'is_fast' is true, the number is the number of
# # connections per IO thread, otherwise it is the total number of all connections.
# number_of_connections: 1
# # timeout: -1.0 by default, in seconds, the timeout for executing a command.
# # zero or negative value means no timeout.
# timeout: -1
app:
# number_of_threads: The number of IO threads, 1 by default, if the value is set to 0, the number of threads
# is the number of CPU cores
number_of_threads: 1
# enable_session: False by default
enable_session: false
session_timeout: 0
# string value of SameSite attribute of the Set-Cookie HTTP response header
# valid value is either 'Null' (default), 'Lax', 'Strict' or 'None'
session_same_site: 'Null'
# session_cookie_key: The cookie key of the session, "JSESSIONID" by default
session_cookie_key: 'JSESSIONID'
# session_max_age: The max age of the session cookie, -1 by default
session_max_age: -1
# document_root: Root path of HTTP document, default path is ./
document_root: ./
# home_page: Set the HTML file of the home page, the default value is "index.html"
# If there isn't any handler registered to the path "/", the home page file in the "document_root" is send to clients as a response
# to the request for "/".
home_page: index.html
# use_implicit_page: enable implicit pages if true, true by default
use_implicit_page: true
# implicit_page: Set the file which would the server access in a directory that a user accessed.
# For example, by default, http://localhost/a-directory resolves to http://localhost/a-directory/index.html.
implicit_page: index.html
# static_file_headers: Headers for static files
# static_file_headers:
# - name: field-name
# value: field-value
# upload_path: The path to save the uploaded file. "uploads" by default.
# If the path isn't prefixed with /, ./ or ../,
# it is relative path of document_root path
upload_path: uploads
# file_types:
# HTTP download file types,The file types supported by drogon
# by default are "html", "js", "css", "xml", "xsl", "txt", "svg",
# "ttf", "otf", "woff2", "woff" , "eot", "png", "jpg", "jpeg",
# "gif", "bmp", "ico", "icns", etc.
file_types:
- gif
- png
- jpg
- js
- css
- html
- ico
- swf
- xap
- apk
- cur
- xml
# mime: A dictionary that extends the internal MIME type support. Maps extensions into new MIME types
# note: This option only adds MIME to the sever. `file_types` above have to be set for the server to serve them.
mime: {
# text/markdown: md
# text/gemini:
# - gmi
# - gemini
}
# locations: An array of locations of static files for GET requests.
locations:
# uri_prefix: The URI prefix of the location prefixed with "/", the default value is "" that disables the location.
- uri_prefix: '' # /.well-known/acme-challenge/
# default_content_type: The default content type of the static files without
# an extension. empty string by default.
default_content_type: text/plain
# alias: The location in file system, if it is prefixed with "/", it
# presents an absolute path, otherwise it presents a relative path to
# the document_root path.
# The default value is "" which means use the document root path as the location base path.
alias: ''
# is_case_sensitive: indicates whether the URI prefix is case sensitive.
is_case_sensitive: false
# allow_all: true by default. If it is set to false, only static files with a valid extension can be accessed.
allow_all: true
# is_recursive: true by default. If it is set to false, files in sub directories can't be accessed.
is_recursive: true
# filters: string array, the filters applied to the location.
filters: []
# max_connections: maximum number of connections, 100000 by default
max_connections: 100000
# max_connections_per_ip: maximum number of connections per client, 0 by default which means no limit
max_connections_per_ip: 0
# Load_dynamic_views: False by default, when set to true, drogon
# compiles and loads dynamically "CSP View Files" in directories defined
# by "dynamic_views_path"
load_dynamic_views: false
# dynamic_views_path: If the path isn't prefixed with /, ./ or ../,
# it is relative path of document_root path
dynamic_views_path:
- ./views
# dynamic_views_output_path: Default by an empty string which means the output path of source
# files is the path where the csp files locate. If the path isn't prefixed with /, it is relative
# path of the current working directory.
dynamic_views_output_path: ''
# json_parser_stack_limit: 1000 by default, the maximum number of stack depth when reading a json string by the jsoncpp library.
json_parser_stack_limit: 1000
# enable_unicode_escaping_in_json: true by default, enable unicode escaping in json.
enable_unicode_escaping_in_json: true
# float_precision_in_json: set precision of float number in json.
float_precision_in_json:
# precision: 0 by default, 0 means use the default precision of the jsoncpp lib.
precision: 0
# precision_type: must be "significant" or "decimal", defaults to "significant" that means
# setting max number of significant digits in string, "decimal" means setting max number of
# digits after "." in string
precision_type: significant
# log: Set log output, drogon output logs to stdout by default
log:
# use_spdlog: Use spdlog library to log
use_spdlog: false
# log_path: Log file path,empty by default,in which case,logs are output to the stdout
# log_path: ./
# logfile_base_name: Log file base name,empty by default which means drogon names logfile as
# drogon.log ...
logfile_base_name: ''
# log_size_limit: 100000000 bytes by default,
# When the log file size reaches "log_size_limit", the log file is switched.
log_size_limit: 100000000
# max_files: 0 by default,
# When the number of old log files exceeds "max_files", the oldest file will be deleted. 0 means never delete.
max_files: 0
# log_level: "DEBUG" by default,options:"TRACE","DEBUG","INFO","WARN"
# The TRACE level is only valid when built in DEBUG mode.
log_level: DEBUG
# display_local_time: false by default, if true, the log time is displayed in local time
display_local_time: false
# run_as_daemon: False by default
run_as_daemon: false
# handle_sig_term: True by default
handle_sig_term: true
# relaunch_on_error: False by default, if true, the program will be restart by the parent after exiting;
relaunch_on_error: false
# use_sendfile: True by default, if true, the program
# uses sendfile() system-call to send static files to clients;
use_sendfile: true
# use_gzip: True by default, use gzip to compress the response body's content;
use_gzip: true
# use_brotli: False by default, use brotli to compress the response body's content;
use_brotli: false
# static_files_cache_time: 5 (seconds) by default, the time in which the static file response is cached,
# 0 means cache forever, the negative value means no cache
static_files_cache_time: 5
# simple_controllers_map: Used to configure mapping from path to simple controller
# simple_controllers_map:
# - path: /path/name
# controller: controllerClassName
# http_methods:
# - get
# - post
# filters:
# - FilterClassName
# idle_connection_timeout: Defaults to 60 seconds, the lifetime
# of the connection without read or write
idle_connection_timeout: 60
# server_header_field: Set the 'Server' header field in each response sent by drogon,
# empty string by default with which the 'Server' header field is set to "Server: drogon/version string\r\n"
server_header_field: ''
# enable_server_header: Set true to force drogon to add a 'Server' header to each HTTP response. The default
# value is true.
enable_server_header: true
# enable_date_header: Set true to force drogon to add a 'Date' header to each HTTP response. The default
# value is true.
enable_date_header: true
# keepalive_requests: Set the maximum number of requests that can be served through one keep-alive connection.
# After the maximum number of requests are made, the connection is closed.
# The default value of 0 means no limit.
keepalive_requests: 0
# pipelining_requests: Set the maximum number of unhandled requests that can be cached in pipelining buffer.
# After the maximum number of requests are made, the connection is closed.
# The default value of 0 means no limit.
pipelining_requests: 0
# gzip_static: If it is set to true, when the client requests a static file, drogon first finds the compressed
# file with the extension ".gz" in the same path and send the compressed file to the client.
# The default value of gzip_static is true.
gzip_static: true
# br_static: If it is set to true, when the client requests a static file, drogon first finds the compressed
# file with the extension ".br" in the same path and send the compressed file to the client.
# The default value of br_static is true.
br_static: true
# client_max_body_size: Set the maximum body size of HTTP requests received by drogon. The default value is "1M".
# One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit.
client_max_body_size: 1M
# max_memory_body_size: Set the maximum body size in memory of HTTP requests received by drogon. The default value is "64K" bytes.
# If the body size of a HTTP request exceeds this limit, the body is stored to a temporary file for processing.
# Setting it to "" means no limit.
client_max_memory_body_size: 64K
# client_max_websocket_message_size: Set the maximum size of messages sent by WebSocket client. The default value is "128K".
# One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit.
client_max_websocket_message_size: 128K
# reuse_port: Defaults to false, users can run multiple processes listening on the same port at the same time.
reuse_port: false
# enabled_compressed_request: Defaults to false. If true the server will automatically decompress compressed request bodies.
# Currently only gzip and br are supported. Note: max_memory_body_size and max_body_size applies twice for compressed requests.
# Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request
# will be rejected.
enabled_compressed_request: false
# enable_request_stream: Defaults to false. If true the server will enable stream mode for http requests.
# See the wiki for more details.
enable_request_stream: false
# plugins: Define all plugins running in the application
plugins:
# name: The class name of the plugin
- name: drogon::plugin::PromExporter
# dependencies: Plugins that the plugin depends on. It can be commented out
dependencies: []
# config: The configuration of the plugin. This json object is the parameter to initialize the plugin.
# It can be commented out
config:
path: /metrics
- name: drogon::plugin::AccessLogger
dependencies: []
config:
use_spdlog: false
log_path: ''
log_format: ''
log_file: access.log
log_size_limit: 0
use_local_time: true
log_index: 0
# show_microseconds: true
# custom_time_format: ''
# use_real_ip: false
# custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method.
custom_config: {}
@@ -0,0 +1,11 @@
#include <drogon/drogon.h>
int main() {
//Set HTTP listener address and port
drogon::app().addListener("0.0.0.0", 5555);
//Load config file
//drogon::app().loadConfigFile("../config.json");
//drogon::app().loadConfigFile("../config.yaml");
//Run HTTP framework,the method will block in the internal event loop
drogon::app().run();
return 0;
}
@@ -0,0 +1,30 @@
/**
*
* [[filename]].cc
*
*/
#include "[[filename]].h"
using namespace drogon;
<%c++auto namespaceStr=@@.get<std::string>("namespaceString");
if(!namespaceStr.empty())
$$<<"using namespace "<<namespaceStr<<";\n";
%>
void [[className]]::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
{
//Edit your logic here
if (1)
{
//Passed
fccb();
return;
}
//Check failed
auto res = drogon::HttpResponse::newHttpResponse();
res->setStatusCode(k500InternalServerError);
fcb(res);
}
@@ -0,0 +1,32 @@
/**
*
* [[filename]].h
*
*/
#pragma once
#include <drogon/HttpFilter.h>
using namespace drogon;
<%c++
auto namespaceVector=@@.get<std::vector<std::string>>("namespaceVector");
if(namespaceVector.empty())
$$<<"\n";
for(auto &namespaceName:namespaceVector){%>
namespace {%namespaceName%}
{
<%c++}
$$<<"\n";%>
class [[className]] : public HttpFilter<[[className]]>
{
public:
[[className]]() {}
void doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb) override;
};
<%c++for(size_t i=0;i<namespaceVector.size();i++){%>
}
<%c++}%>
@@ -0,0 +1,561 @@
# Created by https://www.toptal.com/developers/gitignore/api/intellij+all,visualstudio,visualstudiocode,cmake,c,c++
# Edit at https://www.toptal.com/developers/gitignore?templates=intellij+all,visualstudio,visualstudiocode,cmake,c,c++
### C ###
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
### C++ ###
# Prerequisites
# Compiled Object files
*.slo
# Precompiled Headers
# Linker files
# Debugger Files
# Compiled Dynamic libraries
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
# Executables
### CMake ###
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
CMakeUserPresets.json
### CMake Patch ###
# External projects
*-prefix/
### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
### VisualStudioCode ###
.vscode/*
!.vscode/tasks.json
!.vscode/launch.json
*.code-workspace
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.meta
*.iobj
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*[.json, .xml, .info]
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
### VisualStudio Patch ###
# Additional files built by Visual Studio
*.tlog
# End of https://www.toptal.com/developers/gitignore/api/intellij+all,visualstudio,visualstudiocode,cmake,c,c++
File diff suppressed because it is too large Load Diff
+570
View File
@@ -0,0 +1,570 @@
<%inc#include "create_model.h"
using namespace drogon_ctl;
%>
/**
*
* [[className]].h
* DO NOT EDIT. This file is generated by drogon_ctl
*
*/
#pragma once
#include <drogon/orm/Result.h>
#include <drogon/orm/Row.h>
#include <drogon/orm/Field.h>
#include <drogon/orm/SqlBinder.h>
#include <drogon/orm/Mapper.h>
#include <drogon/orm/BaseBuilder.h>
#ifdef __cpp_impl_coroutine
#include <drogon/orm/CoroMapper.h>
#endif
#include <trantor/utils/Date.h>
#include <trantor/utils/Logger.h>
#include <json/json.h>
#include <string>
#include <string_view>
#include <memory>
#include <vector>
#include <tuple>
#include <stdint.h>
#include <iostream>
namespace drogon
{
namespace orm
{
class DbClient;
using DbClientPtr = std::shared_ptr<DbClient>;
}
}
namespace drogon_model
{
namespace [[dbName]]
{
<%c++
auto &schema=@@.get<std::string>("schema");
if(!schema.empty())
{
$$<<"namespace "<<schema<<"\n";
$$<<"{\n";
}
std::vector<std::string> relationshipClassNames;
auto &relationships=@@.get<std::vector<Relationship>>("relationships");
for(auto &relationship : relationships)
{
auto &name = relationship.targetTableName();
auto relationshipClassName = nameTransform(name, true);
relationshipClassNames.push_back(relationshipClassName);
if(relationship.type() == Relationship::Type::ManyToMany)
{
auto &pivotTableName = relationship.pivotTable().tableName();
auto pivotTableClassName = nameTransform(pivotTableName, true);
relationshipClassNames.push_back(pivotTableClassName);
}
}
std::sort(relationshipClassNames.begin(), relationshipClassNames.end());
relationshipClassNames.erase(std::unique(relationshipClassNames.begin(), relationshipClassNames.end()), relationshipClassNames.end());
for(std::string &relationshipClassName : relationshipClassNames)
{
%>
class {%relationshipClassName%};
<%c++
}
%>
class [[className]]
{
public:
struct Cols
{
<%c++
auto cols=@@.get<std::vector<ColumnInfo>>("columns");
for(size_t i=0;i<cols.size();i++)
{
$$<<" static const std::string _"<<cols[i].colName_<<";\n";
}
%>
};
static const int primaryKeyNumber;
static const std::string tableName;
static const bool hasPrimaryKey;
<%c++if(@@.get<int>("hasPrimaryKey")<=1){%>
static const std::string primaryKeyName;
<%c++if(!@@.get<std::string>("primaryKeyType").empty()){%>
using PrimaryKeyType = [[primaryKeyType]];
const PrimaryKeyType &getPrimaryKey() const;
<%c++}else{%>
using PrimaryKeyType = void;
int getPrimaryKey() const { assert(false); return 0; }
<%c++}%>
<%c++}else{
auto pkTypes=@@.get<std::vector<std::string>>("primaryKeyType");
std::string typelist;
for(size_t i=0;i<pkTypes.size();i++)
{
typelist += pkTypes[i];
if(i<(pkTypes.size()-1))
typelist += ",";
}
%>
static const std::vector<std::string> primaryKeyName;
using PrimaryKeyType = std::tuple<{%typelist%}>;//<%c++
auto pkName=@@.get<std::vector<std::string>>("primaryKeyName");
for(size_t i=0;i<pkName.size();i++)
{
$$<<pkName[i];
if(i<(pkName.size()-1))
$$<<",";
}
%>
PrimaryKeyType getPrimaryKey() const;
<%c++}%>
/**
* @brief constructor
* @param r One row of records in the SQL query result.
* @param indexOffset Set the offset to -1 to access all columns by column names,
* otherwise access all columns by offsets.
* @note If the SQL is not a style of 'select * from table_name ...' (select all
* columns by an asterisk), please set the offset to -1.
*/
explicit [[className]](const drogon::orm::Row &r, const ssize_t indexOffset = 0) noexcept;
/**
* @brief constructor
* @param pJson The json object to construct a new instance.
*/
explicit [[className]](const Json::Value &pJson) noexcept(false);
/**
* @brief constructor
* @param pJson The json object to construct a new instance.
* @param pMasqueradingVector The aliases of table columns.
*/
[[className]](const Json::Value &pJson, const std::vector<std::string> &pMasqueradingVector) noexcept(false);
[[className]]() = default;
void updateByJson(const Json::Value &pJson) noexcept(false);
void updateByMasqueradedJson(const Json::Value &pJson,
const std::vector<std::string> &pMasqueradingVector) noexcept(false);
static bool validateJsonForCreation(const Json::Value &pJson, std::string &err);
static bool validateMasqueradedJsonForCreation(const Json::Value &,
const std::vector<std::string> &pMasqueradingVector,
std::string &err);
static bool validateJsonForUpdate(const Json::Value &pJson, std::string &err);
static bool validateMasqueradedJsonForUpdate(const Json::Value &,
const std::vector<std::string> &pMasqueradingVector,
std::string &err);
static bool validJsonOfField(size_t index,
const std::string &fieldName,
const Json::Value &pJson,
std::string &err,
bool isForCreation);
<%c++
for(const auto &col:cols)
{
$$<<" /** For column "<<col.colName_<<" */\n";
if(!col.colType_.empty())
{
$$<<" ///Get the value of the column "<<col.colName_<<", returns the default value if the column is null\n";
$$<<" const "<<col.colType_<<" &getValueOf"<<col.colTypeName_<<"() const noexcept;\n";
if(col.colType_=="std::vector<char>")
{
$$<<" ///Return the column value by std::string with binary data\n";
$$<<" std::string getValueOf"<<col.colTypeName_<<"AsString() const noexcept;\n";
}
$$<<" ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null\n";
$$<<" const std::shared_ptr<"<<col.colType_<<"> &get"<<col.colTypeName_<<"() const noexcept;\n";
$$<<" ///Set the value of the column "<<col.colName_<<"\n";
$$<<" void set"<<col.colTypeName_<<"(const "<<col.colType_<<" &p"<<col.colTypeName_<<") noexcept;\n";
if(col.colType_=="std::string")
$$<<" void set"<<col.colTypeName_<<"("<<col.colType_<<" &&p"<<col.colTypeName_<<") noexcept;\n";
if(col.colType_=="std::vector<char>")
{
$$<<" void set"<<col.colTypeName_<<"(const std::string &p"<<col.colTypeName_<<") noexcept;\n";
}
if(!col.notNull_)
{
$$<<" void set"<<col.colTypeName_<<"ToNull() noexcept;\n";
}
}
else
$$<<" //FIXME!!"<<" getValueOf"<<col.colTypeName_<<"() const noexcept;\n";
$$<<"\n";
}
%>
static size_t getColumnNumber() noexcept { return {% cols.size() %}; }
static const std::string &getColumnName(size_t index) noexcept(false);
Json::Value toJson() const;
std::string toString() const;
Json::Value toMasqueradedJson(const std::vector<std::string> &pMasqueradingVector) const;
/// Relationship interfaces
<%c++
for(auto &relationship : relationships)
{
if(relationship.targetKey().empty() || relationship.originalKey().empty())
{
continue;
}
auto &name=relationship.targetTableName();
auto relationshipClassName=nameTransform(name, true);
auto relationshipValName=nameTransform(name, false);
auto alias=relationship.targetTableAlias();
auto aliasValName=nameTransform(alias, false);
if(relationship.type() == Relationship::Type::HasOne)
{
if(!alias.empty())
{
if(alias[0] <= 'z' && alias[0] >= 'a')
{
alias[0] += ('A' - 'a');
}
std::string alind(alias.length(), ' ');
%>
{%relationshipClassName%} get{%alias%}(const drogon::orm::DbClientPtr &clientPtr) const;
void get{%alias%}(const drogon::orm::DbClientPtr &clientPtr,
{%alind%} const std::function<void({%relationshipClassName%})> &rcb,
{%alind%} const drogon::orm::ExceptionCallback &ecb) const;
<%c++
}
else
{
std::string relationshipClassInde(relationshipClassName.length(), ' ');
%>
{%relationshipClassName%} get{%relationshipClassName%}(const drogon::orm::DbClientPtr &clientPtr) const;
void get{%relationshipClassName%}(const drogon::orm::DbClientPtr &clientPtr,
{%relationshipClassInde%} const std::function<void({%relationshipClassName%})> &rcb,
{%relationshipClassInde%} const drogon::orm::ExceptionCallback &ecb) const;
<%c++
}
}
else if(relationship.type() == Relationship::Type::HasMany)
{
if(!alias.empty())
{
if(alias[0] <= 'z' && alias[0] >= 'a')
{
alias[0] += ('A' - 'a');
}
std::string alind(alias.length(), ' ');
%>
std::vector<{%relationshipClassName%}> get{%alias%}(const drogon::orm::DbClientPtr &clientPtr) const;
void get{%alias%}(const drogon::orm::DbClientPtr &clientPtr,
{%alind%} const std::function<void(std::vector<{%relationshipClassName%}>)> &rcb,
{%alind%} const drogon::orm::ExceptionCallback &ecb) const;
<%c++
}
else
{
std::string relationshipClassInde(relationshipClassName.length(), ' ');
%>
std::vector<{%relationshipClassName%}> get{%relationshipClassName%}(const drogon::orm::DbClientPtr &clientPtr) const;
void get{%relationshipClassName%}(const drogon::orm::DbClientPtr &clientPtr,
{%relationshipClassInde%} const std::function<void(std::vector<{%relationshipClassName%}>)> &rcb,
{%relationshipClassInde%} const drogon::orm::ExceptionCallback &ecb) const;
<%c++
}
}
else if(relationship.type() == Relationship::Type::ManyToMany)
{
auto &pivotTableName=relationship.pivotTable().tableName();
auto pivotTableClassName=nameTransform(pivotTableName, true);
if(!alias.empty())
{
if(alias[0] <= 'z' && alias[0] >= 'a')
{
alias[0] += ('A' - 'a');
}
std::string alind(alias.length(), ' ');
%>
std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>> get{%alias%}(const drogon::orm::DbClientPtr &clientPtr) const;
void get{%alias%}(const drogon::orm::DbClientPtr &clientPtr,
{%alind%} const std::function<void(std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>)> &rcb,
{%alind%} const drogon::orm::ExceptionCallback &ecb) const;
<%c++
}
else
{
std::string relationshipClassInde(relationshipClassName.length(), ' ');
%>
std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>> get{%relationshipClassName%}(const drogon::orm::DbClientPtr &clientPtr) const;
void get{%relationshipClassName%}(const drogon::orm::DbClientPtr &clientPtr,
{%relationshipClassInde%} const std::function<void(std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>)> &rcb,
{%relationshipClassInde%} const drogon::orm::ExceptionCallback &ecb) const;
<%c++
}
}
}
%>
private:
friend drogon::orm::Mapper<[[className]]>;
friend drogon::orm::BaseBuilder<[[className]], true, true>;
friend drogon::orm::BaseBuilder<[[className]], true, false>;
friend drogon::orm::BaseBuilder<[[className]], false, true>;
friend drogon::orm::BaseBuilder<[[className]], false, false>;
#ifdef __cpp_impl_coroutine
friend drogon::orm::CoroMapper<[[className]]>;
#endif
static const std::vector<std::string> &insertColumns() noexcept;
void outputArgs(drogon::orm::internal::SqlBinder &binder) const;
const std::vector<std::string> updateColumns() const;
void updateArgs(drogon::orm::internal::SqlBinder &binder) const;
///For mysql or sqlite3
void updateId(const uint64_t id);
<%c++
for(auto col:cols)
{
if(!col.colType_.empty())
$$<<" std::shared_ptr<"<<col.colType_<<"> "<<col.colValName_<<"_;\n";
}
%>
struct MetaData
{
const std::string colName_;
const std::string colType_;
const std::string colDatabaseType_;
const ssize_t colLength_;
const bool isAutoVal_;
const bool isPrimaryKey_;
const bool notNull_;
};
static const std::vector<MetaData> metaData_;
bool dirtyFlag_[{%cols.size()%}]={ false };
public:
static const std::string &sqlForFindingByPrimaryKey()
{
<%c++
auto rdbms=@@.get<std::string>("rdbms");
if(@@.get<int>("hasPrimaryKey")<=1){
if(!@@.get<std::string>("primaryKeyType").empty()){%>
static const std::string sql="select * from " + tableName + " where [[primaryKeyName]] = {%(rdbms=="postgresql"?"$1":"?")%}";
<%c++}else{%>
static const std::string sql="";
<%c++}%>
<%c++}else{
auto pkName=@@.get<std::vector<std::string>>("primaryKeyName");
%>
static const std::string sql="select * from " + tableName + " where <%c++
for(size_t i=0;i<pkName.size();i++)
{
if(rdbms=="postgresql")
{
$$<<pkName[i]<<" = $"<<i+1;
}
else
{
$$<<pkName[i]<<" = ?";
}
if(i<(pkName.size()-1))
$$<<" and ";
}
$$<<"\";\n";
}
%>
return sql;
}
static const std::string &sqlForDeletingByPrimaryKey()
{
<%c++
if(@@.get<int>("hasPrimaryKey")<=1){
if(!@@.get<std::string>("primaryKeyType").empty()){%>
static const std::string sql="delete from " + tableName + " where [[primaryKeyName]] = {%(rdbms=="postgresql"?"$1":"?")%}";
<%c++}else{%>
static const std::string sql="";
<%c++}%>
<%c++}else{
auto pkName=@@.get<std::vector<std::string>>("primaryKeyName");
%>
static const std::string sql="delete from " + tableName + " where <%c++
for(size_t i=0;i<pkName.size();i++)
{
if(rdbms=="postgresql")
{
$$<<pkName[i]<<" = $"<<i+1;
}
else
{
$$<<pkName[i]<<" = ?";
}
if(i<(pkName.size()-1))
$$<<" and ";
}
$$<<"\";\n";
}
%>
return sql;
}
std::string sqlForInserting(bool &needSelection) const
{
std::string sql="insert into " + tableName + " (";
size_t parametersCount = 0;
needSelection = false;
<%c++
bool selFlag=false;
for(size_t i=0;i<cols.size();i++)
{
if(cols[i].isAutoVal_)
{
if(@@.get<std::string>("rdbms")=="sqlite3")
{
continue;
}
if(@@.get<int>("hasPrimaryKey")>0)
{
selFlag = true;
}
$$<<" sql += \""<<cols[i].colName_<<",\";\n";
$$<<" ++parametersCount;\n";
continue;
}
if(cols[i].colType_.empty())
continue;
if(cols[i].hasDefaultVal_)
{
if(@@.get<std::string>("rdbms")!="sqlite3")
{
$$<<" sql += \""<<cols[i].colName_<<",\";\n";
$$<<" ++parametersCount;\n";
}
else
{
%>
if(dirtyFlag_[{%i%}])
{
sql += "{%cols[i].colName_%},";
++parametersCount;
}
<%c++
}
if(@@.get<int>("hasPrimaryKey")>0||@@.get<std::string>("rdbms")=="postgresql")
{
%>
if(!dirtyFlag_[{%i%}])
{
needSelection=true;
}
<%c++
}
}
else
{
%>
if(dirtyFlag_[{%i%}])
{
sql += "{%cols[i].colName_%},";
++parametersCount;
}
<%c++
}
}
if(selFlag)
{
$$<<" needSelection=true;\n";
}
%>
if(parametersCount > 0)
{
sql[sql.length()-1]=')';
sql += " values (";
}
else
sql += ") values (";
<%c++
if(@@.get<std::string>("rdbms")=="postgresql")
{
%>
int placeholder=1;
char placeholderStr[64];
size_t n=0;
<%c++
}
for(size_t i=0;i<cols.size();i++)
{
if(cols[i].isAutoVal_)
{
if(@@.get<std::string>("rdbms")!="sqlite3")
{
%>
sql +="default,";
<%c++
}
continue;
}
if(cols[i].colType_.empty())
continue;
%>
if(dirtyFlag_[{%i%}])
{
<%c++
if(@@.get<std::string>("rdbms")=="postgresql")
{
%>
n = snprintf(placeholderStr,sizeof(placeholderStr),"$%d,",placeholder++);
sql.append(placeholderStr, n);
<%c++
}else
{
%>
sql.append("?,");
<%c++
}
%>
}
<%c++
if(cols[i].hasDefaultVal_&&@@.get<std::string>("rdbms")!="sqlite3")
{
%>
else
{
sql +="default,";
}
<%c++
}
}
%>
if(parametersCount > 0)
{
sql.resize(sql.length() - 1);
}
<%c++
if(rdbms=="postgresql")
{
%>
if(needSelection)
{
sql.append(") returning *");
}
else
{
sql.append(1, ')');
}
<%c++
}
else
{
$$<<" sql.append(1, ')');\n";
}
%>
LOG_TRACE << sql;
return sql;
}
};
<%c++
if(!schema.empty())
{
$$<<"} // namespace "<<schema<<"\n";
}
%>
} // namespace [[dbName]]
} // namespace drogon_model
@@ -0,0 +1,104 @@
{
//rdbms: server type, postgresql,mysql or sqlite3
"rdbms": "postgresql",
//filename: sqlite3 db file name
//"filename":"",
//host: server address,localhost by default;
"host": "127.0.0.1",
//port: server port, 5432 by default;
"port": 5432,
//dbname: Database name;
"dbname": "",
//schema: valid for postgreSQL, "public" by default;
"schema": "public",
//user: User name
"user": "",
//password or passwd: Password
"password": "",
//client_encoding: The character set used by drogon_ctl. it is empty string by default which
//means use the default character set.
//"client_encoding": "",
//table: An array of tables to be modelized. if the array is empty, all revealed tables are modelized.
"tables": [],
//convert: the value can be changed by a function call before it is stored into database or
//after it is read from database
"convert": {
"enabled": false,
"items":[{
"table": "user",
"column": "password",
"method": {
//after_db_read: name of the method which is called after reading from database, signature: void([const] std::shared_ptr [&])
"after_db_read": "decrypt_password",
//before_db_write: name of the method which is called before writing to database, signature: void([const] std::shared_ptr [&])
"before_db_write": "encrypt_password"
},
"includes": [
"\"file_local_search_path.h\"","<file_in_global_search_path.h>"
]
}]
},
"relationships": {
"enabled": false,
"items": [{
"type": "has one",
"original_table_name": "products",
"original_table_alias": "product",
"original_key": "id",
"target_table_name": "skus",
"target_table_alias": "SKU",
"target_key": "product_id",
"enable_reverse": true
},
{
"type": "has many",
"original_table_name": "products",
"original_table_alias": "product",
"original_key": "id",
"target_table_name": "reviews",
"target_table_alias": "",
"target_key": "product_id",
"enable_reverse": true
},
{
"type": "many to many",
"original_table_name": "products",
"original_table_alias": "",
"original_key": "id",
"pivot_table": {
"table_name": "carts_products",
"original_key": "product_id",
"target_key": "cart_id"
},
"target_table_name": "carts",
"target_table_alias": "",
"target_key": "id",
"enable_reverse": true
}
]
},
"restful_api_controllers": {
"enabled": false,
// resource_uri: The URI to access the resource, the default value
// is '/*' in which the asterisk represents the table name.
// If this option is set to a empty string, the URI is composed of the namespaces and the class name.
"resource_uri": "/*",
// class_name: "Restful*Ctrl" by default, the asterisk represents the table name.
// This option can contain namespaces.
"class_name": "Restful*Ctrl",
// filters: an array of filter names.
"filters": [],
// db_client: the database client used by the controller. this option must be consistent with
// the configuration of the application.
"db_client": {
//name: Name of the client,'default' by default
"name": "default",
//is_fast:
"is_fast": false
},
// directory: The directory where the controller source files are stored.
"directory": "controllers",
// generate_base_only: false by default. Set to true to avoid overwriting custom subclasses.
"generate_base_only": false
}
}
@@ -0,0 +1,23 @@
/**
*
* [[filename]].cc
*
*/
#include "[[filename]].h"
using namespace drogon;
<%c++auto namespaceStr=@@.get<std::string>("namespaceString");
if(!namespaceStr.empty())
$$<<"using namespace "<<namespaceStr<<";\n";
%>
void [[className]]::initAndStart(const Json::Value &config)
{
/// Initialize and start the plugin
}
void [[className]]::shutdown()
{
/// Shutdown the plugin
}
@@ -0,0 +1,35 @@
/**
*
* [[filename]].h
*
*/
#pragma once
#include <drogon/plugins/Plugin.h>
<%c++
auto namespaceVector=@@.get<std::vector<std::string>>("namespaceVector");
if(namespaceVector.empty())
$$<<"\n";
for(auto &namespaceName:namespaceVector){%>
namespace {%namespaceName%}
{
<%c++}
$$<<"\n";%>
class [[className]] : public drogon::Plugin<[[className]]>
{
public:
[[className]]() {}
/// This method must be called by drogon to initialize and start the plugin.
/// It must be implemented by the user.
void initAndStart(const Json::Value &config) override;
/// This method must be called by drogon to shutdown the plugin.
/// It must be implemented by the user.
void shutdown() override;
};
<%c++for(size_t i=0;i<namespaceVector.size();i++){%>
}
<%c++}%>
@@ -0,0 +1,494 @@
<%inc#include "create_model.h"
using namespace drogon_ctl;
%>
/**
*
* [[fileName]]Base.cc
* DO NOT EDIT. This file is generated by drogon_ctl automatically.
* Users should implement business logic in the derived class.
*/
#include "[[fileName]]Base.h"
#include <string>
<%c++
auto tableInfo = @@.get<DrTemplateData>("tableInfo");
auto modelName = tableInfo.get<std::string>("className");
bool hasPrimaryKey = (tableInfo.get<int>("hasPrimaryKey")==1);
auto namespaceVector=@@.get<std::vector<std::string>>("namespaceVector");
std::string namespaceStr;
for(auto &name:namespaceVector)
{
namespaceStr.append(name);
namespaceStr.append("::");
}
if(!namespaceStr.empty())
{
namespaceStr.resize(namespaceStr.length()-2);
$$<<"using namespace "<<namespaceStr<<";\n";
}
std::string indentStr(@@.get<std::string>("className").length(), ' ');
%>
<%c++
if(hasPrimaryKey)
{%>
void [[className]]Base::getOne(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback,
{%indentStr%} {%modelName%}::PrimaryKeyType &&id)
{
auto dbClientPtr = getDbClient();
auto callbackPtr =
std::make_shared<std::function<void(const HttpResponsePtr &)>>(
std::move(callback));
drogon::orm::Mapper<{%modelName%}> mapper(dbClientPtr);
mapper.findByPrimaryKey(
id,
[req, callbackPtr, this]({%modelName%} r) {
(*callbackPtr)(HttpResponse::newHttpJsonResponse(makeJson(req, r)));
},
[callbackPtr](const DrogonDbException &e) {
const drogon::orm::UnexpectedRows *s=dynamic_cast<const drogon::orm::UnexpectedRows *>(&e.base());
if(s)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
(*callbackPtr)(resp);
return;
}
LOG_ERROR<<e.base().what();
Json::Value ret;
ret["error"] = "database error";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k500InternalServerError);
(*callbackPtr)(resp);
});
}
void [[className]]Base::updateOne(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback,
{%indentStr%} {%modelName%}::PrimaryKeyType &&id)
{
auto jsonPtr=req->jsonObject();
if(!jsonPtr)
{
Json::Value ret;
ret["error"]="No json object is found in the request";
auto resp= HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
{%modelName%} object;
std::string err;
if(!doCustomValidations(*jsonPtr, err))
{
Json::Value ret;
ret["error"] = err;
auto resp= HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
try
{
if(isMasquerading())
{
if(!{%modelName%}::validateMasqueradedJsonForUpdate(*jsonPtr, masqueradingVector(), err))
{
Json::Value ret;
ret["error"] = err;
auto resp= HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
object.updateByMasqueradedJson(*jsonPtr, masqueradingVector());
}
else
{
if(!{%modelName%}::validateJsonForUpdate(*jsonPtr, err))
{
Json::Value ret;
ret["error"] = err;
auto resp= HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
object.updateByJson(*jsonPtr);
}
}
catch(const Json::Exception &e)
{
LOG_ERROR << e.what();
Json::Value ret;
ret["error"]="Field type error";
auto resp= HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
if(object.getPrimaryKey() != id)
{
Json::Value ret;
ret["error"]="Bad primary key";
auto resp= HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
auto dbClientPtr = getDbClient();
auto callbackPtr =
std::make_shared<std::function<void(const HttpResponsePtr &)>>(
std::move(callback));
drogon::orm::Mapper<{%modelName%}> mapper(dbClientPtr);
mapper.update(
object,
[callbackPtr](const size_t count)
{
if(count == 1)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k202Accepted);
(*callbackPtr)(resp);
}
else if(count == 0)
{
Json::Value ret;
ret["error"]="No resources are updated";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k404NotFound);
(*callbackPtr)(resp);
}
else
{
LOG_FATAL << "More than one resource is updated: " << count;
Json::Value ret;
ret["error"] = "database error";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k500InternalServerError);
(*callbackPtr)(resp);
}
},
[callbackPtr](const DrogonDbException &e) {
LOG_ERROR << e.base().what();
Json::Value ret;
ret["error"] = "database error";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k500InternalServerError);
(*callbackPtr)(resp);
});
}
void [[className]]Base::deleteOne(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback,
{%indentStr%} {%modelName%}::PrimaryKeyType &&id)
{
auto dbClientPtr = getDbClient();
auto callbackPtr =
std::make_shared<std::function<void(const HttpResponsePtr &)>>(
std::move(callback));
drogon::orm::Mapper<{%modelName%}> mapper(dbClientPtr);
mapper.deleteByPrimaryKey(
id,
[callbackPtr](const size_t count) {
if(count == 1)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k204NoContent);
(*callbackPtr)(resp);
}
else if(count == 0)
{
Json::Value ret;
ret["error"] = "No resources deleted";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k404NotFound);
(*callbackPtr)(resp);
}
else
{
LOG_FATAL << "Delete more than one records: " << count;
Json::Value ret;
ret["error"] = "Database error";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k500InternalServerError);
(*callbackPtr)(resp);
}
},
[callbackPtr](const DrogonDbException &e) {
LOG_ERROR << e.base().what();
Json::Value ret;
ret["error"] = "database error";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k500InternalServerError);
(*callbackPtr)(resp);
});
}
<%c++}%>
void [[className]]Base::get(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback)
{
auto dbClientPtr = getDbClient();
drogon::orm::Mapper<{%modelName%}> mapper(dbClientPtr);
auto &parameters = req->parameters();
auto iter = parameters.find("sort");
if(iter != parameters.end())
{
auto sortFields = drogon::utils::splitString(iter->second, ",");
for(auto &field : sortFields)
{
if(field.empty())
continue;
if(field[0] == '+')
{
field = field.substr(1);
mapper.orderBy(field, SortOrder::ASC);
}
else if(field[0] == '-')
{
field = field.substr(1);
mapper.orderBy(field, SortOrder::DESC);
}
else
{
mapper.orderBy(field, SortOrder::ASC);
}
}
}
iter = parameters.find("offset");
if(iter != parameters.end())
{
try{
auto offset = std::stoll(iter->second);
mapper.offset(offset);
}
catch(...)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
}
iter = parameters.find("limit");
if(iter != parameters.end())
{
try{
auto limit = std::stoll(iter->second);
mapper.limit(limit);
}
catch(...)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
}
auto callbackPtr =
std::make_shared<std::function<void(const HttpResponsePtr &)>>(
std::move(callback));
auto jsonPtr = req->jsonObject();
if(jsonPtr && jsonPtr->isMember("filter"))
{
try{
auto criteria = makeCriteria((*jsonPtr)["filter"]);
mapper.findBy(criteria,
[req, callbackPtr, this](const std::vector<{%modelName%}> &v) {
Json::Value ret;
ret.resize(0);
for (auto &obj : v)
{
ret.append(makeJson(req, obj));
}
(*callbackPtr)(HttpResponse::newHttpJsonResponse(ret));
},
[callbackPtr](const DrogonDbException &e) {
LOG_ERROR << e.base().what();
Json::Value ret;
ret["error"] = "database error";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k500InternalServerError);
(*callbackPtr)(resp);
});
}
catch(const std::exception &e)
{
LOG_ERROR << e.what();
Json::Value ret;
ret["error"] = e.what();
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k400BadRequest);
(*callbackPtr)(resp);
return;
}
}
else
{
mapper.findAll([req, callbackPtr, this](const std::vector<{%modelName%}> &v) {
Json::Value ret;
ret.resize(0);
for (auto &obj : v)
{
ret.append(makeJson(req, obj));
}
(*callbackPtr)(HttpResponse::newHttpJsonResponse(ret));
},
[callbackPtr](const DrogonDbException &e) {
LOG_ERROR << e.base().what();
Json::Value ret;
ret["error"] = "database error";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k500InternalServerError);
(*callbackPtr)(resp);
});
}
}
void [[className]]Base::create(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback)
{
auto jsonPtr=req->jsonObject();
if(!jsonPtr)
{
Json::Value ret;
ret["error"]="No json object is found in the request";
auto resp= HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
std::string err;
if(!doCustomValidations(*jsonPtr, err))
{
Json::Value ret;
ret["error"] = err;
auto resp= HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
if(isMasquerading())
{
if(!{%modelName%}::validateMasqueradedJsonForCreation(*jsonPtr, masqueradingVector(), err))
{
Json::Value ret;
ret["error"] = err;
auto resp= HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
}
else
{
if(!{%modelName%}::validateJsonForCreation(*jsonPtr, err))
{
Json::Value ret;
ret["error"] = err;
auto resp= HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
}
try
{
{%modelName%} object =
(isMasquerading()?
{%modelName%}(*jsonPtr, masqueradingVector()) :
{%modelName%}(*jsonPtr));
auto dbClientPtr = getDbClient();
auto callbackPtr =
std::make_shared<std::function<void(const HttpResponsePtr &)>>(
std::move(callback));
drogon::orm::Mapper<{%modelName%}> mapper(dbClientPtr);
mapper.insert(
object,
[req, callbackPtr, this]({%modelName%} newObject){
(*callbackPtr)(HttpResponse::newHttpJsonResponse(
makeJson(req, newObject)));
},
[callbackPtr](const DrogonDbException &e){
LOG_ERROR << e.base().what();
Json::Value ret;
ret["error"] = "database error";
auto resp = HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k500InternalServerError);
(*callbackPtr)(resp);
});
}
catch(const Json::Exception &e)
{
LOG_ERROR << e.what();
Json::Value ret;
ret["error"]="Field type error";
auto resp= HttpResponse::newHttpJsonResponse(ret);
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
}
/*
void [[className]]Base::update(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback)
{
}*/
[[className]]Base::[[className]]Base()
: RestfulController({
<%c++
tableInfo = @@.get<DrTemplateData>("tableInfo");
const auto &cols=tableInfo.get<std::vector<ColumnInfo>>("columns");
for(size_t i=0; i<cols.size(); ++i)
{
auto &col = cols[i];
if(i < (cols.size()-1))
{
%>
"{%col.colName_%}",
<%c++
}else{
%>
"{%col.colName_%}"
<%c++
}
}
%>
})
{
/**
* The items in the vector are aliases of column names in the table.
* if one item is set to an empty string, the related column is not sent
* to clients.
*/
enableMasquerading({
<%c++
for(size_t i=0; i<cols.size(); ++i)
{
auto &col = cols[i];
if(i < (cols.size()-1))
{
%>
"{%col.colName_%}", // the alias for the {%col.colName_%} column.
<%c++
}else{
%>
"{%col.colName_%}" // the alias for the {%col.colName_%} column.
<%c++
}
}
%>
});
}
@@ -0,0 +1,87 @@
<%inc#include "create_model.h"
using namespace drogon_ctl;
%>
/**
*
* [[fileName]]Base.h
* DO NOT EDIT. This file is generated by drogon_ctl automatically.
* Users should implement business logic in the derived class.
*/
#pragma once
#include <drogon/HttpController.h>
#include <drogon/orm/RestfulController.h>
<%c++
auto tableInfo = @@.get<DrTemplateData>("tableInfo");
auto modelName = tableInfo.get<std::string>("className");
$$<<"#include \""<<modelName<<".h\"\n";
bool hasPrimaryKey = (tableInfo.get<int>("hasPrimaryKey")==1);
$$<<"using namespace drogon;\n";
$$<<"using namespace drogon::orm;\n";
$$<<"using namespace drogon_model::"<<tableInfo.get<std::string>("dbName");
auto &schema=tableInfo.get<std::string>("schema");
if(!schema.empty())
{
$$<<"::"<<schema<<";\n";
}
else
{
$$<<";\n";
}
auto namespaceVector=@@.get<std::vector<std::string>>("namespaceVector");
for(auto &name:namespaceVector)
{
%>
namespace {%name%}
{
<%c++}%>
/**
* @brief this class is created by the drogon_ctl command.
* this class is a restful API controller for reading and writing the [[tableName]] table.
*/
class [[className]]Base : public RestfulController
{
public:
<%c++if(hasPrimaryKey)
{
%>
void getOne(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
{%modelName%}::PrimaryKeyType &&id);
void updateOne(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
{%modelName%}::PrimaryKeyType &&id);
void deleteOne(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
{%modelName%}::PrimaryKeyType &&id);
<%c++}
%>
void get(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void create(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
// void update(const HttpRequestPtr &req,
// std::function<void(const HttpResponsePtr &)> &&callback);
orm::DbClientPtr getDbClient()
{
return drogon::app().get{%(@@.get<bool>("isFastDbClient")?"Fast":"")%}DbClient(dbClientName_);
}
protected:
/// Ensure that subclasses inherited from this class are instantiated.
[[className]]Base();
const std::string dbClientName_{"[[dbClientName]]"};
};
<%c++ for(size_t i=0;i<namespaceVector.size();++i)
{
$$<<"}\n";
}
%>
@@ -0,0 +1,58 @@
/**
*
* [[fileName]].cc
* This file is generated by drogon_ctl
*
*/
#include "[[fileName]].h"
#include <string>
<%c++
auto namespaceVector=@@.get<std::vector<std::string>>("namespaceVector");
std::string namespaceStr;
for(auto &name:namespaceVector)
{
namespaceStr.append(name);
namespaceStr.append("::");
}
if(!namespaceStr.empty())
{
namespaceStr.resize(namespaceStr.length()-2);
$$<<"using namespace "<<namespaceStr<<";\n";
}
std::string indentStr(@@.get<std::string>("className").length(), ' ');
%>
void [[className]]::getOne(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback,
{%indentStr%} std::string &&id)
{
}
void [[className]]::get(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback)
{
}
void [[className]]::create(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback)
{
}
void [[className]]::updateOne(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback,
{%indentStr%} std::string &&id)
{
}
/*
void [[className]]::update(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback)
{
}*/
void [[className]]::deleteOne(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback,
{%indentStr%} std::string &&id)
{
}
@@ -0,0 +1,70 @@
/**
*
* [[fileName]].cc
* This file is generated by drogon_ctl
*
*/
#include "[[fileName]].h"
#include <string>
<%c++
auto tableInfo = @@.get<DrTemplateData>("tableInfo");
auto modelName = tableInfo.get<std::string>("className");
bool hasPrimaryKey = (tableInfo.get<int>("hasPrimaryKey")==1);
auto namespaceVector=@@.get<std::vector<std::string>>("namespaceVector");
std::string namespaceStr;
for(auto &name:namespaceVector)
{
namespaceStr.append(name);
namespaceStr.append("::");
}
if(!namespaceStr.empty())
{
namespaceStr.resize(namespaceStr.length()-2);
$$<<"using namespace "<<namespaceStr<<";\n";
}
std::string indentStr(@@.get<std::string>("className").length(), ' ');
%>
<%c++
if(hasPrimaryKey)
{%>
void [[className]]::getOne(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback,
{%indentStr%} {%modelName%}::PrimaryKeyType &&id)
{
[[className]]Base::getOne(req, std::move(callback), std::move(id));
}
void [[className]]::updateOne(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback,
{%indentStr%} {%modelName%}::PrimaryKeyType &&id)
{
[[className]]Base::updateOne(req, std::move(callback), std::move(id));
}
void [[className]]::deleteOne(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback,
{%indentStr%} {%modelName%}::PrimaryKeyType &&id)
{
[[className]]Base::deleteOne(req, std::move(callback), std::move(id));
}
<%c++}%>
void [[className]]::get(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback)
{
[[className]]Base::get(req, std::move(callback));
}
void [[className]]::create(const HttpRequestPtr &req,
{%indentStr%} std::function<void(const HttpResponsePtr &)> &&callback)
{
[[className]]Base::create(req, std::move(callback));
}
@@ -0,0 +1,104 @@
<%inc#include "create_model.h"
using namespace drogon_ctl;
%>
/**
*
* [[fileName]].h
* This file is generated by drogon_ctl
*
*/
#pragma once
#include <drogon/HttpController.h>
#include "[[className]]Base.h"
<%c++
auto tableInfo = @@.get<DrTemplateData>("tableInfo");
auto modelName = tableInfo.get<std::string>("className");
$$<<"#include \""<<modelName<<".h\"\n";
bool hasPrimaryKey = (tableInfo.get<int>("hasPrimaryKey")==1);
$$<<"using namespace drogon;\n";
$$<<"using namespace drogon_model::"<<tableInfo.get<std::string>("dbName");
auto &schema=tableInfo.get<std::string>("schema");
if(!schema.empty())
{
$$<<"::"<<schema<<";\n";
}
else
{
$$<<";\n";
}
auto namespaceVector=@@.get<std::vector<std::string>>("namespaceVector");
for(auto &name:namespaceVector)
{
%>
namespace {%name%}
{
<%c++}%>
/**
* @brief this class is created by the drogon_ctl command.
* this class is a restful API controller for reading and writing the [[tableName]] table.
*/
class [[className]]: public drogon::HttpController<[[className]]>, public [[className]]Base
{
public:
METHOD_LIST_BEGIN
<%c++
auto resource=@@.get<std::string>("resource");
if(resource.empty())
{
if(hasPrimaryKey)
{
%>
METHOD_ADD([[className]]::getOne,"/{1}",Get,Options[[filters]]);
METHOD_ADD([[className]]::updateOne,"/{1}",Put,Options[[filters]]);
METHOD_ADD([[className]]::deleteOne,"/{1}",Delete,Options[[filters]]);
<%c++}%>
METHOD_ADD([[className]]::get,"",Get,Options[[filters]]);
METHOD_ADD([[className]]::create,"",Post,Options[[filters]]);
//METHOD_ADD([[className]]::update,"",Put,Options[[filters]]);
<%c++
}else
{
if(hasPrimaryKey)
{
%>
ADD_METHOD_TO([[className]]::getOne,"{%resource%}/{1}",Get,Options[[filters]]);
ADD_METHOD_TO([[className]]::updateOne,"{%resource%}/{1}",Put,Options[[filters]]);
ADD_METHOD_TO([[className]]::deleteOne,"{%resource%}/{1}",Delete,Options[[filters]]);
<%c++}%>
ADD_METHOD_TO([[className]]::get,"{%resource%}",Get,Options[[filters]]);
ADD_METHOD_TO([[className]]::create,"{%resource%}",Post,Options[[filters]]);
//ADD_METHOD_TO([[className]]::update,"{%resource%}",Put,Options[[filters]]);
<%c++}%>
METHOD_LIST_END
<%c++if(hasPrimaryKey)
{
%>
void getOne(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
{%modelName%}::PrimaryKeyType &&id);
void updateOne(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
{%modelName%}::PrimaryKeyType &&id);
void deleteOne(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
{%modelName%}::PrimaryKeyType &&id);
<%c++}
%>
void get(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void create(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
};
<%c++ for(size_t i=0;i<namespaceVector.size();++i)
{
$$<<"}\n";
}
%>
@@ -0,0 +1,80 @@
<%inc#include "create_model.h"
using namespace drogon_ctl;
%>
/**
*
* [[fileName]].h
* This file is generated by drogon_ctl
*
*/
#pragma once
#include <drogon/HttpController.h>
<%c++
$$<<"using namespace drogon;\n";
auto namespaceVector=@@.get<std::vector<std::string>>("namespaceVector");
for(auto &name:namespaceVector)
{
%>
namespace {%name%}
{
<%c++
}
%>
/**
* @brief this class is created by the drogon_ctl command ([[ctlCommand]]).
* this class is a restful API controller.
*/
class [[className]]: public drogon::HttpController<[[className]]>
{
public:
METHOD_LIST_BEGIN
// use METHOD_ADD to add your custom processing function here;
<%c++
auto resource=@@.get<std::string>("resource");
if(resource.empty())
{
%>
METHOD_ADD([[className]]::getOne,"/{1}",Get,Options[[filters]]);
METHOD_ADD([[className]]::get,"",Get,Options[[filters]]);
METHOD_ADD([[className]]::create,"",Post,Options[[filters]]);
METHOD_ADD([[className]]::updateOne,"/{1}",Put,Options[[filters]]);
//METHOD_ADD([[className]]::update,"",Put,Options[[filters]]);
METHOD_ADD([[className]]::deleteOne,"/{1}",Delete,Options[[filters]]);
<%c++
}else
{
%>
ADD_METHOD_TO([[className]]::getOne,"{%resource%}/{1}",Get,Options[[filters]]);
ADD_METHOD_TO([[className]]::updateOne,"{%resource%}/{1}",Put,Options[[filters]]);
ADD_METHOD_TO([[className]]::deleteOne,"{%resource%}/{1}",Delete,Options[[filters]]);
ADD_METHOD_TO([[className]]::get,"{%resource%}",Get,Options[[filters]]);
ADD_METHOD_TO([[className]]::create,"{%resource%}",Post,Options[[filters]]);
//ADD_METHOD_TO([[className]]::update,"{%resource%}",Put,Options[[filters]]);
<%c++}%>
METHOD_LIST_END
void getOne(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
std::string &&id);
void updateOne(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
std::string &&id);
void deleteOne(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
std::string &&id);
void get(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void create(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
// void update(const HttpRequestPtr &req,
// std::function<void(const HttpResponsePtr &)> &&callback);
};
<%c++ for(size_t i=0;i<namespaceVector.size();++i)
{
$$<<"}\n";
}
%>
@@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.5)
project([[ProjectName]]_test CXX)
add_executable(${PROJECT_NAME} test_main.cc)
# ##############################################################################
# If you include the drogon source code locally in your project, use this method
# to add drogon
# target_link_libraries(${PROJECT_NAME} PRIVATE drogon)
#
# and comment out the following lines
target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon)
ParseAndAddDrogonTests(${PROJECT_NAME})
@@ -0,0 +1,32 @@
#define DROGON_TEST_MAIN
#include <drogon/drogon_test.h>
#include <drogon/drogon.h>
DROGON_TEST(BasicTest)
{
// Add your tests here
}
int main(int argc, char** argv)
{
using namespace drogon;
std::promise<void> p1;
std::future<void> f1 = p1.get_future();
// Start the main loop on another thread
std::thread thr([&]() {
// Queues the promise to be fulfilled after starting the loop
app().getLoop()->queueInLoop([&p1]() { p1.set_value(); });
app().run();
});
// The future is only satisfied after the event loop started
f1.get();
int status = test::run(argc, argv);
// Ask the event loop to shutdown and wait
app().getLoop()->queueInLoop([]() { app().quit(); });
thr.join();
return status;
}
+68
View File
@@ -0,0 +1,68 @@
/**
*
* version.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "version.h"
#include <drogon/config.h>
#include <drogon/version.h>
#include <drogon/utils/Utilities.h>
#include <trantor/net/Resolver.h>
#include <trantor/utils/Utilities.h>
#include <iostream>
using namespace drogon_ctl;
static const char banner[] =
R"( _
__| |_ __ ___ __ _ ___ _ __
/ _` | '__/ _ \ / _` |/ _ \| '_ \
| (_| | | | (_) | (_| | (_) | | | |
\__,_|_| \___/ \__, |\___/|_| |_|
|___/
)";
void version::handleCommand(std::vector<std::string> &parameters)
{
const auto tlsBackend = trantor::utils::tlsBackend();
const bool tlsSupported = drogon::utils::supportsTls();
std::cout << banner << std::endl;
std::cout << "A utility for drogon" << std::endl;
std::cout << "Version: " << DROGON_VERSION << std::endl;
std::cout << "Git commit: " << DROGON_VERSION_SHA1 << std::endl;
std::cout << "Compilation: \n Compiler: " << COMPILER_COMMAND
<< "\n Compiler ID: " << COMPILER_ID
<< "\n Compilation flags: " << COMPILATION_FLAGS
<< INCLUDING_DIRS << std::endl;
std::cout << "Libraries: \n postgresql: "
<< (USE_POSTGRESQL ? "yes" : "no") << " (pipeline mode: "
<< (LIBPQ_SUPPORTS_BATCH_MODE ? "yes)\n" : "no)\n")
<< " mariadb: " << (USE_MYSQL ? "yes\n" : "no\n")
<< " sqlite3: " << (USE_SQLITE3 ? "yes\n" : "no\n");
std::cout << " ssl/tls backend: " << tlsBackend << "\n";
#ifdef USE_BROTLI
std::cout << " brotli: yes\n";
#else
std::cout << " brotli: no\n";
#endif
#ifdef USE_REDIS
std::cout << " hiredis: yes\n";
#else
std::cout << " hiredis: no\n";
#endif
std::cout << " c-ares: "
<< (trantor::Resolver::isCAresUsed() ? "yes\n" : "no\n");
#ifdef HAS_YAML_CPP
std::cout << " yaml-cpp: yes\n";
#else
std::cout << " yaml-cpp: no\n";
#endif
}
+42
View File
@@ -0,0 +1,42 @@
/**
*
* version.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/DrObject.h>
#include "CommandHandler.h"
using namespace drogon;
namespace drogon_ctl
{
class version : public DrObject<version>, public CommandHandler
{
public:
void handleCommand(std::vector<std::string> &parameters) override;
std::string script() override
{
return "display version of this tool";
}
bool isTopCommand() override
{
return true;
}
version()
{
}
};
} // namespace drogon_ctl