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// }