Add management header to hosts files and enhance serialization formatting; update tests to reflect changes.
This commit is contained in:
parent
5a2e0d2623
commit
0ee720c5ef
3 changed files with 163 additions and 6 deletions
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue