cs_utils/utils/
log_formatter.rs

1// use std::fmt::{self, Write};
2
3// /// Formatter aims to help to write nice log output.
4// /// 
5// /// ### Examples
6// /// 
7// /// ```
8// /// ```
9// pub struct LogFormatter<'a, 'b> {
10//     formatter: &'a mut fmt::Formatter<'b>,
11//     total_width: usize,
12//     ident: String,
13// }
14
15// // formatter.write_fmt(format_args!("\nDNS record:"))?;
16
17// // // TODO: use one from utils-rs
18// // let mut packet_formatter = NetworkPacketFormatter::new(
19// //     formatter,
20// //     50,
21// //     "  ",
22// // );
23
24// // packet_formatter.write("Hostname:", &self.hostname())?;
25// // packet_formatter.write("IP Address:", &self.ip_address())?;
26// // packet_formatter.write("TTL:", &self.ttl())?;
27
28// // return Ok(());
29
30// impl<'a, 'b> LogFormatter<'a, 'b> {
31//     pub fn new<StrRef: AsRef<str>>(
32//         formatter: &'a mut fmt::Formatter<'b>,
33//         total_width: usize,
34//         ident: StrRef,
35//     ) -> Self {
36//         let ident = ident.as_ref().to_string();
37
38//         return LogFormatter {
39//             formatter,
40//             total_width,
41//             ident,
42//         };
43//     }
44
45//     pub fn write_line<S: AsRef<str>, T: AsRef<str>>(
46//         &mut self,
47//         caption: S,
48//         data: T,
49//     ) -> Result<(), fmt::Error> {
50//         let data_str = data.as_ref();
51//         let caption_str = caption.as_ref();
52//         let width = self.total_width - self.ident.len() - caption_str.len();
53    
54//         write!(
55//             self.formatter,
56//             "\n{ident}{caption}{:.>width$}",
57//             data_str,
58//             width = width,
59//             ident = &self.ident,
60//             caption = caption_str,
61//         )?;
62    
63//         return Ok(());
64//     }
65
66//     pub fn write<S: AsRef<str>>(
67//         &mut self,
68//         line: S,
69//     ) -> Result<(), fmt::Error> {
70
71//         let line = line.as_ref();
72//         write!(
73//             self.formatter,
74//             "\n{ident}{line}",
75//             line = line,
76//             ident = &self.ident,
77//         )?;
78    
79//         return Ok(());
80//     }
81
82//     pub fn caption<S: AsRef<str>>(
83//         &mut self,
84//         caption: S,
85//     ) -> Result<(), fmt::Error> {
86//         self.formatter
87//             .write_str(&LogFormatter::make_caption(caption))?;
88
89//         return Ok(());
90//     }
91
92//     pub fn make_caption<S: AsRef<str>>(
93//         caption: S,
94//     ) -> String {
95//         return format!("\n## {}:", &caption.as_ref());
96//     }
97
98//     pub fn print_caption<S: AsRef<str>>(
99//         caption: S,
100//     ) {
101//         return println!("{}", &LogFormatter::make_caption(caption));
102//     }
103// }
104
105
106/// Formatter aims to help to write nice log output.
107/// 
108/// ### Examples
109/// 
110/// ```
111/// ```
112pub struct LogFormatter {
113    total_width: usize,
114    ident: String,
115}
116
117use std::{fmt::{Display, self}, io::{self, ErrorKind}, str, marker::PhantomData};
118
119pub struct FmtWriteToIoWrite<'a, T: fmt::Write + 'a> {
120    fmt_writer: T,
121    _ph: &'a PhantomData<T>,
122}
123
124impl<'a, T: fmt::Write + 'a> io::Write for FmtWriteToIoWrite<'a, T> {
125    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
126        let string = str::from_utf8(&buf[..])
127            .expect("Cannot convert buffer to UTF8 string.");
128        
129        match self.fmt_writer.write_str(string) {
130            Ok(_) => {
131                return Ok(buf.len());
132            },
133            Err(error) => {
134                let io_error = io::Error::new(
135                    ErrorKind::WriteZero,
136                    error.to_string(),
137                );
138                return Err(io_error);
139            },
140        }
141    }
142
143    fn flush(&mut self) -> io::Result<()> {
144        return Ok(());
145    }
146}
147
148impl<'a, T: fmt::Write + 'a> FmtWriteToIoWrite<'a, T> {
149    pub fn new(
150        fmt_writer: &'a mut T,
151    ) -> Box<dyn io::Write + 'a> {
152        return Box::new(
153            FmtWriteToIoWrite {
154                fmt_writer,
155                _ph: &PhantomData,
156            },
157        );
158    }
159}
160
161impl LogFormatter {
162    pub fn new<StrRef: AsRef<str>>(
163        total_width: usize,
164        ident: StrRef,
165    ) -> Self {
166        let ident = ident.as_ref().to_string();
167
168        // TODO: test this
169        assert!(
170            ident.len() < total_width,
171            "Identation length must be smaller than total width.",
172        );
173
174        return LogFormatter {
175            total_width,
176            ident,
177        };
178    }
179
180    pub fn caption<S: AsRef<str>>(
181        &self,
182        caption: S,
183    ) -> String {
184        let ident_size = self.ident.len() / 2;
185
186        return format!(
187            "\n{ident}{: >width$}{caption}\n\n",
188            ident = "",
189            caption = caption.as_ref(),
190            width = ident_size,
191        );
192    }
193
194    pub fn value_line<S: AsRef<str>, T: Display>(
195        &self,
196        label: S,
197        value: T,
198    ) -> String {
199        let value_str = label.as_ref();
200        let used_width = self.ident.len() + value_str.len();
201
202        // TODO: test this
203        let width = if self.total_width >= used_width {
204            self.total_width - used_width
205        } else {
206            self.total_width
207        };
208
209        return format!(
210            "{ident}{value}{:.>width$}\n",
211            value,
212            ident = &self.ident,
213            value = value_str,
214            width = width,
215        );
216    }
217
218    pub fn line<S: AsRef<str>>(
219        &self,
220        line: S,
221    ) -> String {
222        let line = line.as_ref();
223
224        return format!(
225            "{ident}{line}",
226            line = line,
227            ident = &self.ident,
228        );
229    }
230
231    pub fn line_start<S: AsRef<str>>(
232        &self,
233        label: S,
234    ) -> String {
235        // TODO: split long lines to multiple ones of `width` length
236        let used_width = self.ident.len() + label.as_ref().len();
237
238        // TODO: test this
239        let width = if self.total_width >= used_width {
240            self.total_width - used_width
241        } else {
242            self.total_width
243        };
244    
245        return format!(
246            "{ident}{caption}{:>width$}\n",
247            ident = &self.ident,
248            caption = label.as_ref(),
249            width = width,
250        );
251    }
252
253    pub fn line_end<S: AsRef<str>>(
254        &self,
255        label: S,
256    ) -> String {
257        let used_width = self.ident.len();
258
259        // TODO: test this
260        let width = if self.total_width >= used_width {
261            self.total_width - used_width
262        } else {
263            self.total_width
264        };
265    
266        return format!(
267            "{ident}{:>width$}\n",
268            label.as_ref(),
269            ident = &self.ident,
270            width = width,
271        );
272    }
273
274    pub fn underline(&self) -> String {
275        let width = self.total_width;
276
277        return format!(
278            "{ident}{:->width$}\n",
279            ident = &self.ident,
280            width = width,
281        );
282    }
283}
284
285pub struct WriteFormatter<'a> {
286    log_formatter: LogFormatter,
287    writer: Box<dyn io::Write + 'a>,
288}
289
290impl<'a> WriteFormatter<'a> {
291    pub fn new<StrRef: AsRef<str>, W: io::Write + 'static>(
292        total_width: usize,
293        ident: StrRef,
294        writer: W,
295    ) -> Self {
296        let ident = ident.as_ref().to_string();
297
298        return WriteFormatter {
299            log_formatter: LogFormatter::new(total_width, ident),
300            writer: Box::new(writer),
301        };
302    }
303
304    pub fn new_from_fmt<StrRef: AsRef<str>, R: fmt::Write + 'a>(
305        total_width: usize,
306        ident: StrRef,
307        formatter: &'a mut R,
308    ) -> Self {
309        let writer = FmtWriteToIoWrite::new(formatter);
310
311        return WriteFormatter {
312            log_formatter: LogFormatter::new(total_width, ident),
313            writer,
314        };
315    }
316
317    pub fn caption<S: AsRef<str>>(
318        &mut self,
319        caption: S,
320    ) -> &Self {
321        let message = self.log_formatter.caption(caption);
322
323        self.writer.write_all(message.as_bytes())
324            .expect("Failed to write caption.");
325
326        return self;
327    }
328
329    pub fn value_line<S: AsRef<str>, T: Display>(
330        &mut self,
331        label: S,
332        value: T,
333    ) -> &Self {
334        let message = self.log_formatter.value_line(label, value);
335
336        self.writer.write_all(message.as_bytes())
337            .expect("Failed to write value line.");
338
339        return self;
340    }
341
342    pub fn line<S: AsRef<str>>(
343        &mut self,
344        line_str: S,
345    ) -> &Self {
346        let message = self.log_formatter.line(line_str);
347
348        self.writer.write_all(message.as_bytes())
349            .expect("Failed to write underline.");
350
351        return self;
352    }
353
354    pub fn underline(&mut self) -> &Self {
355        let message = self.log_formatter.underline();
356
357        self.writer.write_all(message.as_bytes())
358            .expect("Failed to write underline.");
359
360        return self;
361    }
362
363    pub fn line_start<S: AsRef<str>>(
364        &mut self,
365        label: S,
366    ) -> &Self {
367        let message = self.log_formatter.line_start(label);
368
369        self.writer.write_all(message.as_bytes())
370            .expect("Failed to write lien start.");
371
372        return self;
373    }
374
375    pub fn line_end<S: AsRef<str>>(
376        &mut self,
377        label: S,
378    ) -> &Self {
379        let message = self.log_formatter.line_end(label);
380
381        self.writer.write_all(message.as_bytes())
382            .expect("Failed to write line end.");
383
384        return self;
385    }
386}
387
388// #[cfg(test)]
389// mod tests {
390//     mod caption {
391//         use k9::assert_matches_snapshot;
392
393//         use crate::LogFormatter;
394
395//         #[test]
396//         fn creates_caption_from_str() {
397//             let formatter = LogFormatter::new(50, "  ");
398//             let test_string = "Start".to_string();
399
400//             assert_matches_snapshot!(
401//                 formatter.caption(&test_string),
402//                 "Must create formatted caption (str)."
403//             );
404
405//             assert_matches_snapshot!(
406//                 formatter.caption(test_string.as_str()),
407//                 "Must create formatted caption (as_str)."
408//             );
409
410//             assert_matches_snapshot!(
411//                 formatter.caption("Start"),
412//                 "Must create formatted caption (str)."
413//             );
414//         }
415
416//         #[test]
417//         fn creates_caption_from_string() {
418//             let formatter = LogFormatter::new(50, "  ");
419
420//             assert_matches_snapshot!(
421//                 formatter.caption("Start".to_string()),
422//                 "Must create formatted caption (string)."
423//             );
424//         }
425//     }
426
427//     mod line {
428//         use k9::assert_matches_snapshot;
429//         use rstest::rstest;
430//         use crate::LogFormatter;
431
432//         #[rstest]
433//         #[case(&("some-string".to_string()))]
434//         #[case("some-string")]
435//         #[test]
436//         fn creates_line_for_str<T: Display>(
437//             #[case] test_string: T,
438//         ) {
439//             let formatter = LogFormatter::new(50, "  ");
440
441//             assert_matches_snapshot!(
442//                 formatter.value_line("Name", test_string),
443//                 "Must create formatted line (str)."
444//             );
445//         }
446
447//         #[rstest]
448//         #[case(&("some-different-string".to_string()))]
449//         #[case("some-different-string")]
450//         #[test]
451//         fn creates_line_for_as_str<T: Display>(
452//             #[case] test_string: T,
453//         ) {
454//             let formatter = LogFormatter::new(50, "  ");
455//             let test_string = test_string.to_string();
456
457//             assert_matches_snapshot!(
458//                 formatter.value_line("Name", test_string.as_str()),
459//                 "Must create formatted line (as_str)."
460//             );
461//         }
462
463//         #[test]
464//         fn creates_line_for_string() {
465//             let formatter = LogFormatter::new(50, "  ");
466
467//             assert_matches_snapshot!(
468//                 formatter.value_line("Name", "value".to_string()),
469//                 "Must create formatted line (string)."
470//             );
471//         }
472
473//         use std::fmt::Display;
474
475//         struct TestStruct {}
476
477//         impl Display for TestStruct {
478//             fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
479//                 f.write_fmt(
480//                     format_args!("display output: {}", "200"),
481//                 )?;
482
483//                 return Ok(());
484//             }
485//         }
486
487//         #[rstest]
488//         #[case(500)]
489//         #[case(true)]
490//         #[case(false)]
491//         #[case(TestStruct {})]
492//         #[test]
493//         fn creates_line_for_display_items<T: Display>(
494//             #[case] data: T,
495//         ) {
496//             let formatter = LogFormatter::new(50, "  ");
497
498//             assert_matches_snapshot!(
499//                 formatter.value_line("Name", data),
500//                 "Must create formatted line (display)."
501//             );
502//         }
503//     }
504
505//     mod ident {
506//         use k9::assert_matches_snapshot;
507//         use rstest::rstest;
508//         use crate::LogFormatter;
509
510//         #[rstest]
511//         #[case(&("some-other-string".to_string()))]
512//         #[case("some-other-string")]
513//         #[test]
514//         fn idents_str<S: AsRef<str>>(
515//             #[case] test_string: S,
516//         ) {
517//             let formatter = LogFormatter::new(50, "  ");
518
519//             assert_matches_snapshot!(
520//                 formatter.line(test_string),
521//                 "Must ident a line (str)."
522//             );
523//         }
524
525//         #[rstest]
526//         #[case(&("some-other-string".to_string()))]
527//         #[case("some-other-string")]
528//         #[test]
529//         fn idents_as_str<S: AsRef<str>>(
530//             #[case] test_string: S,
531//         ) {
532//             let formatter = LogFormatter::new(50, "  ");
533//             let test_string = test_string.as_ref().to_string();
534
535//             assert_matches_snapshot!(
536//                 formatter.line(test_string.as_str()),
537//                 "Must ident a line (as_str)."
538//             );
539//         }
540
541//         #[test]
542//         fn idents_string() {
543//             let formatter = LogFormatter::new(50, "  ");
544//             let test_string = "string-for-identation".to_string();
545
546//             assert_matches_snapshot!(
547//                 formatter.line(test_string),
548//                 "Must ident a line (string)."
549//             );
550//         }
551//     }
552// }