#!/usr/bin/env python3 """ BlackRoad Code Library Search Search and retrieve components from the code library. """ import sqlite3 import json from pathlib import Path from typing import List, Dict, Optional from datetime import datetime, timedelta import re class LibrarySearch: """Search interface for the code library.""" def __init__(self, library_path: str = "~/blackroad-code-library"): self.library_path = Path(library_path).expanduser() self.db_path = self.library_path / "index" / "components.db" if not self.db_path.exists(): raise FileNotFoundError(f"Library database not found at {self.db_path}. Run scanner first.") def search(self, query: str, filters: Optional[Dict] = None, limit: int = 10) -> List[Dict]: """ Search for components. Args: query: Search query (matches name, tags, description) filters: Optional filters (language, type, framework, min_quality, max_age_days) limit: Max results to return Returns: List of matching components """ filters = filters or {} conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row cursor = conn.cursor() # Build query sql = """ SELECT * FROM components WHERE ( name LIKE ? OR tags LIKE ? OR description LIKE ? OR type LIKE ? ) """ params = [f"%{query}%"] * 4 # Add filters if 'language' in filters: sql += " AND language = ?" params.append(filters['language']) if 'type' in filters: sql += " AND type = ?" params.append(filters['type']) if 'framework' in filters: sql += " AND framework = ?" params.append(filters['framework']) if 'min_quality' in filters: sql += " AND quality_score >= ?" params.append(filters['min_quality']) if 'max_age_days' in filters: cutoff_date = (datetime.now() - timedelta(days=filters['max_age_days'])).isoformat() sql += " AND (last_used_at >= ? OR created_at >= ?)" params.extend([cutoff_date, cutoff_date]) if 'repo' in filters: sql += " AND repo = ?" params.append(filters['repo']) # Order by quality and recency sql += " ORDER BY quality_score DESC, created_at DESC LIMIT ?" params.append(limit) cursor.execute(sql, params) rows = cursor.fetchall() conn.close() return [dict(row) for row in rows] def search_by_tag(self, tag: str, limit: int = 10) -> List[Dict]: """Search components by tag.""" return self.search(tag, limit=limit) def get_component(self, component_id: str) -> Optional[Dict]: """Get a specific component by ID.""" conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute("SELECT * FROM components WHERE id = ?", (component_id,)) row = cursor.fetchone() conn.close() return dict(row) if row else None def get_similar_components(self, component_id: str, limit: int = 5) -> List[Dict]: """ Find similar components based on: - Same type - Overlapping tags - Same language/framework """ component = self.get_component(component_id) if not component: return [] conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row cursor = conn.cursor() # Find components with similar characteristics cursor.execute(""" SELECT * FROM components WHERE id != ? AND ( type = ? OR language = ? OR framework = ? OR tags LIKE ? ) ORDER BY quality_score DESC LIMIT ? """, ( component_id, component['type'], component['language'], f"%{json.loads(component['tags'])[0]}%", limit )) rows = cursor.fetchall() conn.close() return [dict(row) for row in rows] def get_stats(self) -> Dict: """Get library statistics.""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() stats = {} # Total components cursor.execute("SELECT COUNT(*) FROM components") stats['total_components'] = cursor.fetchone()[0] # By language cursor.execute(""" SELECT language, COUNT(*) as count FROM components GROUP BY language ORDER BY count DESC """) stats['by_language'] = dict(cursor.fetchall()) # By type cursor.execute(""" SELECT type, COUNT(*) as count FROM components GROUP BY type ORDER BY count DESC """) stats['by_type'] = dict(cursor.fetchall()) # By framework cursor.execute(""" SELECT framework, COUNT(*) as count FROM components WHERE framework IS NOT NULL GROUP BY framework ORDER BY count DESC """) stats['by_framework'] = dict(cursor.fetchall()) # Quality distribution cursor.execute(""" SELECT ROUND(quality_score) as score, COUNT(*) as count FROM components GROUP BY ROUND(quality_score) ORDER BY score DESC """) stats['quality_distribution'] = dict(cursor.fetchall()) # Repository stats cursor.execute("SELECT * FROM repositories ORDER BY component_count DESC") conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute("SELECT * FROM repositories ORDER BY component_count DESC") stats['repositories'] = [dict(row) for row in cursor.fetchall()] conn.close() return stats def format_result(self, component: Dict) -> str: """Format a component result for display.""" tags = json.loads(component['tags']) deps = json.loads(component['dependencies']) output = f""" {'='*70} ⭐ {component['name']} (Quality: {component['quality_score']}/10) {'='*70} Type: {component['type']} Language: {component['language']} Framework: {component.get('framework') or 'N/A'} Repository: {component['repo']} Location: {component['file_path']}:{component['start_line']}-{component['end_line']} Tags: {', '.join(tags[:8])} Dependencies: {', '.join(deps[:5]) if deps else 'None'} Description: {component['description']} Code Preview: {component['code_snippet']} ... Full path: {component['file_path']} """ return output def format_results(self, components: List[Dict]) -> str: """Format multiple results for display.""" if not components: return "No components found." output = f"\nFound {len(components)} component(s):\n" for i, comp in enumerate(components, 1): tags = json.loads(comp['tags']) output += f"\n{i}. ⭐ {comp['name']} ({comp['language']}/{comp['type']}) - {comp['quality_score']:.1f}/10\n" output += f" šŸ“ {comp['repo']}/{Path(comp['file_path']).name}:{comp['start_line']}\n" output += f" šŸ·ļø {', '.join(tags[:5])}\n" return output def main(): """CLI interface for library search.""" import argparse parser = argparse.ArgumentParser(description='Search the BlackRoad code library') parser.add_argument('query', nargs='?', help='Search query') parser.add_argument('--library', default='~/blackroad-code-library', help='Library path') parser.add_argument('--language', help='Filter by language') parser.add_argument('--type', help='Filter by type') parser.add_argument('--framework', help='Filter by framework') parser.add_argument('--min-quality', type=float, help='Minimum quality score') parser.add_argument('--max-age', type=int, help='Max age in days') parser.add_argument('--limit', type=int, default=10, help='Max results') parser.add_argument('--stats', action='store_true', help='Show library statistics') parser.add_argument('--id', help='Get specific component by ID') parser.add_argument('--similar', help='Find similar components to ID') args = parser.parse_args() try: search = LibrarySearch(args.library) except FileNotFoundError as e: print(f"Error: {e}") print("Hint: Run the scanner first to create the library database.") return # Show stats if args.stats: stats = search.get_stats() print("\nšŸ“Š Library Statistics") print("=" * 70) print(f"\nTotal Components: {stats['total_components']}") print("\nBy Language:") for lang, count in stats['by_language'].items(): print(f" {lang}: {count}") print("\nBy Type:") for type_, count in stats['by_type'].items(): print(f" {type_}: {count}") if stats['by_framework']: print("\nBy Framework:") for framework, count in stats['by_framework'].items(): print(f" {framework}: {count}") print("\nRepositories:") for repo in stats['repositories'][:10]: print(f" {repo['name']}: {repo['component_count']} components") return # Get specific component if args.id: comp = search.get_component(args.id) if comp: print(search.format_result(comp)) else: print(f"Component {args.id} not found.") return # Find similar components if args.similar: comps = search.get_similar_components(args.similar, args.limit) print(search.format_results(comps)) return # Search if not args.query: parser.print_help() return filters = {} if args.language: filters['language'] = args.language if args.type: filters['type'] = args.type if args.framework: filters['framework'] = args.framework if args.min_quality: filters['min_quality'] = args.min_quality if args.max_age: filters['max_age_days'] = args.max_age results = search.search(args.query, filters, args.limit) print(search.format_results(results)) # If only one result, show details if len(results) == 1: print("\n" + "=" * 70) print("Showing details for single result:") print(search.format_result(results[0])) if __name__ == '__main__': main()