From f4bb6153855102febea2fa8408f6447e26e0d722 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 13 Feb 2026 15:47:43 +0300 Subject: [PATCH] Add edit command with checksum verification - New command: c_util edit [checksum2...] - Verifies all checksums before editing - Auto-learns indentation style from surrounding code - Applies correct indentation to new code block - Preserves preprocessor directives (no indentation) - Supports both tabs and spaces for indentation - Format: "line_num checksum: line_content" for show/description --- c_util | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 199 insertions(+), 1 deletion(-) diff --git a/c_util b/c_util index 955e9d8..a9d0e81 100755 --- a/c_util +++ b/c_util @@ -5,6 +5,7 @@ Commands: toc - Show table of contents for all files description - Show selected items with comments show [name2] - Show code for functions/structs/enums + edit - 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 [name2] ... | show [name2] ...") + print("Usage: c_util toc | description [name2] ... | show [name2] ... | edit ...") 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 [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)