1#[cfg(feature = "assert-no-tracing")]
19use crate::assert::soft_assertions_enabled;
20use std::fmt::{self, Debug, Formatter, Write};
21use std::ops::Deref;
22
23#[cfg(not(feature = "assert-no-tracing"))]
27fn soft_assertions_enabled() -> bool {
28 false
29}
30
31pub trait StrExt {
33 fn quoted(&self) -> QuotedStr<'_>;
60 fn escaped(&self) -> EscapedStr<'_>;
72}
73
74impl StrExt for str {
75 fn quoted(&self) -> QuotedStr<'_> {
76 QuotedStr(self)
77 }
78 fn escaped(&self) -> EscapedStr<'_> {
79 EscapedStr(self)
80 }
81}
82
83#[derive(Debug)]
88pub struct QuotedStr<'a>(&'a str);
89
90impl<'a> fmt::Display for QuotedStr<'a> {
91 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92 f.write_char('"')?;
93 for c in self.chars() {
94 match c {
95 '"' => f.write_str("\\\"")?,
96 _ => f.write_char(c)?,
97 }
98 }
99 f.write_char('"')
100 }
101}
102
103impl<'a> Deref for QuotedStr<'a> {
104 type Target = str;
105
106 fn deref(&self) -> &str {
107 self.0
108 }
109}
110
111#[derive(Debug)]
115pub struct EscapedStr<'a>(&'a str);
116
117impl<'a> fmt::Display for EscapedStr<'a> {
118 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119 f.write_char('"')?;
120 for c in self.chars() {
121 match c {
123 '"' => f.write_str("\\\"")?,
124 '\n' => f.write_str("\\n")?,
125 '\r' => f.write_str("\\r")?,
126 '\t' => f.write_str("\\t")?,
127 _ => f.write_char(c)?,
128 }
129 }
130 f.write_char('"')
131 }
132}
133
134impl<'a> Deref for EscapedStr<'a> {
135 type Target = str;
136
137 fn deref(&self) -> &str {
138 self.0
139 }
140}
141
142pub fn bracketed<'a, D>(open: &'a str, close: &'a str, contents: D) -> impl fmt::Display + 'a
145where
146 D: fmt::Display + 'a,
147{
148 struct Bracketed<'a, D> {
149 open: &'a str,
150 close: &'a str,
151 contents: D,
152 }
153
154 impl<'a, D> fmt::Display for Bracketed<'a, D>
155 where
156 D: fmt::Display,
157 {
158 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
159 write!(f, "{}{}{}", self.open, self.contents, self.close)
160 }
161 }
162
163 Bracketed {
164 open,
165 close,
166 contents,
167 }
168}
169
170pub fn closure_to_display<F>(fun: F) -> impl fmt::Display
172where
173 F: Fn(&mut fmt::Formatter) -> fmt::Result,
174{
175 struct Mapped<F> {
176 fun: F,
177 }
178
179 impl<F> fmt::Display for Mapped<F>
180 where
181 F: Fn(&mut fmt::Formatter) -> fmt::Result,
182 {
183 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
184 (self.fun)(formatter)
185 }
186 }
187
188 Mapped { fun }
189}
190
191pub fn separated<'a, I>(separator: &'a str, iter: I) -> impl fmt::Display + 'a
194where
195 I: IntoIterator,
196 I::IntoIter: Clone + 'a,
197 I::Item: fmt::Display + 'a,
198{
199 struct Separated<'a, I> {
200 separator: &'a str,
201 iter: I,
202 }
203
204 impl<'a, I> fmt::Display for Separated<'a, I>
205 where
206 I: Iterator + Clone,
207 I::Item: fmt::Display,
208 {
209 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
210 for (i, item) in self.iter.clone().enumerate() {
211 if i != 0 {
212 write!(f, "{}", self.separator)?;
213 }
214 write!(f, "{}", item)?;
215 }
216 Ok(())
217 }
218 }
219
220 Separated {
221 separator,
222 iter: iter.into_iter(),
223 }
224}
225
226#[derive(Debug, Clone)]
231pub struct Indent {
232 unit: String,
233 buff: String,
234 mark: Vec<usize>,
235}
236
237impl AsMut<Indent> for Indent {
238 fn as_mut(&mut self) -> &mut Indent {
239 self
240 }
241}
242
243impl Indent {
244 pub fn new(unit: char, step: usize) -> Indent {
247 Indent {
248 unit: std::iter::repeat(unit).take(step).collect::<String>(),
249 buff: String::with_capacity(unit.len_utf8()),
250 mark: vec![],
251 }
252 }
253
254 fn inc(&mut self, rhs: usize) {
255 for _ in 0..rhs {
256 self.buff += &self.unit;
257 }
258 }
259
260 fn dec(&mut self, rhs: usize) {
261 let tail = rhs.saturating_mul(self.unit.len());
262 let head = self.buff.len().saturating_sub(tail);
263 self.buff.truncate(head);
264 }
265
266 pub fn set(&mut self) {
268 self.mark.push(self.buff.len());
269 }
270
271 pub fn reset(&mut self) {
273 if let Some(len) = self.mark.pop() {
274 while self.buff.len() < len {
275 self.buff += &self.unit;
276 }
277 self.buff.truncate(len);
278 }
279 }
280}
281
282pub trait IndentLike {
286 fn indented<F>(&mut self, f: F) -> fmt::Result
289 where
290 F: FnMut(&mut Self) -> fmt::Result;
291
292 fn indented_if<F>(&mut self, guard: bool, f: F) -> fmt::Result
295 where
296 F: FnMut(&mut Self) -> fmt::Result;
297}
298
299impl<T: AsMut<Indent>> IndentLike for T {
300 fn indented<F>(&mut self, mut f: F) -> fmt::Result
301 where
302 F: FnMut(&mut Self) -> fmt::Result,
303 {
304 *self.as_mut() += 1;
305 let result = f(self);
306 *self.as_mut() -= 1;
307 result
308 }
309
310 fn indented_if<F>(&mut self, guard: bool, mut f: F) -> fmt::Result
311 where
312 F: FnMut(&mut Self) -> fmt::Result,
313 {
314 if guard {
315 *self.as_mut() += 1;
316 }
317 let result = f(self);
318
319 if guard {
320 *self.as_mut() -= 1;
321 }
322 result
323 }
324}
325
326impl Default for Indent {
327 fn default() -> Self {
328 Indent::new(' ', 2)
329 }
330}
331
332impl std::ops::AddAssign<usize> for Indent {
333 fn add_assign(&mut self, rhs: usize) {
334 self.inc(rhs)
335 }
336}
337
338impl std::ops::SubAssign<usize> for Indent {
339 fn sub_assign(&mut self, rhs: usize) {
340 self.dec(rhs)
341 }
342}
343
344impl fmt::Display for Indent {
345 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346 f.write_str(&self.buff)
347 }
348}
349
350#[derive(Debug, Clone, PartialEq)]
353pub struct MaxLenString<const MAX: usize>(String);
354
355impl<const MAX: usize> MaxLenString<MAX> {
356 pub fn new(s: String) -> Result<Self, String> {
375 if s.len() > MAX {
376 return Err(s);
377 }
378
379 Ok(MaxLenString(s))
380 }
381
382 pub fn into_inner(self) -> String {
384 self.0
385 }
386
387 pub fn as_str(&self) -> &str {
389 self
390 }
391}
392
393impl<const MAX: usize> Deref for MaxLenString<MAX> {
394 type Target = str;
395
396 fn deref(&self) -> &Self::Target {
397 &self.0
398 }
399}
400
401impl<const MAX: usize> fmt::Display for MaxLenString<MAX> {
402 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403 f.write_str(&self.0)
404 }
405}
406
407impl<const MAX: usize> TryFrom<String> for MaxLenString<MAX> {
408 type Error = String;
409
410 fn try_from(s: String) -> Result<Self, Self::Error> {
411 Self::new(s)
412 }
413}
414
415impl<'a, const MAX: usize> TryFrom<&'a String> for MaxLenString<MAX> {
416 type Error = String;
417
418 fn try_from(s: &'a String) -> Result<Self, Self::Error> {
419 Self::try_from(s.clone())
420 }
421}
422
423impl<'a, const MAX: usize> TryFrom<&'a str> for MaxLenString<MAX> {
424 type Error = String;
425
426 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
427 Self::try_from(String::from(s))
428 }
429}
430
431pub fn redact(s: impl Debug) -> impl Debug {
437 Redacting {
438 value: s,
439 debug_mode: soft_assertions_enabled(),
440 }
441}
442
443struct Redacting<A: Debug> {
444 value: A,
445 debug_mode: bool,
446}
447
448struct RedactingWriter<'a, 'b>(&'a mut Formatter<'b>);
449
450impl<'a, 'b> Write for RedactingWriter<'a, 'b> {
451 fn write_str(&mut self, s: &str) -> fmt::Result {
452 for c in s.chars() {
453 self.0.write_char(if c.is_digit(10) {
454 '#'
455 } else if c.is_alphabetic() {
456 'X'
457 } else {
458 c
459 })?;
460 }
461 Ok(())
462 }
463}
464
465impl<A: Debug> Debug for Redacting<A> {
466 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
467 if self.debug_mode || f.alternate() {
468 self.value.fmt(f)
469 } else {
470 f.write_char('<')?;
471 let mut write = RedactingWriter(f);
472 write!(&mut write, "{:?}", &self.value)?;
473 f.write_char('>')
474 }
475 }
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481
482 #[crate::test]
483 fn test_indent() {
484 let mut indent = Indent::new('~', 3);
485 indent += 1;
486 assert_eq!(indent.to_string(), "~~~".to_string());
487 indent += 3;
488 assert_eq!(indent.to_string(), "~~~~~~~~~~~~".to_string());
489 indent -= 2;
490 assert_eq!(indent.to_string(), "~~~~~~".to_string());
491 indent -= 4;
492 assert_eq!(indent.to_string(), "".to_string());
493 indent += 1;
494 assert_eq!(indent.to_string(), "~~~".to_string());
495 }
496
497 #[crate::test]
498 fn test_redact() {
499 pub fn redact(s: impl Debug) -> impl Debug {
501 Redacting {
502 value: s,
503 debug_mode: false,
504 }
505 }
506
507 assert_eq!(
508 r#"<"XXXX_XXXXXX">"#,
509 format!("{:?}", &redact(&"TEST_STRING"))
510 );
511 assert_eq!(
512 r#""TEST_STRING""#,
513 format!("{:#?}", &redact(&"TEST_STRING"))
514 );
515 assert_eq!("<#.###>", format!("{:?}", &redact(&1.234f32)));
516 }
517}