245 lines
7.3 KiB
Python
245 lines
7.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Requirement analyzer for Bubble Tea TUIs.
|
|
Extracts structured requirements from natural language.
|
|
"""
|
|
|
|
import re
|
|
from typing import Dict, List
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
|
|
from utils.validators import RequirementValidator
|
|
|
|
|
|
# TUI archetype keywords
|
|
ARCHETYPE_KEYWORDS = {
|
|
'file-manager': ['file', 'directory', 'browse', 'navigator', 'ranger', 'three-column'],
|
|
'installer': ['install', 'package', 'progress', 'setup', 'installation'],
|
|
'dashboard': ['dashboard', 'monitor', 'real-time', 'metrics', 'status'],
|
|
'form': ['form', 'input', 'wizard', 'configuration', 'settings'],
|
|
'viewer': ['view', 'display', 'log', 'text', 'document', 'reader'],
|
|
'chat': ['chat', 'message', 'conversation', 'messaging'],
|
|
'table-viewer': ['table', 'data', 'spreadsheet', 'grid'],
|
|
'menu': ['menu', 'select', 'choose', 'options'],
|
|
'editor': ['edit', 'editor', 'compose', 'write']
|
|
}
|
|
|
|
|
|
def extract_requirements(description: str) -> Dict:
|
|
"""
|
|
Extract structured requirements from description.
|
|
|
|
Args:
|
|
description: Natural language TUI description
|
|
|
|
Returns:
|
|
Dictionary with structured requirements
|
|
|
|
Example:
|
|
>>> reqs = extract_requirements("Build a log viewer with search")
|
|
>>> reqs['archetype']
|
|
'viewer'
|
|
"""
|
|
# Validate input
|
|
validator = RequirementValidator()
|
|
validation = validator.validate_description(description)
|
|
|
|
desc_lower = description.lower()
|
|
|
|
# Extract archetype
|
|
archetype = classify_tui_type(description)
|
|
|
|
# Extract features
|
|
features = identify_features(description)
|
|
|
|
# Extract interactions
|
|
interactions = identify_interactions(description)
|
|
|
|
# Extract data types
|
|
data_types = identify_data_types(description)
|
|
|
|
# Determine view type
|
|
views = determine_view_type(description)
|
|
|
|
# Special requirements
|
|
special = identify_special_requirements(description)
|
|
|
|
requirements = {
|
|
'archetype': archetype,
|
|
'features': features,
|
|
'interactions': interactions,
|
|
'data_types': data_types,
|
|
'views': views,
|
|
'special_requirements': special,
|
|
'original_description': description,
|
|
'validation': validation.to_dict()
|
|
}
|
|
|
|
return requirements
|
|
|
|
|
|
def classify_tui_type(description: str) -> str:
|
|
"""Classify TUI archetype from description."""
|
|
desc_lower = description.lower()
|
|
|
|
# Score each archetype
|
|
scores = {}
|
|
for archetype, keywords in ARCHETYPE_KEYWORDS.items():
|
|
score = sum(1 for kw in keywords if kw in desc_lower)
|
|
if score > 0:
|
|
scores[archetype] = score
|
|
|
|
if not scores:
|
|
return 'general'
|
|
|
|
# Return highest scoring archetype
|
|
return max(scores.items(), key=lambda x: x[1])[0]
|
|
|
|
|
|
def identify_features(description: str) -> List[str]:
|
|
"""Identify features from description."""
|
|
features = []
|
|
desc_lower = description.lower()
|
|
|
|
feature_keywords = {
|
|
'navigation': ['navigate', 'move', 'browse', 'arrow'],
|
|
'selection': ['select', 'choose', 'pick'],
|
|
'search': ['search', 'find', 'filter', 'query'],
|
|
'editing': ['edit', 'modify', 'change', 'update'],
|
|
'display': ['display', 'show', 'view', 'render'],
|
|
'input': ['input', 'enter', 'type'],
|
|
'progress': ['progress', 'loading', 'install'],
|
|
'preview': ['preview', 'peek', 'preview pane'],
|
|
'scrolling': ['scroll', 'scrollable'],
|
|
'sorting': ['sort', 'order', 'rank'],
|
|
'filtering': ['filter', 'narrow'],
|
|
'highlighting': ['highlight', 'emphasize', 'mark']
|
|
}
|
|
|
|
for feature, keywords in feature_keywords.items():
|
|
if any(kw in desc_lower for kw in keywords):
|
|
features.append(feature)
|
|
|
|
return features if features else ['display']
|
|
|
|
|
|
def identify_interactions(description: str) -> Dict[str, List[str]]:
|
|
"""Identify user interaction types."""
|
|
desc_lower = description.lower()
|
|
|
|
keyboard = []
|
|
mouse = []
|
|
|
|
# Keyboard interactions
|
|
kbd_keywords = {
|
|
'navigation': ['arrow', 'hjkl', 'navigate', 'move'],
|
|
'selection': ['enter', 'select', 'choose'],
|
|
'search': ['/', 'search', 'find'],
|
|
'quit': ['q', 'quit', 'exit', 'esc'],
|
|
'help': ['?', 'help']
|
|
}
|
|
|
|
for interaction, keywords in kbd_keywords.items():
|
|
if any(kw in desc_lower for kw in keywords):
|
|
keyboard.append(interaction)
|
|
|
|
# Default keyboard interactions
|
|
if not keyboard:
|
|
keyboard = ['navigation', 'selection', 'quit']
|
|
|
|
# Mouse interactions
|
|
if any(word in desc_lower for word in ['mouse', 'click', 'drag']):
|
|
mouse = ['click', 'scroll']
|
|
|
|
return {
|
|
'keyboard': keyboard,
|
|
'mouse': mouse
|
|
}
|
|
|
|
|
|
def identify_data_types(description: str) -> List[str]:
|
|
"""Identify data types being displayed."""
|
|
desc_lower = description.lower()
|
|
|
|
data_type_keywords = {
|
|
'files': ['file', 'directory', 'folder'],
|
|
'text': ['text', 'log', 'document'],
|
|
'tabular': ['table', 'data', 'rows', 'columns'],
|
|
'messages': ['message', 'chat', 'conversation'],
|
|
'packages': ['package', 'dependency', 'module'],
|
|
'metrics': ['metric', 'stat', 'data point'],
|
|
'config': ['config', 'setting', 'option']
|
|
}
|
|
|
|
data_types = []
|
|
for dtype, keywords in data_type_keywords.items():
|
|
if any(kw in desc_lower for kw in keywords):
|
|
data_types.append(dtype)
|
|
|
|
return data_types if data_types else ['text']
|
|
|
|
|
|
def determine_view_type(description: str) -> str:
|
|
"""Determine if single or multi-view."""
|
|
desc_lower = description.lower()
|
|
|
|
multi_keywords = ['multi-view', 'multiple view', 'tabs', 'tabbed', 'switch', 'views']
|
|
three_pane_keywords = ['three', 'three-column', 'three pane']
|
|
|
|
if any(kw in desc_lower for kw in three_pane_keywords):
|
|
return 'three-pane'
|
|
elif any(kw in desc_lower for kw in multi_keywords):
|
|
return 'multi'
|
|
else:
|
|
return 'single'
|
|
|
|
|
|
def identify_special_requirements(description: str) -> List[str]:
|
|
"""Identify special requirements."""
|
|
desc_lower = description.lower()
|
|
special = []
|
|
|
|
special_keywords = {
|
|
'validation': ['validate', 'validation', 'check'],
|
|
'real-time': ['real-time', 'live', 'streaming'],
|
|
'async': ['async', 'background', 'concurrent'],
|
|
'persistence': ['save', 'persist', 'store'],
|
|
'theming': ['theme', 'color', 'style']
|
|
}
|
|
|
|
for req, keywords in special_keywords.items():
|
|
if any(kw in desc_lower for kw in keywords):
|
|
special.append(req)
|
|
|
|
return special
|
|
|
|
|
|
def main():
|
|
"""Test requirement analyzer."""
|
|
print("Testing Requirement Analyzer\n" + "=" * 50)
|
|
|
|
test_cases = [
|
|
"Build a log viewer with search and highlighting",
|
|
"Create a file manager with three-column view",
|
|
"Design an installer with progress bars",
|
|
"Make a form wizard with validation"
|
|
]
|
|
|
|
for i, desc in enumerate(test_cases, 1):
|
|
print(f"\n{i}. Testing: '{desc}'")
|
|
reqs = extract_requirements(desc)
|
|
print(f" Archetype: {reqs['archetype']}")
|
|
print(f" Features: {', '.join(reqs['features'])}")
|
|
print(f" Data types: {', '.join(reqs['data_types'])}")
|
|
print(f" View type: {reqs['views']}")
|
|
print(f" Validation: {reqs['validation']['summary']}")
|
|
|
|
print("\n✅ All tests passed!")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|