From 5c88630123be937e16a4ab1a09ab5aa25fd95f30 Mon Sep 17 00:00:00 2001 From: an-tao Date: Sat, 24 Jul 2021 21:26:45 +0800 Subject: [PATCH 01/18] Add support for swagger --- drogon_ctl/create.cc | 4 +- drogon_ctl/create_project.cc | 9 +- drogon_ctl/create_swagger.cc | 187 ++++++++++++++++++++++++++ drogon_ctl/create_swagger.h | 31 +++++ drogon_ctl/templates/swagger_cc.csp | 22 +++ drogon_ctl/templates/swagger_h.csp | 23 ++++ drogon_ctl/templates/swagger_json.csp | 16 +++ 7 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 drogon_ctl/create_swagger.cc create mode 100644 drogon_ctl/create_swagger.h create mode 100644 drogon_ctl/templates/swagger_cc.csp create mode 100644 drogon_ctl/templates/swagger_h.csp create mode 100644 drogon_ctl/templates/swagger_json.csp diff --git a/drogon_ctl/create.cc b/drogon_ctl/create.cc index bd5be99c2d..03d5576c6c 100644 --- a/drogon_ctl/create.cc +++ b/drogon_ctl/create.cc @@ -45,7 +45,9 @@ std::string create::detail() "drogon_ctl create model [-o ] [ " "--clear-output]" "[--table=] [-f]//" - "create model classes in model_path\n"; + "create model classes in model_path\n\n" + "drogon_ctl create swagger //" + "create swagger controller in swagger_path\n\n"; } void create::handleCommand(std::vector ¶meters) diff --git a/drogon_ctl/create_project.cc b/drogon_ctl/create_project.cc index 1be5a70f1e..ce394ffd20 100644 --- a/drogon_ctl/create_project.cc +++ b/drogon_ctl/create_project.cc @@ -77,7 +77,11 @@ static void newModelConfigFile(std::ofstream &configFile) auto templ = DrTemplateBase::newTemplate("model_json"); configFile << templ->genText(); } - +static void newSwaggerConfigFile(std::ofstream &configFile) +{ + auto templ = DrTemplateBase::newTemplate("swagger_json"); + configFile << templ->genText(); +} static void newTestMainFile(std::ofstream &mainFile) { auto templ = DrTemplateBase::newTemplate("test_main"); @@ -127,6 +131,7 @@ void create_project::createProject(const std::string &projectName) drogon::utils::createPath("build"); drogon::utils::createPath("models"); drogon::utils::createPath("test"); + drogon::utils::createPath("swagger"); std::ofstream gitFile(".gitignore", std::ofstream::out); newGitIgFile(gitFile); @@ -136,6 +141,8 @@ void create_project::createProject(const std::string &projectName) newConfigYamlFile(configYamlFile); std::ofstream modelConfigFile("models/model.json", std::ofstream::out); newModelConfigFile(modelConfigFile); + std::ofstream swaggerConfigFile("swagger/swagger.json", std::ofstream::out); + newSwaggerConfigFile(swaggerConfigFile); std::ofstream testMainFile("test/test_main.cc", std::ofstream::out); newTestMainFile(testMainFile); std::ofstream testCmakeFile("test/CMakeLists.txt", std::ofstream::out); diff --git a/drogon_ctl/create_swagger.cc b/drogon_ctl/create_swagger.cc new file mode 100644 index 0000000000..c8d474427a --- /dev/null +++ b/drogon_ctl/create_swagger.cc @@ -0,0 +1,187 @@ +/** + * + * create_swagger.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_swagger.h" +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#include +#else +#include +#include +#endif +#include + +using namespace drogon_ctl; + +static void forEachControllerHeaderIn( + const std::string &path, + const std::function &cb) +{ + DIR *dp; + struct dirent *dirp; + struct stat st; + + /* open dirent directory */ + if ((dp = opendir(path.c_str())) == NULL) + { + // perror("opendir:"); + LOG_ERROR << "can't open dir,path:" << path; + return; + } + + /** + * read all files in this dir + **/ + while ((dirp = readdir(dp)) != NULL) + { + /* ignore hidden files */ + if (dirp->d_name[0] == '.') + continue; + /* get dirent status */ + std::string filename = dirp->d_name; + if (filename.find(".h") != filename.length() - 2) + continue; + std::string fullname = path; + fullname.append("/").append(filename); + if (stat(fullname.c_str(), &st) == -1) + { + perror("stat"); + closedir(dp); + return; + } + + /* if dirent is a directory, find files recursively */ + if (S_ISDIR(st.st_mode)) + { + forEachControllerHeaderIn(fullname, cb); + } + else + { + cb(fullname); + } + } + closedir(dp); + return; +} +static void parseControllerHeader(const std::string &headerFile, + Json::Value &docs) +{ + std::ifstream infile(headerFile); + + for (std::string buffer; std::getline(infile, buffer);) + { + } +} +static std::string makeSwaggerDocument(const Json::Value &config) +{ + Json::Value ret; + ret["swagger"] = "2.0"; + ret["info"] = config.get("info", {}); + forEachControllerHeaderIn("controllers", [&ret](const std::string &header) { + std::cout << "Parsing " << header << " ...\n"; + parseControllerHeader(header, ret); + }); + return ret.toStyledString(); +} + +static void createSwaggerHeader(const std::string &path, + const Json::Value &config) +{ + drogon::HttpViewData data; + data["docs_url"] = config.get("docs_url", "/swagger").asString(); + std::ofstream ctlHeader(path + "/SwaggerCtrl.h", std::ofstream::out); + auto templ = DrTemplateBase::newTemplate("swagger_h.csp"); + ctlHeader << templ->genText(data); +} + +static void createSwaggerSource(const std::string &path, + const Json::Value &config) +{ + drogon::HttpViewData data; + data["docs_string"] = makeSwaggerDocument(config); + + std::ofstream ctlSource(path + "/SwaggerCtrl.cc", std::ofstream::out); + auto templ = DrTemplateBase::newTemplate("swagger_cc.csp"); + ctlSource << templ->genText(data); +} + +static void createSwagger(const std::string &path) +{ +#ifndef _WIN32 + DIR *dp; + if ((dp = opendir(path.c_str())) == NULL) + { + std::cerr << "No such file or directory : " << path << std::endl; + return; + } + closedir(dp); +#endif + auto configFile = path + "/swagger.json"; +#ifdef _WIN32 + if (_access(configFile.c_str(), 0) != 0) +#else + if (access(configFile.c_str(), 0) != 0) +#endif + { + std::cerr << "Config file " << configFile << " not found!" << std::endl; + exit(1); + } +#ifdef _WIN32 + if (_access(configFile.c_str(), 04) != 0) +#else + if (access(configFile.c_str(), R_OK) != 0) +#endif + { + std::cerr << "No permission to read config file " << configFile + << std::endl; + exit(1); + } + + std::ifstream infile(configFile.c_str(), std::ifstream::in); + if (infile) + { + Json::Value configJsonRoot; + try + { + infile >> configJsonRoot; + createSwaggerHeader(path, configJsonRoot); + createSwaggerSource(path, configJsonRoot); + } + catch (const std::exception &exception) + { + std::cerr << "Configuration file format error! in " << configFile + << ":" << std::endl; + std::cerr << exception.what() << std::endl; + exit(1); + } + } +} +void create_swagger::handleCommand(std::vector ¶meters) +{ + if (parameters.size() < 1) + { + std::cout << "please input a path to create the swagger controller" + << std::endl; + exit(1); + } + auto path = parameters[0]; + createSwagger(path); +} \ No newline at end of file diff --git a/drogon_ctl/create_swagger.h b/drogon_ctl/create_swagger.h new file mode 100644 index 0000000000..48525015a9 --- /dev/null +++ b/drogon_ctl/create_swagger.h @@ -0,0 +1,31 @@ +/** + * + * create_swagger.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 +#include "CommandHandler.h" +using namespace drogon; +namespace drogon_ctl +{ +class create_swagger : public DrObject, public CommandHandler +{ + public: + virtual void handleCommand(std::vector ¶meters) override; + virtual std::string script() override + { + return "create swagger docs controller"; + } +}; +} // namespace drogon_ctl \ No newline at end of file diff --git a/drogon_ctl/templates/swagger_cc.csp b/drogon_ctl/templates/swagger_cc.csp new file mode 100644 index 0000000000..74e220a988 --- /dev/null +++ b/drogon_ctl/templates/swagger_cc.csp @@ -0,0 +1,22 @@ +/** + * + * SwaggerCtrl.h + * This file is generated by drogon_ctl automatically, do not edit it. + * + */ + +#include "SwaggerCtrl.h" + +using namespace drogon; +using namespace drogon_swagger; + +void SwaggerCtrl::getOpenApiDocs( + const HttpRequestPtr &req, + std::function &&callback) +{ + Json::Value ret; + auto resp = drogon::HttpResponse::newHttpResponse(); + resp->setBody(R"([[docs_string]])"); + resp->setContentTypeCode(CT_APPLICATION_JSON); + callback(resp); +} \ No newline at end of file diff --git a/drogon_ctl/templates/swagger_h.csp b/drogon_ctl/templates/swagger_h.csp new file mode 100644 index 0000000000..4764b9e5fc --- /dev/null +++ b/drogon_ctl/templates/swagger_h.csp @@ -0,0 +1,23 @@ +/** + * + * SwaggerCtrl.h + * This file is generated by drogon_ctl automatically, do not edit it. + * + */ + +#include + +using namespace drogon; +namespace drogon_swagger +{ +class SwaggerCtrl : public drogon::HttpController +{ + public: + METHOD_LIST_BEGIN + ADD_METHOD_TO(SwaggerCtrl::getOpenApiDocs, "[[docs_url]]", Get); + METHOD_LIST_END + void getOpenApiDocs( + const drogon::HttpRequestPtr &req, + std::function &&callback); +}; +} // namespace drogon_swagger \ No newline at end of file diff --git a/drogon_ctl/templates/swagger_json.csp b/drogon_ctl/templates/swagger_json.csp new file mode 100644 index 0000000000..f5e1797376 --- /dev/null +++ b/drogon_ctl/templates/swagger_json.csp @@ -0,0 +1,16 @@ +{ + "docs_url": "/swagger", + "info": { + "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", + "version": "1.0.5", + "title": "Swagger Petstore", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + } +} \ No newline at end of file From de39fa2285fadf6fe36c76172fcd718f6e0dc8f1 Mon Sep 17 00:00:00 2001 From: an-tao Date: Sun, 25 Jul 2021 16:47:54 +0800 Subject: [PATCH 02/18] Add create_swagger.cc --- drogon_ctl/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/drogon_ctl/CMakeLists.txt b/drogon_ctl/CMakeLists.txt index f6b12bddab..ed379ff192 100755 --- a/drogon_ctl/CMakeLists.txt +++ b/drogon_ctl/CMakeLists.txt @@ -7,6 +7,7 @@ set(ctl_sources create_plugin.cc create_project.cc create_view.cc + create_swagger.cc help.cc main.cc press.cc From dc67f6e40261ca8c72bbf9ba65e9df43498ac112 Mon Sep 17 00:00:00 2001 From: an-tao Date: Sun, 25 Jul 2021 20:52:24 +0800 Subject: [PATCH 03/18] update --- drogon_ctl/create_swagger.cc | 59 +++++++++++++++--------------------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/drogon_ctl/create_swagger.cc b/drogon_ctl/create_swagger.cc index c8d474427a..8b8ade89ee 100644 --- a/drogon_ctl/create_swagger.cc +++ b/drogon_ctl/create_swagger.cc @@ -12,6 +12,7 @@ * */ +#include "../lib/src/filesystem.h" #include "create_swagger.h" #include #include @@ -30,55 +31,45 @@ #include using namespace drogon_ctl; +using drogon::filesystem; static void forEachControllerHeaderIn( - const std::string &path, + std::string strPath, const std::function &cb) { - DIR *dp; - struct dirent *dirp; - struct stat st; + while (1) + { + char cEnd = *strPath.rbegin(); + if (cEnd == '\\' || cEnd == '/') + { + strPath = strPath.substr(0, strPath.length() - 1); + } + else + { + break; + } + } - /* open dirent directory */ - if ((dp = opendir(path.c_str())) == NULL) + if (strPath.empty() || strPath == (".") || strPath == ("..")) + return; + + std::error_code ec; + filesystem::path fsPath(strPath); + if (!filesystem::exists(strPath, ec)) { - // perror("opendir:"); - LOG_ERROR << "can't open dir,path:" << path; return; } - - /** - * read all files in this dir - **/ - while ((dirp = readdir(dp)) != NULL) + for (auto &itr : filesystem::directory_iterator(fsPath)) { - /* ignore hidden files */ - if (dirp->d_name[0] == '.') - continue; - /* get dirent status */ - std::string filename = dirp->d_name; - if (filename.find(".h") != filename.length() - 2) - continue; - std::string fullname = path; - fullname.append("/").append(filename); - if (stat(fullname.c_str(), &st) == -1) + if (filesystem::is_directory(itr.status())) { - perror("stat"); - closedir(dp); - return; - } - - /* if dirent is a directory, find files recursively */ - if (S_ISDIR(st.st_mode)) - { - forEachControllerHeaderIn(fullname, cb); + forEachControllerHeaderIn(itr.path().string(), cb); } else { - cb(fullname); + cb(itr.path().string()); } } - closedir(dp); return; } static void parseControllerHeader(const std::string &headerFile, From f6aa119d4c2fed7107cc0745c120da14c3a201f8 Mon Sep 17 00:00:00 2001 From: an-tao Date: Sun, 25 Jul 2021 23:33:42 +0800 Subject: [PATCH 04/18] Find .h --- drogon_ctl/create_swagger.cc | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drogon_ctl/create_swagger.cc b/drogon_ctl/create_swagger.cc index 8b8ade89ee..2007afd73c 100644 --- a/drogon_ctl/create_swagger.cc +++ b/drogon_ctl/create_swagger.cc @@ -31,7 +31,6 @@ #include using namespace drogon_ctl; -using drogon::filesystem; static void forEachControllerHeaderIn( std::string strPath, @@ -54,20 +53,24 @@ static void forEachControllerHeaderIn( return; std::error_code ec; - filesystem::path fsPath(strPath); - if (!filesystem::exists(strPath, ec)) + drogon::filesystem::path fsPath(strPath); + if (!drogon::filesystem::exists(strPath, ec)) { return; } - for (auto &itr : filesystem::directory_iterator(fsPath)) + for (auto &itr : drogon::filesystem::directory_iterator(fsPath)) { - if (filesystem::is_directory(itr.status())) + if (drogon::filesystem::is_directory(itr.status())) { forEachControllerHeaderIn(itr.path().string(), cb); } else { - cb(itr.path().string()); + auto fileName = itr.path().string(); + if (fileName.find(".h") == fileName.length() - 2) + { + cb(fileName); + } } } return; From 304bce4860c34bd5ce5b384bfad9662cc5a1570f Mon Sep 17 00:00:00 2001 From: an-tao Date: Fri, 30 Jul 2021 10:17:08 +0800 Subject: [PATCH 05/18] Update create_sagger --- drogon_ctl/create_swagger.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drogon_ctl/create_swagger.cc b/drogon_ctl/create_swagger.cc index 2007afd73c..d7a0ce2279 100644 --- a/drogon_ctl/create_swagger.cc +++ b/drogon_ctl/create_swagger.cc @@ -52,7 +52,7 @@ static void forEachControllerHeaderIn( if (strPath.empty() || strPath == (".") || strPath == ("..")) return; - std::error_code ec; + drogon::error_code ec; drogon::filesystem::path fsPath(strPath); if (!drogon::filesystem::exists(strPath, ec)) { From 624553a10af84ab45bfd3ac60d08a3c871137c56 Mon Sep 17 00:00:00 2001 From: an-tao Date: Sat, 31 Jul 2021 21:46:46 +0800 Subject: [PATCH 06/18] libclang --- drogon_ctl/CMakeLists.txt | 11 +++++++++++ drogon_ctl/create_swagger.cc | 17 ++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/drogon_ctl/CMakeLists.txt b/drogon_ctl/CMakeLists.txt index ed379ff192..431651dd4e 100755 --- a/drogon_ctl/CMakeLists.txt +++ b/drogon_ctl/CMakeLists.txt @@ -84,6 +84,17 @@ else(WIN32) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/dg_ctl" DESTINATION ${INSTALL_BIN_DIR}) endif(WIN32) +find_package(Clang REQUIRED) +include_directories(${LLVM_INCLUDE_DIRS}) +separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) +add_definitions(${LLVM_DEFINITIONS_LIST}) +# Find the libraries that correspond to the LLVM components +# that we wish to use +llvm_map_components_to_libnames(llvm_libs support core irreader) + +# Link against LLVM libraries +target_link_libraries(drogon_ctl PRIVATE ${llvm_libs}) + 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) diff --git a/drogon_ctl/create_swagger.cc b/drogon_ctl/create_swagger.cc index d7a0ce2279..5fdb9ff1ba 100644 --- a/drogon_ctl/create_swagger.cc +++ b/drogon_ctl/create_swagger.cc @@ -30,6 +30,8 @@ #endif #include +#include + using namespace drogon_ctl; static void forEachControllerHeaderIn( @@ -78,11 +80,20 @@ static void forEachControllerHeaderIn( static void parseControllerHeader(const std::string &headerFile, Json::Value &docs) { - std::ifstream infile(headerFile); - - for (std::string buffer; std::getline(infile, buffer);) + CXIndex index = clang_createIndex(0, 0); + CXTranslationUnit unit = clang_parseTranslationUnit( + index, + headerFile.c_str(), nullptr, 0, + nullptr, 0, + CXTranslationUnit_None); + if (unit == nullptr) { + std::cerr << "Unable to parse translation unit. Quitting." << std::endl; + exit(-1); } + + clang_disposeTranslationUnit(unit); + clang_disposeIndex(index); } static std::string makeSwaggerDocument(const Json::Value &config) { From ec3f07921f15b55d9edbb71deae5347e9e85ada4 Mon Sep 17 00:00:00 2001 From: an-tao Date: Sun, 15 Aug 2021 18:25:12 +0800 Subject: [PATCH 07/18] Remove the llvm dependency --- drogon_ctl/CMakeLists.txt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drogon_ctl/CMakeLists.txt b/drogon_ctl/CMakeLists.txt index 431651dd4e..642d15e53e 100755 --- a/drogon_ctl/CMakeLists.txt +++ b/drogon_ctl/CMakeLists.txt @@ -84,16 +84,16 @@ else(WIN32) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/dg_ctl" DESTINATION ${INSTALL_BIN_DIR}) endif(WIN32) -find_package(Clang REQUIRED) -include_directories(${LLVM_INCLUDE_DIRS}) -separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) -add_definitions(${LLVM_DEFINITIONS_LIST}) -# Find the libraries that correspond to the LLVM components -# that we wish to use -llvm_map_components_to_libnames(llvm_libs support core irreader) - -# Link against LLVM libraries -target_link_libraries(drogon_ctl PRIVATE ${llvm_libs}) +#find_package(Clang REQUIRED) +#include_directories(${LLVM_INCLUDE_DIRS}) +#separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) +#add_definitions(${LLVM_DEFINITIONS_LIST}) +## Find the libraries that correspond to the LLVM components +## that we wish to use +#llvm_map_components_to_libnames(llvm_libs support core irreader) +# +## Link against LLVM libraries +#target_link_libraries(drogon_ctl PRIVATE ${llvm_libs}) set(ctl_targets _drogon_ctl drogon_ctl) set_property(TARGET ${ctl_targets} PROPERTY CXX_STANDARD ${DROGON_CXX_STANDARD}) From 7346ea49e4490df38b881f87bbaa500c70175a56 Mon Sep 17 00:00:00 2001 From: an-tao Date: Sun, 15 Aug 2021 19:20:30 +0800 Subject: [PATCH 08/18] use regex to find namespace --- drogon_ctl/create_swagger.cc | 40 +++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/drogon_ctl/create_swagger.cc b/drogon_ctl/create_swagger.cc index 5fdb9ff1ba..c3652a7363 100644 --- a/drogon_ctl/create_swagger.cc +++ b/drogon_ctl/create_swagger.cc @@ -29,8 +29,7 @@ #include #endif #include - -#include +#include using namespace drogon_ctl; @@ -69,7 +68,8 @@ static void forEachControllerHeaderIn( else { auto fileName = itr.path().string(); - if (fileName.find(".h") == fileName.length() - 2) + if (fileName.find(".h") == fileName.length() - 2 || + fileName.find(".hpp") == fileName.length() - 4) { cb(fileName); } @@ -80,20 +80,32 @@ static void forEachControllerHeaderIn( static void parseControllerHeader(const std::string &headerFile, Json::Value &docs) { - CXIndex index = clang_createIndex(0, 0); - CXTranslationUnit unit = clang_parseTranslationUnit( - index, - headerFile.c_str(), nullptr, 0, - nullptr, 0, - CXTranslationUnit_None); - if (unit == nullptr) + std::ifstream infile(utils::toNativePath(headerFile), + std::ifstream::binary); + if (!infile) { - std::cerr << "Unable to parse translation unit. Quitting." << std::endl; - exit(-1); + std::cout << "can't open the header file:" << headerFile << "\n"; + return; } + std::streambuf *pbuf = infile.rdbuf(); + std::streamsize filesize = pbuf->pubseekoff(0, std::ifstream::end); + pbuf->pubseekoff(0, std::ifstream::beg); // rewind + std::string fileContent; + fileContent.resize(filesize); + pbuf->sgetn(&fileContent[0], filesize); + std::cout << fileContent; - clang_disposeTranslationUnit(unit); - clang_disposeIndex(index); + std::regex rx(R"(namespace[ \r\n]+([^ \r\n]+)[ \r\n]*\{)"); + std::smatch results; + while (std::regex_search(fileContent, results, rx)) + { + if (results.size() > 1) + { + for (int i = 1; i < results.size(); i++) + std::cout << "nnn:" << i << results[i] << std::endl; + } + fileContent = results.suffix(); + } } static std::string makeSwaggerDocument(const Json::Value &config) { From 66878d2318a8e74fed7f39c3ab30847f5ca06bfc Mon Sep 17 00:00:00 2001 From: an-tao Date: Sun, 15 Aug 2021 22:43:18 +0800 Subject: [PATCH 09/18] Parsing header works --- drogon_ctl/create_swagger.cc | 208 +++++++++++++++++++++++++++++++++-- 1 file changed, 197 insertions(+), 11 deletions(-) diff --git a/drogon_ctl/create_swagger.cc b/drogon_ctl/create_swagger.cc index c3652a7363..ad79c2bacf 100644 --- a/drogon_ctl/create_swagger.cc +++ b/drogon_ctl/create_swagger.cc @@ -32,7 +32,202 @@ #include using namespace drogon_ctl; +namespace internal +{ +class StructNode; +using StructNodePtr = std::shared_ptr; +class StructNode +{ + public: + enum NodeType + { + kRoot = 0, + kClass, + kNameSpace + }; + StructNode(const std::string &content, + const std::string &name, + NodeType type = kRoot) + : type_(type), name_(name), content_(content) + { + LOG_DEBUG << "new node:" << name << "-" << type; + if (type != kClass) + children_ = parse(content); + } + const std::string &content() const + { + return content_; + } + const std::string &name() const + { + return name_; + } + NodeType type() const + { + return type_; + } + void print() const + { + print(0); + } + + private: + std::vector children_; + std::string content_; + NodeType type_; + std::string name_; + std::pair findContentOfClassOrNameSpace( + const std::string &content, + std::string::size_type start) + { + int braces = 0; + std::string::size_type pos1{start}; + std::string::size_type pos2{content.size() - 1}; + for (auto i = start; i < content.size(); i++) + { + if (content[i] == '{') + { + braces++; + if (braces == 1) + { + pos1 = i + 1; + } + } + else if (content[i] == '}') + { + braces--; + if (braces == 0) + { + pos2 = i; + break; + } + } + } + return {content.substr(pos1, pos2 - pos1), content.substr(pos2 + 1)}; + } + std::pair findClass(const std::string &content) + { + LOG_DEBUG << "findClass: " << content; + if (content.empty()) + return {nullptr, ""}; + std::regex rx(R"(class[ \r\n]+([^ \r\n]+)[ \r\n]+)"); + std::smatch results; + if (std::regex_search(content, results, rx)) + { + assert(results.size() > 1); + auto nextPart = + findContentOfClassOrNameSpace(content, results.position()); + return {std::make_shared(nextPart.first, + results[1].str(), + kClass), + nextPart.second}; + } + return {nullptr, ""}; + } + std::tuple findNameSpace( + const std::string &content) + { + LOG_DEBUG << "findNameSpace"; + if (content.empty()) + return {"", nullptr, ""}; + std::regex rx(R"(namespace[ \r\n]+([^ \r\n]+)[ \r\n]*\{)"); + std::smatch results; + if (std::regex_search(content, results, rx)) + { + assert(results.size() > 1); + auto pos = results.position(); + auto first = content.substr(0, pos); + auto nextPart = findContentOfClassOrNameSpace(content, pos); + auto npNodePtr = std::make_shared(nextPart.first, + results[1].str(), + kNameSpace); + + return {first, npNodePtr, nextPart.second}; + } + else + { + return {"", nullptr, ""}; + } + } + std::vector parse(const std::string &content) + { + std::vector res; + auto t = findNameSpace(content); + if (std::get<1>(t)) + { + res.emplace_back(std::get<1>(t)); + auto firstPart = std::get<0>(t); + while (1) + { + auto p = findClass(firstPart); + if (p.first) + { + res.emplace_back(p.first); + firstPart = p.second; + } + else + { + break; + } + } + auto subsequentNode = parse(std::get<2>(t)); + for (auto &node : subsequentNode) + { + res.emplace_back(node); + } + return res; + } + std::string classPart = content; + while (1) + { + auto p = findClass(classPart); + if (p.first) + { + res.emplace_back(p.first); + classPart = p.second; + } + else + { + break; + } + } + return res; + } + void print(int indent) const + { + std::string ind(indent, ' '); + std::cout << ind; + switch (type_) + { + case kRoot: + { + std::cout << "Root\n" << ind << "{\n"; + break; + } + case kClass: + { + std::cout << "class " << name_ << "\n" << ind << "{\n"; + break; + } + case kNameSpace: + { + std::cout << "namespace " << name_ << "\n" << ind << "{\n"; + break; + } + } + + for (auto child : children_) + { + child->print(indent + 2); + } + std::cout << ind << "}"; + if (type_ == kClass) + std::cout << ";"; + std::cout << "\n"; + } +}; +} // namespace internal static void forEachControllerHeaderIn( std::string strPath, const std::function &cb) @@ -95,17 +290,8 @@ static void parseControllerHeader(const std::string &headerFile, pbuf->sgetn(&fileContent[0], filesize); std::cout << fileContent; - std::regex rx(R"(namespace[ \r\n]+([^ \r\n]+)[ \r\n]*\{)"); - std::smatch results; - while (std::regex_search(fileContent, results, rx)) - { - if (results.size() > 1) - { - for (int i = 1; i < results.size(); i++) - std::cout << "nnn:" << i << results[i] << std::endl; - } - fileContent = results.suffix(); - } + internal::StructNode r(fileContent, "root", internal::StructNode::kRoot); + r.print(); } static std::string makeSwaggerDocument(const Json::Value &config) { From c467c995fe87ddfb96e707c71dea370e0ed8523b Mon Sep 17 00:00:00 2001 From: an-tao Date: Sun, 15 Aug 2021 23:12:49 +0800 Subject: [PATCH 10/18] fix --- drogon_ctl/create_swagger.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drogon_ctl/create_swagger.cc b/drogon_ctl/create_swagger.cc index ad79c2bacf..e95d7ca398 100644 --- a/drogon_ctl/create_swagger.cc +++ b/drogon_ctl/create_swagger.cc @@ -111,7 +111,7 @@ class StructNode LOG_DEBUG << "findClass: " << content; if (content.empty()) return {nullptr, ""}; - std::regex rx(R"(class[ \r\n]+([^ \r\n]+)[ \r\n]+)"); + std::regex rx(R"(class[ \r\n]+([^ \r\n\{]+)[ \r\n\{:]+)"); std::smatch results; if (std::regex_search(content, results, rx)) { From c35c1f262151619fafd064a784d4cbf650f43d48 Mon Sep 17 00:00:00 2001 From: an-tao Date: Sun, 15 Aug 2021 23:27:11 +0800 Subject: [PATCH 11/18] Fix for old compiler --- drogon_ctl/create_swagger.cc | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/drogon_ctl/create_swagger.cc b/drogon_ctl/create_swagger.cc index e95d7ca398..7e109ddb6c 100644 --- a/drogon_ctl/create_swagger.cc +++ b/drogon_ctl/create_swagger.cc @@ -104,13 +104,15 @@ class StructNode } } } - return {content.substr(pos1, pos2 - pos1), content.substr(pos2 + 1)}; + return std::pair(content.substr(pos1, + pos2 - pos1), + content.substr(pos2 + 1)); } std::pair findClass(const std::string &content) { LOG_DEBUG << "findClass: " << content; if (content.empty()) - return {nullptr, ""}; + return std::pair(nullptr, ""); std::regex rx(R"(class[ \r\n]+([^ \r\n\{]+)[ \r\n\{:]+)"); std::smatch results; if (std::regex_search(content, results, rx)) @@ -118,19 +120,22 @@ class StructNode assert(results.size() > 1); auto nextPart = findContentOfClassOrNameSpace(content, results.position()); - return {std::make_shared(nextPart.first, - results[1].str(), - kClass), - nextPart.second}; + return std::pair( + std::make_shared(nextPart.first, + results[1].str(), + kClass), + nextPart.second); } - return {nullptr, ""}; + return std::pair(nullptr, ""); } std::tuple findNameSpace( const std::string &content) { LOG_DEBUG << "findNameSpace"; if (content.empty()) - return {"", nullptr, ""}; + return std::tuple("", + nullptr, + ""); std::regex rx(R"(namespace[ \r\n]+([^ \r\n]+)[ \r\n]*\{)"); std::smatch results; if (std::regex_search(content, results, rx)) @@ -143,11 +148,14 @@ class StructNode results[1].str(), kNameSpace); - return {first, npNodePtr, nextPart.second}; + return std::tuple( + first, npNodePtr, nextPart.second); } else { - return {"", nullptr, ""}; + return std::tuple("", + nullptr, + ""); } } std::vector parse(const std::string &content) @@ -221,6 +229,10 @@ class StructNode { child->print(indent + 2); } + if (type_ == kClass) + { + std::cout << content_ << "\n"; + } std::cout << ind << "}"; if (type_ == kClass) std::cout << ";"; From 31832ace51f2a2376f3c4df708bd0dc343f64637 Mon Sep 17 00:00:00 2001 From: an-tao Date: Tue, 17 Aug 2021 22:43:22 +0800 Subject: [PATCH 12/18] Add some class definations for swagger --- drogon_ctl/CMakeLists.txt | 1 + drogon_ctl/HandlerParser.cc | 168 ++++++++++++++++++++++++++++ drogon_ctl/HandlerParser.h | 100 +++++++++++++++++ drogon_ctl/create_swagger.cc | 208 +---------------------------------- 4 files changed, 270 insertions(+), 207 deletions(-) create mode 100644 drogon_ctl/HandlerParser.cc create mode 100644 drogon_ctl/HandlerParser.h diff --git a/drogon_ctl/CMakeLists.txt b/drogon_ctl/CMakeLists.txt index 642d15e53e..7c0295b70b 100755 --- a/drogon_ctl/CMakeLists.txt +++ b/drogon_ctl/CMakeLists.txt @@ -7,6 +7,7 @@ set(ctl_sources create_plugin.cc create_project.cc create_view.cc + HandlerParser.cc create_swagger.cc help.cc main.cc diff --git a/drogon_ctl/HandlerParser.cc b/drogon_ctl/HandlerParser.cc new file mode 100644 index 0000000000..b973f820c5 --- /dev/null +++ b/drogon_ctl/HandlerParser.cc @@ -0,0 +1,168 @@ +#include "HandlerParser.h" +#include +#include + +using namespace drogon::internal; + +std::pair StructNode::findContentOfClassOrNameSpace( + const std::string &content, + std::string::size_type start) +{ + int braces = 0; + std::string::size_type pos1{start}; + std::string::size_type pos2{content.size() - 1}; + for (auto i = start; i < content.size(); i++) + { + if (content[i] == '{') + { + braces++; + if (braces == 1) + { + pos1 = i + 1; + } + } + else if (content[i] == '}') + { + braces--; + if (braces == 0) + { + pos2 = i; + break; + } + } + } + return std::pair(content.substr(pos1, + pos2 - pos1), + content.substr(pos2 + 1)); +} +std::pair StructNode::findClass( + const std::string &content) +{ + LOG_DEBUG << "findClass: " << content; + if (content.empty()) + return std::pair(nullptr, ""); + std::regex rx(R"(class[ \r\n]+([^ \r\n\{]+)[ \r\n\{:]+)"); + std::smatch results; + if (std::regex_search(content, results, rx)) + { + assert(results.size() > 1); + auto nextPart = + findContentOfClassOrNameSpace(content, results.position()); + return std::pair( + std::make_shared(nextPart.first, + results[1].str(), + kClass), + nextPart.second); + } + return std::pair(nullptr, ""); +} +std::tuple StructNode::findNameSpace( + const std::string &content) +{ + LOG_DEBUG << "findNameSpace"; + if (content.empty()) + return std::tuple("", + nullptr, + ""); + std::regex rx(R"(namespace[ \r\n]+([^ \r\n]+)[ \r\n]*\{)"); + std::smatch results; + if (std::regex_search(content, results, rx)) + { + assert(results.size() > 1); + auto pos = results.position(); + auto first = content.substr(0, pos); + auto nextPart = findContentOfClassOrNameSpace(content, pos); + auto npNodePtr = std::make_shared(nextPart.first, + results[1].str(), + kNameSpace); + + return std::tuple( + first, npNodePtr, nextPart.second); + } + else + { + return std::tuple("", + nullptr, + ""); + } +} +std::vector StructNode::parse(const std::string &content) +{ + std::vector res; + auto t = findNameSpace(content); + if (std::get<1>(t)) + { + res.emplace_back(std::get<1>(t)); + auto firstPart = std::get<0>(t); + while (1) + { + auto p = findClass(firstPart); + if (p.first) + { + res.emplace_back(p.first); + firstPart = p.second; + } + else + { + break; + } + } + auto subsequentNode = parse(std::get<2>(t)); + for (auto &node : subsequentNode) + { + res.emplace_back(node); + } + return res; + } + std::string classPart = content; + while (1) + { + auto p = findClass(classPart); + if (p.first) + { + res.emplace_back(p.first); + classPart = p.second; + } + else + { + break; + } + } + return res; +} +void StructNode::print(int indent) const +{ + std::string ind(indent, ' '); + std::cout << ind; + switch (type_) + { + case kRoot: + { + std::cout << "Root\n" << ind << "{\n"; + break; + } + case kClass: + { + std::cout << "class " << name_ << "\n" << ind << "{\n"; + break; + } + case kNameSpace: + { + std::cout << "namespace " << name_ << "\n" << ind << "{\n"; + break; + } + } + + for (auto child : children_) + { + child->print(indent + 2); + } + if (type_ == kClass) + { + std::cout << content_ << "\n"; + } + std::cout << ind << "}"; + if (type_ == kClass) + std::cout << ";"; + std::cout << "\n"; +} \ No newline at end of file diff --git a/drogon_ctl/HandlerParser.h b/drogon_ctl/HandlerParser.h new file mode 100644 index 0000000000..1d7dcd6ed2 --- /dev/null +++ b/drogon_ctl/HandlerParser.h @@ -0,0 +1,100 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace drogon +{ +namespace internal +{ +class StructNode; +using StructNodePtr = std::shared_ptr; +class StructNode +{ + public: + enum NodeType + { + kRoot = 0, + kClass, + kNameSpace + }; + + StructNode(const std::string &content, + const std::string &name, + NodeType type = kRoot) + : type_(type), name_(name), content_(content) + { + LOG_DEBUG << "new node:" << name << "-" << type; + if (type != kClass) + children_ = parse(content); + } + const std::string &content() const + { + return content_; + } + const std::string &name() const + { + return name_; + } + NodeType type() const + { + return type_; + } + void print() const + { + print(0); + } + + private: + std::vector children_; + std::string content_; + NodeType type_; + std::string name_; + std::pair findContentOfClassOrNameSpace( + const std::string &content, + std::string::size_type start); + std::pair findClass(const std::string &content); + std::tuple findNameSpace( + const std::string &content); + std::vector parse(const std::string &content); + void print(int indent) const; +}; + +class ParametersInfo +{ + public: + std::string getName() const; + std::string getType() const; + std::string getConstraint() const; +}; +class RoutingInfo +{ + public: + std::string getPath() const; + std::vector getFilters() const; + std::vector getHttpMethods() const; + std::string getScripts() const; + std::string getContentType() const; + std::string getReturnContentType() const; + std::vector getParameterInfo() const; + std::vector getReturnParameterInfo() const; +}; + +class HandlerInfo +{ + public: + std::string getClassName() const; + std::string getNamespace() const; + std::vector getRoutingInfo() const; +}; +class HandlerParser +{ + public: + std::vector parse(); + HandlerParser(const StructNodePtr root); +}; +} // namespace internal +} // namespace drogon \ No newline at end of file diff --git a/drogon_ctl/create_swagger.cc b/drogon_ctl/create_swagger.cc index 7e109ddb6c..698519df49 100644 --- a/drogon_ctl/create_swagger.cc +++ b/drogon_ctl/create_swagger.cc @@ -14,6 +14,7 @@ #include "../lib/src/filesystem.h" #include "create_swagger.h" +#include "HandlerParser.h" #include #include #include @@ -32,214 +33,7 @@ #include using namespace drogon_ctl; -namespace internal -{ -class StructNode; -using StructNodePtr = std::shared_ptr; -class StructNode -{ - public: - enum NodeType - { - kRoot = 0, - kClass, - kNameSpace - }; - - StructNode(const std::string &content, - const std::string &name, - NodeType type = kRoot) - : type_(type), name_(name), content_(content) - { - LOG_DEBUG << "new node:" << name << "-" << type; - if (type != kClass) - children_ = parse(content); - } - const std::string &content() const - { - return content_; - } - const std::string &name() const - { - return name_; - } - NodeType type() const - { - return type_; - } - void print() const - { - print(0); - } - - private: - std::vector children_; - std::string content_; - NodeType type_; - std::string name_; - std::pair findContentOfClassOrNameSpace( - const std::string &content, - std::string::size_type start) - { - int braces = 0; - std::string::size_type pos1{start}; - std::string::size_type pos2{content.size() - 1}; - for (auto i = start; i < content.size(); i++) - { - if (content[i] == '{') - { - braces++; - if (braces == 1) - { - pos1 = i + 1; - } - } - else if (content[i] == '}') - { - braces--; - if (braces == 0) - { - pos2 = i; - break; - } - } - } - return std::pair(content.substr(pos1, - pos2 - pos1), - content.substr(pos2 + 1)); - } - std::pair findClass(const std::string &content) - { - LOG_DEBUG << "findClass: " << content; - if (content.empty()) - return std::pair(nullptr, ""); - std::regex rx(R"(class[ \r\n]+([^ \r\n\{]+)[ \r\n\{:]+)"); - std::smatch results; - if (std::regex_search(content, results, rx)) - { - assert(results.size() > 1); - auto nextPart = - findContentOfClassOrNameSpace(content, results.position()); - return std::pair( - std::make_shared(nextPart.first, - results[1].str(), - kClass), - nextPart.second); - } - return std::pair(nullptr, ""); - } - std::tuple findNameSpace( - const std::string &content) - { - LOG_DEBUG << "findNameSpace"; - if (content.empty()) - return std::tuple("", - nullptr, - ""); - std::regex rx(R"(namespace[ \r\n]+([^ \r\n]+)[ \r\n]*\{)"); - std::smatch results; - if (std::regex_search(content, results, rx)) - { - assert(results.size() > 1); - auto pos = results.position(); - auto first = content.substr(0, pos); - auto nextPart = findContentOfClassOrNameSpace(content, pos); - auto npNodePtr = std::make_shared(nextPart.first, - results[1].str(), - kNameSpace); - return std::tuple( - first, npNodePtr, nextPart.second); - } - else - { - return std::tuple("", - nullptr, - ""); - } - } - std::vector parse(const std::string &content) - { - std::vector res; - auto t = findNameSpace(content); - if (std::get<1>(t)) - { - res.emplace_back(std::get<1>(t)); - auto firstPart = std::get<0>(t); - while (1) - { - auto p = findClass(firstPart); - if (p.first) - { - res.emplace_back(p.first); - firstPart = p.second; - } - else - { - break; - } - } - auto subsequentNode = parse(std::get<2>(t)); - for (auto &node : subsequentNode) - { - res.emplace_back(node); - } - return res; - } - std::string classPart = content; - while (1) - { - auto p = findClass(classPart); - if (p.first) - { - res.emplace_back(p.first); - classPart = p.second; - } - else - { - break; - } - } - return res; - } - void print(int indent) const - { - std::string ind(indent, ' '); - std::cout << ind; - switch (type_) - { - case kRoot: - { - std::cout << "Root\n" << ind << "{\n"; - break; - } - case kClass: - { - std::cout << "class " << name_ << "\n" << ind << "{\n"; - break; - } - case kNameSpace: - { - std::cout << "namespace " << name_ << "\n" << ind << "{\n"; - break; - } - } - - for (auto child : children_) - { - child->print(indent + 2); - } - if (type_ == kClass) - { - std::cout << content_ << "\n"; - } - std::cout << ind << "}"; - if (type_ == kClass) - std::cout << ";"; - std::cout << "\n"; - } -}; -} // namespace internal static void forEachControllerHeaderIn( std::string strPath, const std::function &cb) From 77303e40fd48e27c0aa7dde023f0d4018cfce737 Mon Sep 17 00:00:00 2001 From: an-tao Date: Sun, 28 Nov 2021 23:25:54 +0800 Subject: [PATCH 13/18] Update --- lib/src/HttpFileImpl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/HttpFileImpl.cc b/lib/src/HttpFileImpl.cc index 57323ca347..177c9587e7 100644 --- a/lib/src/HttpFileImpl.cc +++ b/lib/src/HttpFileImpl.cc @@ -122,7 +122,7 @@ int HttpFileImpl::saveTo( } else { - LOG_ERROR << "save failed!"; + LOG_ERROR << "save failed! file=" << pathAndFileName; return -1; } } From d9d19906b6a7287ce31f35bac9649f731ca7b046 Mon Sep 17 00:00:00 2001 From: an-tao Date: Sun, 28 Nov 2021 23:50:39 +0800 Subject: [PATCH 14/18] sys log --- lib/src/HttpFileImpl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/HttpFileImpl.cc b/lib/src/HttpFileImpl.cc index 177c9587e7..59d372520d 100644 --- a/lib/src/HttpFileImpl.cc +++ b/lib/src/HttpFileImpl.cc @@ -122,7 +122,7 @@ int HttpFileImpl::saveTo( } else { - LOG_ERROR << "save failed! file=" << pathAndFileName; + LOG_SYSERR << "save failed! file=" << pathAndFileName; return -1; } } From acde804988fbd860ca945f03c4b7b142bd0e2c99 Mon Sep 17 00:00:00 2001 From: an-tao Date: Mon, 29 Nov 2021 00:28:04 +0800 Subject: [PATCH 15/18] flag --- lib/src/HttpFileImpl.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/HttpFileImpl.cc b/lib/src/HttpFileImpl.cc index 59d372520d..d4935b30b6 100644 --- a/lib/src/HttpFileImpl.cc +++ b/lib/src/HttpFileImpl.cc @@ -113,7 +113,8 @@ int HttpFileImpl::saveTo( { LOG_TRACE << "save uploaded file:" << pathAndFileName; auto wPath = utils::toNativePath(pathAndFileName.native()); - std::ofstream file(wPath, std::ios::binary); + std::ofstream file(wPath, + std::ios::binary | std::ios::out | std::ios::trunc); if (file.is_open()) { file.write(fileContent_.data(), fileContent_.size()); From 745346fd769d4f08353165c86f9ec3f49087460e Mon Sep 17 00:00:00 2001 From: antao Date: Thu, 30 Apr 2026 23:24:38 +0800 Subject: [PATCH 16/18] Update --- drogon_ctl/create_swagger.cc | 895 ++++++++++++++++++++++++-- drogon_ctl/templates/swagger_cc.csp | 2 +- drogon_ctl/templates/swagger_json.csp | 1 + 3 files changed, 852 insertions(+), 46 deletions(-) diff --git a/drogon_ctl/create_swagger.cc b/drogon_ctl/create_swagger.cc index 698519df49..d272bf4b38 100644 --- a/drogon_ctl/create_swagger.cc +++ b/drogon_ctl/create_swagger.cc @@ -12,33 +12,819 @@ * */ -#include "../lib/src/filesystem.h" #include "create_swagger.h" -#include "HandlerParser.h" #include #include #include +#include +#include +#include #include #include #include #ifndef _WIN32 #include #include -#include #else #include #include #endif #include +#include +#include #include using namespace drogon_ctl; +namespace +{ +enum class ContextType +{ + kNamespace, + kClass +}; + +struct ContextNode +{ + ContextType type; + std::string name; +}; + +static std::string trim(const std::string &s) +{ + size_t begin = 0; + while (begin < s.size() && std::isspace(static_cast(s[begin]))) + { + ++begin; + } + size_t end = s.size(); + while (end > begin && std::isspace(static_cast(s[end - 1]))) + { + --end; + } + return s.substr(begin, end - begin); +} + +static std::string toLower(std::string s) +{ + std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + return s; +} + +static bool startsWith(const std::string &s, + size_t start, + const std::string &prefix) +{ + if (start + prefix.size() > s.size()) + { + return false; + } + return s.compare(start, prefix.size(), prefix) == 0; +} + +static std::string stripCppComments(const std::string &content) +{ + std::string out; + out.reserve(content.size()); + bool inString = false; + bool inChar = false; + bool inRawString = false; + size_t rawEndPos = 0; + + for (size_t i = 0; i < content.size(); ++i) + { + if (inRawString) + { + out.push_back(content[i]); + if (i + 1 >= rawEndPos) + { + inRawString = false; + } + continue; + } + + if (!inString && !inChar && startsWith(content, i, "R\"")) + { + auto open = content.find('(', i + 2); + if (open != std::string::npos) + { + auto marker = content.substr(i + 2, open - (i + 2)); + auto endMarker = ")" + marker + "\""; + auto endPos = content.find(endMarker, open + 1); + if (endPos != std::string::npos) + { + rawEndPos = endPos + endMarker.size() - 1; + inRawString = true; + out.push_back(content[i]); + continue; + } + } + } + + if (!inString && !inChar && i + 1 < content.size() && content[i] == '/' && + content[i + 1] == '/') + { + while (i < content.size() && content[i] != '\n') + { + ++i; + } + if (i < content.size()) + { + out.push_back(content[i]); + } + continue; + } + + if (!inString && !inChar && i + 1 < content.size() && content[i] == '/' && + content[i + 1] == '*') + { + i += 2; + while (i + 1 < content.size() && + !(content[i] == '*' && content[i + 1] == '/')) + { + ++i; + } + ++i; + continue; + } + + out.push_back(content[i]); + if (!inChar && content[i] == '"' && (i == 0 || content[i - 1] != '\\')) + { + inString = !inString; + } + else if (!inString && content[i] == '\'' && + (i == 0 || content[i - 1] != '\\')) + { + inChar = !inChar; + } + } + + return out; +} + +static bool parseMacroInvocation(const std::string &content, + size_t openParenPos, + std::string &argsContent, + size_t &endPos) +{ + if (openParenPos >= content.size() || content[openParenPos] != '(') + { + return false; + } + int depth = 0; + bool inString = false; + bool inChar = false; + for (size_t i = openParenPos; i < content.size(); ++i) + { + auto ch = content[i]; + if (!inChar && ch == '"' && (i == 0 || content[i - 1] != '\\')) + { + inString = !inString; + continue; + } + if (!inString && ch == '\'' && (i == 0 || content[i - 1] != '\\')) + { + inChar = !inChar; + continue; + } + if (inString || inChar) + { + continue; + } + + if (ch == '(') + { + ++depth; + } + else if (ch == ')') + { + --depth; + if (depth == 0) + { + argsContent = content.substr(openParenPos + 1, + i - openParenPos - 1); + endPos = i; + return true; + } + } + } + + return false; +} + +static std::vector splitTopLevelArgs(const std::string &args) +{ + std::vector out; + std::string current; + int parenDepth = 0; + int braceDepth = 0; + int bracketDepth = 0; + bool inString = false; + bool inChar = false; + + for (size_t i = 0; i < args.size(); ++i) + { + char ch = args[i]; + + if (!inChar && ch == '"' && (i == 0 || args[i - 1] != '\\')) + { + inString = !inString; + current.push_back(ch); + continue; + } + if (!inString && ch == '\'' && (i == 0 || args[i - 1] != '\\')) + { + inChar = !inChar; + current.push_back(ch); + continue; + } + if (inString || inChar) + { + current.push_back(ch); + continue; + } + + if (ch == '(') + ++parenDepth; + else if (ch == ')') + --parenDepth; + else if (ch == '{') + ++braceDepth; + else if (ch == '}') + --braceDepth; + else if (ch == '[') + ++bracketDepth; + else if (ch == ']') + --bracketDepth; + + if (ch == ',' && parenDepth == 0 && braceDepth == 0 && bracketDepth == 0) + { + out.emplace_back(trim(current)); + current.clear(); + continue; + } + + current.push_back(ch); + } + if (!trim(current).empty()) + { + out.emplace_back(trim(current)); + } + return out; +} + +static std::string parseCppStringLiteral(const std::string &token) +{ + auto s = trim(token); + if (s.empty()) + { + return ""; + } + + if (s.size() >= 2 && s.front() == '"' && s.back() == '"') + { + std::string out; + out.reserve(s.size()); + for (size_t i = 1; i + 1 < s.size(); ++i) + { + if (s[i] == '\\' && i + 1 < s.size() - 1) + { + ++i; + } + out.push_back(s[i]); + } + return out; + } + + if (startsWith(s, 0, "R\"") && s.size() >= 4) + { + auto open = s.find('('); + auto close = s.rfind(')'); + if (open != std::string::npos && close != std::string::npos && close > open) + { + return s.substr(open + 1, close - open - 1); + } + } + + return ""; +} + +static std::string joinClassPath(const std::vector &context, + const std::string &methodToken) +{ + std::vector ns; + std::string className; + for (const auto &node : context) + { + if (node.type == ContextType::kNamespace) + { + ns.push_back(node.name); + } + else if (node.type == ContextType::kClass) + { + className = node.name; + } + } + + if (className.empty()) + { + auto pos = methodToken.rfind("::"); + if (pos != std::string::npos) + { + className = methodToken.substr(0, pos); + } + } + + if (className.empty()) + { + return ""; + } + + std::string path = "/"; + for (const auto &name : ns) + { + auto parts = drogon::utils::splitString(name, "::"); + for (const auto &p : parts) + { + if (!p.empty()) + { + path += p; + path.push_back('/'); + } + } + } + path += className; + return path; +} + +static std::set parseHttpMethods(const std::vector &args, + size_t startIndex) +{ + static const std::unordered_map methodMap = { + {"get", "get"}, + {"post", "post"}, + {"put", "put"}, + {"delete", "delete"}, + {"patch", "patch"}, + {"head", "head"}, + {"options", "options"}}; + + std::set methods; + for (size_t i = startIndex; i < args.size(); ++i) + { + auto token = trim(args[i]); + if (token.empty()) + { + continue; + } + auto pos = token.rfind("::"); + if (pos != std::string::npos) + { + token = token.substr(pos + 2); + } + token = toLower(token); + auto it = methodMap.find(token); + if (it != methodMap.end()) + { + methods.insert(it->second); + } + } + + if (methods.empty()) + { + methods.insert("get"); + } + return methods; +} + +static std::string normalizePathForSwagger(std::string path) +{ + auto queryPos = path.find('?'); + if (queryPos != std::string::npos) + { + path = path.substr(0, queryPos); + } + if (path.empty()) + { + path = "/"; + } + + std::string converted; + converted.reserve(path.size()); + int autoParamIndex = 1; + for (size_t i = 0; i < path.size(); ++i) + { + if (path[i] == '{') + { + auto end = path.find('}', i + 1); + if (end == std::string::npos) + { + converted.push_back(path[i]); + continue; + } + auto inside = trim(path.substr(i + 1, end - i - 1)); + std::string paramName; + if (inside.empty()) + { + paramName = "param" + std::to_string(autoParamIndex++); + } + else if (std::all_of(inside.begin(), inside.end(), [](unsigned char ch) { + return std::isdigit(ch); + })) + { + paramName = "arg" + inside; + } + else + { + paramName = inside; + } + converted += "{" + paramName + "}"; + i = end; + continue; + } + converted.push_back(path[i]); + } + + std::string compact; + compact.reserve(converted.size()); + bool lastSlash = false; + for (auto ch : converted) + { + if (ch == '/') + { + if (!lastSlash) + { + compact.push_back(ch); + } + lastSlash = true; + } + else + { + compact.push_back(ch); + lastSlash = false; + } + } + + if (compact.empty()) + { + return "/"; + } + return compact; +} + +static std::string extractMethodName(const std::string &methodToken) +{ + auto token = trim(methodToken); + auto pos = token.rfind("::"); + if (pos == std::string::npos) + { + return token; + } + return token.substr(pos + 2); +} + +static std::string extractClassName(const std::vector &context, + const std::string &methodToken) +{ + std::string className; + std::vector namespaces; + for (const auto &node : context) + { + if (node.type == ContextType::kNamespace) + { + namespaces.push_back(node.name); + } + else if (node.type == ContextType::kClass) + { + className = node.name; + } + } + + if (className.empty()) + { + auto token = trim(methodToken); + auto pos = token.rfind("::"); + if (pos != std::string::npos) + { + className = token.substr(0, pos); + } + } + + if (className.empty()) + { + return "Controller"; + } + + if (namespaces.empty()) + { + return className; + } + + std::string full; + for (const auto &ns : namespaces) + { + auto parts = drogon::utils::splitString(ns, "::"); + for (const auto &p : parts) + { + if (!p.empty()) + { + if (!full.empty()) + { + full += "::"; + } + full += p; + } + } + } + if (!full.empty()) + { + full += "::"; + } + full += className; + return full; +} + +static std::string sanitizeOperationId(std::string s) +{ + for (auto &c : s) + { + if (!(std::isalnum(static_cast(c)) || c == '_')) + { + c = '_'; + } + } + std::string compact; + compact.reserve(s.size()); + bool lastUnderscore = false; + for (auto c : s) + { + if (c == '_') + { + if (!lastUnderscore) + { + compact.push_back(c); + } + lastUnderscore = true; + } + else + { + compact.push_back(c); + lastUnderscore = false; + } + } + if (compact.empty()) + { + return "operation"; + } + return compact; +} + +static std::vector extractPathParams(const std::string &routePath) +{ + std::vector params; + std::regex pathParamRegex(R"(\{([^\}/]+)\})"); + for (std::sregex_iterator it(routePath.begin(), routePath.end(), pathParamRegex), + end; + it != end; + ++it) + { + auto p = (*it)[1].str(); + if (!p.empty() && + std::find(params.begin(), params.end(), p) == params.end()) + { + params.push_back(std::move(p)); + } + } + return params; +} + +static void fillSwaggerOperation(Json::Value &operation, + const std::string &controllerName, + const std::string &methodName, + const std::string &routePath, + const std::string &httpMethod) +{ + operation["summary"] = methodName + " " + routePath; + operation["tags"] = Json::arrayValue; + operation["tags"].append(controllerName); + operation["operationId"] = + sanitizeOperationId(controllerName + "_" + methodName + "_" + httpMethod); + + auto params = extractPathParams(routePath); + if (!params.empty()) + { + operation["parameters"] = Json::arrayValue; + for (const auto ¶m : params) + { + Json::Value p; + p["name"] = param; + p["in"] = "path"; + p["required"] = true; + p["type"] = "string"; + operation["parameters"].append(std::move(p)); + } + } + + operation["responses"]["200"]["description"] = "OK"; +} + +static void collectEndpointsFromHeader(const std::string &headerFile, + Json::Value &paths) +{ + std::ifstream infile(utils::toNativePath(headerFile), std::ifstream::binary); + if (!infile) + { + std::cerr << "Warning: can't open the header file: " << headerFile << "\n"; + return; + } + + std::string content((std::istreambuf_iterator(infile)), + std::istreambuf_iterator()); + auto noComment = stripCppComments(content); + + std::string namespacePrefix; + { + static const std::regex nsDeclRegex( + R"(namespace\s+([A-Za-z_][A-Za-z0-9_:]*)\s*\{)"); + std::smatch match; + if (std::regex_search(noComment, match, nsDeclRegex)) + { + namespacePrefix = match[1].str(); + } + } + + auto buildControllerIdentity = + [&namespacePrefix](const std::string &methodToken, + std::string &controllerName, + std::string &basePath) { + auto token = trim(methodToken); + auto methodSep = token.rfind("::"); + if (methodSep == std::string::npos) + { + controllerName = "Controller"; + basePath = "/Controller"; + return; + } + + auto classPart = token.substr(0, methodSep); + std::vector nsParts; + std::string className = classPart; + auto classSep = classPart.rfind("::"); + if (classSep != std::string::npos) + { + className = classPart.substr(classSep + 2); + auto explicitNs = classPart.substr(0, classSep); + nsParts = drogon::utils::splitString(explicitNs, "::"); + } + else if (!namespacePrefix.empty()) + { + nsParts = drogon::utils::splitString(namespacePrefix, "::"); + } + + controllerName.clear(); + for (const auto &p : nsParts) + { + if (p.empty()) + continue; + if (!controllerName.empty()) + { + controllerName += "::"; + } + controllerName += p; + } + if (!controllerName.empty()) + { + controllerName += "::"; + } + controllerName += className; + + basePath = "/"; + for (const auto &p : nsParts) + { + if (!p.empty()) + { + basePath += p; + basePath.push_back('/'); + } + } + basePath += className; + }; + + size_t pos = 0; + while (pos < noComment.size()) + { + auto methodPos = noComment.find("METHOD_ADD(", pos); + auto addToPos = noComment.find("ADD_METHOD_TO(", pos); + + bool isMethodAdd = false; + size_t macroPos = std::string::npos; + if (methodPos == std::string::npos) + { + macroPos = addToPos; + } + else if (addToPos == std::string::npos) + { + macroPos = methodPos; + isMethodAdd = true; + } + else if (methodPos < addToPos) + { + macroPos = methodPos; + isMethodAdd = true; + } + else + { + macroPos = addToPos; + } + + if (macroPos == std::string::npos) + { + break; + } + + const std::string macroName = isMethodAdd ? "METHOD_ADD" : "ADD_METHOD_TO"; + std::string argsText; + size_t endPos = macroPos; + if (parseMacroInvocation(noComment, + macroPos + macroName.size(), + argsText, + endPos)) + { + auto args = splitTopLevelArgs(argsText); + if (args.size() >= 2) + { + std::string controllerName; + std::string routePath; + auto methodName = extractMethodName(args[0]); + + if (isMethodAdd) + { + std::string basePath; + buildControllerIdentity(args[0], controllerName, basePath); + auto pattern = parseCppStringLiteral(args[1]); + if (!basePath.empty()) + { + if (pattern.empty()) + { + routePath = basePath; + } + else if (pattern.front() == '/') + { + routePath = basePath + pattern; + } + else + { + routePath = basePath + "/" + pattern; + } + } + } + else + { + std::string basePath; + buildControllerIdentity(args[0], controllerName, basePath); + auto pathExpr = parseCppStringLiteral(args[1]); + if (!pathExpr.empty()) + { + if (pathExpr.front() != '/') + { + pathExpr = "/" + pathExpr; + } + routePath = pathExpr; + } + } + + if (!routePath.empty()) + { + routePath = normalizePathForSwagger(routePath); + auto methods = parseHttpMethods(args, 2); + for (const auto &m : methods) + { + fillSwaggerOperation(paths[routePath][m], + controllerName, + methodName, + routePath, + m); + } + } + } + pos = endPos + 1; + } + else + { + pos = macroPos + 1; + } + } +} +} // namespace + static void forEachControllerHeaderIn( std::string strPath, const std::function &cb) { - while (1) + while (!strPath.empty()) { char cEnd = *strPath.rbegin(); if (cEnd == '\\' || cEnd == '/') @@ -54,60 +840,63 @@ static void forEachControllerHeaderIn( if (strPath.empty() || strPath == (".") || strPath == ("..")) return; - drogon::error_code ec; - drogon::filesystem::path fsPath(strPath); - if (!drogon::filesystem::exists(strPath, ec)) + std::error_code ec; + std::filesystem::path fsPath(strPath); + if (!std::filesystem::exists(fsPath, ec) || + !std::filesystem::is_directory(fsPath, ec)) { return; } - for (auto &itr : drogon::filesystem::directory_iterator(fsPath)) + + std::filesystem::recursive_directory_iterator it( + fsPath, + std::filesystem::directory_options::skip_permission_denied, + ec); + std::filesystem::recursive_directory_iterator end; + while (it != end) { - if (drogon::filesystem::is_directory(itr.status())) + if (ec) { - forEachControllerHeaderIn(itr.path().string(), cb); + ec.clear(); + it.increment(ec); + continue; } - else + + if (it->is_regular_file(ec)) { - auto fileName = itr.path().string(); - if (fileName.find(".h") == fileName.length() - 2 || - fileName.find(".hpp") == fileName.length() - 4) + auto ext = toLower(it->path().extension().string()); + if (ext == ".h" || ext == ".hpp") { - cb(fileName); + cb(it->path().string()); } } + + ec.clear(); + it.increment(ec); } - return; } -static void parseControllerHeader(const std::string &headerFile, - Json::Value &docs) -{ - std::ifstream infile(utils::toNativePath(headerFile), - std::ifstream::binary); - if (!infile) - { - std::cout << "can't open the header file:" << headerFile << "\n"; - return; - } - std::streambuf *pbuf = infile.rdbuf(); - std::streamsize filesize = pbuf->pubseekoff(0, std::ifstream::end); - pbuf->pubseekoff(0, std::ifstream::beg); // rewind - std::string fileContent; - fileContent.resize(filesize); - pbuf->sgetn(&fileContent[0], filesize); - std::cout << fileContent; - internal::StructNode r(fileContent, "root", internal::StructNode::kRoot); - r.print(); -} -static std::string makeSwaggerDocument(const Json::Value &config) +static std::string makeSwaggerDocument(const Json::Value &config, + const std::string &controllersPath) { Json::Value ret; ret["swagger"] = "2.0"; - ret["info"] = config.get("info", {}); - forEachControllerHeaderIn("controllers", [&ret](const std::string &header) { - std::cout << "Parsing " << header << " ...\n"; - parseControllerHeader(header, ret); + if (config.isMember("info") && config["info"].isObject()) + { + ret["info"] = config["info"]; + } + else + { + ret["info"]["title"] = "Drogon API"; + ret["info"]["version"] = "1.0.0"; + } + + Json::Value paths(Json::objectValue); + forEachControllerHeaderIn(controllersPath, [&paths](const std::string &header) { + collectEndpointsFromHeader(header, paths); }); + + ret["paths"] = std::move(paths); return ret.toStyledString(); } @@ -122,10 +911,11 @@ static void createSwaggerHeader(const std::string &path, } static void createSwaggerSource(const std::string &path, - const Json::Value &config) + const Json::Value &config, + const std::string &controllersPath) { drogon::HttpViewData data; - data["docs_string"] = makeSwaggerDocument(config); + data["docs_string"] = makeSwaggerDocument(config, controllersPath); std::ofstream ctlSource(path + "/SwaggerCtrl.cc", std::ofstream::out); auto templ = DrTemplateBase::newTemplate("swagger_cc.csp"); @@ -171,8 +961,20 @@ static void createSwagger(const std::string &path) try { infile >> configJsonRoot; + std::filesystem::path projectRoot = + std::filesystem::path(path).parent_path(); + auto controllersPath = + configJsonRoot.get("controllers_path", "controllers").asString(); + std::filesystem::path controllersFsPath(controllersPath); + if (controllersFsPath.is_relative()) + { + controllersFsPath = projectRoot / controllersFsPath; + } + createSwaggerHeader(path, configJsonRoot); - createSwaggerSource(path, configJsonRoot); + createSwaggerSource(path, + configJsonRoot, + controllersFsPath.string()); } catch (const std::exception &exception) { @@ -193,4 +995,7 @@ void create_swagger::handleCommand(std::vector ¶meters) } auto path = parameters[0]; createSwagger(path); -} \ No newline at end of file +} + +// See create.cc for rationale. +template class drogon::DrObject; diff --git a/drogon_ctl/templates/swagger_cc.csp b/drogon_ctl/templates/swagger_cc.csp index 74e220a988..7fd28b17a5 100644 --- a/drogon_ctl/templates/swagger_cc.csp +++ b/drogon_ctl/templates/swagger_cc.csp @@ -1,6 +1,6 @@ /** * - * SwaggerCtrl.h + * SwaggerCtrl.cc * This file is generated by drogon_ctl automatically, do not edit it. * */ diff --git a/drogon_ctl/templates/swagger_json.csp b/drogon_ctl/templates/swagger_json.csp index f5e1797376..bd08b03026 100644 --- a/drogon_ctl/templates/swagger_json.csp +++ b/drogon_ctl/templates/swagger_json.csp @@ -1,5 +1,6 @@ { "docs_url": "/swagger", + "controllers_path": "controllers", "info": { "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", "version": "1.0.5", From 3ce5e28c14a6fd0716ae3b034a9dc565d9c930cd Mon Sep 17 00:00:00 2001 From: antao Date: Thu, 30 Apr 2026 23:32:30 +0800 Subject: [PATCH 17/18] Lint --- .gitignore | 1 + drogon_ctl/HandlerParser.cc | 6 ++- drogon_ctl/HandlerParser.h | 9 ++++- drogon_ctl/create_project.cc | 2 + drogon_ctl/create_swagger.cc | 71 ++++++++++++++++++++---------------- drogon_ctl/create_swagger.h | 4 +- 6 files changed, 59 insertions(+), 34 deletions(-) diff --git a/.gitignore b/.gitignore index 78e79b7f44..441f43107d 100755 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ install trace.json .cache/ build_examples/ +.kiro diff --git a/drogon_ctl/HandlerParser.cc b/drogon_ctl/HandlerParser.cc index b973f820c5..c5cc519872 100644 --- a/drogon_ctl/HandlerParser.cc +++ b/drogon_ctl/HandlerParser.cc @@ -35,6 +35,7 @@ std::pair StructNode::findContentOfClassOrNameSpace( pos2 - pos1), content.substr(pos2 + 1)); } + std::pair StructNode::findClass( const std::string &content) { @@ -56,6 +57,7 @@ std::pair StructNode::findClass( } return std::pair(nullptr, ""); } + std::tuple StructNode::findNameSpace( const std::string &content) { @@ -86,6 +88,7 @@ std::tuple StructNode::findNameSpace( ""); } } + std::vector StructNode::parse(const std::string &content) { std::vector res; @@ -130,6 +133,7 @@ std::vector StructNode::parse(const std::string &content) } return res; } + void StructNode::print(int indent) const { std::string ind(indent, ' '); @@ -165,4 +169,4 @@ void StructNode::print(int indent) const if (type_ == kClass) std::cout << ";"; std::cout << "\n"; -} \ No newline at end of file +} diff --git a/drogon_ctl/HandlerParser.h b/drogon_ctl/HandlerParser.h index 1d7dcd6ed2..75601a2077 100644 --- a/drogon_ctl/HandlerParser.h +++ b/drogon_ctl/HandlerParser.h @@ -12,6 +12,7 @@ namespace internal { class StructNode; using StructNodePtr = std::shared_ptr; + class StructNode { public: @@ -31,18 +32,22 @@ class StructNode if (type != kClass) children_ = parse(content); } + const std::string &content() const { return content_; } + const std::string &name() const { return name_; } + NodeType type() const { return type_; } + void print() const { print(0); @@ -70,6 +75,7 @@ class ParametersInfo std::string getType() const; std::string getConstraint() const; }; + class RoutingInfo { public: @@ -90,6 +96,7 @@ class HandlerInfo std::string getNamespace() const; std::vector getRoutingInfo() const; }; + class HandlerParser { public: @@ -97,4 +104,4 @@ class HandlerParser HandlerParser(const StructNodePtr root); }; } // namespace internal -} // namespace drogon \ No newline at end of file +} // namespace drogon diff --git a/drogon_ctl/create_project.cc b/drogon_ctl/create_project.cc index ce394ffd20..4b84898757 100644 --- a/drogon_ctl/create_project.cc +++ b/drogon_ctl/create_project.cc @@ -77,11 +77,13 @@ static void newModelConfigFile(std::ofstream &configFile) auto templ = DrTemplateBase::newTemplate("model_json"); configFile << templ->genText(); } + static void newSwaggerConfigFile(std::ofstream &configFile) { auto templ = DrTemplateBase::newTemplate("swagger_json"); configFile << templ->genText(); } + static void newTestMainFile(std::ofstream &mainFile) { auto templ = DrTemplateBase::newTemplate("test_main"); diff --git a/drogon_ctl/create_swagger.cc b/drogon_ctl/create_swagger.cc index d272bf4b38..2a5d06a0aa 100644 --- a/drogon_ctl/create_swagger.cc +++ b/drogon_ctl/create_swagger.cc @@ -53,7 +53,8 @@ struct ContextNode static std::string trim(const std::string &s) { size_t begin = 0; - while (begin < s.size() && std::isspace(static_cast(s[begin]))) + while (begin < s.size() && + std::isspace(static_cast(s[begin]))) { ++begin; } @@ -123,8 +124,8 @@ static std::string stripCppComments(const std::string &content) } } - if (!inString && !inChar && i + 1 < content.size() && content[i] == '/' && - content[i + 1] == '/') + if (!inString && !inChar && i + 1 < content.size() && + content[i] == '/' && content[i + 1] == '/') { while (i < content.size() && content[i] != '\n') { @@ -137,8 +138,8 @@ static std::string stripCppComments(const std::string &content) continue; } - if (!inString && !inChar && i + 1 < content.size() && content[i] == '/' && - content[i + 1] == '*') + if (!inString && !inChar && i + 1 < content.size() && + content[i] == '/' && content[i + 1] == '*') { i += 2; while (i + 1 < content.size() && @@ -204,8 +205,8 @@ static bool parseMacroInvocation(const std::string &content, --depth; if (depth == 0) { - argsContent = content.substr(openParenPos + 1, - i - openParenPos - 1); + argsContent = + content.substr(openParenPos + 1, i - openParenPos - 1); endPos = i; return true; } @@ -260,7 +261,8 @@ static std::vector splitTopLevelArgs(const std::string &args) else if (ch == ']') --bracketDepth; - if (ch == ',' && parenDepth == 0 && braceDepth == 0 && bracketDepth == 0) + if (ch == ',' && parenDepth == 0 && braceDepth == 0 && + bracketDepth == 0) { out.emplace_back(trim(current)); current.clear(); @@ -303,7 +305,8 @@ static std::string parseCppStringLiteral(const std::string &token) { auto open = s.find('('); auto close = s.rfind(')'); - if (open != std::string::npos && close != std::string::npos && close > open) + if (open != std::string::npos && close != std::string::npos && + close > open) { return s.substr(open + 1, close - open - 1); } @@ -360,8 +363,9 @@ static std::string joinClassPath(const std::vector &context, return path; } -static std::set parseHttpMethods(const std::vector &args, - size_t startIndex) +static std::set parseHttpMethods( + const std::vector &args, + size_t startIndex) { static const std::unordered_map methodMap = { {"get", "get"}, @@ -431,9 +435,11 @@ static std::string normalizePathForSwagger(std::string path) { paramName = "param" + std::to_string(autoParamIndex++); } - else if (std::all_of(inside.begin(), inside.end(), [](unsigned char ch) { - return std::isdigit(ch); - })) + else if (std::all_of(inside.begin(), + inside.end(), + [](unsigned char ch) { + return std::isdigit(ch); + })) { paramName = "arg" + inside; } @@ -586,7 +592,8 @@ static std::vector extractPathParams(const std::string &routePath) { std::vector params; std::regex pathParamRegex(R"(\{([^\}/]+)\})"); - for (std::sregex_iterator it(routePath.begin(), routePath.end(), pathParamRegex), + for (std::sregex_iterator + it(routePath.begin(), routePath.end(), pathParamRegex), end; it != end; ++it) @@ -610,8 +617,8 @@ static void fillSwaggerOperation(Json::Value &operation, operation["summary"] = methodName + " " + routePath; operation["tags"] = Json::arrayValue; operation["tags"].append(controllerName); - operation["operationId"] = - sanitizeOperationId(controllerName + "_" + methodName + "_" + httpMethod); + operation["operationId"] = sanitizeOperationId( + controllerName + "_" + methodName + "_" + httpMethod); auto params = extractPathParams(routePath); if (!params.empty()) @@ -634,10 +641,12 @@ static void fillSwaggerOperation(Json::Value &operation, static void collectEndpointsFromHeader(const std::string &headerFile, Json::Value &paths) { - std::ifstream infile(utils::toNativePath(headerFile), std::ifstream::binary); + std::ifstream infile(utils::toNativePath(headerFile), + std::ifstream::binary); if (!infile) { - std::cerr << "Warning: can't open the header file: " << headerFile << "\n"; + std::cerr << "Warning: can't open the header file: " << headerFile + << "\n"; return; } @@ -745,13 +754,12 @@ static void collectEndpointsFromHeader(const std::string &headerFile, break; } - const std::string macroName = isMethodAdd ? "METHOD_ADD" : "ADD_METHOD_TO"; + const std::string macroName = + isMethodAdd ? "METHOD_ADD" : "ADD_METHOD_TO"; std::string argsText; size_t endPos = macroPos; - if (parseMacroInvocation(noComment, - macroPos + macroName.size(), - argsText, - endPos)) + if (parseMacroInvocation( + noComment, macroPos + macroName.size(), argsText, endPos)) { auto args = splitTopLevelArgs(argsText); if (args.size() >= 2) @@ -849,9 +857,7 @@ static void forEachControllerHeaderIn( } std::filesystem::recursive_directory_iterator it( - fsPath, - std::filesystem::directory_options::skip_permission_denied, - ec); + fsPath, std::filesystem::directory_options::skip_permission_denied, ec); std::filesystem::recursive_directory_iterator end; while (it != end) { @@ -892,9 +898,10 @@ static std::string makeSwaggerDocument(const Json::Value &config, } Json::Value paths(Json::objectValue); - forEachControllerHeaderIn(controllersPath, [&paths](const std::string &header) { - collectEndpointsFromHeader(header, paths); - }); + forEachControllerHeaderIn(controllersPath, + [&paths](const std::string &header) { + collectEndpointsFromHeader(header, paths); + }); ret["paths"] = std::move(paths); return ret.toStyledString(); @@ -964,7 +971,8 @@ static void createSwagger(const std::string &path) std::filesystem::path projectRoot = std::filesystem::path(path).parent_path(); auto controllersPath = - configJsonRoot.get("controllers_path", "controllers").asString(); + configJsonRoot.get("controllers_path", "controllers") + .asString(); std::filesystem::path controllersFsPath(controllersPath); if (controllersFsPath.is_relative()) { @@ -985,6 +993,7 @@ static void createSwagger(const std::string &path) } } } + void create_swagger::handleCommand(std::vector ¶meters) { if (parameters.size() < 1) diff --git a/drogon_ctl/create_swagger.h b/drogon_ctl/create_swagger.h index 48525015a9..cb3c90954e 100644 --- a/drogon_ctl/create_swagger.h +++ b/drogon_ctl/create_swagger.h @@ -17,15 +17,17 @@ #include #include "CommandHandler.h" using namespace drogon; + namespace drogon_ctl { class create_swagger : public DrObject, public CommandHandler { public: virtual void handleCommand(std::vector ¶meters) override; + virtual std::string script() override { return "create swagger docs controller"; } }; -} // namespace drogon_ctl \ No newline at end of file +} // namespace drogon_ctl From 3c76e685329e81d891a5f8cd2e83dfaf1c3b19fa Mon Sep 17 00:00:00 2001 From: antao Date: Thu, 30 Apr 2026 23:35:47 +0800 Subject: [PATCH 18/18] Cpplint --- drogon_ctl/create_swagger.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drogon_ctl/create_swagger.h b/drogon_ctl/create_swagger.h index cb3c90954e..10eb74ef70 100644 --- a/drogon_ctl/create_swagger.h +++ b/drogon_ctl/create_swagger.h @@ -23,9 +23,9 @@ namespace drogon_ctl class create_swagger : public DrObject, public CommandHandler { public: - virtual void handleCommand(std::vector ¶meters) override; + void handleCommand(std::vector ¶meters) override; - virtual std::string script() override + std::string script() override { return "create swagger docs controller"; }