generated from coulomb/repo-seed
Implemented all cleanup items from CLEANUP_REPORT.md: Legacy Code Removal: - Removed document-controls-legacy.js wrapper - Updated 4 test files to use DocumentControls directly - Updated scripts/list_components.py acronym mappings - Updated tests/test_component_listing.py expectations Archive and Organization: - Moved relicts/ to docs/prototypes/ with README explaining history - Moved MIGRATION_STATUS.md to docs/migration/ - Removed IMPLEMENTATION_NOTES.md legacy references Test Verification: - All 68 JavaScript tests passing (Jest) - All 3 Python component tests passing - No breaking changes to functionality The codebase is now cleaner with no legacy wrappers or empty directories. Migration is complete and documented. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
318 lines
11 KiB
Python
Executable File
318 lines
11 KiB
Python
Executable File
#!/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) |