| Crates.io | udiffx |
| lib.rs | udiffx |
| version | 0.1.10 |
| created_at | 2026-01-02 05:04:39.124405+00 |
| updated_at | 2026-01-19 18:43:36.575486+00 |
| description | Parse and apply LLM-optimized unified diff + XML file changes |
| homepage | https://github.com/jeremychone/rust-udiffx |
| repository | https://github.com/jeremychone/rust-udiffx |
| max_upload_size | |
| id | 2017994 |
| size | 121,737 |
Parse and apply an AI-optimized “file changes” envelope that carries multiple file operations in a single block, using unified diff patches for updates.
This crate is designed for LLM output that needs to be machine-parsable and efficient for large files with small edits.
FILE_CHANGESA response contains one root container:
<FILE_CHANGES> ... </FILE_CHANGES>Inside it, you can mix multiple directives:
<FILE_NEW file_path="..."> ... </FILE_NEW><FILE_PATCH file_path="..."> ... </FILE_PATCH> (unified diff content)<FILE_RENAME from_path="..." to_path="..." /><FILE_DELETE file_path="..." />Notes:
<FILE_DELETE ... /> are supported.The crate exposes two main operations:
<FILE_CHANGES> block from a string.Key public types:
FileChanges, an iterable list of directives.FileDirective, one directive (new, patch, rename, delete, fail).ApplyChangesStatus, per-directive success and error reporting.Error / Result<T>, the crate error type and alias.Use load_files_context to gather files matching globs and format them for an LLM context.
use udiffx::{load_files_context, Result};
fn main() -> Result<()> {
let base_dir = "./my-project";
let globs = &["src/**/*.rs", "Cargo.toml"];
if let Some(context) = load_files_context(base_dir, globs)? {
println!("{context}");
}
Ok(())
}
Output format:
<FILE_CONTENT path="Cargo.toml">
... content ...
</FILE_CONTENT>
<FILE_CONTENT path="src/main.rs">
... content ...
</FILE_CONTENT>
Use extract_file_changes to parse a model response or any input string.
use udiffx::{extract_file_changes, Result};
fn main() -> Result<()> {
let input = r#"
Some text...
<FILE_CHANGES>
<FILE_NEW file_path="src/hello.rs">
pub fn hello() { println!("Hello"); }
</FILE_NEW>
<FILE_DELETE file_path="old.txt" />
</FILE_CHANGES>
"#;
let (changes, _extruded) = extract_file_changes(input, false)?;
if changes.is_empty() {
println!("No changes found");
return Ok(());
}
for d in &changes {
println!("{d:?}");
}
Ok(())
}
extract_content parameter:
extract_content = false parses tags and returns extruded = None.extract_content = true also returns the input with the extracted <FILE_CHANGES> block removed as Some(String).Use apply_file_changes to execute directives relative to a base directory.
base_dir.base_dir.diffy (unified diff parsing and application).use simple_fs::SPath;
use udiffx::{apply_file_changes, extract_file_changes, Result};
fn main() -> Result<()> {
let base_dir = SPath::new("./my-project");
let input = r#"
<FILE_CHANGES>
<FILE_PATCH file_path="src/main.rs">
@@ -1,3 +1,3 @@
-fn main() { println!("Hello"); }
+fn main() { println!("Hello, world"); }
</FILE_PATCH>
</FILE_CHANGES>
"#;
let (changes, _) = extract_file_changes(input, false)?;
let status = apply_file_changes(&base_dir, changes)?;
for d in status.items {
if d.success() {
println!("OK {} {}", d.kind(), d.file_path());
} else {
println!(
"FAIL {} {}: {}",
d.kind(),
d.file_path(),
d.error_msg().unwrap_or("unknown error")
);
}
}
Ok(())
}
FILE_NEW: creates or overwrites a file. Parent directories are created.FILE_PATCH: reads the target file, applies a unified diff, and writes the result back.FILE_RENAME: renames or moves from_path to to_path.FILE_DELETE: removes a file or directory recursively.If extraction fails for a directive (unknown tag, missing attribute, etc.), the directive is represented as:
FileDirective::Fail { kind, file_path, error_msg }When applying, Fail directives always yield an error for that directive and are reported via ApplyChangesInfo.
<FILE_CHANGES> block when you intend to apply changes.FILE_PATCH for small edits to large files.<FILE_RENAME from_path="a" to_path="b" /><FILE_DELETE file_path="path" />The crate includes the recommended system instructions for LLMs to ensure they output the correct format. This is available via the prompt feature.
[dependencies]
udiffx = { version = "0.1", features = ["prompt"] }
use udiffx::prompt;
let instructions = prompt();
// Pass this to your LLM system message.
MIT OR Apache-2.0