"""Helpful tools for running cli commands and reading, modifying, and creating files in python. This is used primarily for AI's in tool loops for automating tasks involving the filesystem."""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/12_tools.ipynb.

# %% auto 0
__all__ = ['run_cmd', 'rg', 'sed', 'view', 'create', 'insert', 'str_replace', 'strs_replace', 'replace_lines']

# %% ../nbs/12_tools.ipynb
from .utils import *
from shlex import split
from subprocess import run, DEVNULL

# %% ../nbs/12_tools.ipynb
def run_cmd(
    cmd:str, # The command name to run
    argstr:str='', # All args to the command, will be split with shlex
    disallow_re:str=None, # optional regex which, if matched on argstr, will disallow the command
    allow_re:str=None # optional regex which, if not matched on argstr, will disallow the command
):
    "Run `cmd` passing split `argstr`, optionally checking for allowed argstr"
    if disallow_re and re.search(disallow_re, argstr): return 'Error: args disallowed'
    if allow_re    and re.search(   allow_re, argstr): return 'Error: args not allowed'
    try: outp = run([cmd] + split(argstr), text=True, stdin=DEVNULL, capture_output=True)
    except Exception as e: return f'Error running cmd: {str(e)}'
    res = outp.stdout
    if res and outp.stderr: res += '\n'
    return res + outp.stderr

# %% ../nbs/12_tools.ipynb
def rg(
    argstr:str, # All args to the command, will be split with shlex
    disallow_re:str=None, # optional regex which, if matched on argstr, will disallow the command
    allow_re:str=None # optional regex which, if not matched on argstr, will disallow the command
):
    "Run the `rg` command with the args in `argstr` (no need to backslash escape)"
    return run_cmd('rg', '-n '+argstr, disallow_re=disallow_re, allow_re=allow_re)

# %% ../nbs/12_tools.ipynb
def sed(
    argstr:str, # All args to the command, will be split with shlex
    disallow_re:str=None, # optional regex which, if matched on argstr, will disallow the command
    allow_re:str=None # optional regex which, if not matched on argstr, will disallow the command
):
    "Run the `sed` command with the args in `argstr` (e.g for reading a section of a file)"
    return run_cmd('sed', argstr, allow_re=allow_re, disallow_re=disallow_re)

# %% ../nbs/12_tools.ipynb
def view(
    path:str, # Path to directory or file to view
    view_range:tuple[int,int]=None, # Optional 1-indexed (start, end) line range for files, end=-1 for EOF
    nums:bool=False # Whether to show line numbers
):
    'View directory or file contents with optional line range and numbers'
    try:
        p = Path(path).expanduser().resolve()
        if not p.exists(): return f'Error: File not found: {p}'
        header = None
        if p.is_dir():
            files = [str(f) for f in p.glob('**/*') 
                    if not any(part.startswith('.') for part in f.relative_to(p).parts)]
            lines = files
            header = f'Directory contents of {p}:'
        else: lines = p.read_text().splitlines()
        s, e = 1, len(lines)
        if view_range:
            s,e = view_range
            if not (1<=s<=len(lines)): return f'Error: Invalid start line {s}'
            if e!=-1 and not (s<=e<= len(lines)): return f'Error: Invalid end line {e}'
            lines = lines[s-1:None if e==-1 else e]
        if nums: lines = [f'{i+s:6d} │ {l}' for i, l in enumerate(lines)]
        content = '\n'.join(lines)
        return f'{header}\n{content}' if header else content
    except Exception as e: return f'Error viewing: {str(e)}'

# %% ../nbs/12_tools.ipynb
def create(
    path: str, # Path where the new file should be created
    file_text: str, # Content to write to the file
    overwrite:bool=False # Whether to overwrite existing files
) -> str:
    'Creates a new file with the given content at the specified path'
    try:
        p = Path(path)
        if p.exists():
            if not overwrite: return f'Error: File already exists: {p}'
        p.parent.mkdir(parents=True, exist_ok=True)
        p.write_text(file_text)
        return f'Created file {p}.'
    except Exception as e: return f'Error creating file: {str(e)}'

# %% ../nbs/12_tools.ipynb
def insert(
    path: str, # Path to the file to modify
    insert_line: int, # Line number where to insert (0-based indexing)
    new_str: str # Text to insert at the specified line
) -> str:
    'Insert new_str at specified line number'
    try:
        p = Path(path)
        if not p.exists(): return f'Error: File not found: {p}'
        content = p.read_text().splitlines()
        if not (0 <= insert_line <= len(content)): return f'Error: Invalid line number {insert_line}'
        content.insert(insert_line, new_str)
        new_content = '\n'.join(content)
        p.write_text(new_content)
        return f'Inserted text at line {insert_line} in {p}'
    except Exception as e: return f'Error inserting text: {str(e)}'

# %% ../nbs/12_tools.ipynb
def str_replace(
    path: str, # Path to the file to modify
    old_str: str, # Text to find and replace
    new_str: str # Text to replace with
) -> str:
    'Replace first occurrence of old_str with new_str in file'
    try:
        p = Path(path)
        if not p.exists(): return f'Error: File not found: {p}'
        content = p.read_text()
        count = content.count(old_str)
        if count == 0: return 'Error: Text not found in file'
        if count > 1: return f'Error: Multiple matches found ({count})'
        new_content = content.replace(old_str, new_str, 1)
        p.write_text(new_content)
        return f'Replaced text in {p}'
    except Exception as e: return f'Error replacing text: {str(e)}'

# %% ../nbs/12_tools.ipynb
def strs_replace(
    path:str, # Path to the file to modify
    old_strs:list[str], # List of strings to find and replace
    new_strs:list[str], # List of replacement strings (must match length of old_strs)
):
    "Replace for each str pair in old_strs,new_strs"
    res = [str_replace(path, old, new) for (old,new) in zip(old_strs,new_strs)]
    return 'Results for each replacement:\n' + '; '.join(res)

# %% ../nbs/12_tools.ipynb
def replace_lines(
    path:str, # Path to the file to modify
    start_line:int, # Starting line number to replace (1-based indexing)
    end_line:int, # Ending line number to replace (1-based indexing, inclusive)
    new_content:str, # New content to replace the specified lines
):
    "Replace lines in file using start and end line-numbers (index starting at 1)"
    if not (p := Path(path)).exists(): return f"Error: File not found: {p}"
    content = p.readlines()
    if not new_content.endswith('\n'): new_content+='\n'
    content[start_line-1:end_line] = [new_content]
    p.write_text(''.join(content))
    return f"Replaced lines {start_line} to {end_line}."
