Files
MQL5Data/Documents/JsModules/x-mql-document.tools.js
T
2026-07-10 04:24:32 +03:30

183 lines
5.3 KiB
JavaScript

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