"""File operation utilities for nanocode.""" import glob as globlib import os import re def read_file(path: str, offset: int = 0, limit: int = None) -> str: """Read file contents with line numbers. Args: path: Path to the file to read offset: Line number to start from (0-indexed) limit: Maximum number of lines to read Returns: File contents with line numbers """ lines = open(path).readlines() if limit is None: limit = len(lines) selected = lines[offset : offset + limit] return "".join(f"{offset + idx + 1:4}| {line}" for idx, line in enumerate(selected)) def write_file(path: str, content: str) -> str: """Write content to a file. Args: path: Path to the file to write content: Content to write to the file Returns: 'ok' on success """ with open(path, "w") as f: f.write(content) return "ok" def edit_file(path: str, old: str, new: str, replace_all: bool = False) -> str: """Replace text in a file. Args: path: Path to the file to edit old: Text to find and replace new: Replacement text replace_all: If True, replace all occurrences; otherwise old must be unique Returns: 'ok' on success, error message on failure """ text = open(path).read() if old not in text: return "error: old_string not found" count = text.count(old) if not replace_all and count > 1: return f"error: old_string appears {count} times, must be unique (use replace_all=True)" replacement = text.replace(old, new) if replace_all else text.replace(old, new, 1) with open(path, "w") as f: f.write(replacement) return "ok" def glob_files(pattern: str, path: str = ".") -> str: """Find files matching a glob pattern, sorted by modification time. Args: pattern: Glob pattern to match (e.g., '**/*.py') path: Base directory to search in Returns: Newline-separated list of matching files """ full_pattern = (path + "/" + pattern).replace("//", "/") files = globlib.glob(full_pattern, recursive=True) files = sorted( files, key=lambda f: os.path.getmtime(f) if os.path.isfile(f) else 0, reverse=True, ) return "\n".join(files) or "no files found" def grep_files(pattern: str, path: str = ".") -> str: """Search files for a regex pattern. Args: pattern: Regular expression pattern to search for path: Base directory to search in Returns: Matching lines in format 'filepath:line_num:content' """ regex = re.compile(pattern) hits = [] for filepath in globlib.glob(path + "/**", recursive=True): try: for line_num, line in enumerate(open(filepath), 1): if regex.search(line): hits.append(f"{filepath}:{line_num}:{line.rstrip()}") except Exception: pass return "\n".join(hits[:50]) or "no matches found"