Initial commit
This commit is contained in:
334
scripts/utils/inventory_loader.py
Normal file
334
scripts/utils/inventory_loader.py
Normal file
@@ -0,0 +1,334 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Inventory loader for Bubble Tea examples.
|
||||
Loads and parses CONTEXTUAL-INVENTORY.md from charm-examples-inventory.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InventoryLoadError(Exception):
|
||||
"""Raised when inventory cannot be loaded."""
|
||||
pass
|
||||
|
||||
|
||||
class Example:
|
||||
"""Represents a single Bubble Tea example."""
|
||||
|
||||
def __init__(self, name: str, file_path: str, capability: str):
|
||||
self.name = name
|
||||
self.file_path = file_path
|
||||
self.capability = capability
|
||||
self.key_patterns: List[str] = []
|
||||
self.components: List[str] = []
|
||||
self.use_cases: List[str] = []
|
||||
|
||||
def __repr__(self):
|
||||
return f"Example({self.name}, {self.capability})"
|
||||
|
||||
|
||||
class Inventory:
|
||||
"""Bubble Tea examples inventory."""
|
||||
|
||||
def __init__(self, base_path: str):
|
||||
self.base_path = base_path
|
||||
self.examples: Dict[str, Example] = {}
|
||||
self.capabilities: Dict[str, List[Example]] = {}
|
||||
self.components: Dict[str, List[Example]] = {}
|
||||
|
||||
def add_example(self, example: Example):
|
||||
"""Add example to inventory."""
|
||||
self.examples[example.name] = example
|
||||
|
||||
# Index by capability
|
||||
if example.capability not in self.capabilities:
|
||||
self.capabilities[example.capability] = []
|
||||
self.capabilities[example.capability].append(example)
|
||||
|
||||
# Index by components
|
||||
for component in example.components:
|
||||
if component not in self.components:
|
||||
self.components[component] = []
|
||||
self.components[component].append(example)
|
||||
|
||||
def search_by_keyword(self, keyword: str) -> List[Example]:
|
||||
"""Search examples by keyword in name or patterns."""
|
||||
keyword_lower = keyword.lower()
|
||||
results = []
|
||||
|
||||
for example in self.examples.values():
|
||||
if keyword_lower in example.name.lower():
|
||||
results.append(example)
|
||||
continue
|
||||
|
||||
for pattern in example.key_patterns:
|
||||
if keyword_lower in pattern.lower():
|
||||
results.append(example)
|
||||
break
|
||||
|
||||
return results
|
||||
|
||||
def get_by_capability(self, capability: str) -> List[Example]:
|
||||
"""Get all examples for a capability."""
|
||||
return self.capabilities.get(capability, [])
|
||||
|
||||
def get_by_component(self, component: str) -> List[Example]:
|
||||
"""Get all examples using a component."""
|
||||
return self.components.get(component, [])
|
||||
|
||||
|
||||
def load_inventory(inventory_path: Optional[str] = None) -> Inventory:
|
||||
"""
|
||||
Load Bubble Tea examples inventory from CONTEXTUAL-INVENTORY.md.
|
||||
|
||||
Args:
|
||||
inventory_path: Path to charm-examples-inventory directory
|
||||
If None, tries to find it automatically
|
||||
|
||||
Returns:
|
||||
Loaded Inventory object
|
||||
|
||||
Raises:
|
||||
InventoryLoadError: If inventory cannot be loaded
|
||||
|
||||
Example:
|
||||
>>> inv = load_inventory("/path/to/charm-examples-inventory")
|
||||
>>> examples = inv.search_by_keyword("progress")
|
||||
"""
|
||||
if inventory_path is None:
|
||||
inventory_path = _find_inventory_path()
|
||||
|
||||
inventory_file = Path(inventory_path) / "bubbletea" / "examples" / "CONTEXTUAL-INVENTORY.md"
|
||||
|
||||
if not inventory_file.exists():
|
||||
raise InventoryLoadError(
|
||||
f"Inventory file not found: {inventory_file}\n"
|
||||
f"Expected at: {inventory_path}/bubbletea/examples/CONTEXTUAL-INVENTORY.md"
|
||||
)
|
||||
|
||||
logger.info(f"Loading inventory from: {inventory_file}")
|
||||
|
||||
with open(inventory_file, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
inventory = parse_inventory_markdown(content, str(inventory_path))
|
||||
|
||||
logger.info(f"Loaded {len(inventory.examples)} examples")
|
||||
logger.info(f"Categories: {len(inventory.capabilities)}")
|
||||
|
||||
return inventory
|
||||
|
||||
|
||||
def parse_inventory_markdown(content: str, base_path: str) -> Inventory:
|
||||
"""
|
||||
Parse CONTEXTUAL-INVENTORY.md markdown content.
|
||||
|
||||
Args:
|
||||
content: Markdown content
|
||||
base_path: Base path for example files
|
||||
|
||||
Returns:
|
||||
Inventory object with parsed examples
|
||||
"""
|
||||
inventory = Inventory(base_path)
|
||||
|
||||
# Parse quick reference table
|
||||
table_matches = re.finditer(
|
||||
r'\|\s*(.+?)\s*\|\s*`(.+?)`\s*\|',
|
||||
content
|
||||
)
|
||||
|
||||
need_to_file = {}
|
||||
for match in table_matches:
|
||||
need = match.group(1).strip()
|
||||
file_path = match.group(2).strip()
|
||||
need_to_file[need] = file_path
|
||||
|
||||
# Parse detailed sections (## Examples by Capability)
|
||||
capability_pattern = r'### (.+?)\n\n\*\*Use (.+?) when you need:\*\*(.+?)(?=\n\n\*\*|### |\Z)'
|
||||
|
||||
capability_sections = re.finditer(capability_pattern, content, re.DOTALL)
|
||||
|
||||
for section in capability_sections:
|
||||
capability = section.group(1).strip()
|
||||
example_name = section.group(2).strip()
|
||||
description = section.group(3).strip()
|
||||
|
||||
# Extract file path and key patterns
|
||||
file_match = re.search(r'\*\*File\*\*: `(.+?)`', description)
|
||||
patterns_match = re.search(r'\*\*Key patterns\*\*: (.+?)(?=\n|$)', description)
|
||||
|
||||
if file_match:
|
||||
file_path = file_match.group(1).strip()
|
||||
example = Example(example_name, file_path, capability)
|
||||
|
||||
if patterns_match:
|
||||
patterns_text = patterns_match.group(1).strip()
|
||||
example.key_patterns = [p.strip() for p in patterns_text.split(',')]
|
||||
|
||||
# Extract components from file name and patterns
|
||||
example.components = _extract_components(example_name, example.key_patterns)
|
||||
|
||||
inventory.add_example(example)
|
||||
|
||||
return inventory
|
||||
|
||||
|
||||
def _extract_components(name: str, patterns: List[str]) -> List[str]:
|
||||
"""Extract component names from example name and patterns."""
|
||||
components = []
|
||||
|
||||
# Common component keywords
|
||||
component_keywords = [
|
||||
'textinput', 'textarea', 'viewport', 'table', 'list', 'pager',
|
||||
'paginator', 'spinner', 'progress', 'timer', 'stopwatch',
|
||||
'filepicker', 'help', 'tabs', 'autocomplete'
|
||||
]
|
||||
|
||||
name_lower = name.lower()
|
||||
for keyword in component_keywords:
|
||||
if keyword in name_lower:
|
||||
components.append(keyword)
|
||||
|
||||
for pattern in patterns:
|
||||
pattern_lower = pattern.lower()
|
||||
for keyword in component_keywords:
|
||||
if keyword in pattern_lower and keyword not in components:
|
||||
components.append(keyword)
|
||||
|
||||
return components
|
||||
|
||||
|
||||
def _find_inventory_path() -> str:
|
||||
"""
|
||||
Try to find charm-examples-inventory automatically.
|
||||
|
||||
Searches in common locations:
|
||||
- ./charm-examples-inventory
|
||||
- ../charm-examples-inventory
|
||||
- ~/charmtuitemplate/vinw/charm-examples-inventory
|
||||
|
||||
Returns:
|
||||
Path to inventory directory
|
||||
|
||||
Raises:
|
||||
InventoryLoadError: If not found
|
||||
"""
|
||||
search_paths = [
|
||||
Path.cwd() / "charm-examples-inventory",
|
||||
Path.cwd().parent / "charm-examples-inventory",
|
||||
Path.home() / "charmtuitemplate" / "vinw" / "charm-examples-inventory"
|
||||
]
|
||||
|
||||
for path in search_paths:
|
||||
if (path / "bubbletea" / "examples" / "CONTEXTUAL-INVENTORY.md").exists():
|
||||
logger.info(f"Found inventory at: {path}")
|
||||
return str(path)
|
||||
|
||||
raise InventoryLoadError(
|
||||
"Could not find charm-examples-inventory automatically.\n"
|
||||
f"Searched: {[str(p) for p in search_paths]}\n"
|
||||
"Please provide inventory_path parameter."
|
||||
)
|
||||
|
||||
|
||||
def build_capability_index(inventory: Inventory) -> Dict[str, List[str]]:
|
||||
"""
|
||||
Build index of capabilities to example names.
|
||||
|
||||
Args:
|
||||
inventory: Loaded inventory
|
||||
|
||||
Returns:
|
||||
Dict mapping capability names to example names
|
||||
"""
|
||||
index = {}
|
||||
for capability, examples in inventory.capabilities.items():
|
||||
index[capability] = [ex.name for ex in examples]
|
||||
return index
|
||||
|
||||
|
||||
def build_component_index(inventory: Inventory) -> Dict[str, List[str]]:
|
||||
"""
|
||||
Build index of components to example names.
|
||||
|
||||
Args:
|
||||
inventory: Loaded inventory
|
||||
|
||||
Returns:
|
||||
Dict mapping component names to example names
|
||||
"""
|
||||
index = {}
|
||||
for component, examples in inventory.components.items():
|
||||
index[component] = [ex.name for ex in examples]
|
||||
return index
|
||||
|
||||
|
||||
def get_example_details(inventory: Inventory, example_name: str) -> Optional[Example]:
|
||||
"""
|
||||
Get detailed information about a specific example.
|
||||
|
||||
Args:
|
||||
inventory: Loaded inventory
|
||||
example_name: Name of example to look up
|
||||
|
||||
Returns:
|
||||
Example object or None if not found
|
||||
"""
|
||||
return inventory.examples.get(example_name)
|
||||
|
||||
|
||||
def main():
|
||||
"""Test inventory loader."""
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
print("Testing Inventory Loader\n" + "=" * 50)
|
||||
|
||||
try:
|
||||
# Load inventory
|
||||
print("\n1. Loading inventory...")
|
||||
inventory = load_inventory()
|
||||
print(f"✓ Loaded {len(inventory.examples)} examples")
|
||||
print(f"✓ {len(inventory.capabilities)} capability categories")
|
||||
|
||||
# Test search
|
||||
print("\n2. Testing keyword search...")
|
||||
results = inventory.search_by_keyword("progress")
|
||||
print(f"✓ Found {len(results)} examples for 'progress':")
|
||||
for ex in results[:3]:
|
||||
print(f" - {ex.name} ({ex.capability})")
|
||||
|
||||
# Test capability lookup
|
||||
print("\n3. Testing capability lookup...")
|
||||
cap_examples = inventory.get_by_capability("Installation and Progress Tracking")
|
||||
print(f"✓ Found {len(cap_examples)} installation examples")
|
||||
|
||||
# Test component lookup
|
||||
print("\n4. Testing component lookup...")
|
||||
comp_examples = inventory.get_by_component("spinner")
|
||||
print(f"✓ Found {len(comp_examples)} examples using 'spinner'")
|
||||
|
||||
# Test indices
|
||||
print("\n5. Building indices...")
|
||||
cap_index = build_capability_index(inventory)
|
||||
comp_index = build_component_index(inventory)
|
||||
print(f"✓ Capability index: {len(cap_index)} categories")
|
||||
print(f"✓ Component index: {len(comp_index)} components")
|
||||
|
||||
print("\n✅ All tests passed!")
|
||||
|
||||
except InventoryLoadError as e:
|
||||
print(f"\n❌ Error loading inventory: {e}")
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
Reference in New Issue
Block a user