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