Crate textum

Crate textum 

Source
Expand description

A syntactic patching library with character-level granularity.

textum provides a robust way to apply patches to source files using rope data structures for efficient editing and tree-sitter for syntactic awareness. Unlike traditional line-based patch formats, textum operates at character granularity and can compose multiple patches with automatic offset tracking.

§Examples

use textum::{Patch, PatchSet};
use ropey::Rope;

let mut rope = Rope::from_str("hello world");
let patch = Patch {
    file: "test.txt".to_string(),
    range: (6, 11),
    replacement: Some("rust".into()),
    #[cfg(feature = "symbol_path")]
    symbol_path: None,
    #[cfg(feature = "line_tol")]
    max_line_drift: None,
};

patch.apply(&mut rope).unwrap();
assert_eq!(rope.to_string(), "hello rust");

§Patch feature gating

The Patch struct has three required fields: file, range, and replacement, i.e. it is a triple of (from, to, replacement) specifying a patch for a particular file as input.

  • The file may be STDIN.
  • The range may be empty (i.e. of zero length), such as [5, 5], making its replacement effectively an insertion.

§Use the facet feature for a Facet derive macro

A Patch compiled with the facet feature is decorated with the facet::Facet derive macro which means you can deserialise from JSON objects, and because they are given default values, the optional fields symbol_path and max_line_drift are not required.

That is, you can write a patch concisely like this:

#[cfg(feature = "json")]
fn example() -> Result<(), textum::PatchError> {
    use textum::{Patch, PatchSet};

    let dry_run = true; // Change to false to actually write files

    let input = r#"[
      {"file": "tests/fixtures/sample.txt", "range": [0, 5], "replacement": "goodbye"}
    ]"#;

    let patches: Vec<Patch> = facet_json::from_str(&input)?;

    let mut set = PatchSet::new();
    for patch in patches {
        set.add(patch);
    }

    match set.apply_to_files() {
        Ok(results) => {
            for (file, content) in results {
                if dry_run {
                    println!("Would write {} to {}", content, file);
                } else {
                    std::fs::write(&file, content)?;
                    println!("Wrote to {}", file);
                }
            }
        }
        Err(e) => return Err(e),
    }

    Ok(())
}

Re-exports§

pub use composer::PatchSet;
pub use patch::Patch;
pub use patch::PatchError;

Modules§

composer
Composition and application of multiple patches.
patch
Core patch types and application logic.