Files
testdrive-jsui/scripts/list_components.py
tegwick 1fe4b6b9fa refactor: complete post-migration cleanup
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>
2025-12-16 11:43:42 +01:00

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)