Files
zesdex/src/tool/fs/write.rs
T

85 lines
3.2 KiB
Rust
Raw Normal View History

//! Tool: `write` — write content to a file, creating parent directories on demand.
use std::fs;
use serde_json::{json, Value};
use anyhow::{Result, anyhow};
use super::super::Tool;
use super::super::ToolCtx;
use super::super::resolve_path;
use super::super::check_graduated_checks;
use super::helpers::arg_str;
/// Tool: write content to a file, auto-creating parent directories as needed.
pub struct Write;
impl Tool for Write {
fn name(&self) -> &'static str {
"write"
}
fn description(&self) -> &'static str {
"Write content to a file, creating parent directories as needed"
}
fn parameters(&self) -> Value {
json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path to the file to write (relative to workspace root)"
},
"content": {
"type": "string",
"description": "Content to write to the file"
},
"reason": {
"type": "string",
"description": "Reason for the change (must be non-empty)"
}
},
"required": ["path", "content", "reason"]
})
}
/// Write content to a file, creating parent directories as needed.
///
/// Flow: validate args (non-empty reason) → resolve path → create parent
/// dirs → write file → report byte count (+ optional graduated checks).
///
/// Why: requires a non-empty `reason` to discourage stray writes; parent
/// directories are created silently so the tool works for new paths.
fn run(&self, ctx: &ToolCtx, args: &Value) -> Result<String> {
let rel = arg_str(args, "path")?;
let content = arg_str(args, "content")?;
let reason = arg_str(args, "reason")?;
if reason.trim().is_empty() {
anyhow::bail!("reason must be a non-empty string");
}
let check_matches = check_graduated_checks(&rel, &content, &ctx.graduated_checks);
let path = resolve_path(&ctx.workspaces, &rel)?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.map_err(|e| anyhow!("failed to create parent directories for '{}': {}", rel, e))?;
}
fs::write(&path, &content)
.map_err(|e| anyhow!("failed to write '{}': {}", rel, e))?;
// Notify the LSP server of the on-disk change so diagnostics stay in
// sync. Never fails the write itself: a lock failure or LSP error is
// folded into the returned message instead of propagated as an Err.
let lsp_note = if let Ok(mut lsp) = ctx.lsp_manager.lock() {
match lsp.did_change_file(&path) {
Ok(()) => String::new(),
Err(e) => format!(" (LSP: {})", e),
}
} else {
String::new()
};
if check_matches.is_empty() {
Ok(format!("wrote {} bytes to {}{}", content.len(), rel, lsp_note))
} else {
Ok(format!("wrote {} bytes to {}{}. Graduated checks matched: {}", content.len(), rel, lsp_note, check_matches.join(", ")))
}
}
}