mirror of https://github.com/OpenVidu/openvidu.git
ov-components: enhance directive table generation logic and improve error handling
parent
b7d9f822de
commit
c628b2ab68
|
@ -3,168 +3,334 @@ const glob = require('glob');
|
|||
|
||||
const startApiLine = '<!-- start-dynamic-api-directives-content -->';
|
||||
const apiDirectivesTable =
|
||||
'| **Parameter** | **Type** | **Reference** | \n' +
|
||||
'|:--------------------------------: | :-------: | :---------------------------------------------: |';
|
||||
'| **Parameter** | **Type** | **Reference** | \n' +
|
||||
'|:--------------------------------: | :-------: | :---------------------------------------------: |';
|
||||
const endApiLine = '<!-- end-dynamic-api-directives-content -->';
|
||||
|
||||
/**
|
||||
* Get all directive files from the API directives directory
|
||||
*/
|
||||
function getDirectiveFiles() {
|
||||
// Directory where directive files are located
|
||||
const directivesDir = 'projects/openvidu-components-angular/src/lib/directives/api';
|
||||
return listFiles(directivesDir, '.directive.ts');
|
||||
const directivesDir = 'projects/openvidu-components-angular/src/lib/directives/api';
|
||||
return listFiles(directivesDir, '.directive.ts');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all component files
|
||||
*/
|
||||
function getComponentFiles() {
|
||||
// Directory where component files are located
|
||||
const componentsDir = 'projects/openvidu-components-angular/src/lib/components';
|
||||
return listFiles(componentsDir, '.component.ts');
|
||||
const componentsDir = 'projects/openvidu-components-angular/src/lib/components';
|
||||
return listFiles(componentsDir, '.component.ts');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all admin files
|
||||
*/
|
||||
function getAdminFiles() {
|
||||
// Directory where component files are located
|
||||
const componentsDir = 'projects/openvidu-components-angular/src/lib/admin';
|
||||
return listFiles(componentsDir, '.component.ts');
|
||||
const componentsDir = 'projects/openvidu-components-angular/src/lib/admin';
|
||||
return listFiles(componentsDir, '.component.ts');
|
||||
}
|
||||
|
||||
/**
|
||||
* List all files with specific extension in directory
|
||||
*/
|
||||
function listFiles(directoryPath, fileExtension) {
|
||||
const files = glob.sync(`${directoryPath}/**/*${fileExtension}`);
|
||||
if (files.length === 0) {
|
||||
throw new Error(`No ${fileExtension} files found in ${directoryPath}`);
|
||||
}
|
||||
return files;
|
||||
const files = glob.sync(`${directoryPath}/**/*${fileExtension}`);
|
||||
if (files.length === 0) {
|
||||
throw new Error(`No ${fileExtension} files found in ${directoryPath}`);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract component selector from component file
|
||||
*/
|
||||
function getComponentSelector(componentFile) {
|
||||
const componentContent = fs.readFileSync(componentFile, 'utf8');
|
||||
const selectorMatch = componentContent.match(/@Component\({[^]*?selector:\s*['"]([^'"]+)['"][^]*?}\)/s);
|
||||
|
||||
if (!selectorMatch) {
|
||||
throw new Error(`Unable to find selector in component file: ${componentFile}`);
|
||||
}
|
||||
|
||||
return selectorMatch[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a directive class has @internal annotation
|
||||
*/
|
||||
function isInternalDirective(directiveContent, className) {
|
||||
const classRegex = new RegExp(`(/\\*\\*[\\s\\S]*?\\*/)?\\s*@Directive\\([\\s\\S]*?\\)\\s*export\\s+class\\s+${escapeRegex(className)}`, 'g');
|
||||
const match = classRegex.exec(directiveContent);
|
||||
|
||||
if (match && match[1]) {
|
||||
return match[1].includes('@internal');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract attribute name from selector for a specific component
|
||||
*/
|
||||
function extractAttributeForComponent(selector, componentSelector) {
|
||||
// Split selector by comma and trim whitespace
|
||||
const selectorParts = selector.split(',').map(part => part.trim());
|
||||
|
||||
// Find the part that matches our component
|
||||
for (const part of selectorParts) {
|
||||
if (part.includes(componentSelector)) {
|
||||
// Extract attribute from this specific part
|
||||
const attributeMatch = part.match(/\[([^\]]+)\]/);
|
||||
if (attributeMatch) {
|
||||
return attributeMatch[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: if no specific match, return the first attribute found
|
||||
const fallbackMatch = selector.match(/\[([^\]]+)\]/);
|
||||
return fallbackMatch ? fallbackMatch[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all directive classes from a directive file
|
||||
*/
|
||||
function extractDirectiveClasses(directiveContent) {
|
||||
const classes = [];
|
||||
|
||||
// Regex to find all directive class definitions with their preceding @Directive decorators
|
||||
const directiveClassRegex = /@Directive\(\s*{\s*selector:\s*['"]([^'"]+)['"][^}]*}\s*\)\s*export\s+class\s+(\w+)/gs;
|
||||
|
||||
let match;
|
||||
while ((match = directiveClassRegex.exec(directiveContent)) !== null) {
|
||||
const selector = match[1];
|
||||
const className = match[2];
|
||||
|
||||
// Skip internal directives
|
||||
if (isInternalDirective(directiveContent, className)) {
|
||||
console.log(`Skipping internal directive: ${className}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
classes.push({
|
||||
selector,
|
||||
className
|
||||
});
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all directives from a directive file that match a component selector
|
||||
*/
|
||||
function extractDirectivesForComponent(directiveFile, componentSelector) {
|
||||
const directiveContent = fs.readFileSync(directiveFile, 'utf8');
|
||||
const directives = [];
|
||||
|
||||
// Get all directive classes in the file (excluding internal ones)
|
||||
const directiveClasses = extractDirectiveClasses(directiveContent);
|
||||
|
||||
// Filter classes that match the component selector
|
||||
const matchingClasses = directiveClasses.filter(directiveClass =>
|
||||
directiveClass.selector.includes(componentSelector)
|
||||
);
|
||||
|
||||
// For each matching class, extract input type information
|
||||
matchingClasses.forEach(directiveClass => {
|
||||
// Extract the correct attribute name for this component
|
||||
const attributeName = extractAttributeForComponent(directiveClass.selector, componentSelector);
|
||||
|
||||
if (attributeName) {
|
||||
const inputInfo = extractInputInfo(directiveContent, attributeName, directiveClass.className);
|
||||
|
||||
if (inputInfo) {
|
||||
directives.push({
|
||||
attribute: attributeName,
|
||||
type: inputInfo.type,
|
||||
className: directiveClass.className
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return directives;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract input information (type) for a specific attribute and class
|
||||
*/
|
||||
function extractInputInfo(directiveContent, attributeName, className) {
|
||||
// Create a regex to find the specific class section
|
||||
const classRegex = new RegExp(`export\\s+class\\s+${escapeRegex(className)}[^}]*?{([^]*?)(?=export\\s+class|$)`, 's');
|
||||
const classMatch = directiveContent.match(classRegex);
|
||||
|
||||
if (!classMatch) {
|
||||
console.warn(`Could not find class ${className}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const classContent = classMatch[1];
|
||||
|
||||
// Regex to find the @Input setter for this attribute within the class
|
||||
const inputRegex = new RegExp(
|
||||
`@Input\\(\\)\\s+set\\s+${escapeRegex(attributeName)}\\s*\\(\\s*\\w+:\\s*([^)]+)\\s*\\)`,
|
||||
'g'
|
||||
);
|
||||
|
||||
const inputMatch = inputRegex.exec(classContent);
|
||||
if (!inputMatch) {
|
||||
console.warn(`Could not find @Input setter for attribute: ${attributeName} in class: ${className}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
let type = inputMatch[1].trim();
|
||||
|
||||
// Clean up the type (remove extra whitespace, etc.)
|
||||
type = type.replace(/\s+/g, ' ');
|
||||
|
||||
return {
|
||||
type: type
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape special regex characters
|
||||
*/
|
||||
function escapeRegex(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate API directives table for components
|
||||
*/
|
||||
function generateApiDirectivesTable(componentFiles, directiveFiles) {
|
||||
componentFiles.forEach((componentFile) => {
|
||||
try {
|
||||
console.log(`Processing component: ${componentFile}`);
|
||||
|
||||
const componentSelector = getComponentSelector(componentFile);
|
||||
const readmeFilePath = componentFile.replace('.ts', '.md');
|
||||
|
||||
console.log(`Component selector: ${componentSelector}`);
|
||||
|
||||
// Initialize table with header
|
||||
initializeDynamicTableContent(readmeFilePath);
|
||||
|
||||
const allDirectives = [];
|
||||
|
||||
// Extract directives from all directive files
|
||||
directiveFiles.forEach((directiveFile) => {
|
||||
console.log(`Checking directive file: ${directiveFile}`);
|
||||
const directives = extractDirectivesForComponent(directiveFile, componentSelector);
|
||||
allDirectives.push(...directives);
|
||||
});
|
||||
|
||||
console.log(`Found ${allDirectives.length} directives for ${componentSelector}`);
|
||||
|
||||
// Sort directives alphabetically by attribute name
|
||||
allDirectives.sort((a, b) => a.attribute.localeCompare(b.attribute));
|
||||
|
||||
// Add rows to table
|
||||
allDirectives.forEach((directive) => {
|
||||
addRowToTable(readmeFilePath, directive.attribute, directive.type, directive.className);
|
||||
});
|
||||
|
||||
// If no directives found, add "no directives" message
|
||||
if (allDirectives.length === 0) {
|
||||
removeApiTableContent(readmeFilePath);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error processing component ${componentFile}:`, error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize table with header
|
||||
*/
|
||||
function initializeDynamicTableContent(filePath) {
|
||||
replaceDynamicTableContent(filePath, apiDirectivesTable);
|
||||
replaceDynamicTableContent(filePath, apiDirectivesTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace table content with "no directives" message
|
||||
*/
|
||||
function removeApiTableContent(filePath) {
|
||||
const content = '_No API directives available for this component_. \n';
|
||||
replaceDynamicTableContent(filePath, content);
|
||||
const content = '_No API directives available for this component_. \n';
|
||||
replaceDynamicTableContent(filePath, content);
|
||||
}
|
||||
|
||||
function apiTableContentIsEmpty(filePath) {
|
||||
try {
|
||||
const data = fs.readFileSync(filePath, 'utf8');
|
||||
const startIdx = data.indexOf(startApiLine);
|
||||
const endIdx = data.indexOf(endApiLine);
|
||||
if (startIdx !== -1 && endIdx !== -1) {
|
||||
const capturedContent = data.substring(startIdx + startApiLine.length, endIdx).trim();
|
||||
return capturedContent === apiDirectivesTable;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function writeApiDirectivesTable(componentFiles, directiveFiles) {
|
||||
componentFiles.forEach((componentFile) => {
|
||||
// const componentName = componentFile.split('/').pop()
|
||||
const componentFileName = componentFile.split('/').pop().replace('.component.ts', '');
|
||||
const componentName = componentFileName.replace(/(?:^|-)([a-z])/g, (_, char) => char.toUpperCase());
|
||||
const readmeFilePath = componentFile.replace('.ts', '.md');
|
||||
const componentContent = fs.readFileSync(componentFile, 'utf8');
|
||||
const selectorMatch = componentContent.match(/@Component\({[^]*?selector: ['"]([^'"]+)['"][^]*?}\)/);
|
||||
const componentSelectorName = selectorMatch[1];
|
||||
initializeDynamicTableContent(readmeFilePath);
|
||||
|
||||
if (!componentSelectorName) {
|
||||
throw new Error(`Unable to find the component name in the file ${componentFileName}`);
|
||||
}
|
||||
|
||||
// const directiveRegex = new RegExp(`@Directive\\(\\s*{[^}]*selector:\\s*['"]${componentName}\\s*\\[([^'"]+)\\]`, 'g');
|
||||
const directiveRegex = /^\s*(selector):\s*(['"])(.*?)\2\s*$/gm;
|
||||
|
||||
directiveFiles.forEach((directiveFile) => {
|
||||
const directiveContent = fs.readFileSync(directiveFile, 'utf8');
|
||||
|
||||
let directiveNameMatch;
|
||||
while ((directiveNameMatch = directiveRegex.exec(directiveContent)) !== null) {
|
||||
if (directiveNameMatch[0].includes('@Directive({\n//')) {
|
||||
// Skip directives that are commented out
|
||||
continue;
|
||||
}
|
||||
const selectorValue = directiveNameMatch[3].split(',');
|
||||
const directiveMatch = selectorValue.find((value) => value.includes(componentSelectorName));
|
||||
|
||||
if (directiveMatch) {
|
||||
const directiveName = directiveMatch.match(/\[(.*?)\]/).pop();
|
||||
const className = directiveName.replace(/(^\w{1})|(\s+\w{1})/g, (letter) => letter.toUpperCase()) + 'Directive';
|
||||
const inputRegex = new RegExp(
|
||||
`@Input\\(\\)\\s+set\\s+(${directiveName.replace(/\[/g, '\\[').replace(/\]/g, '\\]')})\\((\\w+):\\s+(\\w+)`
|
||||
);
|
||||
const inputMatch = directiveContent.match(inputRegex);
|
||||
const inputType = inputMatch && inputMatch.pop();
|
||||
|
||||
if (inputType && className) {
|
||||
let finalClassName = componentName === 'Videoconference' ? className : componentName + className;
|
||||
addRowToTable(readmeFilePath, directiveName, inputType, finalClassName);
|
||||
}
|
||||
} else {
|
||||
console.log(`The selector "${componentSelectorName}" does not match with ${selectorValue}. Skipping...`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (apiTableContentIsEmpty(readmeFilePath)) {
|
||||
removeApiTableContent(readmeFilePath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to add a row to a Markdown table in a file
|
||||
/**
|
||||
* Add a row to the markdown table
|
||||
*/
|
||||
function addRowToTable(filePath, parameter, type, reference) {
|
||||
// Read the current content of the file
|
||||
try {
|
||||
const data = fs.readFileSync(filePath, 'utf8');
|
||||
try {
|
||||
const data = fs.readFileSync(filePath, 'utf8');
|
||||
const markdownRow = `| **${parameter}** | \`${type}\` | [${reference}](../directives/${reference}.html) |`;
|
||||
|
||||
// Define the target line and the Markdown row
|
||||
const markdownRow = `| **${parameter}** | \`${type}\` | [${reference}](../directives/${reference}.html) |`;
|
||||
const lines = data.split('\n');
|
||||
const targetIndex = lines.findIndex((line) => line.includes(endApiLine));
|
||||
|
||||
// Find the line that contains the table
|
||||
const lines = data.split('\n');
|
||||
const targetIndex = lines.findIndex((line) => line.includes(endApiLine));
|
||||
|
||||
if (targetIndex !== -1) {
|
||||
// Insert the new row above the target line
|
||||
lines.splice(targetIndex, 0, markdownRow);
|
||||
|
||||
// Join the lines back together
|
||||
const updatedContent = lines.join('\n');
|
||||
|
||||
// Write the updated content to the file
|
||||
fs.writeFileSync(filePath, updatedContent, 'utf8');
|
||||
console.log('Row added successfully.');
|
||||
} else {
|
||||
console.error('Table not found in the file.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error writing to file:', error);
|
||||
}
|
||||
if (targetIndex !== -1) {
|
||||
lines.splice(targetIndex, 0, markdownRow);
|
||||
const updatedContent = lines.join('\n');
|
||||
fs.writeFileSync(filePath, updatedContent, 'utf8');
|
||||
console.log(`Added directive: ${parameter} -> ${reference}`);
|
||||
} else {
|
||||
console.error('End marker not found in file:', filePath);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error adding row to table:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace content between start and end markers
|
||||
*/
|
||||
function replaceDynamicTableContent(filePath, content) {
|
||||
// Read the current content of the file
|
||||
try {
|
||||
const data = fs.readFileSync(filePath, 'utf8');
|
||||
const pattern = new RegExp(`${startApiLine}([\\s\\S]*?)${endApiLine}`, 'g');
|
||||
try {
|
||||
const data = fs.readFileSync(filePath, 'utf8');
|
||||
const pattern = new RegExp(`${startApiLine}([\\s\\S]*?)${endApiLine}`, 'g');
|
||||
|
||||
// Replace the content between startLine and endLine with the replacement table
|
||||
const modifiedContent = data.replace(pattern, (match, capturedContent) => {
|
||||
return startApiLine + '\n' + content + '\n' + endApiLine;
|
||||
});
|
||||
// Write the modified content back to the file
|
||||
fs.writeFileSync(filePath, modifiedContent, 'utf8');
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
console.log(`${filePath} not found! Maybe it is an internal component. Skipping...`);
|
||||
} else {
|
||||
console.error('Error writing to file:', error);
|
||||
}
|
||||
}
|
||||
const modifiedContent = data.replace(pattern, (match, capturedContent) => {
|
||||
return startApiLine + '\n' + content + '\n' + endApiLine;
|
||||
});
|
||||
|
||||
fs.writeFileSync(filePath, modifiedContent, 'utf8');
|
||||
console.log(`Updated table content in: ${filePath}`);
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
console.log(`${filePath} not found! Maybe it is an internal component. Skipping...`);
|
||||
} else {
|
||||
console.error('Error writing to file:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const directiveFiles = getDirectiveFiles();
|
||||
const componentFiles = getComponentFiles();
|
||||
const adminFiles = getAdminFiles();
|
||||
writeApiDirectivesTable(componentFiles.concat(adminFiles), directiveFiles);
|
||||
// Main execution
|
||||
if (require.main === module) {
|
||||
try {
|
||||
const directiveFiles = getDirectiveFiles();
|
||||
const componentFiles = getComponentFiles();
|
||||
const adminFiles = getAdminFiles();
|
||||
|
||||
console.log('Starting directive table generation...');
|
||||
generateApiDirectivesTable(componentFiles.concat(adminFiles), directiveFiles);
|
||||
console.log('Directive table generation completed!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Script execution failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Export functions for testing
|
||||
module.exports = {
|
||||
generateApiDirectivesTable,
|
||||
getDirectiveFiles,
|
||||
getComponentFiles,
|
||||
getAdminFiles
|
||||
};
|
Loading…
Reference in New Issue