From eb02119439bb127800fedfe2d41b00e1e3fd507b Mon Sep 17 00:00:00 2001 From: Igor Date: Fri, 16 Aug 2024 16:41:36 +0200 Subject: [PATCH] added jsdoc extractor --- src/framework/global/api/interactiveapi.cpp | 15 ++ src/framework/global/api/interactiveapi.h | 1 - tools/jsdoc/jsdoc_extractor/.gitignore | 74 ++++++ .../jsdoc/jsdoc_extractor/src/CMakeLists.txt | 17 ++ tools/jsdoc/jsdoc_extractor/src/main.cpp | 218 ++++++++++++++++++ tools/jsdoc/jsdoc_extractor/src/utils.h | 61 +++++ 6 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 tools/jsdoc/jsdoc_extractor/.gitignore create mode 100644 tools/jsdoc/jsdoc_extractor/src/CMakeLists.txt create mode 100644 tools/jsdoc/jsdoc_extractor/src/main.cpp create mode 100644 tools/jsdoc/jsdoc_extractor/src/utils.h diff --git a/src/framework/global/api/interactiveapi.cpp b/src/framework/global/api/interactiveapi.cpp index 9649ca975ffb1..48fc3025d363d 100644 --- a/src/framework/global/api/interactiveapi.cpp +++ b/src/framework/global/api/interactiveapi.cpp @@ -25,16 +25,31 @@ using namespace muse::api; +/** APIDOC namespace: interactive + * User interaction - informational messages, error messages, questions and other dialogs. + * @namespace + */ + InteractiveApi::InteractiveApi(IApiEngine* e) : ApiObject(e) { } +/** APIDOC method: info(title, text) + * Show information message + * @param {String} title Title + * @param {String} text Message + */ + void InteractiveApi::info(const QString& title, const QString& text) { interactive()->info(title.toStdString(), text.toStdString()); } +/** APIDOC method: openUrl(url) + * Open URL in external browser + * @param {String} url URL + */ void InteractiveApi::openUrl(const QString& url) { interactive()->openUrl(QUrl(url)); diff --git a/src/framework/global/api/interactiveapi.h b/src/framework/global/api/interactiveapi.h index 54f9d2cb60f75..5f7c9b1f5af99 100644 --- a/src/framework/global/api/interactiveapi.h +++ b/src/framework/global/api/interactiveapi.h @@ -37,7 +37,6 @@ class InteractiveApi : public ApiObject public: explicit InteractiveApi(IApiEngine* e); - API_DOC(info, "Show information dialog") Q_INVOKABLE void info(const QString& title, const QString& text); API_DOC(openUrl, "Open URL in external browser") diff --git a/tools/jsdoc/jsdoc_extractor/.gitignore b/tools/jsdoc/jsdoc_extractor/.gitignore new file mode 100644 index 0000000000000..4a0b530afd203 --- /dev/null +++ b/tools/jsdoc/jsdoc_extractor/.gitignore @@ -0,0 +1,74 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* +CMakeLists.txt.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/tools/jsdoc/jsdoc_extractor/src/CMakeLists.txt b/tools/jsdoc/jsdoc_extractor/src/CMakeLists.txt new file mode 100644 index 0000000000000..335f4f6361f4b --- /dev/null +++ b/tools/jsdoc/jsdoc_extractor/src/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.5) + +project(jsdoc_extractor LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(jsdoc_extractor + main.cpp + utils.h +) + +include(GNUInstallDirs) +install(TARGETS jsdoc_extractor + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/tools/jsdoc/jsdoc_extractor/src/main.cpp b/tools/jsdoc/jsdoc_extractor/src/main.cpp new file mode 100644 index 0000000000000..c54fbb5a379c1 --- /dev/null +++ b/tools/jsdoc/jsdoc_extractor/src/main.cpp @@ -0,0 +1,218 @@ +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +namespace fs = std::filesystem; + +static const char* VERSION = "0.1"; +static const std::string APIDOC_BEGIN = "/** APIDOC"; +static const std::string APIDOC_END = "*/"; +static const std::string APIDOC_NSPACE = "namespace:"; +static const std::string APIDOC_METHOD = "method:"; + +struct Filter { + std::string ignoreFile; + std::vector exts; +}; + +static void scan(std::vector& files, const fs::path& root, const Filter& filter) +{ + if (!filter.ignoreFile.empty() && fs::exists(root / fs::path(filter.ignoreFile))) { + // ignore + return; + } + + for (auto& item : fs::directory_iterator(root)) { + fs::path itemPath = item.path(); + if (fs::is_directory(item.path())) { + // recursion + scan(files, itemPath, filter); + } else if (fs::is_regular_file(itemPath)) { + if (filter.exts.empty() || contains(filter.exts, itemPath.extension().string())) { + files.push_back(itemPath); + } + } + } +} + +static std::string extracDoc(const fs::path& fname) +{ + std::ifstream f; + f.open(fname); + + struct State { + bool nspaceStared = false; + std::string nspaceName; + std::string methodName; + }; + + std::string doc; + std::stringstream ts; + State state; + + std::string line; + while (f) { + std::getline(f, line); + + trim(line); + + if (startsWith(line, APIDOC_BEGIN)) { + // remove /** APIDOC + line = line.substr(APIDOC_BEGIN.size()); + ltrim(line); + + // check namespace + // namespace: interactive + if (startsWith(line, APIDOC_NSPACE)) { + state.nspaceName = line.substr(APIDOC_NSPACE.size()); + ltrim(state.nspaceName); + state.nspaceStared = true; + ts << "/**\n"; + } + // check method + // method: info(title, text) + else if (startsWith(line, APIDOC_METHOD)) { + state.methodName = line.substr(APIDOC_METHOD.size()); + ltrim(state.methodName); + ts << "\t" << "/**\n"; + } + + continue; + } + + if (startsWith(line, APIDOC_END)) { + // write ns + if (!state.nspaceName.empty()) { + ts << "*/\n"; + ts << "const " << state.nspaceName << " = {\n"; + state.nspaceName.clear(); + } + // write method + else if (!state.methodName.empty()) { + ts << "\t*/\n"; + ts << "\t" << state.methodName << " {},\n"; + ts << "\n"; + state.methodName.clear(); + } + + continue; + } + + if (!state.nspaceName.empty()) { + ts << line << "\n"; + continue; + } + + if (!state.methodName.empty()) { + ts << "\t" << line << "\n"; + continue; + } + } + + if (state.nspaceStared) { + ts << "};"; + } + + return ts.str(); +} + +static void saveDoc(const std::string& doc, const fs::path& outDir, const std::string& name) +{ + fs::path fname = outDir / name; + fname += ".js"; + std::fstream(fname, std::ios::out | std::ios::trunc) << doc; +} + +static void printHelp() +{ + std::cout << "Hello World! I am jsdoc extractor!" << "\n"; + std::cout << "This is a utility for scanning files, extracting documentation from them and creating JS files. \n"; + std::cout << "use:\n"; + std::cout << "-d --dir /path root dir for scan\n"; + std::cout << "-o --out /path output path\n"; + std::cout << "-i --ignore filename if present this file, dir (and subdirs) will be ignored\n"; + std::cout << "-e --extensions ext1,extn allowed extensions\n"; + std::cout << "-h, --help this help\n"; + std::cout << "-v, --version print version\n"; +} + +int main(int argc, char** argv) +{ + // default + std::string dir = "."; + std::string out = "./out"; + Filter filter; + + // parse args + { + std::vector args; + for (int a = 0; a < argc; ++a) { + args.push_back(argv[a]); + } + + for (size_t i = 0; i < args.size(); ++i) { + if (i == 0) { + continue; + } + + const std::string& arg = args.at(i); + if (arg == "-v" || arg == "--version") { + std::cout << "scan_files version: " << VERSION << "\n"; + } else if (arg == "-h" || arg == "--help") { + printHelp(); + } else if (arg == "-d" || arg == "--dir") { + dir = args.at(++i); + } else if (arg == "-o" || arg == "--out") { + out = args.at(++i); + } else if (arg == "-i" || arg == "--ignore") { + filter.ignoreFile = args.at(++i); + } else if (arg == "-e" || arg == "--extensions") { + std::string extsStr = args.at(++i); + std::vector exts; + split(extsStr, exts, ","); + for (const std::string& e : exts) { + if (e.empty()) { + continue; + } + + if (e.at(0) != '.') { + filter.exts.push_back('.' + e); + } else { + filter.exts.push_back(e); + } + } + } else { + std::cout << "invalid option -- '" << arg << "', try '--help' for more information.\n"; + } + } + } + + std::vector files; + scan(files, fs::path(dir), filter); + + if (files.empty()) { + std::cout << "not found files\n"; + return 0; + } + + std::filesystem::create_directories(out); + + for (const fs::path& f: files) { + if (f.stem() != "interactiveapi") { + continue; + } + + std::cout << f.generic_string() << std::endl; + std::string doc = extracDoc(f); + if (!doc.empty()) { + saveDoc(doc, out, f.stem()); + } + } + + return 0; +} diff --git a/tools/jsdoc/jsdoc_extractor/src/utils.h b/tools/jsdoc/jsdoc_extractor/src/utils.h new file mode 100644 index 0000000000000..ce607beddb83b --- /dev/null +++ b/tools/jsdoc/jsdoc_extractor/src/utils.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +template +static bool contains(const std::vector& vec, const T& val) +{ + auto it = std::find(vec.cbegin(), vec.cend(), val); + return it != vec.cend(); +} + +static void ltrim(std::string& s) +{ + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { + return !std::isspace(ch); + })); +} + +static void rtrim(std::string& s) +{ + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { + return !std::isspace(ch); + }).base(), s.end()); +} + +static void trim(std::string& s) +{ + ltrim(s); + rtrim(s); +} + +static void split(const std::string& str, std::vector& out, const std::string& delim) +{ + std::size_t current, previous = 0; + current = str.find(delim); + std::size_t delimLen = delim.length(); + + while (current != std::string::npos) { + out.push_back(str.substr(previous, current - previous)); + previous = current + delimLen; + current = str.find(delim, previous); + } + out.push_back(str.substr(previous, current - previous)); +} + +static bool startsWith(const std::string& str, const std::string& start) +{ + if (str.size() < start.size()) { + return false; + } + + for (size_t i = 0; i < start.size(); ++i) { + if (str.at(i) != start.at(i)) { + return false; + } + } + + return true; +}