"""
Actualización de la clase IP4Mask con nuevos métodos para obtener la máscara y el conteo de hosts disponibles. Se mejora la integración con la clase Class_IP4 y se ajusta el autocompletado en la aplicación principal. Se modifica la configuración de la ventana y se añaden sugerencias contextuales para el autocompletado. """
This commit is contained in:
parent
23676b9ef9
commit
036eeb4291
|
@ -0,0 +1,38 @@
|
||||||
|
import sympy
|
||||||
|
|
||||||
|
class ClassBase:
|
||||||
|
"""Clase base para todas las clases del sistema"""
|
||||||
|
|
||||||
|
def __init__(self, value, original_str=""):
|
||||||
|
self._value = value
|
||||||
|
self._original_str = original_str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def original_str(self):
|
||||||
|
return self._original_str
|
||||||
|
|
||||||
|
# Sistema de ayuda y autocompletado
|
||||||
|
@staticmethod
|
||||||
|
def Helper(input_str):
|
||||||
|
"""Override en subclases"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def PopupFunctionList():
|
||||||
|
"""Override en subclases"""
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Métodos básicos comunes
|
||||||
|
def __str__(self):
|
||||||
|
return str(self._value)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}('{self._original_str}')"
|
||||||
|
|
||||||
|
# Necesitaremos importar sympy en los archivos que usen SympyClassBase.
|
||||||
|
# sympy.sympify también se menciona.
|
||||||
|
# from sympy import sympify (si es necesario globalmente aquí o en las clases hijas)
|
|
@ -12,6 +12,12 @@ Hex[ff]
|
||||||
|
|
||||||
Hex[ff].toDecimal()
|
Hex[ff].toDecimal()
|
||||||
|
|
||||||
IP4[110.1.30.70,255.255.255.0]
|
n=IP4[110.1.30.70;255.255.255.0]
|
||||||
|
|
||||||
IP4[110.1.30.70,255.255.255.0]
|
n.mask()
|
||||||
|
|
||||||
|
m=IP4Mask[23]
|
||||||
|
|
||||||
|
IP4Mask[22]
|
||||||
|
|
||||||
|
IP4[110.1.30.70;255.255.255.0]
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"window_geometry": "1020x700+2638+160",
|
"window_geometry": "1020x700+2638+160",
|
||||||
"sash_pos_x": 303
|
"sash_pos_x": 332
|
||||||
}
|
}
|
147
ip4_type.py
147
ip4_type.py
|
@ -1,11 +1,13 @@
|
||||||
"""
|
"""
|
||||||
Clase híbrida para direcciones IPv4
|
Clase híbrida para direcciones IPv4
|
||||||
"""
|
"""
|
||||||
|
from class_base import ClassBase
|
||||||
from sympy_Base import SympyClassBase
|
from sympy_Base import SympyClassBase
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
import re
|
import re
|
||||||
|
import sympy
|
||||||
|
|
||||||
class IP4Mask:
|
class IP4Mask(ClassBase):
|
||||||
"""
|
"""
|
||||||
Helper class to manage IPv4 masks.
|
Helper class to manage IPv4 masks.
|
||||||
It can be initialized with an integer prefix (0-32) or a netmask string (e.g., "255.255.255.0").
|
It can be initialized with an integer prefix (0-32) or a netmask string (e.g., "255.255.255.0").
|
||||||
|
@ -14,27 +16,32 @@ class IP4Mask:
|
||||||
_mask_int: int
|
_mask_int: int
|
||||||
|
|
||||||
def __init__(self, mask_input: Union[int, str]):
|
def __init__(self, mask_input: Union[int, str]):
|
||||||
|
prefix = self._parse_mask(mask_input)
|
||||||
|
super().__init__(prefix, str(mask_input))
|
||||||
|
self._prefix = prefix
|
||||||
|
self._mask_int = self._prefix_to_mask_int(self._prefix)
|
||||||
|
|
||||||
|
def _parse_mask(self, mask_input: Union[int, str]) -> int:
|
||||||
|
"""Helper to parse mask_input and return prefix."""
|
||||||
|
prefix_val: int
|
||||||
if isinstance(mask_input, int):
|
if isinstance(mask_input, int):
|
||||||
if not (0 <= mask_input <= 32):
|
if not (0 <= mask_input <= 32):
|
||||||
raise ValueError(f"Invalid prefix length: {mask_input}. Must be between 0 and 32.")
|
raise ValueError(f"Invalid prefix length: {mask_input}. Must be between 0 and 32.")
|
||||||
self._prefix = mask_input
|
prefix_val = mask_input
|
||||||
elif isinstance(mask_input, str):
|
elif isinstance(mask_input, str):
|
||||||
try:
|
try:
|
||||||
# Try to interpret as prefix length string, e.g., "24"
|
parsed_int = int(mask_input)
|
||||||
prefix_val = int(mask_input)
|
if not (0 <= parsed_int <= 32):
|
||||||
if not (0 <= prefix_val <= 32):
|
|
||||||
raise ValueError(f"Invalid prefix string: '{mask_input}'. Must be between 0 and 32.")
|
raise ValueError(f"Invalid prefix string: '{mask_input}'. Must be between 0 and 32.")
|
||||||
self._prefix = prefix_val
|
prefix_val = parsed_int
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# Try to interpret as netmask string, e.g., "255.255.255.0"
|
parsed_prefix_from_str = self._netmask_str_to_prefix(mask_input)
|
||||||
parsed_prefix = self._netmask_str_to_prefix(mask_input)
|
if parsed_prefix_from_str is None:
|
||||||
if parsed_prefix is None:
|
|
||||||
raise ValueError(f"Invalid netmask string format: '{mask_input}'.")
|
raise ValueError(f"Invalid netmask string format: '{mask_input}'.")
|
||||||
self._prefix = parsed_prefix
|
prefix_val = parsed_prefix_from_str
|
||||||
else:
|
else:
|
||||||
raise TypeError(f"Invalid type for mask_input: {type(mask_input)}. Must be int or str.")
|
raise TypeError(f"Invalid type for mask_input: {type(mask_input)}. Must be int or str.")
|
||||||
|
return prefix_val
|
||||||
self._mask_int = self._prefix_to_mask_int(self._prefix)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _is_valid_ip_octet_str(octet_str: str) -> bool:
|
def _is_valid_ip_octet_str(octet_str: str) -> bool:
|
||||||
|
@ -90,17 +97,61 @@ class IP4Mask:
|
||||||
return self._prefix == other._prefix
|
return self._prefix == other._prefix
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def Helper(input_str):
|
||||||
|
if re.match(r"^\s*IP4Mask\\b", input_str, re.IGNORECASE):
|
||||||
|
return 'Máscara: IP4Mask[24], IP4Mask[255.255.255.0]. Funciones: get_prefix_int(), get_netmask_str(), hosts_count()'
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def PopupFunctionList():
|
||||||
|
return [
|
||||||
|
("get_prefix_int", "Obtiene prefijo CIDR (ej: 24)"),
|
||||||
|
("get_netmask_str", "Obtiene máscara como string (ej: 255.255.255.0)"),
|
||||||
|
("hosts_count", "Número de hosts disponibles"),
|
||||||
|
("to_sympy", "Convierte a expresión SymPy para álgebra"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def hosts_count(self):
|
||||||
|
"""Número de hosts disponibles"""
|
||||||
|
return 2**(32 - self._prefix) - 2 if self._prefix < 31 else 0
|
||||||
|
|
||||||
|
def to_sympy(self):
|
||||||
|
"""Convierte a SymPy cuando se necesite álgebra"""
|
||||||
|
return sympy.sympify(self._prefix)
|
||||||
|
|
||||||
class Class_IP4(SympyClassBase):
|
class Class_IP4(SympyClassBase):
|
||||||
"""Clase híbrida para direcciones IPv4"""
|
"""Clase híbrida para direcciones IPv4"""
|
||||||
|
|
||||||
def __new__(cls, *args):
|
def __new__(cls, *args, **kwargs):
|
||||||
"""Crear objeto SymPy válido"""
|
temp_ip_str_candidate = args[0] if args else ""
|
||||||
obj = SympyClassBase.__new__(cls)
|
temp_ip_str = ""
|
||||||
|
if isinstance(temp_ip_str_candidate, str):
|
||||||
|
if '/' in temp_ip_str_candidate:
|
||||||
|
temp_ip_str = temp_ip_str_candidate.split('/', 1)[0].strip()
|
||||||
|
elif ' ' in temp_ip_str_candidate:
|
||||||
|
temp_ip_str = temp_ip_str_candidate.split(' ', 1)[0].strip()
|
||||||
|
else:
|
||||||
|
temp_ip_str = temp_ip_str_candidate.strip()
|
||||||
|
|
||||||
|
if IP4Mask._is_valid_ip_string(temp_ip_str):
|
||||||
|
ip_parts_for_new = [int(x) for x in temp_ip_str.split('.')]
|
||||||
|
ip_int_for_new = (ip_parts_for_new[0] << 24) | \
|
||||||
|
(ip_parts_for_new[1] << 16) | \
|
||||||
|
(ip_parts_for_new[2] << 8) | \
|
||||||
|
ip_parts_for_new[3]
|
||||||
|
obj = SympyClassBase.__new__(cls, ip_int_for_new)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid IP address format for __new__: {temp_ip_str}")
|
||||||
|
else:
|
||||||
|
if not args:
|
||||||
|
raise ValueError("IP4 constructor requires arguments.")
|
||||||
|
obj = SympyClassBase.__new__(cls, *args)
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""Inicialización de IP4"""
|
self._raw_constructor_args = args
|
||||||
self._raw_constructor_args = args # Store for __repr__
|
|
||||||
|
|
||||||
ip_str: str
|
ip_str: str
|
||||||
self._mask_obj: Optional[IP4Mask] = None
|
self._mask_obj: Optional[IP4Mask] = None
|
||||||
|
@ -114,109 +165,93 @@ class Class_IP4(SympyClassBase):
|
||||||
|
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
input_str = args[0]
|
input_str = args[0]
|
||||||
# Try "ip/prefix"
|
|
||||||
if '/' in input_str:
|
if '/' in input_str:
|
||||||
parts = input_str.split('/', 1)
|
parts = input_str.split('/', 1)
|
||||||
ip_str = parts[0].strip()
|
ip_str = parts[0].strip()
|
||||||
if len(parts) > 1 and parts[1].strip():
|
if len(parts) > 1 and parts[1].strip():
|
||||||
self._mask_obj = IP4Mask(parts[1].strip())
|
self._mask_obj = IP4Mask(parts[1].strip())
|
||||||
# If no prefix after '/', it's just an IP
|
|
||||||
# Try "ip mask_str" (space separated)
|
|
||||||
elif ' ' in input_str:
|
elif ' ' in input_str:
|
||||||
parts = input_str.split()
|
parts = input_str.split(' ', 1)
|
||||||
ip_str = parts[0].strip()
|
ip_str = parts[0].strip()
|
||||||
if len(parts) > 1 and parts[1].strip():
|
if len(parts) > 1 and parts[1].strip():
|
||||||
self._mask_obj = IP4Mask(parts[1].strip())
|
self._mask_obj = IP4Mask(parts[1].strip())
|
||||||
# If no mask after space, it's just an IP
|
else:
|
||||||
else: # Just an IP string
|
|
||||||
ip_str = input_str.strip()
|
ip_str = input_str.strip()
|
||||||
|
|
||||||
elif len(args) == 2:
|
elif len(args) == 2:
|
||||||
ip_str = args[0].strip()
|
ip_str = args[0].strip()
|
||||||
mask_arg = args[1]
|
mask_arg = args[1]
|
||||||
|
|
||||||
if isinstance(mask_arg, IP4Mask): # Allow passing IP4Mask instance directly
|
if isinstance(mask_arg, IP4Mask):
|
||||||
self._mask_obj = mask_arg
|
self._mask_obj = mask_arg
|
||||||
elif isinstance(mask_arg, (str, int)): # Parser will pass str, programmatic use might pass int
|
elif isinstance(mask_arg, (str, int)):
|
||||||
self._mask_obj = IP4Mask(mask_arg)
|
self._mask_obj = IP4Mask(mask_arg)
|
||||||
else:
|
else:
|
||||||
raise TypeError(f"Second argument (mask) for IP4 must be int, str, or IP4Mask instance, got {type(mask_arg)}")
|
raise TypeError(f"Second argument (mask) for IP4 must be int, str, or IP4Mask instance, got {type(mask_arg)}")
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"IP4 constructor takes 1 or 2 arguments, got {len(args)}: {args}")
|
raise ValueError(f"IP4 constructor takes 1 or 2 arguments, got {len(args)}: {args}")
|
||||||
|
|
||||||
if not IP4Mask._is_valid_ip_string(ip_str): # Use IP4Mask's validator
|
if not IP4Mask._is_valid_ip_string(ip_str):
|
||||||
raise ValueError(f"Invalid IP address format: {ip_str}")
|
raise ValueError(f"Invalid IP address format: {ip_str}")
|
||||||
|
|
||||||
self._ip_str = ip_str
|
self._ip_str = ip_str
|
||||||
ip_parts = [int(x) for x in ip_str.split('.')]
|
ip_parts = [int(x) for x in ip_str.split('.')]
|
||||||
self._ip_int = (ip_parts[0] << 24) | (ip_parts[1] << 16) | (ip_parts[2] << 8) | ip_parts[3]
|
self._ip_int = (ip_parts[0] << 24) | (ip_parts[1] << 16) | (ip_parts[2] << 8) | ip_parts[3]
|
||||||
|
|
||||||
# Determine the 'original_str' for SympyClassBase's _sympystr.
|
|
||||||
# This string is what appears inside ClassName(...) in Sympy output.
|
|
||||||
# It should reflect the arguments as they would be if typed in brackets.
|
|
||||||
sympy_base_original_str: str
|
sympy_base_original_str: str
|
||||||
if len(args) == 1 and isinstance(args[0], str):
|
if len(args) == 1 and isinstance(args[0], str):
|
||||||
# Covers "1.1.1.1/24", "1.1.1.1 255.255.0.0", or just "1.1.1.1"
|
|
||||||
sympy_base_original_str = args[0]
|
sympy_base_original_str = args[0]
|
||||||
elif len(args) == 2:
|
elif len(args) == 2:
|
||||||
# Reconstruct as "ip_str;mask_representation"
|
|
||||||
mask_arg_for_repr = args[1]
|
mask_arg_for_repr = args[1]
|
||||||
if isinstance(mask_arg_for_repr, IP4Mask): # Should not happen from parser
|
if isinstance(mask_arg_for_repr, IP4Mask):
|
||||||
mask_repr_str = str(mask_arg_for_repr.get_prefix_int())
|
mask_repr_str = str(mask_arg_for_repr.get_prefix_int())
|
||||||
elif isinstance(mask_arg_for_repr, int):
|
elif isinstance(mask_arg_for_repr, int):
|
||||||
mask_repr_str = str(mask_arg_for_repr)
|
mask_repr_str = str(mask_arg_for_repr)
|
||||||
else: # string
|
else:
|
||||||
mask_repr_str = str(mask_arg_for_repr) # e.g., "24" or "255.255.255.0"
|
mask_repr_str = str(mask_arg_for_repr)
|
||||||
sympy_base_original_str = f"{args[0]};{mask_repr_str}"
|
sympy_base_original_str = f"{args[0]};{mask_repr_str}"
|
||||||
else: # Only ip_str was derived, no mask (or error caught earlier)
|
else:
|
||||||
sympy_base_original_str = self._ip_str
|
sympy_base_original_str = self._ip_str
|
||||||
|
|
||||||
super().__init__(self._ip_int, sympy_base_original_str)
|
super().__init__(self._ip_int, sympy_base_original_str)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
# This should be a valid Python expression to recreate the object.
|
|
||||||
arg_reprs = [repr(arg) for arg in self._raw_constructor_args]
|
arg_reprs = [repr(arg) for arg in self._raw_constructor_args]
|
||||||
return f"{self.__class__.__name__}({', '.join(arg_reprs)})"
|
return f"{self.__class__.__name__}({', '.join(arg_reprs)})"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Representación string para display"""
|
|
||||||
if self._mask_obj:
|
if self._mask_obj:
|
||||||
return f"{self._ip_str}/{self._mask_obj.get_prefix_int()}"
|
return f"{self._ip_str}/{self._mask_obj.get_prefix_int()}"
|
||||||
return self._ip_str
|
return self._ip_str
|
||||||
|
|
||||||
def _sympystr(self, printer):
|
def _sympystr(self, printer):
|
||||||
"""Representación SymPy string"""
|
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def Helper(input_str):
|
def Helper(input_str):
|
||||||
"""Ayuda contextual para IP4"""
|
if re.match(r"^\s*IP4\\b", input_str, re.IGNORECASE):
|
||||||
if re.match(r"^\s*IP4\b", input_str, re.IGNORECASE):
|
return ('Ej: IP4[192.168.1.1/24], IP4[10.0.0.1;8], IP4[172.16.0.5;255.255.0.0]\\n'
|
||||||
return ('Ej: IP4[192.168.1.1/24], IP4[10.0.0.1;8], IP4[172.16.0.5;255.255.0.0]\n'
|
'Funciones: NetworkAddress(), BroadcastAddress(), Nodes(), get_netmask_str(), get_prefix_length(), mask()')
|
||||||
'Funciones: NetworkAddress(), BroadcastAddress(), Nodes(), get_netmask_str(), get_prefix_length()')
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def PopupFunctionList():
|
def PopupFunctionList():
|
||||||
"""Lista de métodos sugeridos para autocompletado de IP4"""
|
|
||||||
return [
|
return [
|
||||||
("NetworkAddress", "Obtiene la dirección de red"),
|
("NetworkAddress", "Dirección de red"),
|
||||||
("BroadcastAddress", "Obtiene la dirección de broadcast"),
|
("BroadcastAddress", "Dirección de broadcast"),
|
||||||
("Nodes", "Cantidad de nodos usables en la subred"),
|
("Nodes", "Hosts disponibles"),
|
||||||
|
("mask", "Objeto máscara con sus métodos"),
|
||||||
("get_netmask_str", "Obtiene la máscara de red (ej: 255.255.255.0)"),
|
("get_netmask_str", "Obtiene la máscara de red (ej: 255.255.255.0)"),
|
||||||
("get_prefix_length", "Obtiene la longitud del prefijo CIDR (ej: 24)"),
|
("get_prefix_length", "Obtiene la longitud del prefijo CIDR (ej: 24)"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_netmask_str(self) -> Optional[str]:
|
def get_netmask_str(self) -> Optional[str]:
|
||||||
"""Returns the netmask as a string (e.g., "255.255.255.0") if a mask is defined."""
|
|
||||||
return self._mask_obj.get_mask_str() if self._mask_obj else None
|
return self._mask_obj.get_mask_str() if self._mask_obj else None
|
||||||
|
|
||||||
def get_prefix_length(self) -> Optional[int]:
|
def get_prefix_length(self) -> Optional[int]:
|
||||||
"""Returns the prefix length (e.g., 24) if a mask is defined."""
|
|
||||||
return self._mask_obj.get_prefix_int() if self._mask_obj else None
|
return self._mask_obj.get_prefix_int() if self._mask_obj else None
|
||||||
|
|
||||||
def NetworkAddress(self):
|
def NetworkAddress(self):
|
||||||
"""Obtiene la dirección de red"""
|
|
||||||
prefix = self.get_prefix_length()
|
prefix = self.get_prefix_length()
|
||||||
if prefix is None:
|
if prefix is None:
|
||||||
raise ValueError("No prefix/mask defined for NetworkAddress calculation.")
|
raise ValueError("No prefix/mask defined for NetworkAddress calculation.")
|
||||||
|
@ -231,10 +266,9 @@ class Class_IP4(SympyClassBase):
|
||||||
network_int & 0xFF
|
network_int & 0xFF
|
||||||
]
|
]
|
||||||
network_str = '.'.join(str(x) for x in parts)
|
network_str = '.'.join(str(x) for x in parts)
|
||||||
return Class_IP4(network_str, prefix) # Return new IP4 object for the network
|
return Class_IP4(network_str, prefix)
|
||||||
|
|
||||||
def BroadcastAddress(self):
|
def BroadcastAddress(self):
|
||||||
"""Obtiene la dirección de broadcast"""
|
|
||||||
prefix = self.get_prefix_length()
|
prefix = self.get_prefix_length()
|
||||||
if prefix is None:
|
if prefix is None:
|
||||||
raise ValueError("No prefix/mask defined for BroadcastAddress calculation.")
|
raise ValueError("No prefix/mask defined for BroadcastAddress calculation.")
|
||||||
|
@ -249,14 +283,19 @@ class Class_IP4(SympyClassBase):
|
||||||
broadcast_int & 0xFF
|
broadcast_int & 0xFF
|
||||||
]
|
]
|
||||||
broadcast_str = '.'.join(str(x) for x in parts)
|
broadcast_str = '.'.join(str(x) for x in parts)
|
||||||
return Class_IP4(broadcast_str, prefix) # Return new IP4 object for broadcast
|
return Class_IP4(broadcast_str, prefix)
|
||||||
|
|
||||||
def Nodes(self):
|
def Nodes(self):
|
||||||
"""Obtiene el número de nodos disponibles"""
|
|
||||||
prefix = self.get_prefix_length()
|
prefix = self.get_prefix_length()
|
||||||
if prefix is None:
|
if prefix is None:
|
||||||
raise ValueError("No prefix/mask defined for Nodes calculation.")
|
raise ValueError("No prefix/mask defined for Nodes calculation.")
|
||||||
|
|
||||||
if prefix >= 31: # For /31 and /32, typically 0 usable host addresses in standard subnetting
|
if prefix >= 31:
|
||||||
return 0
|
return 0
|
||||||
return (2 ** (32 - prefix)) - 2
|
return (2 ** (32 - prefix)) - 2
|
||||||
|
|
||||||
|
def mask(self) -> Optional[IP4Mask]:
|
||||||
|
"""Retorna objeto máscara para autocompletado"""
|
||||||
|
if not self._mask_obj:
|
||||||
|
raise ValueError("No mask defined for this IP4 object.")
|
||||||
|
return self._mask_obj
|
|
@ -300,13 +300,11 @@ class HybridCalculatorApp:
|
||||||
current_line_num = int(line_num_str)
|
current_line_num = int(line_num_str)
|
||||||
char_idx_after_dot = int(char_num_str)
|
char_idx_after_dot = int(char_num_str)
|
||||||
|
|
||||||
if char_idx_after_dot == 0: # Should not happen if a dot was typed
|
if char_idx_after_dot == 0:
|
||||||
print("DEBUG: Autocomplete: Cursor at beginning of line after dot. No action.")
|
print("DEBUG: Autocomplete: Cursor at beginning of line after dot. No action.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Índice del punto en la línea actual (0-based)
|
|
||||||
dot_char_index_in_line = char_idx_after_dot - 1
|
dot_char_index_in_line = char_idx_after_dot - 1
|
||||||
# Texto en la línea actual HASTA el punto (sin incluirlo)
|
|
||||||
text_on_line_up_to_dot = self.input_text.get(f"{current_line_num}.0", f"{current_line_num}.{dot_char_index_in_line}")
|
text_on_line_up_to_dot = self.input_text.get(f"{current_line_num}.0", f"{current_line_num}.{dot_char_index_in_line}")
|
||||||
|
|
||||||
stripped_text_before_dot = text_on_line_up_to_dot.strip()
|
stripped_text_before_dot = text_on_line_up_to_dot.strip()
|
||||||
|
@ -315,23 +313,45 @@ class HybridCalculatorApp:
|
||||||
if not stripped_text_before_dot:
|
if not stripped_text_before_dot:
|
||||||
print("DEBUG: Dot on empty line or after spaces. Offering global suggestions.")
|
print("DEBUG: Dot on empty line or after spaces. Offering global suggestions.")
|
||||||
suggestions = []
|
suggestions = []
|
||||||
custom_types_suggestions = [
|
|
||||||
("Hex", "Tipo Hexadecimal. Ej: Hex[FF]"),
|
|
||||||
("Bin", "Tipo Binario. Ej: Bin[1010]"),
|
|
||||||
("Dec", "Tipo Decimal. Ej: Dec[42]"),
|
|
||||||
("IP4", "Tipo Dirección IPv4. Ej: IP4[1.2.3.4/24]"),
|
|
||||||
("Chr", "Tipo Carácter. Ej: Chr[A]"),
|
|
||||||
]
|
|
||||||
suggestions.extend(custom_types_suggestions)
|
|
||||||
|
|
||||||
|
# MODIFIED: Get suggestions from HybridEvaluationEngine's base_context
|
||||||
|
if hasattr(self.engine, 'base_context') and isinstance(self.engine.base_context, dict):
|
||||||
|
for name, class_or_func in self.engine.base_context.items():
|
||||||
|
# Solo queremos clases (tipos) y funciones para el autocompletado global principal.
|
||||||
|
# Evitamos alias en minúscula si la versión capitalizada ya está (heurística simple).
|
||||||
|
if name[0].isupper(): # Prioritize capitalized names for classes/main functions
|
||||||
|
hint = f"Tipo o función: {name}"
|
||||||
|
if hasattr(class_or_func, '__doc__') and class_or_func.__doc__:
|
||||||
|
first_line_doc = class_or_func.__doc__.strip().split('\n')[0]
|
||||||
|
hint = f"{name} - {first_line_doc}"
|
||||||
|
elif hasattr(class_or_func, 'Helper'): # Usar Helper si está disponible
|
||||||
|
# Para obtener un hint del Helper, necesitamos llamarlo.
|
||||||
|
# Algunas clases Helper esperan el nombre de la clase.
|
||||||
|
try:
|
||||||
|
helper_text = class_or_func.Helper(name) # Pasar el nombre de la clase
|
||||||
|
if helper_text:
|
||||||
|
hint = helper_text.split('\n')[0] # Primera línea del helper
|
||||||
|
except Exception as e_helper:
|
||||||
|
print(f"DEBUG: Error calling Helper for {name}: {e_helper}")
|
||||||
|
pass # Mantener el hint genérico
|
||||||
|
suggestions.append((name, hint))
|
||||||
|
|
||||||
|
# Añadir funciones de SympyHelper (si no están ya en base_context de forma similar)
|
||||||
|
# Considerar si SympyHelper.PopupFunctionList() devuelve cosas ya cubiertas.
|
||||||
try:
|
try:
|
||||||
sympy_functions = SympyHelper.PopupFunctionList()
|
sympy_functions = SympyHelper.PopupFunctionList()
|
||||||
if sympy_functions:
|
if sympy_functions:
|
||||||
suggestions.extend(sympy_functions)
|
# Evitar duplicados si los nombres ya están de base_context
|
||||||
|
current_suggestion_names = {s[0] for s in suggestions}
|
||||||
|
for fname, fhint in sympy_functions:
|
||||||
|
if fname not in current_suggestion_names:
|
||||||
|
suggestions.append((fname, fhint))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList() for global: {e}")
|
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList() for global: {e}")
|
||||||
|
|
||||||
if suggestions:
|
if suggestions:
|
||||||
|
# Ordenar alfabéticamente para consistencia
|
||||||
|
suggestions.sort(key=lambda x: x[0])
|
||||||
self._show_autocomplete_popup(suggestions, is_global_popup=True)
|
self._show_autocomplete_popup(suggestions, is_global_popup=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,11 @@ from contextlib import contextmanager
|
||||||
from tl_bracket_parser import BracketParser
|
from tl_bracket_parser import BracketParser
|
||||||
from tl_popup import PlotResult
|
from tl_popup import PlotResult
|
||||||
from sympy_Base import SympyClassBase
|
from sympy_Base import SympyClassBase
|
||||||
from ip4_type import Class_IP4 as Class_IP4
|
from ip4_type import Class_IP4, IP4Mask
|
||||||
from hex_type import Class_Hex as Class_Hex
|
from hex_type import Class_Hex
|
||||||
from bin_type import Class_Bin as Class_Bin
|
from bin_type import Class_Bin
|
||||||
from dec_type import Class_Dec as Class_Dec
|
from dec_type import Class_Dec
|
||||||
from chr_type import Class_Chr as Class_Chr
|
from chr_type import Class_Chr
|
||||||
|
|
||||||
|
|
||||||
class HybridEvaluationEngine:
|
class HybridEvaluationEngine:
|
||||||
|
@ -90,12 +90,14 @@ class HybridEvaluationEngine:
|
||||||
'Dec': Class_Dec,
|
'Dec': Class_Dec,
|
||||||
'IP4': Class_IP4,
|
'IP4': Class_IP4,
|
||||||
'Chr': Class_Chr,
|
'Chr': Class_Chr,
|
||||||
|
'IP4Mask': IP4Mask,
|
||||||
# Alias en minúsculas
|
# Alias en minúsculas
|
||||||
'hex': Class_Hex,
|
'hex': Class_Hex,
|
||||||
'bin': Class_Bin,
|
'bin': Class_Bin,
|
||||||
'dec': Class_Dec,
|
'dec': Class_Dec,
|
||||||
'ip4': Class_IP4,
|
'ip4': Class_IP4,
|
||||||
'chr': Class_Chr,
|
'chr': Class_Chr,
|
||||||
|
'ip4mask': IP4Mask,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Funciones de utilidad
|
# Funciones de utilidad
|
||||||
|
|
|
@ -5,24 +5,31 @@ import sympy
|
||||||
from sympy import Basic, Symbol, sympify
|
from sympy import Basic, Symbol, sympify
|
||||||
from typing import Any, Optional, Dict
|
from typing import Any, Optional, Dict
|
||||||
import re
|
import re
|
||||||
|
from class_base import ClassBase
|
||||||
|
|
||||||
|
|
||||||
class SympyClassBase(Basic):
|
class SympyClassBase(ClassBase, sympy.Basic):
|
||||||
"""
|
"""Para clases que necesitan álgebra completa"""
|
||||||
Clase base híbrida que combina SymPy Basic con funcionalidad de calculadora
|
|
||||||
Todas las clases especializadas deben heredar de esta
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
"""Crear objeto SymPy válido"""
|
# La subclase (ej. Class_IP4) es responsable de pasar los argumentos correctos
|
||||||
obj = Basic.__new__(cls)
|
# que sympy.Basic.__new__ espera.
|
||||||
|
obj = sympy.Basic.__new__(cls, *args, **kwargs)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def __init__(self, value, original_str=""):
|
def __init__(self, value, original_str=""):
|
||||||
"""Inicialización de funcionalidad especializada"""
|
ClassBase.__init__(self, value, original_str)
|
||||||
self._value = value
|
# La inicialización de sympy.Basic se maneja principalmente a través de __new__.
|
||||||
self._original_str = original_str
|
# Atributos adicionales de sympy como self.args son establecidos por sympy.Basic
|
||||||
self._init_specialized()
|
# basado en los argumentos pasados a __new__.
|
||||||
|
|
||||||
|
def _sympystr(self, printer):
|
||||||
|
# Las subclases deben implementar esto para una representación SymPy adecuada.
|
||||||
|
# Por defecto, usa el __str__ de ClassBase, que es str(self.value).
|
||||||
|
# Si self.value es un objeto Sympy, str(self.value) ya dará una buena representación.
|
||||||
|
# Si self.value no es un objeto Sympy pero se desea una forma SymPy específica,
|
||||||
|
# esto debería ser sobreescrito.
|
||||||
|
return str(self.value)
|
||||||
|
|
||||||
def _init_specialized(self):
|
def _init_specialized(self):
|
||||||
"""Override en subclases para inicialización especializada"""
|
"""Override en subclases para inicialización especializada"""
|
||||||
|
@ -49,10 +56,6 @@ class SympyClassBase(Basic):
|
||||||
"""Función constructora para SymPy"""
|
"""Función constructora para SymPy"""
|
||||||
return self.__class__
|
return self.__class__
|
||||||
|
|
||||||
def _sympystr(self, printer):
|
|
||||||
"""Representación SymPy string"""
|
|
||||||
return f"{self.__class__.__name__}({self._original_str})"
|
|
||||||
|
|
||||||
def _latex(self, printer):
|
def _latex(self, printer):
|
||||||
"""Representación LaTeX"""
|
"""Representación LaTeX"""
|
||||||
return self._sympystr(printer)
|
return self._sympystr(printer)
|
||||||
|
|
|
@ -10,7 +10,7 @@ class BracketParser:
|
||||||
"""Parser que convierte sintaxis con corchetes y detecta ecuaciones contextualmente"""
|
"""Parser que convierte sintaxis con corchetes y detecta ecuaciones contextualmente"""
|
||||||
|
|
||||||
# Clases que soportan sintaxis con corchetes
|
# Clases que soportan sintaxis con corchetes
|
||||||
BRACKET_CLASSES = {'IP4', 'Hex', 'Bin', 'Date', 'Dec', 'Chr'}
|
BRACKET_CLASSES = {'IP4', 'Hex', 'Bin', 'Date', 'Dec', 'Chr', 'IP4Mask'}
|
||||||
|
|
||||||
# Operadores de comparación que pueden formar ecuaciones
|
# Operadores de comparación que pueden formar ecuaciones
|
||||||
EQUATION_OPERATORS = {'==', '!=', '<', '<=', '>', '>=', '='}
|
EQUATION_OPERATORS = {'==', '!=', '<', '<=', '>', '>=', '='}
|
||||||
|
|
Loading…
Reference in New Issue