From 052b2bc3eff85ecf6869a9c0c14af412ec14fdf0 Mon Sep 17 00:00:00 2001 From: Igor Date: Thu, 29 Aug 2024 12:14:54 +0200 Subject: [PATCH] make jdoc extractor on js --- src/framework/global/api/apiobject.h | 2 - src/framework/global/api/interactiveapi.cpp | 4 +- src/framework/global/api/interactiveapi.h | 2 - src/framework/global/api/logapi.h | 4 - tools/jsdoc/jsdoc_extractor.js | 255 ++++++++++++++++++ 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 ----- 9 files changed, 257 insertions(+), 380 deletions(-) create mode 100644 tools/jsdoc/jsdoc_extractor.js delete mode 100644 tools/jsdoc/jsdoc_extractor/.gitignore delete mode 100644 tools/jsdoc/jsdoc_extractor/src/CMakeLists.txt delete mode 100644 tools/jsdoc/jsdoc_extractor/src/main.cpp delete mode 100644 tools/jsdoc/jsdoc_extractor/src/utils.h diff --git a/src/framework/global/api/apiobject.h b/src/framework/global/api/apiobject.h index 189d573c14769..6f524dfca846e 100644 --- a/src/framework/global/api/apiobject.h +++ b/src/framework/global/api/apiobject.h @@ -31,8 +31,6 @@ //! If we move it to the `api` module, we will have to link it to all other modules. //! That’s why it’s located here, because the `global` module links to everything. -#define API_DOC(name, str) Q_INVOKABLE QString name##_doc() const { return str; } - namespace muse::api { class ApiObject : public QObject, public Injectable { diff --git a/src/framework/global/api/interactiveapi.cpp b/src/framework/global/api/interactiveapi.cpp index 48fc3025d363d..3ed4952ac2b8c 100644 --- a/src/framework/global/api/interactiveapi.cpp +++ b/src/framework/global/api/interactiveapi.cpp @@ -35,7 +35,7 @@ InteractiveApi::InteractiveApi(IApiEngine* e) { } -/** APIDOC method: info(title, text) +/** APIDOC method * Show information message * @param {String} title Title * @param {String} text Message @@ -46,7 +46,7 @@ void InteractiveApi::info(const QString& title, const QString& text) interactive()->info(title.toStdString(), text.toStdString()); } -/** APIDOC method: openUrl(url) +/** APIDOC method * Open URL in external browser * @param {String} url URL */ diff --git a/src/framework/global/api/interactiveapi.h b/src/framework/global/api/interactiveapi.h index 5f7c9b1f5af99..acdbc8ebd68da 100644 --- a/src/framework/global/api/interactiveapi.h +++ b/src/framework/global/api/interactiveapi.h @@ -38,8 +38,6 @@ class InteractiveApi : public ApiObject explicit InteractiveApi(IApiEngine* e); Q_INVOKABLE void info(const QString& title, const QString& text); - - API_DOC(openUrl, "Open URL in external browser") Q_INVOKABLE void openUrl(const QString& url); }; } diff --git a/src/framework/global/api/logapi.h b/src/framework/global/api/logapi.h index 9678622e50a2c..c23de7d623fae 100644 --- a/src/framework/global/api/logapi.h +++ b/src/framework/global/api/logapi.h @@ -31,19 +31,15 @@ class LogApi : public api::ApiObject public: explicit LogApi(api::IApiEngine* e); - API_DOC(error, "Write error to console and log") Q_INVOKABLE void error(const QString& message); Q_INVOKABLE void error(const QString& tag, const QString& message); - API_DOC(warn, "Write warning to console and log") Q_INVOKABLE void warn(const QString& message); Q_INVOKABLE void warn(const QString& tag, const QString& message); - API_DOC(info, "Write info to console and log") Q_INVOKABLE void info(const QString& message); Q_INVOKABLE void info(const QString& tag, const QString& message); - API_DOC(debug, "Write debug to console and log") Q_INVOKABLE void debug(const QString& message); Q_INVOKABLE void debug(const QString& tag, const QString& message); }; diff --git a/tools/jsdoc/jsdoc_extractor.js b/tools/jsdoc/jsdoc_extractor.js new file mode 100644 index 0000000000000..4590ad61c4f1d --- /dev/null +++ b/tools/jsdoc/jsdoc_extractor.js @@ -0,0 +1,255 @@ +const fs = require('node:fs'); +const path = require('node:path'); +const readline = require('node:readline'); + +const VERSION = "0.1" + +var filter = { + ignoreFile: "", + exts: [] +}; + +function scan(files, rootPath, filter) +{ + if (filter.ignoreFile !== "" && fs.existsSync(rootPath + "/" + filter.ignoreFile)) { + // ignore + return []; + } + + const items = fs.readdirSync(rootPath, {withFileTypes: true, recursive: false}) + for (var i in items) { + const item = items[i] + + const itemPath = rootPath + "/" + item.name + if (item.isDirectory()) { + // recursion + scan(files, itemPath, filter) + } else if (item.isFile()) { + if ((filter.exts.length === 0) || filter.exts.includes(path.extname(itemPath))) { + files.push(itemPath) + } + } + } +} + +async function extractDoc(file) +{ + const fileStream = fs.createReadStream(file) + const rl = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity + }) + + const APIDOC_BEGIN = "/** APIDOC" + const APIDOC_END = "*/" + const APIDOC_NSPACE = "namespace:" + const APIDOC_METHOD = "method" + + var state = { + namespaceStared: false, + namespaceName: "", + + methodDocContent: false, + methodParams: [], + methodLookName: false, + }; + + var doc = "" + + for await (var line of rl) { + line = line.trim() + + if (line.startsWith(APIDOC_BEGIN)) { + + // remove /** APIDOC + line = line.substring(APIDOC_BEGIN.length); + line = line.trim() + + // check namespace + // `namespace: interactive` + if (line.startsWith(APIDOC_NSPACE)) { + state.namespaceName = line.substring(APIDOC_NSPACE.length).trim() + state.namespaceStared = true; + doc += "/**\n" + } + // check method + // `method` + else if (line.startsWith(APIDOC_METHOD)) { + doc += "\t/**\n" + state.methodDocContent = true + } + + continue; + } + + if (line.startsWith(APIDOC_END)) { + // write ns + if (state.namespaceName !== "") { + doc += "*/\n" + doc += "const " + state.namespaceName + " = {\n\n" + state.namespaceName = "" + } + // end method + else if (state.methodDocContent) { + state.methodDocContent = false + state.methodLookName = true + } + + continue; + } + + if (state.namespaceName !== "") { + doc += line + "\n" + continue; + } + + if (state.methodDocContent) { + doc += "\t" + line + "\n" + + if (line.includes("@param")) { + var words = line.split(' '); + state.methodParams.push(words[3]) + } + + continue; + } + + if (state.methodLookName) { + // Result Class::method(...) + var colonIdx = line.lastIndexOf("::") + if (colonIdx !== -1) { + colonIdx += 2 // skip :: + var braceIdx = line.indexOf("(", colonIdx) + var name = line.substr(colonIdx, (braceIdx - colonIdx)) + + // write method + doc += "\t*/\n" + doc += "\t" + name + "(" + state.methodParams.join(', ') + ") {},\n\n" + + // cleanup state + state.methodLookName = "" + state.methodParams = [] + } + } + } + + if (state.namespaceStared) { + doc += "};"; + } + + return doc +} + +function saveDoc(doc, dir, name) +{ + const outPath = dir + "/" + name + ".js" + fs.writeFileSync(outPath, doc); + + console.log("[saved] ", outPath) +} + +function printVersion() +{ + console.log("version: ", VERSION) +} + +function printHelp() +{ + var h = "Hello World! I am jsdoc extractor!\n" + h += "This is a utility for scanning files, extracting documentation from them and creating JS files. \n" + h += "use:\n" + h += "-d --dir /path root dir for scan\n" + h += "-o --out /path output path\n" + h += "-i --ignore filename if present this file, dir (and subdirs) will be ignored\n" + h += "-e --extensions .ext1,.extn allowed extensions\n" + h += "-h, --help this help\n" + h += "-v, --version print version\n" + + console.log(h) +} + +async function main() +{ + // default + var dir = "."; + var out = "./out"; + + + // parse args + var args = process.argv.slice(2); + { + var i = -1; + while (true) { + ++i + if (i >= args.length) { + break; + } + + const arg = args[i] + + if (arg == "-v" || arg == "--version") { + printVersion() + return 0 + } + else if (arg == "-h" || arg == "--help") { + printHelp() + return 0 + } + else if (arg == "-d" || arg == "--dir") { + dir = args[++i] + } + else if (arg == "-o" || arg == "--out") { + out = args[++i] + } + else if (arg == "-i" || arg == "--ignore") { + filter.ignoreFile = args[++i] + } + else if (arg == "-e" || arg == "--extensions") { + var extsStr = args[++i]; + exts = extsStr.split(',') + for (const e of exts) { + if (e === "") { + continue; + } + + if (e[0] !== '.') { + filter.exts.push('.' + e); + } else { + filter.exts.push(e); + } + } + } + else { + console.log("invalid option -- '", arg, "', try '--help' for more information.\n") + } + } + } + + // scan + var files = [] + scan(files, dir, filter) + + if (filter.length === 0) { + console.log("not found files") + return 0; + } + + if (!fs.existsSync(out)) { + fs.mkdirSync(out) + } + + // extrac + for (var i in files) { + const file = files[i] + const name = path.parse(file).name + + const doc = await extractDoc(file); + if (doc !== "") { + saveDoc(doc, out, name); + } + } + + return 0; +} + +main() diff --git a/tools/jsdoc/jsdoc_extractor/.gitignore b/tools/jsdoc/jsdoc_extractor/.gitignore deleted file mode 100644 index 4a0b530afd203..0000000000000 --- a/tools/jsdoc/jsdoc_extractor/.gitignore +++ /dev/null @@ -1,74 +0,0 @@ -# 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 deleted file mode 100644 index 335f4f6361f4b..0000000000000 --- a/tools/jsdoc/jsdoc_extractor/src/CMakeLists.txt +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index c54fbb5a379c1..0000000000000 --- a/tools/jsdoc/jsdoc_extractor/src/main.cpp +++ /dev/null @@ -1,218 +0,0 @@ -#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 deleted file mode 100644 index ce607beddb83b..0000000000000 --- a/tools/jsdoc/jsdoc_extractor/src/utils.h +++ /dev/null @@ -1,61 +0,0 @@ -#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; -}