@ -5,6 +5,7 @@ Commands:
toc - Show table of contents for all files
description <names...> - Show selected items with comments
show <name1> [name2] - Show code for functions/structs/enums
edit <file> <line> <checksums> - Edit file with checksum verification
"""
import sys
@ -537,6 +538,70 @@ def line_checksum(line):
return f"{checksum:02X}"
def autolearn_indentation(lines, context_start=0, context_end=None):
"""
Analyze indentation style in the file.
Returns dict with:
- indent_char: '\t' or ' '
- indent_size: number of spaces per level (for spaces) or 1 (for tabs)
- base_indent: base indentation string for the context
"""
if context_end is None:
context_end = len(lines)
# Count tab vs space indented lines
tab_lines = 0
space_lines = 0
space_counts = []
for i in range(context_start, min(context_end, len(lines))):
line = lines[i]
if not line.strip():
continue
leading = line[:len(line) - len(line.lstrip())]
if '\t' in leading:
tab_lines += 1
elif ' ' in leading:
space_lines += 1
# Count leading spaces
space_count = len(leading)
if space_count > 0:
space_counts.append(space_count)
# Determine indent character
if tab_lines > space_lines:
indent_char = '\t'
indent_size = 1
else:
indent_char = ' '
# Calculate most common indent size (GCD of space counts)
if space_counts:
def gcd(a, b):
while b:
a, b = b, a % b
return a
indent_size = space_counts[0]
for count in space_counts[1:]:
indent_size = gcd(indent_size, count)
if indent_size == 1:
break
# Default to 4 if we couldn't determine
if indent_size < 2:
indent_size = 4
else:
indent_size = 4
return {
'indent_char': indent_char,
'indent_size': indent_size,
'indent_str': indent_char * indent_size if indent_char == ' ' else '\t'
}
def normalize_whitespace(text):
"""Normalize whitespace: replace multiple spaces/newlines with single space."""
# Replace all whitespace (spaces, tabs, newlines) with single space
@ -663,7 +728,7 @@ def get_function_comments(pre_lines):
def main():
if len(sys.argv) < 2:
print("Usage: c_util toc | description <name1> [name2] ... | show <name1> [name2] ...")
print("Usage: c_util toc | description <name1> [name2] ... | show <name1> [name2] ... | edit <file> <line> <checksums> ... ")
sys.exit(1)
cmd = sys.argv[1]
@ -873,6 +938,139 @@ def main():
print("Not found")
sys.exit(1)
elif cmd == 'edit':
# Format: c_util edit path/file.c start_line checksum1 [checksum2 ...] <<'EOF'
if len(sys.argv) < 5:
print("Usage: c_util edit <file> <start_line> <checksum1> [checksum2 ...]")
print("Then provide new code block via stdin or heredoc")
sys.exit(1)
file_path = sys.argv[2]
# Load the file if not already loaded
if file_path not in contents:
try:
with open(file_path, 'r') as f:
contents[file_path] = f.read().splitlines()
except Exception as e:
print(f"Error reading file {file_path}: {e}", file=sys.stderr)
sys.exit(1)
file_lines = contents[file_path]
try:
start_line = int(sys.argv[3])
except ValueError:
print(f"Invalid line number: {sys.argv[3]}")
sys.exit(1)
# Get expected checksums
expected_checksums = []
for checksum_str in sys.argv[4:]:
try:
expected_checksums.append(int(checksum_str, 16))
except ValueError:
print(f"Invalid checksum format: {checksum_str}")
sys.exit(1)
num_lines_to_replace = len(expected_checksums)
# Verify line numbers are valid
if start_line < 1 or start_line > len(file_lines):
print(f"Line number {start_line} out of range (1-{len(file_lines)})")
sys.exit(1)
if start_line + num_lines_to_replace - 1 > len(file_lines):
print(f"Block extends beyond end of file")
sys.exit(1)
# Verify checksums
actual_checksums = []
for i in range(num_lines_to_replace):
line_idx = start_line - 1 + i
actual_checksum = sum(ord(c) for c in file_lines[line_idx]) % 256
actual_checksums.append(actual_checksum)
expected = expected_checksums[i]
if actual_checksum != expected:
print(f"Checksum mismatch at line {line_idx + 1}:")
print(f" Expected: {expected:02X}")
print(f" Actual: {actual_checksum:02X}")
print(f" Line: {repr(file_lines[line_idx])}")
sys.exit(1)
# Read new code from stdin
print("Reading new code block from stdin...", file=sys.stderr)
new_code_lines = sys.stdin.read().splitlines()
# Determine indentation style from file
indent_info = autolearn_indentation(file_lines, max(0, start_line - 10), min(len(file_lines), start_line + num_lines_to_replace + 10))
# Calculate base indentation from the first line being replaced
first_old_line = file_lines[start_line - 1]
base_indent = first_old_line[:len(first_old_line) - len(first_old_line.lstrip())]
# Determine indentation level from surrounding code
# Count braces before the replacement block
brace_level = 0
for i in range(max(0, start_line - 20), start_line - 1):
line = file_lines[i]
# Skip preprocessor and comments
stripped = line.strip()
if stripped.startswith('#'):
continue
# Count braces (simple approach)
brace_level += line.count('{') - line.count('}')
# Apply indentation to new code
indented_new_lines = []
current_level = brace_level
for i, line in enumerate(new_code_lines):
stripped = line.strip()
# Empty line - keep as is
if not stripped:
indented_new_lines.append('')
continue
# Preprocessor directive - no indentation
if stripped.startswith('#'):
indented_new_lines.append(stripped)
continue
# Check for closing brace - reduce level before this line
if stripped.startswith('}'):
current_level -= 1
# Calculate indentation
if indent_info['indent_char'] == '\t':
indent = '\t' * current_level
else:
indent = ' ' * (indent_info['indent_size'] * current_level)
indented_new_lines.append(indent + stripped)
# Check for opening brace - increase level for next lines
if stripped.endswith('{'):
current_level += 1
# Replace the block
start_idx = start_line - 1
end_idx = start_idx + num_lines_to_replace
new_file_lines = file_lines[:start_idx] + indented_new_lines + file_lines[end_idx:]
# Write back to file
try:
with open(file_path, 'w') as f:
f.write('\n'.join(new_file_lines))
if new_file_lines and not new_file_lines[-1].endswith('\n'):
f.write('\n')
print(f"Successfully edited {file_path}")
print(f"Replaced {num_lines_to_replace} lines with {len(indented_new_lines)} lines")
except Exception as e:
print(f"Error writing file {file_path}: {e}", file=sys.stderr)
sys.exit(1)
else:
print("Unknown command")
sys.exit(1)