From a74781d825f393c57e663546f6ee80023d4a95c6 Mon Sep 17 00:00:00 2001 From: David Chen Date: Mon, 11 Aug 2025 16:02:20 -0700 Subject: [PATCH] local bin --- bin/dart-structure-analyzer | 740 ++++++++++++++++++++++++++++++++++++ bin/nvim-lazy-reset | 21 + bin/upgrade-all | 127 +++++++ bin/yabaigap | 6 + bin/yabainogap | 6 + 5 files changed, 900 insertions(+) create mode 100755 bin/dart-structure-analyzer create mode 100755 bin/nvim-lazy-reset create mode 100755 bin/upgrade-all create mode 100755 bin/yabaigap create mode 100755 bin/yabainogap diff --git a/bin/dart-structure-analyzer b/bin/dart-structure-analyzer new file mode 100755 index 0000000..7be16c0 --- /dev/null +++ b/bin/dart-structure-analyzer @@ -0,0 +1,740 @@ +#!/usr/bin/env python3 +""" +Dart File Structure Analyzer +Analyzes a Dart file and extracts its structure including classes, methods, fields, imports, etc. +""" + +import re +import sys +import json +from pathlib import Path +from typing import List, Dict, Any, Optional + +class DartStructureAnalyzer: + def __init__(self): + self.structure = { + 'imports': [], + 'exports': [], + 'classes': [], + 'functions': [], + 'variables': [], + 'enums': [], + 'mixins': [], + 'extensions': [], + 'typedefs': [] + } + + def analyze_file(self, file_path: str) -> Dict[str, Any]: + """Analyze a Dart file and return its structure""" + try: + with open(file_path, 'r', encoding='utf-8') as file: + content = file.read() + except Exception as e: + return {'error': f'Could not read file: {str(e)}'} + + # Remove comments and strings to avoid false matches + cleaned_content = self._remove_comments_and_strings(content) + lines = content.split('\n') + + self._extract_imports_exports(cleaned_content) + self._extract_classes(cleaned_content, lines) + self._extract_functions(cleaned_content, lines) + self._extract_variables(cleaned_content, lines) + self._extract_enums(cleaned_content, lines) + self._extract_mixins(cleaned_content, lines) + self._extract_extensions(cleaned_content, lines) + self._extract_typedefs(cleaned_content, lines) + + return self.structure + + def _remove_comments_and_strings(self, content: str) -> str: + """Remove comments and string literals to avoid false matches""" + result = [] + i = 0 + while i < len(content): + # Check for single-line comment + if i < len(content) - 1 and content[i:i+2] == '//': + # Skip to end of line + while i < len(content) and content[i] != '\n': + i += 1 + if i < len(content): + result.append('\n') # Keep the newline + i += 1 + # Check for multi-line comment + elif i < len(content) - 1 and content[i:i+2] == '/*': + i += 2 + # Skip to end of comment + while i < len(content) - 1: + if content[i:i+2] == '*/': + i += 2 + break + if content[i] == '\n': + result.append('\n') # Keep newlines in comments + i += 1 + # Check for string literals + elif content[i] in ['"', "'"]: + quote = content[i] + result.append(quote) + i += 1 + # Skip to end of string + while i < len(content): + if content[i] == quote: + result.append(quote) + i += 1 + break + elif content[i] == '\\' and i + 1 < len(content): + # Skip escaped character + i += 2 + else: + i += 1 + else: + result.append(content[i]) + i += 1 + + return ''.join(result) + + def _extract_imports_exports(self, content: str): + """Extract import and export statements""" + import_pattern = r"import\s+['\"]([^'\"]+)['\"](?:\s+as\s+(\w+))?(?:\s+show\s+([^;]+))?(?:\s+hide\s+([^;]+))?;" + export_pattern = r"export\s+['\"]([^'\"]+)['\"](?:\s+show\s+([^;]+))?(?:\s+hide\s+([^;]+))?;" + + for match in re.finditer(import_pattern, content): + import_info = { + 'path': match.group(1), + 'alias': match.group(2), + 'show': match.group(3).strip() if match.group(3) else None, + 'hide': match.group(4).strip() if match.group(4) else None + } + self.structure['imports'].append(import_info) + + for match in re.finditer(export_pattern, content): + export_info = { + 'path': match.group(1), + 'show': match.group(2).strip() if match.group(2) else None, + 'hide': match.group(3).strip() if match.group(3) else None + } + self.structure['exports'].append(export_info) + + def _extract_classes(self, content: str, lines: List[str]): + """Extract class definitions""" + class_pattern = r'(?:abstract\s+)?class\s+(\w+)(?:\s*<[^>]+>)?(?:\s+extends\s+(\w+(?:<[^>]+>)?))?(?:\s+with\s+([^{]+))?(?:\s+implements\s+([^{]+))?\s*{' + + for match in re.finditer(class_pattern, content): + line_num = self._get_line_number(content, match.start(), lines) + class_info = { + 'name': match.group(1), + 'line': line_num, + 'extends': match.group(2).strip() if match.group(2) else None, + 'mixins': [m.strip() for m in match.group(3).split(',')] if match.group(3) else [], + 'implements': [i.strip() for i in match.group(4).split(',')] if match.group(4) else [], + 'methods': [], + 'fields': [], + 'constructors': [] + } + + # Extract class body - find the position right after the opening brace + brace_pos = match.end() - 1 # Position of opening brace + class_body = self._extract_class_body(content, brace_pos) + if class_body: + # The body starts after the opening brace AND any immediate whitespace + body_start_pos = brace_pos + 1 + # Skip any whitespace/newlines immediately after the brace to get to actual content + while body_start_pos < len(content) and content[body_start_pos] in ['\n', ' ', '\t']: + body_start_pos += 1 + self._extract_class_members_absolute(content, class_body, class_info, lines, body_start_pos) + + self.structure['classes'].append(class_info) + + def _extract_class_body(self, content: str, start_pos: int) -> str: + """Extract the body of a class between braces""" + brace_count = 0 + start = start_pos + + for i, char in enumerate(content[start_pos:], start_pos): + if char == '{': + brace_count += 1 + elif char == '}': + brace_count -= 1 + if brace_count == 0: + return content[start + 1:i] + return "" + + def _extract_class_members_absolute(self, full_content: str, class_body: str, class_info: Dict, lines: List[str], body_start_pos: int): + """Extract methods, fields, and constructors from class body with absolute line numbers""" + + # Split class body into lines for better parsing + class_lines = class_body.split('\n') + processed_lines = set() + + # Extract constructors first + constructor_pattern = rf'{re.escape(class_info["name"])}\s*(?:\.\w+)?\s*\([^)]*\)\s*(?::\s*[^{{]+)?\s*{{' + for match in re.finditer(constructor_pattern, class_body): + absolute_pos = body_start_pos + match.start() + line_in_class = class_body[:match.start()].count('\n') + processed_lines.add(line_in_class) + constructor_info = { + 'name': match.group(0).split('(')[0].strip(), + 'line': self._get_line_number(full_content, absolute_pos, lines) + } + class_info['constructors'].append(constructor_info) + + # Extract getters and setters + getter_pattern = r'(?:static\s+)?(?:([\w<>?]+)\s+)?get\s+(\w+)(?:\s*=>\s*[^;]+;|\s*{)' + for match in re.finditer(getter_pattern, class_body): + absolute_pos = body_start_pos + match.start() + line_in_class = class_body[:match.start()].count('\n') + if line_in_class not in processed_lines: + processed_lines.add(line_in_class) + method_info = { + 'name': f"get {match.group(2)}", + 'return_type': match.group(1), + 'line': self._get_line_number(full_content, absolute_pos, lines) + } + class_info['methods'].append(method_info) + + setter_pattern = r'(?:static\s+)?set\s+(\w+)\([^)]*\)(?:\s*=>\s*[^;]+;|\s*{)' + for match in re.finditer(setter_pattern, class_body): + absolute_pos = body_start_pos + match.start() + line_in_class = class_body[:match.start()].count('\n') + if line_in_class not in processed_lines: + processed_lines.add(line_in_class) + method_info = { + 'name': f"set {match.group(1)}", + 'return_type': 'void', + 'line': self._get_line_number(full_content, absolute_pos, lines) + } + class_info['methods'].append(method_info) + + # Extract regular methods - must be method declarations, not calls + method_pattern = r'(?:^|\n)(\s*)(?:static\s+)?(?:@\w+\s+)*(?:([\w<>?]+)\s+)?(\w+)\s*\([^)]*\)\s*(?:async\s*)?(?:\s*=>\s*[^;]+;|\s*{)' + for match in re.finditer(method_pattern, class_body): + indent = match.group(1) + method_name = match.group(3) + return_type = match.group(2) + + # Skip constructors, getters, setters + if method_name == class_info['name'] or method_name in ['get', 'set']: + continue + + # Skip obvious control flow and literals + if method_name in ['if', 'for', 'while', 'switch', 'try', 'catch', 'finally', 'true', 'false', 'null', 'return']: + continue + + # Skip common method calls and expressions + if method_name in ['forEach', 'map', 'where', 'listen', 'then', 'add', 'remove', 'removeWhere', 'singleWhere', 'firstWhere', 'reduce', 'fold', 'any', 'every', 'contains', 'indexOf', 'lastIndexOf', 'join', 'split', 'handleException', 'sendData', 'await']: + continue + + # Methods should have reasonable indentation (2-6 spaces for class methods) + if len(indent) > 8: + continue + + # Check if this is preceded by 'await' or other indicators of method calls + line_start = class_body.rfind('\n', 0, match.start()) + 1 + line_before_method = class_body[line_start:match.start()] + if re.search(r'\b(await|return|=|\(|\[)\s*$', line_before_method): + continue + + # Additional check: method declarations should typically have return types or be void + # If no return type is specified and it's deeply indented, it's likely a method call + if not return_type and len(indent) > 4: + continue + + absolute_pos = body_start_pos + match.start() + line_in_class = class_body[:match.start()].count('\n') + if line_in_class not in processed_lines: + processed_lines.add(line_in_class) + method_info = { + 'name': method_name, + 'return_type': return_type, + 'line': self._get_line_number(full_content, absolute_pos, lines) + } + class_info['methods'].append(method_info) + + # Extract fields (only at class level, not inside methods) + # Pattern 1: Explicit type - "final Type fieldName" or "Type fieldName" + # Also handles comma-separated: "Type field1, field2, field3;" + explicit_type_pattern = r'(?:^|\n)\s*(?:static\s+)?(?:final\s+|const\s+|late\s+)?([\w<>?]+)\s+([^;]+);' + # Pattern 2: Inferred type - "final fieldName = {}" or "final fieldName = Constructor()" + inferred_type_pattern = r'(?:^|\n)\s*(?:static\s+)?(final|const|late)\s+(\w+)\s*=\s*(?:<([^>]+)>\s*\{\}|<([^>]+)>\s*\[\]|(\w+(?:<[^>]+>)?)\s*\()' + + # First, identify method signatures and bodies to exclude them from field detection + method_bodies = [] + # Pattern to match method signatures including multi-line parameter lists + method_signature_pattern = r'(?:^|\n)\s*(?:static\s+)?(?:@\w+\s+)*(?:[\w<>?]+\s+)?(?:get\s+|set\s+)?\w+\s*\(' + + for method_match in re.finditer(method_signature_pattern, class_body): + # Find the complete method signature (including parameters) and body + signature_start = method_match.start() + + # Find the opening parenthesis and match it with closing parenthesis + paren_pos = method_match.end() - 1 # Position of opening parenthesis + paren_count = 0 + signature_end = paren_pos + + for i, char in enumerate(class_body[paren_pos:], paren_pos): + if char == '(': + paren_count += 1 + elif char == ')': + paren_count -= 1 + if paren_count == 0: + signature_end = i + 1 + break + + # Now find the method body (look for opening brace after the closing parenthesis) + body_start = -1 + for i, char in enumerate(class_body[signature_end:], signature_end): + if char == '{': + body_start = i + break + elif char not in [' ', '\n', '\t'] and not char.isalpha(): # Skip 'async' keyword + if char != 'a': # Not starting 'async' + break + + if body_start != -1: + # Find the end of method body + brace_count = 0 + for i, char in enumerate(class_body[body_start:], body_start): + if char == '{': + brace_count += 1 + elif char == '}': + brace_count -= 1 + if brace_count == 0: + # Include the entire method signature and body + method_bodies.append((signature_start, i + 1)) + break + + # Process explicit type fields first + for match in re.finditer(explicit_type_pattern, class_body): + field_names_part = match.group(2).strip() + field_type = match.group(1) + + # Skip if field_type is actually a modifier or control flow keyword + if field_type in ['final', 'const', 'late', 'static', 'var', 'dynamic', 'if', 'for', 'while', 'switch', 'try', 'catch', 'return']: + continue + + # Parse field names (handle comma-separated declarations) + # Split by comma and clean up each field name + field_declarations = [] + if ',' in field_names_part: + # Multiple fields: "field1, field2, field3" + raw_fields = field_names_part.split(',') + for raw_field in raw_fields: + # Clean up and extract just the field name (remove default values) + clean_field = raw_field.split('=')[0].strip() + if clean_field and re.match(r'^\w+$', clean_field): + field_declarations.append(clean_field) + else: + # Single field: "fieldName" or "fieldName = defaultValue" + clean_field = field_names_part.split('=')[0].strip() + if clean_field and re.match(r'^\w+$', clean_field): + field_declarations.append(clean_field) + + # Check if this field declaration is inside a method body + field_pos = match.start() + inside_method = False + for method_start, method_end in method_bodies: + if method_start <= field_pos <= method_end: + inside_method = True + break + + if not inside_method: + absolute_pos = body_start_pos + match.start() + line_in_class = class_body[:match.start()].count('\n') + # Check if we've already processed this specific field declaration + # (not just the line number, since comma-separated fields share the same line) + field_key = (line_in_class, field_type, tuple(sorted(field_declarations))) + if field_key not in processed_lines: + # Additional check: must be at proper indentation for class field + line_start = class_body.rfind('\n', 0, match.start()) + 1 + line_content = class_body[line_start:match.end()] + indent = len(line_content) - len(line_content.lstrip()) + + # Class fields should have minimal indentation (2-4 spaces typically) + if indent <= 6: + processed_lines.add(field_key) + actual_line = self._get_line_number(full_content, absolute_pos, lines) + + # Add each field from the declaration + for field_name in field_declarations: + field_info = { + 'name': field_name, + 'type': field_type, + 'line': actual_line + } + class_info['fields'].append(field_info) + + # Process inferred type fields + for match in re.finditer(inferred_type_pattern, class_body): + modifier = match.group(1) # final, const, late + field_name = match.group(2) + # Extract inferred type from different patterns + if match.group(3): # {} + field_type = f"Map<{match.group(3)}>" + elif match.group(4): # [] + field_type = f"List<{match.group(4)}>" + elif match.group(5): # Constructor() + constructor = match.group(5) + field_type = constructor.split('<')[0] # Remove generic part for constructor name + else: + field_type = "dynamic" + + # Check if this field declaration is inside a method body + field_pos = match.start() + inside_method = False + for method_start, method_end in method_bodies: + if method_start <= field_pos <= method_end: + inside_method = True + break + + if not inside_method: + absolute_pos = body_start_pos + match.start() + line_in_class = class_body[:match.start()].count('\n') + if line_in_class not in processed_lines: + # Additional check: must be at proper indentation for class field + line_start = class_body.rfind('\n', 0, match.start()) + 1 + line_content = class_body[line_start:match.end()] + indent = len(line_content) - len(line_content.lstrip()) + + # Class fields should have minimal indentation (2-4 spaces typically) + if indent <= 6: + processed_lines.add(line_in_class) + field_info = { + 'name': field_name, + 'type': field_type, + 'line': self._get_line_number(full_content, absolute_pos, lines) + } + class_info['fields'].append(field_info) + + def _extract_functions(self, content: str, lines: List[str]): + """Extract top-level function definitions""" + function_pattern = r'(?:^|\n)(?:@\w+\s+)*(?:([\w<>]+)\s+)?(\w+)\s*\([^)]*\)\s*(?:async\s*)?{' + + for match in re.finditer(function_pattern, content): + # Skip if it's inside a class (simple heuristic) + before_match = content[:match.start()] + if self._is_inside_class(before_match): + continue + + line_num = self._get_line_number(content, match.start(), lines) + function_info = { + 'name': match.group(2), + 'return_type': match.group(1), + 'line': line_num + } + self.structure['functions'].append(function_info) + + def _extract_variables(self, content: str, lines: List[str]): + """Extract top-level variable declarations""" + var_pattern = r'(?:^|\n)(?:const\s+|final\s+|var\s+|late\s+)?([\w<>?]+)\s+(\w+)(?:\s*=\s*[^;]+)?;' + + for match in re.finditer(var_pattern, content): + before_match = content[:match.start()] + if self._is_inside_class(before_match): + continue + + line_num = self._get_line_number(content, match.start(), lines) + var_info = { + 'name': match.group(2), + 'type': match.group(1), + 'line': line_num + } + self.structure['variables'].append(var_info) + + def _extract_enums(self, content: str, lines: List[str]): + """Extract enum definitions""" + enum_pattern = r'enum\s+(\w+)\s*{' + + for match in re.finditer(enum_pattern, content): + line_num = self._get_line_number(content, match.start(), lines) + enum_info = { + 'name': match.group(1), + 'line': line_num, + 'values': [] + } + + # Extract enum values - handle both single-line and multi-line enums + # First try to find the enum body between braces + brace_start = match.end() - 1 # Position of opening brace + brace_end = -1 + brace_count = 0 + + for i, char in enumerate(content[brace_start:], brace_start): + if char == '{': + brace_count += 1 + elif char == '}': + brace_count -= 1 + if brace_count == 0: + brace_end = i + break + + if brace_end > brace_start: + enum_body = content[brace_start + 1:brace_end] + + # Remove comments and clean up the enum body + cleaned_enum_body = re.sub(r'//.*$', '', enum_body, flags=re.MULTILINE) + cleaned_enum_body = re.sub(r'/\*.*?\*/', '', cleaned_enum_body, flags=re.DOTALL) + + # Extract enum values - split by comma and clean up + values_text = cleaned_enum_body.strip() + if values_text: + # Split by comma and clean up each value + raw_values = re.split(r'[,\s]+', values_text) + for value in raw_values: + value = value.strip() + # Only include valid identifiers + if value and re.match(r'^\w+$', value): + if value not in ['enum', 'const', 'final', 'static']: + enum_info['values'].append(value) + + self.structure['enums'].append(enum_info) + + def _extract_mixins(self, content: str, lines: List[str]): + """Extract mixin definitions""" + mixin_pattern = r'mixin\s+(\w+)(?:\s+on\s+([^{]+))?\s*{' + + for match in re.finditer(mixin_pattern, content): + line_num = self._get_line_number(content, match.start(), lines) + mixin_info = { + 'name': match.group(1), + 'line': line_num, + 'on': match.group(2).strip() if match.group(2) else None + } + self.structure['mixins'].append(mixin_info) + + def _extract_extensions(self, content: str, lines: List[str]): + """Extract extension definitions""" + extension_pattern = r'extension\s+(?:(\w+)\s+)?on\s+([\w<>]+)\s*{' + + for match in re.finditer(extension_pattern, content): + line_num = self._get_line_number(content, match.start(), lines) + extension_info = { + 'name': match.group(1) if match.group(1) else 'Anonymous', + 'on': match.group(2), + 'line': line_num + } + self.structure['extensions'].append(extension_info) + + def _extract_typedefs(self, content: str, lines: List[str]): + """Extract typedef definitions""" + typedef_pattern = r'typedef\s+(\w+)(?:\s*<[^>]+>)?\s*=\s*([^;]+);' + + for match in re.finditer(typedef_pattern, content): + line_num = self._get_line_number(content, match.start(), lines) + typedef_info = { + 'name': match.group(1), + 'definition': match.group(2).strip(), + 'line': line_num + } + self.structure['typedefs'].append(typedef_info) + + def _is_inside_class(self, content_before: str) -> bool: + """Check if we're inside a class definition""" + open_braces = content_before.count('{') + close_braces = content_before.count('}') + class_count = len(re.findall(r'\bclass\s+\w+', content_before)) + return open_braces > close_braces and class_count > 0 + + def _get_line_number(self, content: str, position: int, lines: List[str]) -> int: + """Get line number for a given position in content""" + if position == 0: + return 1 + return content[:position].count('\n') + 1 + +def print_linear_structure(structure: Dict[str, Any]): + """Print structure in linear format with indentation""" + + print("Format: [line] (type) name - lines are absolute file positions, indented items are class members") + print() + + # Collect all top-level items and sort by line number + all_items = [] + + # Add imports + for imp in structure['imports']: + all_items.append({ + 'line': imp.get('line', 0), + 'type': 'import', + 'data': imp + }) + + # Add exports + for exp in structure['exports']: + all_items.append({ + 'line': exp.get('line', 0), + 'type': 'export', + 'data': exp + }) + + # Add classes + for cls in structure['classes']: + all_items.append({ + 'line': cls['line'], + 'type': 'class', + 'data': cls + }) + + # Add enums + for enum in structure['enums']: + all_items.append({ + 'line': enum['line'], + 'type': 'enum', + 'data': enum + }) + + # Add mixins + for mixin in structure['mixins']: + all_items.append({ + 'line': mixin['line'], + 'type': 'mixin', + 'data': mixin + }) + + # Add extensions + for ext in structure['extensions']: + all_items.append({ + 'line': ext['line'], + 'type': 'extension', + 'data': ext + }) + + # Add functions + for func in structure['functions']: + all_items.append({ + 'line': func['line'], + 'type': 'function', + 'data': func + }) + + # Add variables + for var in structure['variables']: + all_items.append({ + 'line': var['line'], + 'type': 'variable', + 'data': var + }) + + # Add typedefs + for typedef in structure['typedefs']: + all_items.append({ + 'line': typedef['line'], + 'type': 'typedef', + 'data': typedef + }) + + # Sort all items by line number + all_items.sort(key=lambda x: x['line']) + + # Print sorted items + for item in all_items: + if item['type'] == 'import': + print(f"[{item['line']}] (import) {item['data']['path']}") + elif item['type'] == 'export': + print(f"[{item['line']}] (export) {item['data']['path']}") + elif item['type'] == 'class': + cls = item['data'] + print(f"[{cls['line']}] (class) {cls['name']}") + if cls['extends']: + print(f" extends {cls['extends']}") + if cls['mixins']: + print(f" mixins: {', '.join(cls['mixins'])}") + if cls['implements']: + print(f" implements: {', '.join(cls['implements'])}") + + # Collect all class members and sort by line number + members = [] + + # Add constructors + for ctor in cls['constructors']: + members.append({ + 'line': ctor['line'], + 'type': 'constructor', + 'name': ctor['name'], + 'return_type': None + }) + + # Add fields + for field in cls['fields']: + members.append({ + 'line': field['line'], + 'type': 'field', + 'name': field['name'], + 'return_type': field['type'] + }) + + # Add methods + for method in cls['methods']: + members.append({ + 'line': method['line'], + 'type': 'method', + 'name': method['name'], + 'return_type': method['return_type'] + }) + + # Sort all members by line number + members.sort(key=lambda x: x['line']) + + # Print sorted members + for member in members: + if member['type'] == 'constructor': + print(f" [{member['line']}] (constructor) {member['name']}") + elif member['type'] == 'field': + type_info = f" : {member['return_type']}" if member['return_type'] else "" + print(f" [{member['line']}] (field) {member['name']}{type_info}") + elif member['type'] == 'method': + return_type = f"{member['return_type']} " if member['return_type'] else "" + print(f" [{member['line']}] (method) {return_type}{member['name']}") + + elif item['type'] == 'enum': + enum = item['data'] + print(f"[{enum['line']}] (enum) {enum['name']}") + for value in enum['values']: + print(f" (value) {value}") + + elif item['type'] == 'mixin': + mixin = item['data'] + on_clause = f" on {mixin['on']}" if mixin['on'] else "" + print(f"[{mixin['line']}] (mixin) {mixin['name']}{on_clause}") + + elif item['type'] == 'extension': + ext = item['data'] + print(f"[{ext['line']}] (extension) {ext['name']} on {ext['on']}") + + elif item['type'] == 'function': + func = item['data'] + return_type = f"{func['return_type']} " if func['return_type'] else "" + print(f"[{func['line']}] (function) {return_type}{func['name']}") + + elif item['type'] == 'variable': + var = item['data'] + type_info = f" : {var['type']}" if var['type'] else "" + print(f"[{var['line']}] (variable) {var['name']}{type_info}") + + elif item['type'] == 'typedef': + typedef = item['data'] + print(f"[{typedef['line']}] (typedef) {typedef['name']} = {typedef['definition']}") + +def main(): + if len(sys.argv) != 2: + print("Usage: python dart_structure_analyzer.py ") + sys.exit(1) + + file_path = sys.argv[1] + + if not Path(file_path).exists(): + print(f"Error: File '{file_path}' does not exist") + sys.exit(1) + + if not file_path.endswith('.dart'): + print(f"Warning: '{file_path}' does not have a .dart extension") + + analyzer = DartStructureAnalyzer() + structure = analyzer.analyze_file(file_path) + + if 'error' in structure: + print(f"Error: {structure['error']}") + sys.exit(1) + + print_linear_structure(structure) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/bin/nvim-lazy-reset b/bin/nvim-lazy-reset new file mode 100755 index 0000000..741f7d3 --- /dev/null +++ b/bin/nvim-lazy-reset @@ -0,0 +1,21 @@ +#!/bin/bash + +LAZY_DIR="$HOME/.local/share/nvim/lazy" + +if [[ ! -d "$LAZY_DIR" ]]; then + echo "Error: Directory $LAZY_DIR does not exist" + exit 1 +fi + +echo "Discarding git changes in all lazy.nvim plugins..." + +for plugin_dir in "$LAZY_DIR"/*; do + if [[ -d "$plugin_dir" && -d "$plugin_dir/.git" ]]; then + echo "Processing: $(basename "$plugin_dir")" + cd "$plugin_dir" || continue + git reset --hard HEAD 2>/dev/null + git clean -fd 2>/dev/null + fi +done + +echo "Done! All plugin directories have been reset to their HEAD state." \ No newline at end of file diff --git a/bin/upgrade-all b/bin/upgrade-all new file mode 100755 index 0000000..bbe39f8 --- /dev/null +++ b/bin/upgrade-all @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 + +import subprocess +import threading +import time +import sys +from queue import Queue + +class Colors: + RED = '\033[0;31m' + GREEN = '\033[0;32m' + YELLOW = '\033[1;33m' + BLUE = '\033[0;34m' + RESET = '\033[0m' + +class ParallelTaskRunner: + def __init__(self): + self.tasks = [] + self.results = Queue() + self.task_positions = {} + self.lock = threading.Lock() + + def add_task(self, name, command): + """Add a task to the execution queue.""" + self.tasks.append({'name': name, 'command': command}) + + def _update_task_line(self, task_id, text): + """Update a specific task's display line with thread-safe cursor movement.""" + with self.lock: + if task_id in self.task_positions: + lines_up = len(self.task_positions) - self.task_positions[task_id] + print(f"\033[{lines_up}A\r{text}\033[K\033[{lines_up}B", end="", flush=True) + else: + self.task_positions[task_id] = len(self.task_positions) + print(text, flush=True) + + def _execute_task(self, task): + """Execute a single task with real-time status updates.""" + name, command = task['name'], task['command'] + task_id = threading.get_ident() + + self._update_task_line(task_id, f"{Colors.YELLOW}[STARTING]{Colors.RESET} {name}") + + try: + process = subprocess.Popen( + command, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True + ) + + # Animated progress indicator + spinner_chars = "|/-\\" + spinner_idx = 0 + + while process.poll() is None: + spinner = f"{Colors.BLUE}[RUNNING]{Colors.RESET} {name} [{spinner_chars[spinner_idx]}]" + self._update_task_line(task_id, spinner) + spinner_idx = (spinner_idx + 1) % len(spinner_chars) + time.sleep(0.1) + + stdout, _ = process.communicate() + + if process.returncode == 0: + self._update_task_line(task_id, f"{Colors.GREEN}[COMPLETED]{Colors.RESET} {name}") + self.results.put({'name': name, 'success': True, 'output': stdout}) + else: + self._update_task_line(task_id, f"{Colors.RED}[FAILED]{Colors.RESET} {name}") + if stdout: + with self.lock: + print(f"\nError output for {name}:\n{stdout}") + self.results.put({'name': name, 'success': False, 'output': stdout}) + + except Exception as e: + self._update_task_line(task_id, f"{Colors.RED}[FAILED]{Colors.RESET} {name}") + with self.lock: + print(f"\nException for {name}: {e}") + self.results.put({'name': name, 'success': False, 'output': str(e)}) + + def run_all(self): + """Execute all tasks in parallel and return overall success status.""" + print("Starting parallel upgrade tasks...\n") + + # Start all tasks + threads = [ + threading.Thread(target=self._execute_task, args=(task,)) + for task in self.tasks + ] + + for thread in threads: + thread.start() + + for thread in threads: + thread.join() + + # Display summary + print(f"\n{'=' * 47}") + + # Count results properly + all_results = [] + while not self.results.empty(): + all_results.append(self.results.get()) + + failed_count = sum(1 for result in all_results if not result['success']) + + if failed_count == 0: + print(f"{Colors.GREEN}All upgrade tasks completed successfully!{Colors.RESET}") + return True + else: + print(f"{Colors.RED}Some tasks failed. Check output above.{Colors.RESET}") + return False + +def main(): + runner = ParallelTaskRunner() + + runner.add_task("Claude Code Update", "npm install -g @anthropic-ai/claude-code") + runner.add_task("Homebrew Update", "brew update && brew upgrade --greedy") + runner.add_task("Config Git Pull", "cd ~/.config && git pull") + runner.add_task("Neovim Config Git Pull", "cd ~/.config/nvim && git pull") + runner.add_task("SConfig Git Pull", "cd ~/.sconfig && git pull") + + success = runner.run_all() + sys.exit(0 if success else 1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/bin/yabaigap b/bin/yabaigap new file mode 100755 index 0000000..31ac4ba --- /dev/null +++ b/bin/yabaigap @@ -0,0 +1,6 @@ +#!/bin/bash + +/opt/homebrew/bin/yabai -m config top_padding 140 +/opt/homebrew/bin/yabai -m config bottom_padding 140 +/opt/homebrew/bin/yabai -m config left_padding 110 +/opt/homebrew/bin/yabai -m config right_padding 110 diff --git a/bin/yabainogap b/bin/yabainogap new file mode 100755 index 0000000..2a2fb92 --- /dev/null +++ b/bin/yabainogap @@ -0,0 +1,6 @@ +#!/bin/bash + +/opt/homebrew/bin/yabai -m config top_padding 00 +/opt/homebrew/bin/yabai -m config bottom_padding 00 +/opt/homebrew/bin/yabai -m config left_padding 00 +/opt/homebrew/bin/yabai -m config right_padding 00