Add management header to hosts files and enhance serialization formatting; update tests to reflect changes.

This commit is contained in:
Philip Henning 2025-07-30 00:00:53 +02:00
parent 5a2e0d2623
commit 0ee720c5ef
3 changed files with 163 additions and 6 deletions

View file

@ -60,6 +60,8 @@
- ✅ **Error handling**: Comprehensive error handling with user feedback - ✅ **Error handling**: Comprehensive error handling with user feedback
- ✅ **Keyboard shortcuts**: All edit mode shortcuts implemented and tested - ✅ **Keyboard shortcuts**: All edit mode shortcuts implemented and tested
- ✅ **Live testing**: Manual testing confirms all functionality works correctly - ✅ **Live testing**: Manual testing confirms all functionality works correctly
- ✅ **Human-readable formatting**: Tab-based column alignment with proper spacing
- ✅ **Management header**: Automatic addition of management header to hosts files
### Phase 4: Advanced Edit Features ### Phase 4: Advanced Edit Features
- ❌ **Add new entries**: Create new host entries - ❌ **Add new entries**: Create new host entries

View file

@ -92,9 +92,12 @@ class HostsParser:
""" """
lines = [] lines = []
# Ensure header has management line
header_comments = self._ensure_management_header(hosts_file.header_comments)
# Add header comments # Add header comments
if hosts_file.header_comments: if header_comments:
for comment in hosts_file.header_comments: for comment in header_comments:
if comment.strip(): if comment.strip():
lines.append(f"# {comment}") lines.append(f"# {comment}")
else: else:
@ -118,6 +121,132 @@ class HostsParser:
return "\n".join(lines) + "\n" return "\n".join(lines) + "\n"
def _ensure_management_header(self, header_comments: list) -> list:
"""
Ensure the header contains the management line with proper formatting.
Args:
header_comments: List of existing header comments
Returns:
List of header comments with management line added if needed
"""
management_line = "Managed by hosts - https://git.s1q.dev/phg/hosts"
# Check if management line already exists
for comment in header_comments:
if "git.s1q.dev/phg/hosts" in comment:
return header_comments
# If no header exists, create default header
if not header_comments:
return [
"#",
"Host Database",
"",
management_line,
"#"
]
# Check for enclosing comment patterns
enclosing_pattern = self._detect_enclosing_pattern(header_comments)
if enclosing_pattern:
# Insert management line within the enclosing pattern
return self._insert_in_enclosing_pattern(header_comments, management_line, enclosing_pattern)
else:
# No enclosing pattern, append management line
result = header_comments.copy()
result.append(management_line)
return result
def _detect_enclosing_pattern(self, header_comments: list) -> dict | None:
"""
Detect if header has enclosing comment patterns like ###, # #, etc.
Args:
header_comments: List of header comments
Returns:
Dictionary with pattern info or None if no pattern detected
"""
if len(header_comments) < 2:
return None
# Look for matching patterns at start and end, ignoring management line if present
first_line = header_comments[0].strip()
# Find the last line that could be a closing pattern (not the management line)
last_pattern_index = -1
for i in range(len(header_comments) - 1, -1, -1):
line = header_comments[i].strip()
if "git.s1q.dev/phg/hosts" not in line:
last_pattern_index = i
break
if last_pattern_index <= 0:
return None
last_line = header_comments[last_pattern_index].strip()
# Check for ### pattern
if first_line == "###" and last_line == "###":
return {
'type': 'triple_hash',
'start_index': 0,
'end_index': last_pattern_index,
'pattern': '###'
}
# Check for # # pattern
if first_line == "#" and last_line == "#":
return {
'type': 'single_hash',
'start_index': 0,
'end_index': last_pattern_index,
'pattern': '#'
}
# Check for other repeating patterns (like ####, #####, etc.)
if len(first_line) > 1 and first_line == last_line and all(c == '#' for c in first_line):
return {
'type': 'repeating_hash',
'start_index': 0,
'end_index': last_pattern_index,
'pattern': first_line
}
return None
def _insert_in_enclosing_pattern(self, header_comments: list, management_line: str, pattern_info: dict) -> list:
"""
Insert management line within an enclosing comment pattern.
Args:
header_comments: List of header comments
management_line: Management line to insert
pattern_info: Information about the enclosing pattern
Returns:
Updated list of header comments
"""
result = header_comments.copy()
# Find the best insertion point (before the closing pattern)
insert_index = pattern_info['end_index']
# Look for an empty line before the closing pattern to insert after it
# Otherwise, insert right before the closing pattern
if insert_index > 1 and header_comments[insert_index - 1].strip() == "":
# Insert after the empty line, before closing pattern
result.insert(insert_index, management_line)
else:
# Insert empty line and management line before closing pattern
result.insert(insert_index, "")
result.insert(insert_index + 1, management_line)
return result
def _calculate_column_widths(self, entries: list) -> tuple[int, int]: def _calculate_column_widths(self, entries: list) -> tuple[int, int]:
""" """
Calculate the maximum width needed for IP and hostname columns. Calculate the maximum width needed for IP and hostname columns.

View file

@ -168,7 +168,12 @@ class TestHostsParser:
parser = HostsParser() parser = HostsParser()
content = parser.serialize(hosts_file) content = parser.serialize(hosts_file)
expected = """127.0.0.1\tlocalhost expected = """# #
# Host Database
#
# Managed by hosts - https://git.s1q.dev/phg/hosts
# #
127.0.0.1\tlocalhost
192.168.1.1\trouter 192.168.1.1\trouter
""" """
assert content == expected assert content == expected
@ -198,6 +203,7 @@ class TestHostsParser:
expected = """# Header comment 1 expected = """# Header comment 1
# Header comment 2 # Header comment 2
# Managed by hosts - https://git.s1q.dev/phg/hosts
127.0.0.1\tlocalhost\t# Loopback 127.0.0.1\tlocalhost\t# Loopback
# 10.0.0.1\ttest # 10.0.0.1\ttest
@ -211,7 +217,13 @@ class TestHostsParser:
parser = HostsParser() parser = HostsParser()
content = parser.serialize(hosts_file) content = parser.serialize(hosts_file)
assert content == "\n" expected = """# #
# Host Database
#
# Managed by hosts - https://git.s1q.dev/phg/hosts
# #
"""
assert content == expected
def test_write_hosts_file(self): def test_write_hosts_file(self):
"""Test writing hosts file to disk.""" """Test writing hosts file to disk."""
@ -226,7 +238,14 @@ class TestHostsParser:
# Read back and verify # Read back and verify
with open(f.name, 'r') as read_file: with open(f.name, 'r') as read_file:
content = read_file.read() content = read_file.read()
assert content == "127.0.0.1\tlocalhost\n" expected = """# #
# Host Database
#
# Managed by hosts - https://git.s1q.dev/phg/hosts
# #
127.0.0.1\tlocalhost
"""
assert content == expected
os.unlink(f.name) os.unlink(f.name)
@ -259,7 +278,14 @@ class TestHostsParser:
# Check new content # Check new content
with open(f.name, 'r') as new_file: with open(f.name, 'r') as new_file:
new_content = new_file.read() new_content = new_file.read()
assert new_content == "127.0.0.1\tlocalhost\n" expected = """# #
# Host Database
#
# Managed by hosts - https://git.s1q.dev/phg/hosts
# #
127.0.0.1\tlocalhost
"""
assert new_content == expected
# Cleanup # Cleanup
os.unlink(backup_path) os.unlink(backup_path)