Skip to main content

java_diff_utils_rs/
diff_utils.rs

1//! High-level convenience functions for computing diffs, applying patches, and unpatching lists or text.
2
3use std::sync::RwLock;
4
5use crate::algorithm::myers::myers::MyersDiff;
6use crate::algorithm::{DiffAlgorithm, DiffAlgorithmFactory, DiffAlgorithmListener};
7use crate::patch::patch_failed_exception::PatchFailedException;
8use crate::patch::Patch;
9
10static DEFAULT_DIFF_FACTORY: RwLock<Option<Box<dyn DiffAlgorithmFactory<String> + Send + Sync>>> =
11    RwLock::new(None);
12
13pub struct DiffUtils;
14
15impl DiffUtils {
16    pub fn with_default_diff_algorithm_factory(
17        factory: Box<dyn DiffAlgorithmFactory<String> + Send + Sync>,
18    ) {
19        if let Ok(mut guard) = DEFAULT_DIFF_FACTORY.write() {
20            *guard = Some(factory);
21        }
22    }
23
24    pub fn diff<T>(
25        original: &[T],
26        revised: &[T],
27        progress: Option<&dyn DiffAlgorithmListener>,
28    ) -> Patch<T>
29    where
30        T: PartialEq + Clone + 'static,
31    {
32        let algo = Self::get_default_algorithm::<T>();
33        Self::diff_with_algorithm(original, revised, algo.as_ref(), progress, false)
34    }
35
36    pub fn diff_with_options<T>(
37        original: &[T],
38        revised: &[T],
39        include_equal_parts: bool,
40    ) -> Patch<T>
41    where
42        T: PartialEq + Clone + 'static,
43    {
44        let algo = Self::get_default_algorithm::<T>();
45        Self::diff_with_algorithm(original, revised, algo.as_ref(), None, include_equal_parts)
46    }
47
48    pub fn diff_text(
49        source_text: &str,
50        target_text: &str,
51        progress: Option<&dyn DiffAlgorithmListener>,
52    ) -> Patch<String> {
53        let original: Vec<String> = source_text.lines().map(|s| s.to_string()).collect();
54        let revised: Vec<String> = target_text.lines().map(|s| s.to_string()).collect();
55        Self::diff(&original, &revised, progress)
56    }
57
58    pub fn diff_with_equalizer<T, F>(source: &[T], target: &[T], equalizer: Option<F>) -> Patch<T>
59    where
60        T: PartialEq + Clone + 'static,
61        F: Fn(&T, &T) -> bool + Send + Sync + 'static,
62    {
63        if let Some(eq) = equalizer {
64            let algo = MyersDiff::with_equalizer(eq);
65            Self::diff_with_algorithm(source, target, &algo, None, false)
66        } else {
67            let algo = MyersDiff::default();
68            Self::diff_with_algorithm(source, target, &algo, None, false)
69        }
70    }
71
72    pub fn diff_with_algorithm<T>(
73        original: &[T],
74        revised: &[T],
75        algorithm: &dyn DiffAlgorithm<T>,
76        _progress: Option<&dyn DiffAlgorithmListener>,
77        include_equal_parts: bool,
78    ) -> Patch<T>
79    where
80        T: Clone + 'static,
81    {
82        let deltas = algorithm.diff(original, revised);
83        Patch::generate(original, revised, &deltas, include_equal_parts)
84    }
85
86    pub fn diff_inline(original: &str, revised: &str) -> Patch<String> {
87        let orig_list: Vec<String> = original.chars().map(|c| c.to_string()).collect();
88        let rev_list: Vec<String> = revised.chars().map(|c| c.to_string()).collect();
89
90        let mut patch = Self::diff(&orig_list, &rev_list, None);
91
92        for delta in patch.deltas_mut() {
93            let source_lines = Self::compress_lines(delta.source_mut().lines(), "");
94            delta.source_mut().set_lines(source_lines);
95
96            let target_lines = Self::compress_lines(delta.target_mut().lines(), "");
97            delta.target_mut().set_lines(target_lines);
98        }
99
100        patch
101    }
102
103    pub fn patch<T>(original: &[T], patch: &Patch<T>) -> Result<Vec<T>, PatchFailedException>
104    where
105        T: PartialEq + Clone,
106    {
107        patch.apply_to(original).map_err(PatchFailedException::from)
108    }
109
110    pub fn unpatch<T>(revised: &[T], patch: &Patch<T>) -> Result<Vec<T>, PatchFailedException>
111    where
112        T: PartialEq + Clone,
113    {
114        patch.restore(revised).map_err(PatchFailedException::from)
115    }
116
117    fn compress_lines(lines: &[String], delimiter: &str) -> Vec<String> {
118        if lines.is_empty() {
119            Vec::new()
120        } else {
121            vec![lines.join(delimiter)]
122        }
123    }
124
125    fn get_default_algorithm<T: PartialEq + Clone + 'static>() -> Box<dyn DiffAlgorithm<T>> {
126        Box::new(MyersDiff::default())
127    }
128}