difference_rs/
display.rs

1use crate::ChangesetMulti;
2
3use super::{Changeset, Difference};
4use std::{char::REPLACEMENT_CHARACTER, fmt};
5
6impl fmt::Display for Changeset {
7    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
8        for d in &self.diffs {
9            match *d {
10                Difference::Same(ref x) => {
11                    write!(f, "{}{}", x, self.split)?;
12                }
13                Difference::Add(ref x) => {
14                    write!(f, "\x1b[92m{}\x1b[0m{}", x, self.split)?;
15                }
16                Difference::Rem(ref x) => {
17                    write!(f, "\x1b[91m{}\x1b[0m{}", x, self.split)?;
18                }
19            }
20        }
21        Ok(())
22    }
23}
24
25impl fmt::Display for ChangesetMulti {
26    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27        let mut orig_counter = 0;
28        let mut edit_counter = 0;
29        for d in &self.diffs {
30            match *d {
31                Difference::Same(ref x) => {
32                    let orig = x.as_str().split(REPLACEMENT_CHARACTER).collect::<Vec<_>>();
33                    for word in orig {
34                        orig_counter += word.len();
35                        edit_counter += word.len();
36                        if let Some(split) = self
37                            .splits
38                            .iter()
39                            .find(|(idx, _split)| idx == &orig_counter)
40                        {
41                            orig_counter += split.1.len();
42                            edit_counter += split.1.len();
43                            write!(f, "{}{}", word, split.1)?;
44                        } else {
45                            write!(f, "{word}")?;
46                        }
47                    }
48                }
49                Difference::Add(ref x) => {
50                    let edit = x.as_str().split(REPLACEMENT_CHARACTER).collect::<Vec<_>>();
51                    for word in edit {
52                        edit_counter += word.len();
53                        if let Some(split) = self
54                            .edit_splits
55                            .iter()
56                            .find(|(idx, _split)| idx == &edit_counter)
57                        {
58                            edit_counter += split.1.len();
59                            write!(f, "\x1b[92m{}\x1b[0m{}", word, split.1)?;
60                        } else {
61                            write!(f, "\x1b[92m{word}\x1b[0m")?;
62                        }
63                    }
64                }
65                Difference::Rem(ref x) => {
66                    let orig = x.as_str().split(REPLACEMENT_CHARACTER).collect::<Vec<_>>();
67                    for word in orig {
68                        orig_counter += word.len();
69                        if let Some(split) = self
70                            .splits
71                            .iter()
72                            .find(|(idx, _split)| idx == &orig_counter)
73                        {
74                            orig_counter += split.1.len();
75                            write!(f, "\x1b[91m{}\x1b[0m{}", word, split.1)?;
76                        } else {
77                            write!(f, "\x1b[91m{word}\x1b[0m")?;
78                        }
79                    }
80                }
81            }
82        }
83        Ok(())
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::super::Changeset;
90    use std::io::Write;
91    use std::thread;
92    use std::time;
93
94    /// convert slice to vector for `assert_eq`
95    fn vb(b: &'static [u8]) -> Vec<u8> {
96        b.to_vec()
97    }
98
99    /// if the format changes, you can use this to help create the test for color
100    /// just pass it in and copy-paste (validating that it looks right first of course...)
101    #[allow(dead_code)]
102    fn debug_bytes(result: &[u8], expected: &[u8]) {
103        // sleep for a bit so stderr passes us
104        // Static cast
105        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
106        thread::sleep(time::Duration::new(0, 2e8 as u32));
107        println!("Debug Result:");
108        for b in result {
109            print!("{}", *b as char);
110        }
111        println!("Repr Result:");
112        repr_bytes(result);
113        println!();
114        println!("--Result Repr DONE");
115
116        println!("Debug Expected:");
117        for b in expected {
118            print!("{}", *b as char);
119        }
120        println!("Repr Expected:");
121        repr_bytes(expected);
122        println!();
123        println!("--Expected Repr DONE");
124    }
125
126    /// for helping debugging what the actual bytes are
127    /// for writing user tests
128    fn repr_bytes(bytes: &[u8]) {
129        for b in bytes {
130            match *b {
131                // 9 => print!("{}", *b as char), // TAB
132                b'\n' => print!("\\n"),
133                b'\r' => print!("\\r"),
134                32..=126 => print!("{}", *b as char), // visible ASCII
135                _ => print!(r"\x{b:0>2x}"),
136            }
137        }
138    }
139
140    #[test]
141    fn test_display() {
142        let text1 = "Roses are red, violets are blue,\n\
143                     I wrote this library,\n\
144                     just for you.\n\
145                     (It's true).";
146
147        let text2 = "Roses are red, violets are blue,\n\
148                     I wrote this documentation,\n\
149                     just for you.\n\
150                     (It's quite true).";
151        let expected = b"Roses are red, violets are blue,\n\x1b[91mI wrote this library,\x1b\
152            [0m\n\x1b[92mI wrote this documentation,\x1b[0m\njust for you.\n\x1b\
153            [91m(It's true).\x1b[0m\n\x1b[92m(It's quite true).\x1b[0m\n";
154
155        let ch = Changeset::new(text1, text2, "\n");
156        let mut result: Vec<u8> = Vec::new();
157        write!(result, "{ch}").unwrap();
158        debug_bytes(&result, expected);
159        assert_eq!(result, vb(expected));
160    }
161
162    #[test]
163    fn test_display_multi() {
164        let text1 = "https://localhost:8080/path?query=value";
165        let text2 = "https://myapi.com/api/path?query=asset";
166        let expected = b"https://\x1b[91mlocalhost:8080/\x1b[0m\x1b[92mmyapi.com/api/\x1b[0mpath?query=\x1b[91mvalue\x1b[0m\x1b[92masset\x1b[0m";
167
168        let cg = Changeset::new_multi(text1, text2, &["://", "/", "?", "="]);
169        let mut result: Vec<u8> = Vec::new();
170        write!(result, "{cg}").unwrap();
171        debug_bytes(&result, expected);
172        assert_eq!(result, vb(expected));
173    }
174}