#!/usr/bin/env python3 """ TestDrive-JSUI Component Lister Lists all available UI components with descriptions and key information. """ import re import json from pathlib import Path from typing import Dict, List, Optional, Tuple class ComponentInfo: """Information about a UI component.""" def __init__(self, name: str, file_path: str, description: str, component_type: str, dependencies: List[str] = None, methods: List[str] = None, classes: List[str] = None): self.name = name self.file_path = file_path self.description = description self.component_type = component_type self.dependencies = dependencies or [] self.methods = methods or [] self.classes = classes or [] class ComponentAnalyzer: """Analyzes JavaScript component files to extract information.""" def __init__(self, capability_root: Path): self.capability_root = capability_root self.js_root = capability_root / "js" def analyze_file(self, file_path: Path) -> Optional[ComponentInfo]: """Analyze a single JavaScript file for component information.""" if not file_path.exists(): return None content = file_path.read_text() relative_path = str(file_path.relative_to(self.capability_root)) # Extract component name from file path with special handling for acronyms and legacy naming component_name = file_path.stem.replace('-', ' ').title().replace(' ', '') # Special handling for common acronyms and naming patterns acronym_mappings = { 'DomRenderer': 'DOMRenderer', 'DomControls': 'DOMControls', 'HtmlRenderer': 'HTMLRenderer', 'CssProcessor': 'CSSProcessor', 'JsEngine': 'JSEngine', 'ApiClient': 'APIClient', 'UrlHandler': 'URLHandler' } component_name = acronym_mappings.get(component_name, component_name) # Extract description from file header comment description = self._extract_description(content) # Determine component type from path component_type = self._determine_component_type(file_path) # Extract dependencies dependencies = self._extract_dependencies(content) # Extract class names classes = self._extract_classes(content) # Extract method names (public methods) methods = self._extract_public_methods(content) return ComponentInfo( name=component_name, file_path=relative_path, description=description, component_type=component_type, dependencies=dependencies, methods=methods, classes=classes ) def _extract_description(self, content: str) -> str: """Extract description from file header comments.""" # Look for block comment at start of file block_comment_match = re.search(r'/\*\*(.*?)\*/', content, re.DOTALL) if block_comment_match: comment_lines = block_comment_match.group(1).strip() # Clean up comment formatting lines = [] for line in comment_lines.split('\n'): line = line.strip().lstrip('*').strip() if line and not line.startswith('Dependencies:') and not line.startswith('Extracted from'): lines.append(line) elif line.startswith('Dependencies:'): break description = ' '.join(lines) # Take first sentence or reasonable chunk if '.' in description: first_sentence = description.split('.')[0] + '.' if len(first_sentence) < 200: return first_sentence # Fallback to first 150 characters return description[:150] + '...' if len(description) > 150 else description # Fallback to single-line comments line_comment_match = re.search(r'^\s*//\s*(.+)', content, re.MULTILINE) if line_comment_match: return line_comment_match.group(1).strip() return "Component implementation" def _determine_component_type(self, file_path: Path) -> str: """Determine component type from file path.""" path_str = str(file_path) # Check for legacy components first if 'legacy' in path_str.lower(): if 'components' in path_str: return "Legacy UI Component" elif 'controls' in path_str: return "Legacy Control" else: return "Legacy Module" # Standard component types if 'core' in path_str: return "Core" elif 'components' in path_str: return "UI Component" elif 'controls' in path_str: return "Control" elif 'utils' in path_str: return "Utility" else: return "Module" def _extract_dependencies(self, content: str) -> List[str]: """Extract dependencies mentioned in comments or imports.""" dependencies = [] # Look for Dependencies section in comments dep_match = re.search(r'Dependencies:\s*\n(.*?)(?:\*/|\n\s*\n)', content, re.DOTALL) if dep_match: dep_text = dep_match.group(1) # Extract dependency names from bullet points for line in dep_text.split('\n'): line = line.strip().lstrip('*-').strip() if line and not line.startswith('None'): # Clean up dependency description dep_name = line.split('(')[0].split('-')[0].strip() if dep_name: dependencies.append(dep_name) # Look for import statements or requires import_matches = re.findall(r'(?:import|require)\s*\(?[\'"]([^\'\"]+)[\'"]', content) dependencies.extend(import_matches) return list(set(dependencies)) # Remove duplicates def _extract_classes(self, content: str) -> List[str]: """Extract class names from the file.""" class_matches = re.findall(r'class\s+([A-Z][a-zA-Z0-9_]*)', content) return class_matches def _extract_public_methods(self, content: str) -> List[str]: """Extract public method names from classes.""" methods = [] # Look for method definitions (not starting with _) method_matches = re.findall(r'^\s+([a-zA-Z][a-zA-Z0-9_]*)\s*\(.*?\)\s*{', content, re.MULTILINE) # Filter out private methods (starting with _) and constructors public_methods = [m for m in method_matches if not m.startswith('_') and m != 'constructor'] return list(set(public_methods)) # Remove duplicates def list_components(output_format: str = "table") -> None: """List all UI components in the capability.""" capability_root = Path(__file__).parent.parent analyzer = ComponentAnalyzer(capability_root) # Find all JavaScript component files component_files = [] js_root = capability_root / "js" if js_root.exists(): # Get files from components, core, and other directories for pattern in ["**/*.js"]: for file_path in js_root.glob(pattern): # Skip test files and node_modules path_str = str(file_path) if ('test' not in file_path.name.lower() and '/tests/' not in path_str and 'node_modules' not in path_str and not file_path.name.lower().endswith('.test.js')): component_files.append(file_path) # Analyze each file components = [] for file_path in sorted(component_files): component_info = analyzer.analyze_file(file_path) if component_info: components.append(component_info) if not components: print("โŒ No UI components found in the capability") return # Output in requested format if output_format == "json": _output_json(components) elif output_format == "detailed": _output_detailed(components) else: _output_table(components) def _output_table(components: List[ComponentInfo]) -> None: """Output components in table format.""" print("๐Ÿงช TestDrive-JSUI UI Components") print("=" * 60) print() # Group by type by_type = {} for comp in components: if comp.component_type not in by_type: by_type[comp.component_type] = [] by_type[comp.component_type].append(comp) for comp_type, comps in sorted(by_type.items()): print(f"๐Ÿ“ฆ {comp_type} Components ({len(comps)})") print("-" * 40) for comp in sorted(comps, key=lambda x: x.name): print(f" ๐Ÿ”ง {comp.name}") print(f" ๐Ÿ“„ {comp.file_path}") print(f" ๐Ÿ“ {comp.description}") if comp.classes: print(f" ๐Ÿ—๏ธ Classes: {', '.join(comp.classes)}") if comp.methods: key_methods = comp.methods[:4] # Show first 4 methods methods_str = ', '.join(key_methods) if len(comp.methods) > 4: methods_str += f" (+{len(comp.methods) - 4} more)" print(f" โš™๏ธ Methods: {methods_str}") print() print() def _output_detailed(components: List[ComponentInfo]) -> None: """Output components with detailed information.""" print("๐Ÿงช TestDrive-JSUI UI Components - Detailed View") print("=" * 60) print() for i, comp in enumerate(sorted(components, key=lambda x: x.name), 1): print(f"{i}. {comp.name}") print(f" Type: {comp.component_type}") print(f" File: {comp.file_path}") print(f" Description: {comp.description}") if comp.classes: print(f" Classes: {', '.join(comp.classes)}") if comp.methods: print(f" Public Methods ({len(comp.methods)}): {', '.join(sorted(comp.methods))}") if comp.dependencies: print(f" Dependencies: {', '.join(comp.dependencies)}") print() def _output_json(components: List[ComponentInfo]) -> None: """Output components in JSON format.""" data = { "capability": "testdrive-jsui", "total_components": len(components), "components": [] } for comp in sorted(components, key=lambda x: x.name): data["components"].append({ "name": comp.name, "type": comp.component_type, "file_path": comp.file_path, "description": comp.description, "classes": comp.classes, "methods": comp.methods, "dependencies": comp.dependencies }) print(json.dumps(data, indent=2)) if __name__ == "__main__": import sys output_format = "table" if len(sys.argv) > 1: format_arg = sys.argv[1].lower() if format_arg in ["json", "detailed", "table"]: output_format = format_arg else: print(f"โŒ Invalid format: {format_arg}") print("Usage: python list_components.py [table|detailed|json]") sys.exit(1) list_components(output_format)