#!/usr/bin/env python3 """ Go code parser utilities for Bubble Tea maintenance agent. Extracts models, functions, types, and code structure. """ import re from typing import Dict, List, Tuple, Optional from pathlib import Path def extract_model_struct(content: str) -> Optional[Dict[str, any]]: """Extract the main model struct from Go code.""" # Pattern: type XxxModel struct { ... } pattern = r'type\s+(\w*[Mm]odel)\s+struct\s*\{([^}]+)\}' match = re.search(pattern, content, re.DOTALL) if not match: return None model_name = match.group(1) model_body = match.group(2) # Parse fields fields = [] for line in model_body.split('\n'): line = line.strip() if not line or line.startswith('//'): continue # Parse field: name type [tag] field_match = re.match(r'(\w+)\s+([^\s`]+)(?:\s+`([^`]+)`)?', line) if field_match: fields.append({ "name": field_match.group(1), "type": field_match.group(2), "tag": field_match.group(3) if field_match.group(3) else None }) return { "name": model_name, "fields": fields, "field_count": len(fields), "raw_body": model_body } def extract_update_function(content: str) -> Optional[Dict[str, any]]: """Extract the Update() function.""" # Find Update function pattern = r'func\s+\((\w+)\s+(\*?)(\w+)\)\s+Update\s*\([^)]*\)\s*\([^)]*\)\s*\{(.+?)(?=\nfunc\s|\Z)' match = re.search(pattern, content, re.DOTALL | re.MULTILINE) if not match: return None receiver_name = match.group(1) is_pointer = match.group(2) == '*' receiver_type = match.group(3) function_body = match.group(4) # Count cases in switch statements case_count = len(re.findall(r'\bcase\s+', function_body)) # Find message types handled handled_messages = re.findall(r'case\s+(\w+\.?\w*):', function_body) return { "receiver_name": receiver_name, "receiver_type": receiver_type, "is_pointer_receiver": is_pointer, "body_lines": len(function_body.split('\n')), "case_count": case_count, "handled_messages": list(set(handled_messages)), "raw_body": function_body } def extract_view_function(content: str) -> Optional[Dict[str, any]]: """Extract the View() function.""" pattern = r'func\s+\((\w+)\s+(\*?)(\w+)\)\s+View\s*\(\s*\)\s+string\s*\{(.+?)(?=\nfunc\s|\Z)' match = re.search(pattern, content, re.DOTALL | re.MULTILINE) if not match: return None receiver_name = match.group(1) is_pointer = match.group(2) == '*' receiver_type = match.group(3) function_body = match.group(4) # Analyze complexity string_concat_count = len(re.findall(r'\+\s*"', function_body)) lipgloss_calls = len(re.findall(r'lipgloss\.', function_body)) return { "receiver_name": receiver_name, "receiver_type": receiver_type, "is_pointer_receiver": is_pointer, "body_lines": len(function_body.split('\n')), "string_concatenations": string_concat_count, "lipgloss_calls": lipgloss_calls, "raw_body": function_body } def extract_init_function(content: str) -> Optional[Dict[str, any]]: """Extract the Init() function.""" pattern = r'func\s+\((\w+)\s+(\*?)(\w+)\)\s+Init\s*\(\s*\)\s+tea\.Cmd\s*\{(.+?)(?=\nfunc\s|\Z)' match = re.search(pattern, content, re.DOTALL | re.MULTILINE) if not match: return None receiver_name = match.group(1) is_pointer = match.group(2) == '*' receiver_type = match.group(3) function_body = match.group(4) return { "receiver_name": receiver_name, "receiver_type": receiver_type, "is_pointer_receiver": is_pointer, "body_lines": len(function_body.split('\n')), "raw_body": function_body } def extract_custom_messages(content: str) -> List[Dict[str, any]]: """Extract custom message type definitions.""" # Pattern: type xxxMsg struct { ... } pattern = r'type\s+(\w+Msg)\s+struct\s*\{([^}]*)\}' matches = re.finditer(pattern, content, re.DOTALL) messages = [] for match in matches: msg_name = match.group(1) msg_body = match.group(2) # Parse fields fields = [] for line in msg_body.split('\n'): line = line.strip() if not line or line.startswith('//'): continue field_match = re.match(r'(\w+)\s+([^\s]+)', line) if field_match: fields.append({ "name": field_match.group(1), "type": field_match.group(2) }) messages.append({ "name": msg_name, "fields": fields, "field_count": len(fields) }) return messages def extract_tea_commands(content: str) -> List[Dict[str, any]]: """Extract tea.Cmd functions.""" # Pattern: func xxxCmd() tea.Msg { ... } pattern = r'func\s+(\w+)\s*\(\s*\)\s+tea\.Msg\s*\{(.+?)^\}' matches = re.finditer(pattern, content, re.DOTALL | re.MULTILINE) commands = [] for match in matches: cmd_name = match.group(1) cmd_body = match.group(2) # Check for blocking operations has_http = bool(re.search(r'\bhttp\.(Get|Post|Do)', cmd_body)) has_sleep = bool(re.search(r'time\.Sleep', cmd_body)) has_io = bool(re.search(r'\bos\.(Open|Read|Write)', cmd_body)) commands.append({ "name": cmd_name, "body_lines": len(cmd_body.split('\n')), "has_http": has_http, "has_sleep": has_sleep, "has_io": has_io, "is_blocking": has_http or has_io # sleep is expected in commands }) return commands def extract_imports(content: str) -> List[str]: """Extract import statements.""" imports = [] # Single import single_pattern = r'import\s+"([^"]+)"' imports.extend(re.findall(single_pattern, content)) # Multi-line import block block_pattern = r'import\s+\(([^)]+)\)' block_matches = re.finditer(block_pattern, content, re.DOTALL) for match in block_matches: block_content = match.group(1) # Extract quoted imports quoted = re.findall(r'"([^"]+)"', block_content) imports.extend(quoted) return list(set(imports)) def find_bubbletea_components(content: str) -> List[Dict[str, any]]: """Find usage of Bubble Tea components (list, viewport, etc.).""" components = [] component_patterns = { "list": r'list\.Model', "viewport": r'viewport\.Model', "textinput": r'textinput\.Model', "textarea": r'textarea\.Model', "table": r'table\.Model', "progress": r'progress\.Model', "spinner": r'spinner\.Model', "timer": r'timer\.Model', "stopwatch": r'stopwatch\.Model', "filepicker": r'filepicker\.Model', "paginator": r'paginator\.Model', } for comp_name, pattern in component_patterns.items(): if re.search(pattern, content): # Count occurrences count = len(re.findall(pattern, content)) components.append({ "component": comp_name, "occurrences": count }) return components def analyze_code_structure(file_path: Path) -> Dict[str, any]: """Comprehensive code structure analysis.""" try: content = file_path.read_text() except Exception as e: return {"error": str(e)} return { "model": extract_model_struct(content), "update": extract_update_function(content), "view": extract_view_function(content), "init": extract_init_function(content), "custom_messages": extract_custom_messages(content), "tea_commands": extract_tea_commands(content), "imports": extract_imports(content), "components": find_bubbletea_components(content), "file_size": len(content), "line_count": len(content.split('\n')), "uses_lipgloss": '"github.com/charmbracelet/lipgloss"' in content, "uses_bubbletea": '"github.com/charmbracelet/bubbletea"' in content } def find_function_by_name(content: str, func_name: str) -> Optional[str]: """Find a specific function by name and return its body.""" pattern = rf'func\s+(?:\([^)]+\)\s+)?{func_name}\s*\([^)]*\)[^{{]*\{{(.+?)(?=\nfunc\s|\Z)' match = re.search(pattern, content, re.DOTALL | re.MULTILINE) if match: return match.group(1) return None def extract_state_machine_states(content: str) -> Optional[Dict[str, any]]: """Extract state machine enum if present.""" # Pattern: type xxxState int; const ( state1 state2 = iota ... ) state_type_pattern = r'type\s+(\w+State)\s+(int|string)' state_type_match = re.search(state_type_pattern, content) if not state_type_match: return None state_type = state_type_match.group(1) # Find const block with iota const_pattern = rf'const\s+\(([^)]+)\)' const_matches = re.finditer(const_pattern, content, re.DOTALL) states = [] for const_match in const_matches: const_body = const_match.group(1) if state_type in const_body and 'iota' in const_body: # Extract state names state_names = re.findall(rf'(\w+)\s+{state_type}', const_body) states = state_names break return { "type": state_type, "states": states, "count": len(states) } # Example usage and testing if __name__ == "__main__": import sys if len(sys.argv) < 2: print("Usage: go_parser.py ") sys.exit(1) file_path = Path(sys.argv[1]) result = analyze_code_structure(file_path) import json print(json.dumps(result, indent=2))