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