last ...
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* XMQL Helper CLI Tools ...
|
||||
* a module for handle MQL5 Commands running ...
|
||||
*
|
||||
* Maintainer
|
||||
*
|
||||
* Hadi Khazaee Asl <https://saherelm.ir> (hadi_khazaee_asl@yahoo.com)
|
||||
*/
|
||||
|
||||
//
|
||||
// Import Requirements ...
|
||||
const XProjectTools = require("./x-mql-project.tools");
|
||||
|
||||
//
|
||||
// Read Command Arguments ...
|
||||
let cmdName = process.argv[2];
|
||||
|
||||
//
|
||||
XProjectTools.runCommand(cmdName);
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Import Requirements ...
|
||||
const XProjectTools = require("./x-mql-project.tools");
|
||||
|
||||
//
|
||||
// define constants ...
|
||||
const projectName = "X121XKIEA";
|
||||
|
||||
//
|
||||
// Projects Resources ...
|
||||
const projectResources = [
|
||||
"Libraries/*", // Required Libraries
|
||||
"Classes/*", // Required Classes
|
||||
"X121XKIEA/*", // X121XKIEA Projects ...
|
||||
"Indicators/x-saherelm.x121.xki", // Required Indicators
|
||||
"Helpers/x-saherelm.x121.xki.helper", // Indicator's Helpers Classes
|
||||
"Experts/x-saherelm.x121.xki.backtester.ea", // Expert Advisers
|
||||
];
|
||||
|
||||
//
|
||||
run();
|
||||
|
||||
/**
|
||||
* Main Project Function ...
|
||||
*/
|
||||
async function run() {
|
||||
//
|
||||
await XProjectTools.prepareProject(projectName, projectResources);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Import Requirements ...
|
||||
const XProjectTools = require("./x-mql-project.tools");
|
||||
|
||||
//
|
||||
// define constants ...
|
||||
const projectName = "XCATBEA";
|
||||
|
||||
//
|
||||
// Projects Resources ...
|
||||
const projectResources = [
|
||||
"Libraries/*", // Required Libraries
|
||||
"Classes/*", // Required Classes
|
||||
"XCATBEA/*", // XCATBEA Projects ...
|
||||
"Indicators/x-saherelm.x121.xcatb", // Required Indicators
|
||||
"Helpers/x-saherelm.x121.xcatb.helper", // Indicator's Helpers Classes
|
||||
"Experts/x-saherelm.x121.xcatb.ea", // Expert Advisers
|
||||
];
|
||||
|
||||
//
|
||||
run();
|
||||
|
||||
/**
|
||||
* Main Project Function ...
|
||||
*/
|
||||
async function run() {
|
||||
//
|
||||
await XProjectTools.prepareProject(
|
||||
projectName,
|
||||
projectResources
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Import Requirements ...
|
||||
const XProjectTools = require("./x-mql-project.tools");
|
||||
|
||||
//
|
||||
// define constants ...
|
||||
const projectName = "XFIMAEA";
|
||||
|
||||
//
|
||||
// Projects Resources ...
|
||||
const projectResources = [
|
||||
"Libraries/*", // Required Libraries
|
||||
"Classes/*", // Required Classes
|
||||
"XFIMAEA/*", // XOM1EA Projects ...
|
||||
"Indicators/x-saherelm.xfima", // Required Indicators
|
||||
"Indicators/x-saherelm.x121.xct", // Required Indicators
|
||||
"Helpers/x-saherelm.xfima.helper", // Indicator's Helpers Classes
|
||||
"Experts/x-saherelm.xfima.backtester.ea", // Expert Advisers
|
||||
"Experts/x-saherelm.xfima.ea", // Expert Advisers
|
||||
];
|
||||
|
||||
//
|
||||
run();
|
||||
|
||||
/**
|
||||
* Main Project Function ...
|
||||
*/
|
||||
async function run() {
|
||||
//
|
||||
await XProjectTools.prepareProject(projectName, projectResources);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Import Requirements ...
|
||||
const XProjectTools = require("./x-mql-project.tools");
|
||||
|
||||
//
|
||||
// define constants ...
|
||||
const projectName = "XOM1EA";
|
||||
|
||||
//
|
||||
// Projects Resources ...
|
||||
const projectResources = [
|
||||
"Libraries/*", // Required Libraries
|
||||
"Classes/*", // Required Classes
|
||||
"XOM1EA/*", // XOM1EA Projects ...
|
||||
"Indicators/x-saherelm.xom1", // Required Indicators
|
||||
"Helpers/x-saherelm.xom1.helper", // Indicator's Helpers Classes
|
||||
"Experts/x-saherelm.xom1.ea", // Expert Advisers
|
||||
];
|
||||
|
||||
//
|
||||
run();
|
||||
|
||||
/**
|
||||
* Main Project Function ...
|
||||
*/
|
||||
async function run() {
|
||||
//
|
||||
await XProjectTools.prepareProject(projectName, projectResources);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Import Requirements ...
|
||||
const XProjectTools = require("./x-mql-project.tools");
|
||||
|
||||
//
|
||||
// define constants ...
|
||||
const projectName = "XRRTools";
|
||||
|
||||
//
|
||||
// Projects Resources ...
|
||||
const projectResources = [
|
||||
"Libraries/*", // Required Libraries
|
||||
"Classes/*", // Required Classes
|
||||
"Indicators/x-saherelm.x121.xcc", // Required Indicators
|
||||
"Experts/x-saherelm.rr.tools", // Expert Advisers
|
||||
];
|
||||
|
||||
//
|
||||
run();
|
||||
|
||||
/**
|
||||
* Main Project Function ...
|
||||
*/
|
||||
async function run() {
|
||||
//
|
||||
await XProjectTools.prepareProject(projectName, projectResources);
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
/**
|
||||
* XProject Tools Module ...
|
||||
* a module for handle MQL5 Projects Compile and Packing ...
|
||||
*
|
||||
* Maintainer
|
||||
*
|
||||
* Hadi Khazaee Asl <https://saherelm.ir> (hadi_khazaee_asl@yahoo.com)
|
||||
*/
|
||||
|
||||
//
|
||||
// Import Requirements ...
|
||||
const execSync = require("child_process").execSync;
|
||||
const XFileTools = require("./Documents/JsModules/x-file.tools");
|
||||
const XShellTools = require("./Documents/JsModules/x-shell.tools");
|
||||
const XColorTools = require("./Documents/JsModules/x-color.tools");
|
||||
const XTypeTools = require("./Documents/JsModules/x-type-detector.tools");
|
||||
|
||||
//
|
||||
// Definitions ...
|
||||
const packsPath = XFileTools.joinPath(".", "Packs");
|
||||
const workspacePaths = [
|
||||
"Classes", // Required Classes
|
||||
"Experts", // Expert Advisers
|
||||
"Helpers", // Indicator's Helpers Classes
|
||||
"Indicators", // Required Indicators
|
||||
"Libraries", // Required Libraries
|
||||
"XOM1EA", // XOM1EA Projects ...
|
||||
"XFIMAEA", // XOM1EA Projects ...
|
||||
"XCATBEA", // XCATBEA Projects ...
|
||||
"X121XKIEA", // X121XKIEA Projects ...
|
||||
];
|
||||
|
||||
//
|
||||
//#region Tools ...
|
||||
|
||||
/**
|
||||
* Handle Compile and Pack Projects ...
|
||||
*
|
||||
* @param {string} projectName
|
||||
* @param {string[]} resources
|
||||
* @param {boolean} forceCleanup
|
||||
* @param {boolean} forceCompileIndicators
|
||||
*/
|
||||
async function prepareProject(
|
||||
projectName = "",
|
||||
resources = [],
|
||||
forceCleanup = true,
|
||||
forceCompileIndicators = true
|
||||
) {
|
||||
//
|
||||
// Check Resources Has Child ...
|
||||
let isValidArgs =
|
||||
resources.length > 0 &&
|
||||
XTypeTools.isArray(resources);
|
||||
if (!isValidArgs) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Invalid Resource List ...", XColorTools.COLOR_NAMES.Red)
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Check Project Name is Valid ...
|
||||
isValidArgs =
|
||||
projectName.length > 0 &&
|
||||
XTypeTools.isString(projectName);
|
||||
if (!isValidArgs) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Empty Project Name ...", XColorTools.COLOR_NAMES.Red)
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Prepare Pack Folder Exists ...
|
||||
let isExists = XFileTools.isDirectoryExists(packsPath);
|
||||
if (!isExists) {
|
||||
XFileTools.createDirectory(packsPath);
|
||||
}
|
||||
|
||||
//
|
||||
// Check Folder Exists or not ...
|
||||
// if Exists Remove it ...
|
||||
// Create Project Folder on Packs ...
|
||||
const prjFolderOnPacks = XFileTools.joinPath(packsPath, projectName);
|
||||
isExists = XFileTools.isDirectoryExists(prjFolderOnPacks);
|
||||
if (isExists) {
|
||||
await XFileTools.removeDirectory(prjFolderOnPacks, true);
|
||||
}
|
||||
XFileTools.createDirectory(prjFolderOnPacks);
|
||||
|
||||
//
|
||||
// Extract Required to Compile Resources List ...
|
||||
let compileList = await extractResources(resources, true);
|
||||
if (!compileList || compileList.length == 0) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Resource List Extraction to Compile Failed ...", XColorTools.COLOR_NAMES.Red)
|
||||
);
|
||||
|
||||
//
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Compile Resources Files ...
|
||||
for (const f of compileList) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("try to Compile: ", XColorTools.COLOR_NAMES.Yellow),
|
||||
XColorTools.applyForegroundColor(f, XColorTools.COLOR_NAMES.Blue)
|
||||
);
|
||||
|
||||
//
|
||||
await recursiveTriesToCompileFile(f, 0);
|
||||
}
|
||||
|
||||
//
|
||||
// Extract Required to Pack Resources List ...
|
||||
let packList = await extractResources(resources, false);
|
||||
if (!packList || packList.length == 0) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Resource List Extraction to Pack Failed ...", XColorTools.COLOR_NAMES.Red)
|
||||
);
|
||||
|
||||
//
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Loop through Files ...
|
||||
for (const f of packList) {
|
||||
//
|
||||
// Prepare Destination Path ...
|
||||
const fBasePath = XFileTools.basename(f);
|
||||
let fDestPath = f.replace(fBasePath, "");
|
||||
fDestPath = fDestPath.substring(0, fDestPath.length - 1);
|
||||
fDestPath = XFileTools.joinPath(prjFolderOnPacks, fDestPath);
|
||||
|
||||
//
|
||||
const isFDestPathExists = XFileTools.isDirectoryExists(fDestPath);
|
||||
if (!isFDestPathExists) {
|
||||
XFileTools.createDirectory(fDestPath);
|
||||
}
|
||||
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("try to copy: ", XColorTools.COLOR_NAMES.Yellow),
|
||||
XColorTools.applyForegroundColor(f, XColorTools.COLOR_NAMES.Blue),
|
||||
XColorTools.applyForegroundColor(" to : ", XColorTools.COLOR_NAMES.Yellow),
|
||||
XColorTools.applyForegroundColor(fDestPath, XColorTools.COLOR_NAMES.Magenta)
|
||||
);
|
||||
|
||||
//
|
||||
let result = await XFileTools.copyFile(f, fDestPath);
|
||||
if (result) {
|
||||
//
|
||||
const msg = "copy successfully ...";
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Green)
|
||||
);
|
||||
} else {
|
||||
//
|
||||
const msg = "copy failed ...";
|
||||
console.log(XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Red));
|
||||
throw msg;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Project: ", XColorTools.COLOR_NAMES.Green),
|
||||
XColorTools.applyForegroundColor(projectName, XColorTools.COLOR_NAMES.Yellow),
|
||||
XColorTools.applyForegroundColor(" Compiled and Packed Successfully ...", XColorTools.COLOR_NAMES.Green),
|
||||
);
|
||||
|
||||
//
|
||||
// Cleanup WorkSpace if Provided ...
|
||||
if (forceCleanup) {
|
||||
await cleanupWorkSpace();
|
||||
}
|
||||
|
||||
//
|
||||
// Compile Indicators ...
|
||||
if (forceCompileIndicators) {
|
||||
await runCommand("Indicators");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract required Files List ...
|
||||
*
|
||||
* @param {boolean} forCompile
|
||||
*
|
||||
* @returns Promise<string[]>
|
||||
*/
|
||||
async function extractResources(
|
||||
resources = [],
|
||||
forCompile = true
|
||||
) {
|
||||
//
|
||||
let result = [];
|
||||
|
||||
//
|
||||
// Loop Through Project Resources and Extract Files List to Compile ...
|
||||
for (const resource of resources) {
|
||||
//
|
||||
// Prepare Normalized Resource Path ...
|
||||
let resourcePath = XFileTools.joinPath(".", resource);
|
||||
|
||||
//
|
||||
// Handle Wild Card Resources ...
|
||||
if (resourcePath.endsWith("//*") ||
|
||||
resourcePath.endsWith("\\*")) {
|
||||
//
|
||||
resourcePath = resourcePath.substring(0, resourcePath.length - 2);
|
||||
const files = await XFileTools.extractFiles(
|
||||
resourcePath,
|
||||
forCompile ? [".mq5", ".mq4"] : [".ex5", ".ex4"]
|
||||
);
|
||||
|
||||
//
|
||||
result = result.concat(files);
|
||||
}
|
||||
//
|
||||
// Handle Direct File Resources ...
|
||||
else {
|
||||
//
|
||||
// Check MQL 5 Version ...
|
||||
let fileResourcePath = resourcePath + (forCompile ? ".mq5" : ".ex5");
|
||||
|
||||
//
|
||||
// Check File Exists ...
|
||||
let isExists = XFileTools.isFileExists(fileResourcePath);
|
||||
if (!isExists) {
|
||||
//
|
||||
// Check MQL 4 Version ...
|
||||
fileResourcePath = resourcePath + (forCompile ? ".mq4" : ".ex4");
|
||||
isExists = XFileTools.isFileExists(fileResourcePath);
|
||||
}
|
||||
|
||||
//
|
||||
// Add File Path to Files List if Exists ...
|
||||
if (isExists) {
|
||||
result.push(fileResourcePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* try to compile a file ...
|
||||
* @param {string} f
|
||||
* @param {number} numberOfTries
|
||||
*/
|
||||
async function recursiveTriesToCompileFile(f = "", numberOfTries = 0) {
|
||||
//
|
||||
let result = await handleFileCompile(f);
|
||||
if (result) {
|
||||
//
|
||||
const msg = "compiled successfully ...";
|
||||
console.log(XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Green));
|
||||
return result;
|
||||
} else {
|
||||
//
|
||||
if (numberOfTries < 10) {
|
||||
//
|
||||
numberOfTries++;
|
||||
|
||||
//
|
||||
const msg = "retry (" + (numberOfTries + 1) + ") ...";
|
||||
console.log(XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.LightMagenta));
|
||||
await recursiveTriesToCompileFile(f, numberOfTries);
|
||||
} else {
|
||||
//
|
||||
const msg = "failed ...";
|
||||
console.log(XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Red));
|
||||
throw msg;
|
||||
|
||||
//
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* execute compile task on Specific File ...
|
||||
* @param {string} f file path to Execute Compile task ...
|
||||
* @returns
|
||||
*/
|
||||
async function handleFileCompile(f = "") {
|
||||
//
|
||||
let result = false;
|
||||
|
||||
//
|
||||
let exResult = await compileMQLFile(f);
|
||||
if (exResult.includes("0 errors")) {
|
||||
result = true;
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
|
||||
//
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile Specific MQL File ...
|
||||
*
|
||||
* @param {string} path MQL file path ...
|
||||
* @returns
|
||||
*/
|
||||
async function compileMQLFile(path = "") {
|
||||
//
|
||||
// Check File Exists ...
|
||||
const isExists = XFileTools.isFileExists(path);
|
||||
if (!isExists) {
|
||||
//
|
||||
const msg = "file not found ...";
|
||||
console.log(XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Red));
|
||||
throw msg;
|
||||
}
|
||||
|
||||
//
|
||||
const ext = XFileTools.getFileExtension(path);
|
||||
if (ext !== ".mq5" && ext !== "mq4") {
|
||||
//
|
||||
const msg = "invalid file extension ...";
|
||||
console.log(XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Red));
|
||||
throw msg;
|
||||
}
|
||||
|
||||
//
|
||||
const cwd = __dirname;
|
||||
const cmd = "compile.cmd " + path;
|
||||
|
||||
//
|
||||
const result = await XShellTools.execute(cmd, cwd);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Specified Command in workspace ...
|
||||
*
|
||||
* @param {string} cmd
|
||||
* @returns
|
||||
*/
|
||||
async function runCommand(cmd = "") {
|
||||
//
|
||||
// Validate Args ...
|
||||
let isValidArgs = cmd.length > 0 &&
|
||||
XTypeTools.isString(cmd);
|
||||
|
||||
//
|
||||
// Validate Commands ...
|
||||
if (isValidArgs) {
|
||||
//
|
||||
// Check Available Commands ...
|
||||
isValidArgs =
|
||||
cmd === "*" ||
|
||||
cmd === "cleanup" ||
|
||||
workspacePaths.includes(cmd) ||
|
||||
workspacePaths.map(x => x.toLowerCase(x)).includes(cmd.toLowerCase());
|
||||
}
|
||||
if (!isValidArgs) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Invalid Command ...", XColorTools.COLOR_NAMES.Red)
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Compile ...
|
||||
if (cmd == "*") {
|
||||
await compileWorkSpace();
|
||||
} else if (cmd == "cleanup") {
|
||||
await cleanupWorkSpace();
|
||||
} else {
|
||||
//
|
||||
// Select Workspace to Compile ...
|
||||
const folder = workspacePaths
|
||||
.find(x => x.toLowerCase() === cmd.toLowerCase());
|
||||
isValidArgs =
|
||||
XTypeTools.isString(folder) &&
|
||||
folder.length > 0;
|
||||
if (!isValidArgs) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Invalid Command ...", XColorTools.COLOR_NAMES.Red)
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Extract Required to Compile Resources List ...
|
||||
let compileList = await XFileTools.extractFiles(
|
||||
folder,
|
||||
[".mq5", ".mq4"]
|
||||
);
|
||||
if (!compileList || compileList.length == 0) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("Extraction to Compile Failed ...", XColorTools.COLOR_NAMES.Red)
|
||||
);
|
||||
|
||||
//
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Compile Resources Files ...
|
||||
for (const f of compileList) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("try to Compile: ", XColorTools.COLOR_NAMES.Yellow),
|
||||
XColorTools.applyForegroundColor(f, XColorTools.COLOR_NAMES.Blue)
|
||||
);
|
||||
|
||||
//
|
||||
await recursiveTriesToCompileFile(f, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup Workspace compiled files ...
|
||||
*/
|
||||
async function cleanupWorkSpace() {
|
||||
//
|
||||
for (const path of workspacePaths) {
|
||||
//
|
||||
const fSourcePath = XFileTools.joinPath(".", path);
|
||||
const filesToRemove = await XFileTools.extractFiles(fSourcePath, [".ex5", ".ex4"]);
|
||||
for (const f of filesToRemove) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor("try to Remove: ", XColorTools.COLOR_NAMES.Yellow),
|
||||
XColorTools.applyForegroundColor(f, XColorTools.COLOR_NAMES.Blue)
|
||||
);
|
||||
|
||||
//
|
||||
let result = await XFileTools.removeFile(f);
|
||||
if (result) {
|
||||
//
|
||||
const msg = "removed successfully ...";
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Green)
|
||||
);
|
||||
} else {
|
||||
//
|
||||
const msg = "removed failed ...";
|
||||
console.log(XColorTools.applyForegroundColor(msg, XColorTools.COLOR_NAMES.Red));
|
||||
throw msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile Workspace ...
|
||||
*/
|
||||
async function compileWorkSpace() {
|
||||
//
|
||||
for (const path of workspacePaths) {
|
||||
await runCommand(path);
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//
|
||||
//#region Module Exports ...
|
||||
module.exports = {
|
||||
extractResources,
|
||||
recursiveTriesToCompileFile,
|
||||
handleFileCompile,
|
||||
compileMQLFile,
|
||||
runCommand,
|
||||
cleanupWorkSpace,
|
||||
compileWorkSpace,
|
||||
prepareProject
|
||||
};
|
||||
//#endregion
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* XMQL Document Helper CLI Tools ...
|
||||
* a module for handle MQL5 Documents Generating ...
|
||||
*
|
||||
* Maintainer
|
||||
*
|
||||
* Hadi Khazaee Asl <https://saherelm.ir> (hadi_khazaee_asl@yahoo.com)
|
||||
*/
|
||||
|
||||
//
|
||||
// Import Requirements ...
|
||||
const XColorTools = require("./x-color.tools");
|
||||
const XDocumentTools = require("./x-mql-document.tools");
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length < 1) {
|
||||
//
|
||||
// Log ...
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor(
|
||||
"Invalid Usage Command ...",
|
||||
XColorTools.COLOR_NAMES.Red
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor(
|
||||
"Usage: node mql5-doc-extractor.js <path_to_mql5_file> [output_md_file]",
|
||||
XColorTools.COLOR_NAMES.Yellow
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
XColorTools.applyForegroundColor(
|
||||
"Example: node mqlDocumentHelper.js ./MyEA.mq5 ./docs/MyEA.md",
|
||||
XColorTools.COLOR_NAMES.LightBlue
|
||||
)
|
||||
);
|
||||
|
||||
//
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const inputFile = args[0];
|
||||
const outputFile = args[1] || inputFile.replace(/\.(mq5|mqh)$/i, '_DOCS.md');
|
||||
|
||||
XDocumentTools.extractMQL5Documentation(inputFile, outputFile);
|
||||
@@ -0,0 +1,183 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
//
|
||||
//#region Tools ...
|
||||
/**
|
||||
* Extracts Doxygen-style documentation from MQL5 files and generates Markdown.
|
||||
* @param {string} filePath - Path to the .mq5 or .mqh file
|
||||
* @param {string} outputPath - Path to save the generated .md file
|
||||
*/
|
||||
function extractMQL5Documentation(filePath, outputPath) {
|
||||
//
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.error(`Error: File not found at ${filePath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
const extractedDocs = [];
|
||||
|
||||
//
|
||||
let currentCommentBlock = [];
|
||||
let isInsideBlockComment = false;
|
||||
|
||||
//
|
||||
// State machine to parse comments and map them to the next line of code
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
//
|
||||
const line = lines[i];
|
||||
const trimmedLine = line.trim();
|
||||
|
||||
//
|
||||
// 1. Handle Block Comments (/* ... */)
|
||||
if (trimmedLine.startsWith('/*') || trimmedLine.startsWith('/**')) {
|
||||
//
|
||||
isInsideBlockComment = true;
|
||||
currentCommentBlock.push(trimmedLine);
|
||||
if (trimmedLine.endsWith('*/')) {
|
||||
isInsideBlockComment = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
if (isInsideBlockComment) {
|
||||
//
|
||||
currentCommentBlock.push(trimmedLine);
|
||||
if (trimmedLine.endsWith('*/')) {
|
||||
isInsideBlockComment = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// 2. Handle Line Comments (/// or //)
|
||||
if (trimmedLine.startsWith('///') || trimmedLine.startsWith('//')) {
|
||||
//
|
||||
currentCommentBlock.push(trimmedLine);
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// 3. Handle Empty Lines (Keep them to preserve spacing before code)
|
||||
if (trimmedLine === '') {
|
||||
//
|
||||
if (currentCommentBlock.length > 0) {
|
||||
currentCommentBlock.push('');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// 4. We hit actual code. If we have a comment block, map it.
|
||||
if (currentCommentBlock.length > 0) {
|
||||
//
|
||||
// Clean up trailing empty lines in the comment block
|
||||
while (currentCommentBlock.length > 0 &&
|
||||
currentCommentBlock[currentCommentBlock.length - 1].trim() === ''
|
||||
) {
|
||||
currentCommentBlock.pop();
|
||||
}
|
||||
|
||||
//
|
||||
if (currentCommentBlock.length > 0) {
|
||||
//
|
||||
extractedDocs.push({
|
||||
comment: currentCommentBlock.join('\n'),
|
||||
code: trimmedLine
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
currentCommentBlock = [];
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Generate Markdown Output
|
||||
generateMarkdown(filePath, extractedDocs, outputPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the extracted data into a clean Markdown document.
|
||||
*/
|
||||
function generateMarkdown(filePath, docs, outputPath) {
|
||||
//
|
||||
const fileName = path.basename(filePath);
|
||||
let md = `# Documentation for \`${fileName}\`\n\n`;
|
||||
md += `*Extracted on: ${new Date().toLocaleDateString()}*\n\n---\n\n`;
|
||||
|
||||
//
|
||||
let fileHeaderFound = false;
|
||||
|
||||
//
|
||||
docs.forEach((doc, index) => {
|
||||
//
|
||||
// Check if this is the file-level header (usually the very first block)
|
||||
if (index === 0 &&
|
||||
(doc.code.startsWith('#property') || doc.code.startsWith('//+--'))) {
|
||||
//
|
||||
md += `## File Overview\n`;
|
||||
md += formatComment(doc.comment) + '\n\n';
|
||||
fileHeaderFound = true;
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Only document actual functions, classes, structs, or important variables
|
||||
const isDocumentableCode = /^(void|int|double|bool|string|long|ulong|uint|class|struct|enum|input)\s+/.test(doc.code)
|
||||
|| doc.code.includes('class ')
|
||||
|| doc.code.includes('struct ');
|
||||
|
||||
if (isDocumentableCode) {
|
||||
//
|
||||
md += `### \`${extractSignature(doc.code)}\`\n\n`;
|
||||
md += `**Declaration:**\n\`\`\`cpp\n${doc.code}\n\`\`\`\n\n`;
|
||||
md += `**Documentation:**\n${formatComment(doc.comment)}\n\n---\n\n`;
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
fs.writeFileSync(outputPath, md, 'utf8');
|
||||
console.log(`✅ Successfully extracted documentation to: ${outputPath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up Doxygen tags for better Markdown readability.
|
||||
*/
|
||||
function formatComment(comment) {
|
||||
return comment
|
||||
.replace(/\/\*\*?|\*\//g, '') // Remove block comment markers
|
||||
.replace(/^\s*\*\s?/gm, '') // Remove leading asterisks
|
||||
.replace(/^\/\/\/?\s?/gm, '') // Remove line comment markers
|
||||
.replace(/@param\s+(\w+)\s*/g, '**Param `$1`:** ')
|
||||
.replace(/@return\s*/g, '**Returns:** ')
|
||||
.replace(/@brief\s*/g, '**Summary:** ')
|
||||
.replace(/@note\s*/g, '*Note:* ')
|
||||
.replace(/@warning\s*/g, '> **Warning:** ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a clean function/class signature from the code line.
|
||||
*/
|
||||
function extractSignature(codeLine) {
|
||||
//
|
||||
// Basic extraction, stops at the first opening parenthesis or brace
|
||||
const match = codeLine.match(/^(.*?)[\({]/);
|
||||
return match ? match[1].trim() : codeLine;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//
|
||||
//#region Module Exports ...
|
||||
module.exports = {
|
||||
formatComment,
|
||||
generateMarkdown,
|
||||
extractSignature,
|
||||
extractMQL5Documentation,
|
||||
}
|
||||
//#endregion
|
||||
@@ -24,7 +24,10 @@ const workspacePaths = [
|
||||
"Helpers", // Indicator's Helpers Classes
|
||||
"Indicators", // Required Indicators
|
||||
"Libraries", // Required Libraries
|
||||
"XOM1EA", // XOM1EA Projects ...
|
||||
"XFIMAEA", // XOM1EA Projects ...
|
||||
"XCATBEA", // XCATBEA Projects ...
|
||||
"X121XKIEA", // X121XKIEA Projects ...
|
||||
];
|
||||
|
||||
//
|
||||
@@ -70,6 +73,24 @@ async function prepareProject(
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Prepare Pack Folder Exists ...
|
||||
let isExists = XFileTools.isDirectoryExists(packsPath);
|
||||
if (!isExists) {
|
||||
XFileTools.createDirectory(packsPath);
|
||||
}
|
||||
|
||||
//
|
||||
// Check Folder Exists or not ...
|
||||
// if Exists Remove it ...
|
||||
// Create Project Folder on Packs ...
|
||||
const prjFolderOnPacks = XFileTools.joinPath(packsPath, projectName);
|
||||
isExists = XFileTools.isDirectoryExists(prjFolderOnPacks);
|
||||
if (isExists) {
|
||||
await XFileTools.removeDirectory(prjFolderOnPacks, true);
|
||||
}
|
||||
XFileTools.createDirectory(prjFolderOnPacks);
|
||||
|
||||
//
|
||||
// Extract Required to Compile Resources List ...
|
||||
let compileList = await extractResources(resources, true);
|
||||
@@ -112,24 +133,6 @@ async function prepareProject(
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Prepare Pack Folder Exists ...
|
||||
let isExists = XFileTools.isDirectoryExists(packsPath);
|
||||
if (!isExists) {
|
||||
XFileTools.createDirectory(packsPath);
|
||||
}
|
||||
|
||||
//
|
||||
// Check Folder Exists or not ...
|
||||
// if Exists Remove it ...
|
||||
// Create Project Folder on Packs ...
|
||||
const prjFolderOnPacks = XFileTools.joinPath(packsPath, projectName);
|
||||
isExists = XFileTools.isDirectoryExists(prjFolderOnPacks);
|
||||
if (isExists) {
|
||||
await XFileTools.removeDirectory(prjFolderOnPacks, true);
|
||||
}
|
||||
XFileTools.createDirectory(prjFolderOnPacks);
|
||||
|
||||
//
|
||||
// Loop through Files ...
|
||||
for (const f of packList) {
|
||||
@@ -188,7 +191,7 @@ async function prepareProject(
|
||||
//
|
||||
// Compile Indicators ...
|
||||
if (forceCompileIndicators) {
|
||||
await compile("Indicators");
|
||||
await runCommand("Indicators");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,5 +493,5 @@ module.exports = {
|
||||
cleanupWorkSpace,
|
||||
compileWorkSpace,
|
||||
prepareProject
|
||||
}
|
||||
};
|
||||
//#endregion
|
||||
Reference in New Issue
Block a user