diff --git a/src/hosts/core/models.py b/src/hosts/core/models.py index db06792..c9dc8ee 100644 --- a/src/hosts/core/models.py +++ b/src/hosts/core/models.py @@ -200,40 +200,87 @@ class HostsFile: def sort_by_ip(self, ascending: bool = True) -> None: """ - Sort entries by IP address, keeping default entries on top. + Sort entries by IP address, keeping default entries on top in fixed order. Args: ascending: Sort in ascending order if True, descending if False """ - def sort_key(entry): + # Separate default and non-default entries + default_entries = [entry for entry in self.entries if entry.is_default_entry()] + non_default_entries = [entry for entry in self.entries if not entry.is_default_entry()] + + def ip_sort_key(entry): try: ip_str = entry.ip_address.lstrip('# ') ip_obj = ipaddress.ip_address(ip_str) - # Default entries always come first (priority 0), others get priority 1 - priority = 0 if entry.is_default_entry() else 1 - # Create a tuple for sorting: (priority, version, ip_int) - return (priority, ip_obj.version, int(ip_obj)) + # Create a tuple for sorting: (version, ip_int) + return (ip_obj.version, int(ip_obj)) except ValueError: - # If IP parsing fails, use string comparison with high sort priority - priority = 0 if entry.is_default_entry() else 1 - return (priority, 999, entry.ip_address) + # If IP parsing fails, use string comparison + return (999, entry.ip_address) - self.entries.sort(key=sort_key, reverse=not ascending) + # Keep default entries in their natural fixed order (don't sort them) + # Define the fixed order for default entries + default_order = [ + {"ip": "127.0.0.1", "hostname": "localhost"}, + {"ip": "255.255.255.255", "hostname": "broadcasthost"}, + {"ip": "::1", "hostname": "localhost"}, + ] + + # Sort default entries according to their fixed order + def default_sort_key(entry): + for i, default in enumerate(default_order): + if (entry.ip_address == default["ip"] and + entry.hostnames and entry.hostnames[0] == default["hostname"]): + return i + return 999 # fallback for any unexpected default entries + + default_entries.sort(key=default_sort_key) + + # Sort non-default entries according to the specified direction + non_default_entries.sort(key=ip_sort_key, reverse=not ascending) + + # Combine: default entries always first, then sorted non-default entries + self.entries = default_entries + non_default_entries def sort_by_hostname(self, ascending: bool = True) -> None: """ - Sort entries by first hostname, keeping default entries on top. + Sort entries by first hostname, keeping default entries on top in fixed order. Args: ascending: Sort in ascending order if True, descending if False """ - def sort_key(entry): - # Default entries always come first (priority 0), others get priority 1 - priority = 0 if entry.is_default_entry() else 1 - hostname = (entry.hostnames[0] if entry.hostnames else "").lower() - return (priority, hostname) + # Separate default and non-default entries + default_entries = [entry for entry in self.entries if entry.is_default_entry()] + non_default_entries = [entry for entry in self.entries if not entry.is_default_entry()] - self.entries.sort(key=sort_key, reverse=not ascending) + def hostname_sort_key(entry): + hostname = (entry.hostnames[0] if entry.hostnames else "").lower() + return hostname + + # Keep default entries in their natural fixed order (don't sort them) + # Define the fixed order for default entries + default_order = [ + {"ip": "127.0.0.1", "hostname": "localhost"}, + {"ip": "255.255.255.255", "hostname": "broadcasthost"}, + {"ip": "::1", "hostname": "localhost"}, + ] + + # Sort default entries according to their fixed order + def default_sort_key(entry): + for i, default in enumerate(default_order): + if (entry.ip_address == default["ip"] and + entry.hostnames and entry.hostnames[0] == default["hostname"]): + return i + return 999 # fallback for any unexpected default entries + + default_entries.sort(key=default_sort_key) + + # Sort non-default entries according to the specified direction + non_default_entries.sort(key=hostname_sort_key, reverse=not ascending) + + # Combine: default entries always first, then sorted non-default entries + self.entries = default_entries + non_default_entries def find_entries_by_hostname(self, hostname: str) -> List[int]: """