1#![forbid(unsafe_code)]
19#![warn(missing_docs)]
20#![allow(clippy::needless_lifetimes)]
21#![cfg_attr(not(test), no_std)]
22
23use core::fmt::{Debug, Formatter};
24
25pub fn debug_adjacent<T: Debug + IsAdjacent>(items: &[T]) -> DebugAdjacent<T> {
40 DebugAdjacent::new(items)
41}
42
43pub fn debug_adjacent_by<T: Debug, F: Fn(&T, &T) -> bool>(
47 items: &[T],
48 is_adjacent: F,
49) -> DebugAdjacentBy<T, F> {
50 DebugAdjacentBy::new(items, is_adjacent)
51}
52
53macro_rules! int_successor {
54 ($t:ty) => {
55 impl IsAdjacent for $t {
56 fn is_adjacent(&self, other: &Self) -> bool {
57 other.checked_sub(*self) == Some(1)
58 }
59 }
60 };
61}
62int_successor!(u8);
63int_successor!(u16);
64int_successor!(u32);
65int_successor!(u64);
66int_successor!(u128);
67int_successor!(i8);
68int_successor!(i16);
69int_successor!(i32);
70int_successor!(i64);
71int_successor!(i128);
72
73impl IsAdjacent for char {
74 fn is_adjacent(&self, next: &Self) -> bool {
75 if let Some(after_self) = (*self as u32).checked_add(1) {
76 if let Some(after_self) = char::from_u32(after_self) {
77 after_self == *next
78 } else {
79 false
80 }
81 } else {
82 false
83 }
84 }
85}
86
87pub trait IsAdjacent {
95 fn is_adjacent(&self, other: &Self) -> bool;
97}
98
99#[derive(Copy, Clone)]
104pub struct DebugAdjacent<'a, T> {
105 pub items: &'a [T],
107
108 pub sep: &'a str,
110}
111
112impl<'a, T> DebugAdjacent<'a, T> {
113 pub fn new(items: &'a [T]) -> Self {
115 Self { items, sep: "-" }
116 }
117}
118
119impl<'a, T> Debug for DebugAdjacent<'a, T>
120where
121 T: Debug + IsAdjacent,
122{
123 fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
124 let mut need_comma = false;
125
126 let mut iter = self.items.iter().peekable();
127
128 while let Some(first) = iter.next() {
129 if need_comma {
130 f.write_str(", ")?;
131 }
132 need_comma = true;
133
134 let mut this: &T = first;
135 let mut last: Option<&T> = None;
136
137 while let Some(&next) = iter.peek() {
138 if this.is_adjacent(next) {
139 this = next;
140 last = Some(next);
141 _ = iter.next();
142 } else {
143 break;
144 }
145 }
146
147 if let Some(last) = last {
148 <T as Debug>::fmt(first, f)?;
149 f.write_str(self.sep)?;
150 <T as Debug>::fmt(last, f)?;
151 } else {
152 <T as Debug>::fmt(first, f)?;
153 }
154 }
155
156 Ok(())
157 }
158}
159
160#[derive(Copy, Clone)]
165pub struct DebugAdjacentBy<'a, T, F> {
166 pub items: &'a [T],
168 pub sep: &'a str,
170
171 pub is_adjacent: F,
173}
174
175impl<'a, T, F> DebugAdjacentBy<'a, T, F> {
176 pub fn new(items: &'a [T], is_adjacent: F) -> Self
178 where
179 F: Fn(&T, &T) -> bool,
180 {
181 Self {
182 items,
183 is_adjacent,
184 sep: "-",
185 }
186 }
187}
188
189impl<'a, T, F> Debug for DebugAdjacentBy<'a, T, F>
190where
191 T: Debug,
192 F: Fn(&T, &T) -> bool,
193{
194 fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
195 let mut need_comma = false;
196
197 let mut iter = self.items.iter().peekable();
198
199 while let Some(first) = iter.next() {
200 if need_comma {
201 f.write_str(", ")?;
202 }
203 need_comma = true;
204
205 let mut this: &T = first;
206 let mut last: Option<&T> = None;
207
208 while let Some(&next) = iter.peek() {
209 if (self.is_adjacent)(this, next) {
210 this = next;
211 last = Some(next);
212 _ = iter.next();
213 } else {
214 break;
215 }
216 }
217
218 if let Some(last) = last {
219 <T as Debug>::fmt(first, f)?;
220 f.write_str(self.sep)?;
221 <T as Debug>::fmt(last, f)?;
222 } else {
223 <T as Debug>::fmt(first, f)?;
224 }
225 }
226
227 Ok(())
228 }
229}
230
231#[test]
232fn test_dump_ranges() {
233 macro_rules! case {
234 ($input:expr, $expected_output:expr) => {
235 let input: &[_] = &$input;
236 let dump = DebugAdjacent::new(input);
237 let actual_output = format!("{:?}", dump);
238 println!("dump_ranges: {:?} --> {:?}", input, actual_output);
239 assert_eq!(
240 actual_output.as_str(),
241 $expected_output,
242 "input: {:?}",
243 input
244 );
245 };
246 }
247
248 case!([] as [u32; 0], "");
249 case!([10u32], "10");
250 case!([10u32, 20], "10, 20");
251 case!([10u32, 11, 20], "10-11, 20");
252 case!([10u32, 12, 13, 14, 15, 20], "10, 12-15, 20");
253 case!([u32::MAX, 42], "4294967295, 42");
254 case!([i32::MIN, i32::MIN + 1, 42], "-2147483648--2147483647, 42");
255}
256
257#[test]
258fn test_dump_ranges_by() {
259 macro_rules! case {
260 ($input:expr, $expected_output:expr) => {
261 let input: &[_] = &$input;
262 let dump = DebugAdjacentBy::new(input, |&a, &b| a + 1 == b);
263 let actual_output = format!("{:?}", dump);
264 println!("dump_ranges: {:?} --> {:?}", input, actual_output);
265 assert_eq!(
266 actual_output.as_str(),
267 $expected_output,
268 "input: {:?}",
269 input
270 );
271 };
272 }
273
274 case!([] as [u32; 0], "");
275 case!([10u32], "10");
276 case!([10u32, 20], "10, 20");
277 case!([10u32, 11, 20], "10-11, 20");
278 case!([10u32, 12, 13, 14, 15, 20], "10, 12-15, 20");
279}
280
281#[test]
282fn test_dump_ranges_by_swapped() {
283 macro_rules! case {
284 ($input:expr, $expected_output:expr) => {
285 let input: &[_] = &$input;
286 let dump = DebugAdjacentBy::new(input, |&a, &b| a + 1 == b);
287 let actual_output = format!("{:?}", dump);
288 println!("dump_ranges: {:?} --> {:?}", input, actual_output);
289 assert_eq!(
290 actual_output.as_str(),
291 $expected_output,
292 "input: {:?}",
293 input
294 );
295 };
296 }
297
298 case!([] as [u32; 0], "");
299 case!([10u32], "10");
300 case!([10u32, 20], "10, 20");
301 case!([10u32, 11, 20], "10-11, 20");
302 case!([10u32, 12, 13, 14, 15, 20], "10, 12-15, 20");
303}