mirror of https://github.com/OpenVidu/openvidu.git
ov-components: enhance directive table generation logic and improve error handling
parent
b7d9f822de
commit
c628b2ab68
|
@ -7,24 +7,33 @@ const apiDirectivesTable =
|
||||||
'|:--------------------------------: | :-------: | :---------------------------------------------: |';
|
'|:--------------------------------: | :-------: | :---------------------------------------------: |';
|
||||||
const endApiLine = '<!-- end-dynamic-api-directives-content -->';
|
const endApiLine = '<!-- end-dynamic-api-directives-content -->';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all directive files from the API directives directory
|
||||||
|
*/
|
||||||
function getDirectiveFiles() {
|
function getDirectiveFiles() {
|
||||||
// Directory where directive files are located
|
|
||||||
const directivesDir = 'projects/openvidu-components-angular/src/lib/directives/api';
|
const directivesDir = 'projects/openvidu-components-angular/src/lib/directives/api';
|
||||||
return listFiles(directivesDir, '.directive.ts');
|
return listFiles(directivesDir, '.directive.ts');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all component files
|
||||||
|
*/
|
||||||
function getComponentFiles() {
|
function getComponentFiles() {
|
||||||
// Directory where component files are located
|
|
||||||
const componentsDir = 'projects/openvidu-components-angular/src/lib/components';
|
const componentsDir = 'projects/openvidu-components-angular/src/lib/components';
|
||||||
return listFiles(componentsDir, '.component.ts');
|
return listFiles(componentsDir, '.component.ts');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all admin files
|
||||||
|
*/
|
||||||
function getAdminFiles() {
|
function getAdminFiles() {
|
||||||
// Directory where component files are located
|
|
||||||
const componentsDir = 'projects/openvidu-components-angular/src/lib/admin';
|
const componentsDir = 'projects/openvidu-components-angular/src/lib/admin';
|
||||||
return listFiles(componentsDir, '.component.ts');
|
return listFiles(componentsDir, '.component.ts');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all files with specific extension in directory
|
||||||
|
*/
|
||||||
function listFiles(directoryPath, fileExtension) {
|
function listFiles(directoryPath, fileExtension) {
|
||||||
const files = glob.sync(`${directoryPath}/**/*${fileExtension}`);
|
const files = glob.sync(`${directoryPath}/**/*${fileExtension}`);
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
|
@ -33,128 +42,265 @@ function listFiles(directoryPath, fileExtension) {
|
||||||
return files;
|
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) {
|
function initializeDynamicTableContent(filePath) {
|
||||||
replaceDynamicTableContent(filePath, apiDirectivesTable);
|
replaceDynamicTableContent(filePath, apiDirectivesTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace table content with "no directives" message
|
||||||
|
*/
|
||||||
function removeApiTableContent(filePath) {
|
function removeApiTableContent(filePath) {
|
||||||
const content = '_No API directives available for this component_. \n';
|
const content = '_No API directives available for this component_. \n';
|
||||||
replaceDynamicTableContent(filePath, content);
|
replaceDynamicTableContent(filePath, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
function apiTableContentIsEmpty(filePath) {
|
/**
|
||||||
try {
|
* Add a row to the markdown table
|
||||||
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
|
|
||||||
function addRowToTable(filePath, parameter, type, reference) {
|
function addRowToTable(filePath, parameter, type, reference) {
|
||||||
// Read the current content of the file
|
|
||||||
try {
|
try {
|
||||||
const data = fs.readFileSync(filePath, 'utf8');
|
const data = fs.readFileSync(filePath, 'utf8');
|
||||||
|
|
||||||
// Define the target line and the Markdown row
|
|
||||||
const markdownRow = `| **${parameter}** | \`${type}\` | [${reference}](../directives/${reference}.html) |`;
|
const markdownRow = `| **${parameter}** | \`${type}\` | [${reference}](../directives/${reference}.html) |`;
|
||||||
|
|
||||||
// Find the line that contains the table
|
|
||||||
const lines = data.split('\n');
|
const lines = data.split('\n');
|
||||||
const targetIndex = lines.findIndex((line) => line.includes(endApiLine));
|
const targetIndex = lines.findIndex((line) => line.includes(endApiLine));
|
||||||
|
|
||||||
if (targetIndex !== -1) {
|
if (targetIndex !== -1) {
|
||||||
// Insert the new row above the target line
|
|
||||||
lines.splice(targetIndex, 0, markdownRow);
|
lines.splice(targetIndex, 0, markdownRow);
|
||||||
|
|
||||||
// Join the lines back together
|
|
||||||
const updatedContent = lines.join('\n');
|
const updatedContent = lines.join('\n');
|
||||||
|
|
||||||
// Write the updated content to the file
|
|
||||||
fs.writeFileSync(filePath, updatedContent, 'utf8');
|
fs.writeFileSync(filePath, updatedContent, 'utf8');
|
||||||
console.log('Row added successfully.');
|
console.log(`Added directive: ${parameter} -> ${reference}`);
|
||||||
} else {
|
} else {
|
||||||
console.error('Table not found in the file.');
|
console.error('End marker not found in file:', filePath);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error writing to file:', error);
|
console.error('Error adding row to table:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace content between start and end markers
|
||||||
|
*/
|
||||||
function replaceDynamicTableContent(filePath, content) {
|
function replaceDynamicTableContent(filePath, content) {
|
||||||
// Read the current content of the file
|
|
||||||
try {
|
try {
|
||||||
const data = fs.readFileSync(filePath, 'utf8');
|
const data = fs.readFileSync(filePath, 'utf8');
|
||||||
const pattern = new RegExp(`${startApiLine}([\\s\\S]*?)${endApiLine}`, 'g');
|
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) => {
|
const modifiedContent = data.replace(pattern, (match, capturedContent) => {
|
||||||
return startApiLine + '\n' + content + '\n' + endApiLine;
|
return startApiLine + '\n' + content + '\n' + endApiLine;
|
||||||
});
|
});
|
||||||
// Write the modified content back to the file
|
|
||||||
fs.writeFileSync(filePath, modifiedContent, 'utf8');
|
fs.writeFileSync(filePath, modifiedContent, 'utf8');
|
||||||
|
console.log(`Updated table content in: ${filePath}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'ENOENT') {
|
if (error.code === 'ENOENT') {
|
||||||
console.log(`${filePath} not found! Maybe it is an internal component. Skipping...`);
|
console.log(`${filePath} not found! Maybe it is an internal component. Skipping...`);
|
||||||
|
@ -164,7 +310,27 @@ function replaceDynamicTableContent(filePath, content) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const directiveFiles = getDirectiveFiles();
|
// Main execution
|
||||||
const componentFiles = getComponentFiles();
|
if (require.main === module) {
|
||||||
const adminFiles = getAdminFiles();
|
try {
|
||||||
writeApiDirectivesTable(componentFiles.concat(adminFiles), directiveFiles);
|
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