#!/usr/bin/env python3 """ Simple Debug API - Calculadora MAV CAS API CLI simple que usa el motor de evaluación existente para generar debug traces sin modificar el código base. Permite debuggear "como si usaras la aplicación" mediante archivos JSON. Uso: python simple_debug.py debug_input.json python simple_debug.py debug_input.json --output debug_results.json python simple_debug.py debug_input.json --verbose """ import json import sys import argparse from datetime import datetime from pathlib import Path # Importar el motor de evaluación existente from main_evaluation import HybridEvaluationEngine import sympy def _process_evaluation_result_for_debug(result, engine): """ Procesa el resultado de evaluación tal como lo haría la aplicación Retorna información de formato, colores y contenido exacto """ output_parts = [] if result.is_error: # Intentar obtener ayuda como lo hace la app ayuda = _obtener_ayuda_simulada(result.original_line, engine) if ayuda: ayuda_linea = ayuda.replace("\n", " ").replace("\r", " ") if len(ayuda_linea) > 120: ayuda_linea = ayuda_linea[:117] + "..." output_parts.append(("helper", ayuda_linea)) else: output_parts.append(("error", f"Error: {result.error}")) elif result.result_type == "comment": output_parts.append(("comment", result.original_line)) elif result.result_type == "equation_added": output_parts.append(("equation", result.symbolic_result)) elif result.result_type == "assignment": output_parts.append(("info", result.symbolic_result)) # Mostrar evaluación numérica para asignaciones si existe if result.numeric_result is not None and result.numeric_result != result.result: output_parts.append(("numeric", f"≈ {result.numeric_result}")) else: # Resultado normal if result.result is not None: # Determinar tag basado en tipo (simulando lógica dinámica) tag = _get_result_tag_dynamic_debug(result.result, engine) # Verificar si es resultado interactivo (simulado) if _is_interactive_result(result.result): interactive_tag, display_text = _create_interactive_tag_debug(result.result) if interactive_tag: output_parts.append((interactive_tag, display_text)) else: output_parts.append((tag, str(result.result))) else: output_parts.append((tag, str(result.result))) # Añadir pista de clase para el resultado principal class_display_name = _get_class_display_name_dynamic_debug(result.result, engine) if class_display_name: output_parts.append(("class_hint", f"[{class_display_name}]")) # Mostrar evaluación numérica si existe if result.numeric_result is not None and result.numeric_result != result.result: output_parts.append(("numeric", f"≈ {result.numeric_result}")) # Mostrar información adicional if result.info: output_parts.append(("info", f"({result.info})")) # Convertir partes a string formateado como la app formatted_output = _format_output_parts_debug(output_parts) return { 'parts': output_parts, 'formatted_text': formatted_output, 'tag_info': _get_tag_color_info() } def _obtener_ayuda_simulada(input_str, engine): """Simula la obtención de ayuda como lo hace la app""" try: # Intentar usar los helpers del engine si están disponibles if hasattr(engine, 'HELPERS'): for helper in engine.HELPERS: try: ayuda = helper(input_str) if ayuda: return ayuda except: continue except: pass return None def _get_result_tag_dynamic_debug(result, engine): """Simula _get_result_tag_dynamic de la app""" try: registered_classes = engine.get_available_types().get('registered_classes', {}) # Verificar si es una instancia de alguna clase registrada for name, cls in registered_classes.items(): if isinstance(result, cls): name_lower = name.lower() if name_lower == "hex": return "hex" elif name_lower == "bin": return "bin" elif name_lower in ["ip4", "ip"]: return "ip" elif name_lower == "chr": return "chr_type" elif name_lower == "date": return "date" else: return "custom_type" except Exception: pass # Fallback a tags existentes para tipos no registrados if isinstance(result, sympy.Basic): return "symbolic" else: return "result" def _get_class_display_name_dynamic_debug(obj, engine): """Simula _get_class_display_name_dynamic de la app""" try: # Verificar si es una clase registrada dinámicamente registered_classes = engine.get_available_types().get('registered_classes', {}) for name, cls in registered_classes.items(): if isinstance(obj, cls): return name except Exception: pass # Fallback a lógica existente para tipos nativos if isinstance(obj, sympy.logic.boolalg.BooleanAtom): return "Boolean" elif isinstance(obj, sympy.Basic): if hasattr(obj, 'is_number') and obj.is_number: if hasattr(obj, 'is_Integer') and obj.is_Integer: return "Integer" elif hasattr(obj, 'is_Rational') and obj.is_Rational and not obj.is_Integer: return "Rational" elif hasattr(obj, 'is_Float') and obj.is_Float: return "Float" else: return "SympyNumber" else: return "Sympy" elif isinstance(obj, bool): return "Boolean" elif isinstance(obj, (int, float, str, list, dict, tuple, type(None))): class_display_name = type(obj).__name__.capitalize() if class_display_name == "Nonetype": class_display_name = "None" return class_display_name return "" def _is_interactive_result(result): """Simula la lógica is_interactive de EvaluationResult""" try: # Intentar importar PlotResult si existe from tl_popup import PlotResult if isinstance(result, PlotResult): return True except ImportError: pass return isinstance(result, sympy.Matrix) or \ (isinstance(result, list) and len(result) > 3) def _create_interactive_tag_debug(result): """Simula la creación de tags interactivos""" try: from tl_popup import PlotResult if isinstance(result, PlotResult): return f"plot_{id(result)}", f"📊 Ver {result.plot_type.title()}" except ImportError: pass if isinstance(result, sympy.Matrix): rows, cols = result.shape return f"matrix_{id(result)}", f"📋 Ver Matriz {rows}×{cols}" elif isinstance(result, list) and len(result) > 5: return f"list_{id(result)}", f"📋 Ver Lista ({len(result)} elementos)" elif isinstance(result, dict) and len(result) > 3: return f"dict_{id(result)}", f"🔍 Ver Diccionario ({len(result)} entradas)" return None, None def _format_output_parts_debug(output_parts): """Simula cómo _display_output formatea las partes para mostrar""" formatted_lines = [] for part_idx, (tag, content) in enumerate(output_parts): if not content: continue prefix = "" if part_idx > 0: prev_tag, prev_content = output_parts[part_idx-1] if part_idx > 0 else (None, None) if tag not in ["class_hint", "numeric", "info"] and prev_content: prefix = " ; " elif tag in ["numeric", "info"] and prev_content: prefix = " " formatted_lines.append(f"{prefix}[{tag}]{content}") return "".join(formatted_lines) def _get_tag_color_info(): """Información sobre los colores de los tags como en setup_output_tags""" return { "error": {"fg": "#ff6b6b", "font": "bold"}, "result": {"fg": "#abdbe3"}, "symbolic": {"fg": "#82aaff"}, "numeric": {"fg": "#c3e88d"}, "equation": {"fg": "#c792ea"}, "info": {"fg": "#ffcb6b"}, "comment": {"fg": "#546e7a"}, "class_hint": {"fg": "#888888"}, "type_hint": {"fg": "#6a6a6a"}, "custom_type": {"fg": "#f9a825"}, "hex": {"fg": "#f9a825"}, "bin": {"fg": "#4fc3f7"}, "ip": {"fg": "#fff176"}, "date": {"fg": "#ff8a80"}, "chr_type": {"fg": "#80cbc4"}, "helper": {"fg": "#ffd700", "font": "italic"} } def run_debug(input_file: str, output_file: str = None, verbose: bool = False): """ Ejecuta las queries de debug desde un archivo JSON y guarda los resultados. Args: input_file: Archivo JSON con las queries output_file: Archivo de salida (opcional) verbose: Modo verboso """ # Cargar queries del archivo de entrada try: with open(input_file, 'r', encoding='utf-8') as f: data = json.load(f) except FileNotFoundError: print(f"Error: No se encontró el archivo '{input_file}'") sys.exit(1) except json.JSONDecodeError as e: print(f"Error: JSON inválido en '{input_file}': {e}") sys.exit(1) if 'queries' not in data: print("Error: El archivo JSON debe contener una clave 'queries'") sys.exit(1) # Crear motor de evaluación if verbose: print("Iniciando motor de evaluación...") engine = HybridEvaluationEngine() results = [] successful = 0 failed = 0 # Ejecutar cada query for query in data['queries']: if verbose: print(f"Ejecutando query {query.get('index', '?')}: {query.get('content', '')[:50]}...") try: if query['type'] == 'input': # Query de tipo input: evaluar expresión como si fuera entrada del usuario result = engine.evaluate_line(query['content']) output = { 'index': query['index'], 'input': query['content'], 'output': str(result.result) if hasattr(result, 'result') else str(result), 'result_type': type(result.result).__name__ if hasattr(result, 'result') else type(result).__name__, 'success': not (hasattr(result, 'is_error') and result.is_error), 'error': result.error if hasattr(result, 'is_error') and result.is_error else None } # Generar output_raw: resultado exacto como se muestra en la aplicación try: output_raw_data = _process_evaluation_result_for_debug(result, engine) output['output_raw'] = output_raw_data except Exception as e: output['output_raw'] = f"Error generando output_raw: {e}" # Añadir información adicional si está disponible if hasattr(result, 'result') and hasattr(result.result, '__class__'): output['display_class'] = f"[{result.result.__class__.__name__}]" if not (hasattr(result, 'is_error') and result.is_error): successful += 1 else: failed += 1 elif query['type'] == 'exec': # Query de tipo exec: ejecutar código Python para inspeccionar el estado exec_result = eval(query['content'], {'engine': engine}) output = { 'index': query['index'], 'input': query['content'], 'output': str(exec_result), 'result_type': type(exec_result).__name__, 'success': True, 'error': None, 'exec_result': exec_result } successful += 1 else: raise ValueError(f"Tipo de query desconocido: {query['type']}") except Exception as e: # Manejar errores en la ejecución output = { 'index': query['index'], 'input': query['content'], 'output': None, 'result_type': None, 'success': False, 'error': str(e) } failed += 1 if verbose: print(f" Error: {str(e)}") results.append(output) # Preparar salida final final_output = { 'execution_info': { 'timestamp': datetime.now().isoformat() + 'Z', 'total_queries': len(data['queries']), 'successful': successful, 'failed': failed, 'input_file': input_file }, 'results': results } # Determinar archivo de salida if output_file is None: base_name = Path(input_file).stem output_file = f"{base_name}_results.json" # Guardar resultados try: # Usar un encoder personalizado para objetos no serializables def json_serializer(obj): """Serializar objetos no estándar a string""" try: # Intentar conversión normal primero json.dumps(obj) return obj except TypeError: # Si no es serializable, convertir a string return str(obj) # Aplicar serialización personalizada a exec_result for result in final_output['results']: if 'exec_result' in result: result['exec_result'] = json_serializer(result['exec_result']) with open(output_file, 'w', encoding='utf-8') as f: json.dump(final_output, f, indent=2, ensure_ascii=False, default=str) if verbose: print(f"\nResultados guardados en: {output_file}") print(f"Queries exitosas: {successful}") print(f"Queries con error: {failed}") except Exception as e: print(f"Error al guardar resultados: {e}") sys.exit(1) return final_output def main(): """Función principal del CLI""" parser = argparse.ArgumentParser( description='Simple Debug API para Calculadora MAV CAS', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Ejemplos de uso: python simple_debug.py debug_input.json python simple_debug.py debug_input.json --output custom_results.json python simple_debug.py debug_input.json --verbose """ ) parser.add_argument('input_file', help='Archivo JSON con las queries de debug') parser.add_argument('--output', '-o', help='Archivo de salida (por defecto: _results.json)') parser.add_argument('--verbose', '-v', action='store_true', help='Modo verboso') args = parser.parse_args() # Verificar que el archivo de entrada existe if not Path(args.input_file).exists(): print(f"Error: El archivo '{args.input_file}' no existe") sys.exit(1) # Ejecutar debug try: results = run_debug(args.input_file, args.output, args.verbose) # Mostrar resumen si no es modo verboso if not args.verbose: info = results['execution_info'] print(f"Debug completado: {info['successful']}/{info['total_queries']} exitosas") if args.output: print(f"Resultados en: {args.output}") else: base_name = Path(args.input_file).stem print(f"Resultados en: {base_name}_results.json") except KeyboardInterrupt: print("\nInterrumpido por el usuario") sys.exit(1) except Exception as e: print(f"Error inesperado: {e}") sys.exit(1) if __name__ == '__main__': main()