libxdiff/lib.rs
1//! # libxdiff bindings for Rust
2//!
3//! This library contains bindings to the [libxdiff][1] C library. The
4//! underlying library defines "MMFiles" using chains of non-contiguous buffers
5//! to minimize reallocations when appending and mutating.
6//! This wrapper structures the API by defining all [`MMFile`]s to be compact
7//! (backed by a single buffer). The non-compact form is [`MMBlocks`].
8//!
9//! libxdiff tracks iteration over buffers internally, so some operations that
10//! conceptually are read-only end up requiring `&mut` arguments in order to be
11//! safe.
12//!
13//! # Example
14//!
15//! ```rust
16//! use core::str::from_utf8;
17//! use libxdiff::MMFile;
18//!
19//! let mut f1 = MMFile::from_bytes(b"hello world\n");
20//! let mut f2 = MMFile::from_bytes(b"hello world!\n");
21//! let mut diff_lines = Vec::<String>::new();
22//! f1.diff_raw(&mut f2, |line: &[u8]| {
23//! diff_lines.push(from_utf8(line).unwrap().to_owned());
24//! })
25//! .unwrap();
26//!
27//! assert_eq!(
28//! diff_lines,
29//! vec![
30//! "@@ -1,1 +1,1 @@\n",
31//! "-", "hello world\n",
32//! "+", "hello world!\n",
33//! ],
34//! );
35//! ```
36//!
37//! [1]: http://www.xmailserver.org/xdiff-lib.html
38
39#[cfg_attr(not(feature = "std"), no_std)]
40use core::{
41 ffi::{c_long, c_uint, c_ulong, c_void},
42 mem::MaybeUninit,
43 ptr::{addr_of, null_mut},
44 sync::atomic::{AtomicBool, Ordering},
45};
46
47use libc::{free, malloc, realloc, size_t};
48use libxdiff_sys::{memallocator_t, mmfile_t, xdl_init_mmfile, xdl_set_allocator, XDL_MMF_ATOMIC};
49
50mod mmfile;
51pub use mmfile::*;
52
53mod mmblocks;
54pub use mmblocks::*;
55
56#[cfg(test)]
57mod tests;
58
59unsafe extern "C" fn wrap_malloc(_obj: *mut c_void, size: c_uint) -> *mut c_void {
60 malloc(size as size_t)
61}
62
63unsafe extern "C" fn wrap_free(_obj: *mut c_void, ptr: *mut c_void) {
64 free(ptr)
65}
66
67unsafe extern "C" fn wrap_realloc(
68 _obj: *mut c_void,
69 ptr: *mut c_void,
70 size: c_uint,
71) -> *mut c_void {
72 realloc(ptr, size as size_t)
73}
74
75// must call before using any xdl functions and must only call once
76unsafe fn init() {
77 let alloc_struct = memallocator_t {
78 priv_: null_mut(),
79 malloc: Some(wrap_malloc),
80 free: Some(wrap_free),
81 realloc: Some(wrap_realloc),
82 };
83 unsafe { xdl_set_allocator(addr_of!(alloc_struct)) };
84}
85
86static INITIALIZED: AtomicBool = AtomicBool::new(false);
87
88/// Safely ensure libxdiff has been initialized before proceeding. U
89/// This is called automatically when [`MMFile`]s are created. From
90/// that point on, the existence of the object means the library has been
91/// initialized already.
92pub(crate) fn ensure_init() {
93 if let Ok(_) = INITIALIZED.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) {
94 unsafe { init() }
95 }
96}
97
98/// Initialize a new mmfile_t
99pub(crate) fn init_mmfile(len: usize) -> mmfile_t {
100 ensure_init();
101 let mut inner: MaybeUninit<mmfile_t> = MaybeUninit::uninit();
102 let inner_ptr = inner.as_mut_ptr();
103 let err = unsafe { xdl_init_mmfile(inner_ptr, len as c_long, XDL_MMF_ATOMIC as c_ulong) };
104 if err != 0 {
105 panic!("mmfile initialization failed");
106 }
107 unsafe { inner.assume_init() }
108}