Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto Generate structured project documentation using Diátaxis framework #730

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Creating Scripts to generate Diataxis based documentation
  • Loading branch information
Yemaneberhan-Lemma committed Feb 20, 2025
commit 564a925bee9c477c91d0d25ff61883ef2b8b6ab1
43 changes: 39 additions & 4 deletions drupal-doc-gen/utils/aiUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ ${content}`;
return 'Error generating documentation.';
}
}

// Function to generate Diátaxis-based structured documentation
async function generateDiataxisContent(content) {
try {
const formattedPrompt = `You are an expert documentation generator following the Diátaxis framework. Generate documentation based on the following content:
const formattedPrompt = `You are an expert documentation generator following the Diátaxis framework (Tutorials, How-To Guides, Reference, Explanation).
Generate well-structured documentation based on the following content:

Content:
${content}`;
Expand All @@ -66,9 +67,43 @@ ${content}`;

return response.data.choices[0].message.content.trim();
} catch (error) {
console.error('Error generating Diátaxis documentation:', error.response?.data || error.message);
console.error('Error generating Diátaxis documentation:', error.response?.data || error.message);
return 'Error generating Diátaxis documentation.';
}
}

module.exports = { generateAiEnhancedReadme, generateDiataxisContent };
// AI function to extract key points from a file
async function generateCorePoints(fileName, content) {
try {
const formattedPrompt = `You are an expert in extracting only relevant key points for documentation.
Given the content of the file "${fileName}", extract only the most useful core points for documentation.

Content:
${content}`;

const response = await axios.post(
API_BASE_URL,
{
model: MODEL,
messages: [
{ role: 'system', content: 'You are an AI that extracts core documentation points from files.' },
{ role: 'user', content: formattedPrompt }
],
max_tokens: 1000
},
{
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
}
}
);

return response.data.choices[0].message.content.trim();
} catch (error) {
console.error(`❌ Error extracting core points from ${fileName}:`, error.response?.data || error.message);
return 'Error extracting core points.';
}
}

module.exports = { generateAiEnhancedReadme, generateDiataxisContent, generateCorePoints };
64 changes: 64 additions & 0 deletions drupal-doc-gen/utils/extractCoreInfo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const fs = require('fs-extra');
const path = require('path');
const glob = require('glob');
const { readFileIfExists } = require('./fileUtils');
const { generateCorePoints } = require('./aiUtils'); // New AI function

const OUTPUT_FILE = 'core_info.json';

const FILE_SOURCES = {
"Tools and Pre-requisites": ["README.md", "composer.json", ".lando.yml", "docker-compose.yml", ".github/workflows/*.yml"],
"Local Environment Setup": ["README.md", ".lando.yml", "docker-compose.yml", "web/sites/example.settings.local.php"],
"How to Work on This Project": ["README.md", "CONTRIBUTING.md", ".github/workflows/*.yml"],
"Content Structure": ["/config/sync/*", "web/modules/custom/*/README.md", "web/themes/custom/*/README.md"],
"Custom Modules and Themes": ["web/modules/custom/*/README.md", "web/themes/custom/*/README.md", "composer.json"],
"Testing": ["/tests/*", "/core/tests/*", ".github/workflows/*.yml"],
"Monitoring and Logging": ["/config/sync/system.logging.yml", "web/sites/default/settings.php", "web/sites/default/settings.local.php", "sentry.settings.yml"],
"CI/CD": [".github/workflows/*.yml", ".gitlab-ci.yml", "Jenkinsfile", "bitbucket-pipelines.yml"]
};

// Extract core points using AI
async function extractProjectDetails() {
let details = {};
let missingFiles = [];

for (const [category, files] of Object.entries(FILE_SOURCES)) {
details[category] = [];

for (const filePattern of files) {
let matchingFiles = glob.sync(filePattern, { nodir: true });

if (matchingFiles.length === 0) {
missingFiles.push(filePattern);
}

for (const file of matchingFiles) {
const content = readFileIfExists(file);
if (!content.trim()) continue;

console.log(`🧠 Extracting core points from ${file} using AI...`);
const extractedPoints = await generateCorePoints(file, content);

if (extractedPoints) {
details[category].push({ file, extractedPoints });
}
}
}
}

if (missingFiles.length > 0) {
console.warn("⚠️ Warning: Some expected files were not found:", missingFiles.join(", "));
}

return details;
}

// Save extracted details into core_info.json
async function saveExtractedInfo() {
const projectDetails = await extractProjectDetails();

fs.writeFileSync(OUTPUT_FILE, JSON.stringify(projectDetails, null, 2));
console.log(`✅ Core project information extracted and saved in '${OUTPUT_FILE}'`);
}

saveExtractedInfo();
98 changes: 71 additions & 27 deletions drupal-doc-gen/utils/generateDocs.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,89 @@
const fs = require('fs-extra');
const path = require('path');
const { readFileIfExists, ensureDirectories } = require('./fileUtils');
const { generateDiataxisContent } = require('./aiUtils');

const OUTPUT_DIRS = {
'Tutorial': 'docs3/tutorials',
'How-To': 'docs3/how-to',
'Reference': 'docs3/reference',
'Explanation': 'docs3/explanation',
'Index': 'docs3'
const CORE_INFO_FILE = 'core_info.json';
const OUTPUT_DIR = 'docs';

// Ensure the `docs/` directory exists
fs.ensureDirSync(OUTPUT_DIR);

// **Mapping core points to meaningful documentation files**
const DOC_STRUCTURE = {
"tools-and-prerequisites.md": ["Tools and Pre-requisites"],
"local-environment-setup.md": ["Local Environment Setup"],
"how-to-work-on-project.md": ["How to Work on This Project"],
"content-structure.md": ["Content Structure"],
"custom-modules-and-themes.md": ["Custom Modules and Themes"],
"testing-strategy.md": ["Testing"],
"monitoring-and-logging.md": ["Monitoring and Logging"],
"ci-cd-process.md": ["CI/CD"]
};

function extractProjectDetails(rootReadme, configFiles) {
let details = { overview: '', tools: [], setup: '', workflow: '', testing: '', monitoring: '', ci_cd: '' };
const readmeContent = readFileIfExists(rootReadme);
// Decide which documentation files to create based on extracted core points
function determineDocumentationFiles(coreInfo) {
let documentationFiles = {};

if (readmeContent) {
details.overview = readmeContent.split('\\n')[0];
details.setup = readmeContent.match(/## Local Environment Setup[\\s\\S]*?(?=##|$)/gi)?.[0] || '';
details.workflow = readmeContent.match(/## How to work on the project[\\s\\S]*?(?=##|$)/gi)?.[0] || '';
details.testing = readmeContent.match(/## Testing[\\s\\S]*?(?=##|$)/gi)?.[0] || '';
details.monitoring = readmeContent.match(/## Monitoring and Logging[\\s\\S]*?(?=##|$)/gi)?.[0] || '';
details.ci_cd = readmeContent.match(/## CI\/CD[\s\S]*?(?=##|$)/gi)?.[0] || '';
Object.entries(DOC_STRUCTURE).forEach(([docFile, categories]) => {
let content = `# ${docFile.replace(/-/g, ' ').replace('.md', '')}\n\n`;

categories.forEach(category => {
if (coreInfo[category]) {
coreInfo[category].forEach(item => {
content += `### From ${item.file}\n${item.extractedPoints}\n\n`;
});
}
});

documentationFiles[docFile] = content;
});

}
return documentationFiles;
}

// Create meaningful documentation files with extracted content
function createDocumentationFiles(documentationFiles) {
Object.entries(documentationFiles).forEach(([fileName, content]) => {
const filePath = path.join(OUTPUT_DIR, fileName);
fs.writeFileSync(filePath, content);
console.log(`📄 Created: ${filePath}`);
});
}

return details;
// Refine documentation files using AI
async function refineDocumentationWithAI() {
const files = fs.readdirSync(OUTPUT_DIR);
for (const file of files) {
const filePath = path.join(OUTPUT_DIR, file);
let content = fs.readFileSync(filePath, 'utf8');

console.log(`🧠 Enhancing ${filePath} with AI...`);
const enhancedContent = await generateDiataxisContent(content);

fs.writeFileSync(filePath, enhancedContent);
console.log(`✅ AI-enhanced content saved: ${filePath}`);
}
}

async function generateDiataxisDocs(rootReadme, configFiles) {
ensureDirectories(Object.values(OUTPUT_DIRS));
// Main function to generate documentation
async function generateDocs() {
if (!fs.existsSync(CORE_INFO_FILE)) {
console.error(`❌ Missing core information file: ${CORE_INFO_FILE}`);
return;
}

const coreInfo = JSON.parse(fs.readFileSync(CORE_INFO_FILE, 'utf8'));

console.log("📄 Determining meaningful documentation files...");
const documentationFiles = determineDocumentationFiles(coreInfo);

const projectDetails = extractProjectDetails(rootReadme, configFiles);
const structuredDocs = await generateDiataxisContent(JSON.stringify(projectDetails, null, 2));
console.log("📝 Creating structured documentation files...");
createDocumentationFiles(documentationFiles);

const indexContent = `# Project Documentation Index\\n\\n## Introduction\\n${projectDetails.overview}\\n\\n## Tools Used\\n${projectDetails.tools.join(', ')}\\n\\n## Setup\\n${projectDetails.setup}\\n\\n## CI/CD\\n${projectDetails.ci_cd}\\n\\n## Available Modules and Themes:\\n`;
console.log("🚀 Enhancing documentation using AI...");
await refineDocumentationWithAI();

fs.writeFileSync(path.join(OUTPUT_DIRS.Index, 'index.md'), indexContent);
console.log('Diátaxis-based documentation with AI-enhanced insights generated.');
console.log("🎉 Project documentation successfully generated and refined.");
}

module.exports = { generateDiataxisDocs };
generateDocs();
63 changes: 63 additions & 0 deletions drupal-doc-gen/utils/organizeDocs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const fs = require('fs-extra');
const path = require('path');

const SOURCE_DIR = 'docs';
const OUTPUT_DIRS = {
'Tutorial': 'docs_final/tutorials',
'How-To': 'docs_final/how-to',
'Reference': 'docs_final/reference',
'Explanation': 'docs_final/explanation'
};

// Ensure output directories exist
function ensureOutputDirectories() {
Object.values(OUTPUT_DIRS).forEach(dir => fs.ensureDirSync(dir));
}

// Categorize documentation files based on their content
function categorizeDocumentationFiles() {
const files = fs.readdirSync(SOURCE_DIR);
let categorizedFiles = { Tutorial: [], 'How-To': [], Reference: [], Explanation: [] };

files.forEach(file => {
const filePath = path.join(SOURCE_DIR, file);
const content = fs.readFileSync(filePath, 'utf8');

if (content.includes('## Step-by-Step Guide')) {
categorizedFiles.Tutorial.push(file);
} else if (content.includes('## How To')) {
categorizedFiles['How-To'].push(file);
} else if (content.includes('## API Reference') || content.includes('## Configuration')) {
categorizedFiles.Reference.push(file);
} else {
categorizedFiles.Explanation.push(file);
}
});

return categorizedFiles;
}

// Move files into the correct Diátaxis categories
function moveFilesToCategories(categorizedFiles) {
Object.entries(categorizedFiles).forEach(([category, files]) => {
files.forEach(file => {
const srcPath = path.join(SOURCE_DIR, file);
const destPath = path.join(OUTPUT_DIRS[category], file);
fs.moveSync(srcPath, destPath);
console.log(`📂 Moved ${file} to ${category}`);
});
});
}

// Main function
function organizeDocs() {
console.log("📂 Organizing documentation into Diátaxis structure...");
ensureOutputDirectories();

const categorizedFiles = categorizeDocumentationFiles();
moveFilesToCategories(categorizedFiles);

console.log("🎉 Documentation successfully structured into Diátaxis categories.");
}

organizeDocs();
Loading