atomwrite 0.1.35

Atomic file operations CLI for LLM agents — read, write, edit, search, replace with NDJSON output
Documentation
fn validate_line_num(n: usize, total: usize) -> Result<usize> {
    if n == 0 || n > total {
        return Err(AtomwriteError::InvalidInput {
            reason: format!("line {n} out of range (file has {total} lines)"),
        }
        .into());
    }
    Ok(n - 1)
}

fn parse_range(s: &str, total: usize) -> Result<(usize, usize)> {
    let parts: Vec<&str> = s.split(':').collect();
    if parts.len() != 2 {
        return Err(crate::error::AtomwriteError::InvalidInput {
            reason: format!("invalid range format: expected N:M, got {s}"),
        }
        .into());
    }
    let start = parts[0]
        .parse::<usize>()
        .context("invalid range start")?
        .saturating_sub(1);
    let end = parts[1]
        .parse::<usize>()
        .context("invalid range end")?
        .min(total);

    if start >= end {
        return Err(crate::error::AtomwriteError::InvalidInput {
            reason: format!("invalid range: start ({}) >= end ({})", start + 1, end),
        }
        .into());
    }

    Ok((start, end))
}

fn find_line_with(lines: &[&str], marker: &str) -> Result<usize> {
    for (i, line) in lines.iter().enumerate() {
        if line.contains(marker) {
            return Ok(i);
        }
    }
    Err(AtomwriteError::InvalidInput {
        reason: format!("marker not found: {marker:?}"),
    }
    .into())
}

fn find_line_with_after(lines: &[&str], marker: &str, after: usize) -> Result<usize> {
    for (i, line) in lines.iter().enumerate().skip(after) {
        if line.contains(marker) {
            return Ok(i);
        }
    }
    Err(AtomwriteError::InvalidInput {
        reason: format!("end marker not found after line {after}: {marker:?}"),
    }
    .into())
}

fn lines_from_str(s: &str) -> Vec<String> {
    s.lines().map(String::from).collect()
}

fn lines_to_owned(lines: &[&str]) -> Vec<String> {
    let mut v = Vec::with_capacity(lines.len());
    v.extend(lines.iter().map(|s| String::from(*s)));
    v
}

fn join_lines(lines: &[String]) -> String {
    let mut result = lines.join("\n");
    if !result.ends_with('\n') {
        result.push('\n');
    }
    result
}