183 lines
5.3 KiB
JavaScript
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
|