1#[macro_export]
18macro_rules! assert_text_eq {
19 ($left: expr, $right: expr) => {
20 if $left != $right {
21 let orig = $right;
22 let edit = &$left[0..];
23 $crate::print_diff_github_style(orig, edit);
24 panic!("assertion failed")
25 };
26 };
27}
28
29#[macro_export]
30macro_rules! assert_text_starts_with {
31 ($left: expr, $right: expr) => {
32 if !$left.starts_with($right) {
33 let ll = $left.len();
34 let rl = $right.len();
35 let orig = $right;
36 let edit = &$left[0..ll.min(rl)];
37 $crate::print_diff_github_style(orig, edit);
38 panic!("assertion failed")
39 };
40 };
41}
42
43#[macro_export]
44macro_rules! assert_text_ends_with {
45 ($left: expr, $right: expr) => {
46 if !$left.ends_with($right) {
47 let ll = $left.len();
48 let rl = $right.len();
49 let orig = $right;
50 let edit = &$left[if ll > rl { ll - rl } else { 0 }..];
51 $crate::print_diff_github_style(orig, edit);
52 panic!("assertion failed")
53 };
54 };
55}
56
57#[macro_export]
58macro_rules! assert_text_match {
59 ($left: expr, $right: expr) => {
60 let re = regex::Regex::new($right).unwrap();
61 if !re.is_match($left) {
62 assert_eq!($left, concat!("not match of regex: \"", $right, "\""));
63 };
64 };
65}
66
67use difference::{Changeset, Difference};
68use std::string::ToString;
69
70pub fn print_diff_github_style(text1: &str, text2: &str) {
71 let color_green = "\x1b[32m";
73 let color_red = "\x1b[31m";
74 let color_bright_green = "\x1b[1;32m";
75 let color_reverse_red = "\x1b[31;7m";
76 let color_reverse_green = "\x1b[32;7m";
77 let color_end = "\x1b[0m";
78 let mut out_s = String::new();
80 let Changeset { diffs, .. } = Changeset::new(text1, text2, "\n");
82 for i in 0..diffs.len() {
84 let s = match diffs[i] {
85 Difference::Same(ref y) => format_diff_line_same(y),
86 Difference::Add(ref y) => {
87 let opt = if i > 0 {
88 if let Difference::Rem(ref x) = diffs[i - 1] {
89 Some(format_diff_add_rem(
90 "+",
91 x,
92 y,
93 color_green,
94 color_reverse_green,
95 color_end,
96 ))
97 } else {
98 None
99 }
100 } else {
101 None
102 };
103 match opt {
104 Some(a) => a,
105 None => format_diff_line_mark("+", y, color_bright_green, color_end),
106 }
107 }
108 Difference::Rem(ref y) => {
109 let opt = if i < diffs.len() - 1 {
110 if let Difference::Add(ref x) = diffs[i + 1] {
111 Some(format_diff_add_rem(
112 "-",
113 x,
114 y,
115 color_red,
116 color_reverse_red,
117 color_end,
118 ))
119 } else {
120 None
121 }
122 } else {
123 None
124 };
125 match opt {
126 Some(a) => a,
127 None => format_diff_line_mark("-", y, color_red, color_end),
128 }
129 }
130 };
131 out_s.push_str(s.as_str());
132 }
133 print!("{}", out_s.as_str());
135}
136
137#[inline(never)]
138fn format_diff_line_same(y: &str) -> String {
139 let mut s = String::with_capacity(y.len() + 2);
140 for line in y.split_terminator('\n') {
141 s.reserve(line.len() + 2);
142 s.push(' ');
143 s.push_str(line);
144 s.push('\n');
145 }
146 s
147}
148
149#[inline(never)]
150fn format_diff_line_mark(
151 mark: &str, y: &str,
153 color_start: &str,
154 color_end: &str,
155) -> String {
156 let mut s = String::with_capacity(y.len() + 2);
157 for line in y.split_terminator('\n') {
158 s.reserve(line.len() + 2);
159 s.push_str(color_start);
160 s.push_str(mark);
161 s.push_str(line);
162 s.push_str(color_end);
163 s.push('\n');
164 }
165 s
166}
167
168#[inline(never)]
169fn format_diff_add_rem(
170 mark: &str, x: &str,
172 y: &str,
173 color_fore: &str,
174 color_reverse: &str,
175 color_end: &str,
176) -> String {
177 #[derive(PartialEq, Copy, Clone)]
179 enum Cattr {
180 None,
181 Fore,
182 Reve,
183 }
184 let mut ca_v: Vec<(Cattr, String)> = vec![(Cattr::Fore, mark.to_string())];
186 let Changeset { diffs, .. } = Changeset::new(x, y, " ");
188 for c in diffs {
189 match c {
190 Difference::Same(ref z) => {
191 for line in z.split_terminator('\n') {
192 ca_v.push((Cattr::Fore, line.to_string()));
193 ca_v.push((Cattr::None, "\n".to_string()));
194 ca_v.push((Cattr::Fore, mark.to_string()));
195 }
196 let bytes = z.as_bytes();
197 let len = bytes.len();
198 if len >= 1 && bytes[len - 1] != b'\n' {
199 ca_v.pop();
200 ca_v.pop();
201 }
202 ca_v.push((Cattr::Fore, " ".to_string()));
203 }
204 Difference::Add(ref z) => {
205 for line in z.split_terminator('\n') {
206 ca_v.push((Cattr::Reve, line.to_string()));
207 ca_v.push((Cattr::None, "\n".to_string()));
208 ca_v.push((Cattr::Fore, mark.to_string()));
209 }
210 let bytes = z.as_bytes();
211 let len = bytes.len();
212 if len >= 1 && bytes[len - 1] != b'\n' {
213 ca_v.pop();
214 ca_v.pop();
215 }
216 ca_v.push((Cattr::Fore, " ".to_string()));
217 }
218 _ => {}
219 };
220 }
221 let mut out_s = String::with_capacity(x.len().max(y.len()) * 2);
223 let mut prev_a: Cattr = Cattr::None;
224 for (cat, st) in &ca_v {
225 if prev_a != *cat {
227 if prev_a != Cattr::None {
228 out_s.push_str(color_end)
229 }
230 if *cat == Cattr::Fore {
231 out_s.push_str(color_fore);
232 } else if *cat == Cattr::Reve {
233 out_s.push_str(color_reverse);
234 }
235 prev_a = *cat;
236 }
237 out_s.push_str(st.as_str());
238 }
239 if prev_a != Cattr::None {
240 out_s.push_str(color_end);
241 }
242 out_s.push('\n');
243 out_s
245}