last ...
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user