1use core::convert::Infallible;
2use core::fmt::{self, Write};
3use core::ops::Deref;
4use core::pin::Pin;
5use core::str;
6
7use crate::FastWritable;
8
9#[inline]
57pub fn indent<S, I: AsIndent>(
58 source: S,
59 indent: I,
60 first: bool,
61 blank: bool,
62) -> Result<Indent<S, I>, Infallible> {
63 Ok(Indent {
64 source,
65 indent,
66 first,
67 blank,
68 })
69}
70
71pub struct Indent<S, I> {
72 source: S,
73 indent: I,
74 first: bool,
75 blank: bool,
76}
77
78impl<S: fmt::Display, I: AsIndent> fmt::Display for Indent<S, I> {
79 fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
80 let indent = self.indent.as_indent();
81 write!(
82 IndentWriter::new(dest, indent, self.first, self.blank),
83 "{}",
84 self.source
85 )?;
86 Ok(())
87 }
88}
89
90impl<S: FastWritable, I: AsIndent> FastWritable for Indent<S, I> {
91 fn write_into(
92 &self,
93 dest: &mut dyn fmt::Write,
94 values: &dyn crate::Values,
95 ) -> crate::Result<()> {
96 let indent = self.indent.as_indent();
97 self.source.write_into(
98 &mut IndentWriter::new(dest, indent, self.first, self.blank),
99 values,
100 )?;
101 Ok(())
102 }
103}
104
105struct IndentWriter<'a, W> {
106 dest: W,
107 indent: &'a str,
108 first: bool,
109 blank: bool,
110 is_new_line: bool,
111 is_first_line: bool,
112}
113
114impl<'a, W: fmt::Write> IndentWriter<'a, W> {
115 fn new(dest: W, indent: &'a str, first: bool, blank: bool) -> Self {
116 IndentWriter {
117 dest,
118 indent,
119 first,
120 blank,
121 is_new_line: true,
122 is_first_line: true,
123 }
124 }
125}
126
127impl<W: fmt::Write> fmt::Write for IndentWriter<'_, W> {
128 fn write_str(&mut self, s: &str) -> fmt::Result {
129 if self.indent.is_empty() {
130 return self.dest.write_str(s);
131 }
132
133 for line in s.split_inclusive('\n') {
134 if self.is_new_line {
135 if self.is_first_line {
136 if self.first && (self.blank || !matches!(line, "\n" | "\r\n")) {
137 self.dest.write_str(self.indent)?;
138 }
139 self.is_first_line = false;
140 } else if self.blank || !matches!(line, "\n" | "\r\n") {
141 self.dest.write_str(self.indent)?;
142 }
143 }
144 self.dest.write_str(line)?;
145 self.is_new_line = line.ends_with('\n');
146 }
147 Ok(())
148 }
149}
150
151#[cfg_attr(
153 feature = "serde_json",
154 doc = "[prettified JSON data](super::json_pretty) and"
155)]
156pub trait AsIndent {
164 fn as_indent(&self) -> &str;
166}
167
168impl AsIndent for str {
169 #[inline]
170 fn as_indent(&self) -> &str {
171 self
172 }
173}
174
175#[cfg(feature = "alloc")]
176impl AsIndent for alloc::string::String {
177 #[inline]
178 fn as_indent(&self) -> &str {
179 self
180 }
181}
182
183impl AsIndent for usize {
184 #[inline]
185 fn as_indent(&self) -> &str {
186 spaces(*self)
187 }
188}
189
190impl AsIndent for core::num::Wrapping<usize> {
191 #[inline]
192 fn as_indent(&self) -> &str {
193 spaces(self.0)
194 }
195}
196
197impl AsIndent for core::num::NonZeroUsize {
198 #[inline]
199 fn as_indent(&self) -> &str {
200 spaces(self.get())
201 }
202}
203
204fn spaces(width: usize) -> &'static str {
205 const MAX_SPACES: usize = 16;
206 const SPACES: &str = match str::from_utf8(&[b' '; MAX_SPACES]) {
207 Ok(spaces) => spaces,
208 Err(_) => panic!(),
209 };
210
211 &SPACES[..width.min(SPACES.len())]
212}
213
214#[cfg(feature = "alloc")]
215impl<T: AsIndent + alloc::borrow::ToOwned + ?Sized> AsIndent for alloc::borrow::Cow<'_, T> {
216 #[inline]
217 fn as_indent(&self) -> &str {
218 T::as_indent(self)
219 }
220}
221
222crate::impl_for_ref! {
223 impl AsIndent for T {
224 #[inline]
225 fn as_indent(&self) -> &str {
226 <T>::as_indent(self)
227 }
228 }
229}
230
231impl<T> AsIndent for Pin<T>
232where
233 T: Deref,
234 <T as Deref>::Target: AsIndent,
235{
236 #[inline]
237 fn as_indent(&self) -> &str {
238 self.as_ref().get_ref().as_indent()
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use alloc::string::ToString;
245
246 use super::*;
247
248 #[test]
249 fn test_indent() {
250 assert_eq!(
251 indent("hello", 2, false, false).unwrap().to_string(),
252 "hello"
253 );
254 assert_eq!(
255 indent("hello\n", 2, false, false).unwrap().to_string(),
256 "hello\n"
257 );
258 assert_eq!(
259 indent("hello\nfoo", 2, false, false).unwrap().to_string(),
260 "hello\n foo"
261 );
262 assert_eq!(
263 indent("hello\nfoo\n bar", 4, false, false)
264 .unwrap()
265 .to_string(),
266 "hello\n foo\n bar"
267 );
268 assert_eq!(
269 indent("hello", 267_332_238_858, false, false)
270 .unwrap()
271 .to_string(),
272 "hello"
273 );
274
275 assert_eq!(
276 indent("hello\n\n bar", 4, false, false)
277 .unwrap()
278 .to_string(),
279 "hello\n\n bar"
280 );
281 assert_eq!(
282 indent("hello\n\n bar", 4, false, true).unwrap().to_string(),
283 "hello\n \n bar"
284 );
285 assert_eq!(
286 indent("hello\n\n bar", 4, true, false).unwrap().to_string(),
287 " hello\n\n bar"
288 );
289 assert_eq!(
290 indent("hello\n\n bar", 4, true, true).unwrap().to_string(),
291 " hello\n \n bar"
292 );
293 }
294
295 #[test]
296 fn test_indent_str() {
297 assert_eq!(
298 indent("hello\n\n bar", "❗❓", false, false)
299 .unwrap()
300 .to_string(),
301 "hello\n\n❗❓ bar"
302 );
303 assert_eq!(
304 indent("hello\n\n bar", "❗❓", false, true)
305 .unwrap()
306 .to_string(),
307 "hello\n❗❓\n❗❓ bar"
308 );
309 assert_eq!(
310 indent("hello\n\n bar", "❗❓", true, false)
311 .unwrap()
312 .to_string(),
313 "❗❓hello\n\n❗❓ bar"
314 );
315 assert_eq!(
316 indent("hello\n\n bar", "❗❓", true, true)
317 .unwrap()
318 .to_string(),
319 "❗❓hello\n❗❓\n❗❓ bar"
320 );
321 }
322
323 #[test]
324 fn test_indent_chunked() {
325 #[derive(Clone, Copy)]
326 struct Chunked<'a>(&'a str);
327
328 impl<'a> fmt::Display for Chunked<'a> {
329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330 for chunk in self.0.chars() {
331 write!(f, "{chunk}")?;
332 }
333 Ok(())
334 }
335 }
336
337 assert_eq!(
338 indent(Chunked("hello"), 2, false, false)
339 .unwrap()
340 .to_string(),
341 "hello"
342 );
343 assert_eq!(
344 indent(Chunked("hello\n"), 2, false, false)
345 .unwrap()
346 .to_string(),
347 "hello\n"
348 );
349 assert_eq!(
350 indent(Chunked("hello\nfoo"), 2, false, false)
351 .unwrap()
352 .to_string(),
353 "hello\n foo"
354 );
355 assert_eq!(
356 indent(Chunked("hello\nfoo\n bar"), 4, false, false)
357 .unwrap()
358 .to_string(),
359 "hello\n foo\n bar"
360 );
361 assert_eq!(
362 indent(Chunked("hello"), 267_332_238_858, false, false)
363 .unwrap()
364 .to_string(),
365 "hello"
366 );
367
368 assert_eq!(
369 indent(Chunked("hello\n\n bar"), 4, false, false)
370 .unwrap()
371 .to_string(),
372 "hello\n\n bar"
373 );
374 assert_eq!(
375 indent(Chunked("hello\n\n bar"), 4, false, true)
376 .unwrap()
377 .to_string(),
378 "hello\n \n bar"
379 );
380 assert_eq!(
381 indent(Chunked("hello\n\n bar"), 4, true, false)
382 .unwrap()
383 .to_string(),
384 " hello\n\n bar"
385 );
386 assert_eq!(
387 indent(Chunked("hello\n\n bar"), 4, true, true)
388 .unwrap()
389 .to_string(),
390 " hello\n \n bar"
391 );
392 }
393
394 #[test]
395 #[allow(clippy::arc_with_non_send_sync)] #[allow(clippy::type_complexity)] fn test_indent_complicated() {
398 use std::borrow::ToOwned;
399 use std::boxed::Box;
400 use std::cell::{RefCell, RefMut};
401 use std::pin::Pin;
402 use std::rc::Rc;
403 use std::string::String;
404 use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockWriteGuard};
405
406 let prefix = Mutex::new(Box::pin("❗❓".to_owned()));
407 let prefix = RefCell::new(Arc::new(prefix.try_lock().unwrap()));
408 let prefix = RwLock::new(Rc::new(prefix.borrow_mut()));
409 let prefix: RwLockWriteGuard<'_, Rc<RefMut<'_, Arc<MutexGuard<'_, Pin<Box<String>>>>>>> =
410 prefix.try_write().unwrap();
411
412 assert_eq!(
413 indent("hello\n\n bar", &prefix, false, false)
414 .unwrap()
415 .to_string(),
416 "hello\n\n❗❓ bar"
417 );
418 assert_eq!(
419 indent("hello\n\n bar", &prefix, false, true)
420 .unwrap()
421 .to_string(),
422 "hello\n❗❓\n❗❓ bar"
423 );
424 assert_eq!(
425 indent("hello\n\n bar", &prefix, true, false)
426 .unwrap()
427 .to_string(),
428 "❗❓hello\n\n❗❓ bar"
429 );
430 assert_eq!(
431 indent("hello\n\n bar", &prefix, true, true)
432 .unwrap()
433 .to_string(),
434 "❗❓hello\n❗❓\n❗❓ bar"
435 );
436 }
437}