//! The first two tools, `read_file` and `write_file`. `shell` and `http_fetch` come later; until //! then `run` never reaches them, so they stay unknown tools. use std::fs; use std::io::Read; use std::path::Path; use bxcore::tools::{ReadFileArgs, WriteFileArgs}; use crate::Outcome; pub const MAX_READ: usize = 1024 * 1024; fn tool_err(tool: &str, path: &str, why: &str) -> Outcome { Outcome::tool_error(format!("{tool}: {path}: {why}")) } pub fn read_file(args: &ReadFileArgs) -> Outcome { let path = args.path.as_str(); let is_dir = match fs::metadata(path) { Err(e) if e.kind() == std::io::ErrorKind::NotFound => { return tool_err("read_file", path, "no such file"); } Err(e) => return tool_err("read_file", path, &e.to_string()), Ok(meta) => meta.is_dir(), }; if is_dir { return tool_err("read_file", path, "is a directory"); } let file = match fs::File::open(path) { Err(e) => return tool_err("read_file", path, &e.to_string()), Ok(f) => f, }; let mut buf = Vec::new(); let n = match file .take(u64::try_from(MAX_READ).map_or(u64::MAX, |n| n.saturating_add(1))) .read_to_end(&mut buf) { Err(e) => return tool_err("read_file", path, &e.to_string()), Ok(n) => n, }; if n > MAX_READ { return tool_err("read_file", path, &format!("larger than {MAX_READ} bytes")); } match String::from_utf8(buf) { Ok(text) => Outcome::done(text), Err(_) => tool_err("read_file", path, "not UTF-8 text"), } } pub fn write_file(args: &WriteFileArgs) -> Outcome { let path = args.path.as_str(); let content = args.content.as_str(); match Path::new(path).parent() { Some(p) if p.is_dir() => {} _ => return tool_err("write_file", path, "the directory does not exist"), } if Path::new(path).is_dir() { return tool_err("write_file", path, "is a directory"); } if let Err(e) = fs::write(path, content) { return tool_err("write_file", path, &e.to_string()); } Outcome::done(format!("wrote {} bytes to {path}", content.len())) }