generated from coulomb/repo-seed
feat: add refactored testdrive-jsui capability with consolidated architecture
Complete integration of refactored testdrive-jsui capability: ## Refactored Architecture - js/ - All JavaScript source (controls, components, core) - static/ - CSS, images, templates - src/testdrive_jsui/ - Python package - tests/ - Python tests ## Plugin Self-Declaration - get_plugin_source_dir() - plugin declares own location - get_asset_paths() - organized asset paths - No hardcoded discovery logic ## Merged Content - Baseline UI scaffold (tutorials, LICENSE, INTRODUCTION.md) - Refactored capability implementation - Comprehensive documentation Ready for standalone use or integration with markitect. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
319
scripts/list_components.py
Executable file
319
scripts/list_components.py
Executable file
@@ -0,0 +1,319 @@
|
||||
#!/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',
|
||||
'DocumentControlsLegacy': 'DocumentControlsLegacy'
|
||||
}
|
||||
|
||||
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)
|
||||
Reference in New Issue
Block a user