use crate::display::{
renderers::color::helpers::{chunk_string_into_width, pad_to_width},
tracing::FlattenedTracingField,
};
use fnv::FnvHashMap;
use std::{collections::hash_map::Iter as MapIter, slice::Iter as SliceIter};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
pub fn create_field_tailers(
mut tailer_width: usize,
fields: &FnvHashMap<&'static str, FlattenedTracingField>,
skip_cause: bool,
skip_fields: bool,
) -> Vec<String> {
tailer_width -= 1;
if skip_fields || fields.is_empty() {
return Vec::with_capacity(0);
}
let mut tailer_lines = Vec::new();
let mut current_buff = String::with_capacity(tailer_width);
current_buff.push('|');
for (unrep_key, unformatted_unrep_value) in fields {
let mut actual_key = unrep_key.replace('\n', " ").replace('\r', "");
if actual_key == "cause" && skip_cause {
continue;
}
let actual_value = format!("{unformatted_unrep_value}")
.replace('\n', "")
.replace('\r', "");
let buff_used = current_buff.len() > 1;
let comma_width = usize::from(buff_used);
let needed_width = actual_key.width() + actual_value.width() + comma_width;
let mut current_buff_width = current_buff.width();
if current_buff_width + needed_width < tailer_width {
if buff_used {
current_buff.push(',');
}
current_buff += &actual_key;
current_buff.push('=');
current_buff += &actual_value;
continue;
}
if buff_used {
if current_buff_width + comma_width < tailer_width {
current_buff.push(',');
} else {
tailer_lines.push(pad_to_width(current_buff, tailer_width));
current_buff = String::with_capacity(tailer_width);
current_buff.push('|');
current_buff_width = 1;
}
}
let mut key_width = actual_key.width();
while key_width > tailer_width {
let character = actual_key.remove(0);
let character_width = character.width().unwrap_or_default();
key_width -= character_width;
if current_buff_width + character_width >= tailer_width {
tailer_lines.push(pad_to_width(current_buff, tailer_width));
current_buff = String::with_capacity(tailer_width);
current_buff.push('|');
current_buff_width = 1;
}
current_buff.push(character);
current_buff_width += character_width;
}
actual_key.push('=');
append_to_tailer(
tailer_width,
&mut tailer_lines,
&mut current_buff,
&mut current_buff_width,
&actual_key,
);
append_to_tailer(
tailer_width,
&mut tailer_lines,
&mut current_buff,
&mut current_buff_width,
&actual_value,
);
}
if current_buff.len() > 1 {
tailer_lines.push(pad_to_width(current_buff, tailer_width));
}
tailer_lines
}
#[must_use]
pub fn create_combined_message(
width: usize,
fields: &FnvHashMap<&'static str, FlattenedTracingField>,
mut message: String,
hide_fields: bool,
skip_cause: bool,
) -> Vec<String> {
if hide_fields || fields.is_empty() {
return chunk_string_into_width(width, &message);
}
message.push_str("\n\n");
let mut iterators = vec![FieldIterator::Object(fields.iter())];
while let Some(current_iterator) = iterators.last_mut() {
match current_iterator {
FieldIterator::Object(object_iter) => {
if let Some((unrep_key, unformatted_unrep_value)) = object_iter.next() {
format_object_iterated_field(
unrep_key,
unformatted_unrep_value,
&mut message,
&mut iterators,
skip_cause,
);
} else {
iterators.pop();
message.push('\n');
}
}
FieldIterator::NestedObject(object_iter) => {
if let Some((unrep_key, unformatted_unrep_value)) = object_iter.next() {
format_object_iterated_field(
unrep_key,
unformatted_unrep_value,
&mut message,
&mut iterators,
skip_cause,
);
} else {
iterators.pop();
message.push('\n');
}
}
FieldIterator::List(list_iter) => {
if let Some(unformatted_unrep_value) = list_iter.next() {
match unformatted_unrep_value {
FlattenedTracingField::List(inner_list) => {
iterators.push(FieldIterator::List(inner_list.iter()));
}
FlattenedTracingField::Object(inner_obj) => {
iterators.push(FieldIterator::NestedObject(inner_obj.iter()));
}
_ => {
let actual_value = format!("{unformatted_unrep_value}");
message.push_str(&create_field_indent_padding(iterators.len()));
message.push_str(&actual_value);
message.push('\n');
}
}
} else {
iterators.pop();
message.push('\n');
}
}
}
}
chunk_string_into_width(width, &message)
}
fn format_object_iterated_field<'subiter>(
unrep_key: &str,
unformatted_unrep_value: &'subiter FlattenedTracingField,
message: &mut String,
iterators: &mut Vec<FieldIterator<'subiter>>,
skip_cause: bool,
) {
let actual_key = unrep_key.replace('\n', " ").replace('\r', "");
if actual_key == "cause" && skip_cause {
return;
}
message.push_str(&create_field_indent_padding(iterators.len()));
message.push_str(&actual_key);
message.push('=');
match unformatted_unrep_value {
FlattenedTracingField::List(inner_list) => {
iterators.push(FieldIterator::List(inner_list.iter()));
}
FlattenedTracingField::Object(inner_obj) => {
iterators.push(FieldIterator::NestedObject(inner_obj.iter()));
}
_ => {
let actual_value = format!("{unformatted_unrep_value}");
message.push_str(&actual_value);
}
}
message.push('\n');
}
#[must_use]
fn create_field_indent_padding(padding: usize) -> String {
let mut message = String::with_capacity(((padding - 1) * 2) + 1);
for _ in 1_usize..padding {
message.push_str(" |");
}
message.push(' ');
message
}
fn append_to_tailer(
tailer_width: usize,
tailers: &mut Vec<String>,
current_buff: &mut String,
current_buff_width: &mut usize,
to_append: &str,
) {
for character in to_append.chars() {
let character_width = character.width().unwrap_or_default();
if *current_buff_width + character_width >= tailer_width {
let mut buff_as_string = String::with_capacity(tailer_width);
std::mem::swap(&mut buff_as_string, current_buff);
tailers.push(pad_to_width(buff_as_string, tailer_width));
current_buff.push('|');
current_buff.push(character);
*current_buff_width = 1;
} else {
current_buff.push(character);
*current_buff_width += character_width;
}
}
}
enum FieldIterator<'items> {
Object(MapIter<'items, &'static str, FlattenedTracingField>),
NestedObject(MapIter<'items, String, FlattenedTracingField>),
List(SliceIter<'items, FlattenedTracingField>),
}
#[cfg(test)]
mod unit_tests {
use super::*;
use crate::display::renderers::color::helpers::calculate_tailer_width;
#[test]
pub fn calculate_fields() {
let mut fields = FnvHashMap::default();
fields.insert("ip", FlattenedTracingField::Str("192.168.7.42".to_owned()));
fields.insert("port", FlattenedTracingField::UnsignedInt(7500));
assert_eq!(
create_field_tailers(calculate_tailer_width(40), &fields, false, false),
vec![
"|ip=192.1 ".to_owned(),
"|68.7.42,p".to_owned(),
"|ort=7500 ".to_owned(),
],
);
assert_eq!(
create_field_tailers(calculate_tailer_width(80), &fields, false, false),
vec![
"|ip=192.168.7.42,por".to_owned(),
"|t=7500 ".to_owned(),
],
);
}
}