feat: Improve output of tui. From p-r tui-score-info (#11)
WIP: formatted score debug print into a table with a name header and the score information below Co-authored-by: Jean-Gabriel Gill-Couture <jg@nationtech.io> Reviewed-on: #11 Reviewed-by: johnride <jg@nationtech.io> Co-authored-by: Willem <wrolleman@nationtech.io> Co-committed-by: Willem <wrolleman@nationtech.io>
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::Serialize;
|
||||
use serde_value::Value;
|
||||
|
||||
use super::{interpret::Interpret, topology::Topology};
|
||||
|
||||
pub trait Score<T: Topology>:
|
||||
std::fmt::Debug + Send + Sync + CloneBoxScore<T> + SerializeScore<T>
|
||||
std::fmt::Debug + ScoreToString<T> + Send + Sync + CloneBoxScore<T> + SerializeScore<T>
|
||||
{
|
||||
fn create_interpret(&self) -> Box<dyn Interpret<T>>;
|
||||
fn name(&self) -> String;
|
||||
@@ -39,3 +41,191 @@ where
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ScoreToString<T: Topology> {
|
||||
fn print_score_details(&self) -> String;
|
||||
fn format_value_as_string(&self, val: &Value, indent: usize) -> String;
|
||||
fn format_map(&self, map: &BTreeMap<Value, Value>, indent: usize) -> String;
|
||||
fn wrap_or_truncate(&self, s: &str, width: usize) -> Vec<String>;
|
||||
}
|
||||
|
||||
impl<S, T> ScoreToString<T> for S
|
||||
where
|
||||
T: Topology,
|
||||
S: Score<T> + 'static,
|
||||
{
|
||||
fn print_score_details(&self) -> String {
|
||||
let mut output = String::new();
|
||||
output += "\n";
|
||||
output += &self.format_value_as_string(&self.serialize(), 0);
|
||||
output += "\n";
|
||||
output
|
||||
}
|
||||
fn format_map(&self, map: &BTreeMap<Value, Value>, indent: usize) -> String {
|
||||
let pad = " ".repeat(indent * 2);
|
||||
let mut output = String::new();
|
||||
|
||||
output += &format!(
|
||||
"{}+--------------------------+--------------------------------------------------+\n",
|
||||
pad
|
||||
);
|
||||
output += &format!("{}| {:<24} | {:<48} |\n", pad, "score_name", self.name());
|
||||
output += &format!(
|
||||
"{}+--------------------------+--------------------------------------------------+\n",
|
||||
pad
|
||||
);
|
||||
|
||||
for (k, v) in map {
|
||||
let key_str = match k {
|
||||
Value::String(s) => s.clone(),
|
||||
other => format!("{:?}", other),
|
||||
};
|
||||
|
||||
let formatted_val = self.format_value_as_string(v, indent + 1);
|
||||
let mut lines = formatted_val.lines().map(|line| line.trim_start());
|
||||
|
||||
let wrapped_lines: Vec<_> = lines
|
||||
.flat_map(|line| self.wrap_or_truncate(line.trim_start(), 48))
|
||||
.collect();
|
||||
|
||||
if let Some(first) = wrapped_lines.first() {
|
||||
output += &format!("{}| {:<24} | {:<48} |\n", pad, key_str, first);
|
||||
for line in &wrapped_lines[1..] {
|
||||
output += &format!("{}| {:<24} | {:<48} |\n", pad, "", line);
|
||||
}
|
||||
}
|
||||
|
||||
// let first_line = lines.next().unwrap_or("");
|
||||
// output += &format!("{}| {:<24} | {:<48} |\n", pad, key_str, first_line);
|
||||
//
|
||||
// for line in lines {
|
||||
// output += &format!("{}| {:<24} | {:<48} |\n", pad, "", line);
|
||||
// }
|
||||
}
|
||||
|
||||
output += &format!(
|
||||
"{}+--------------------------+--------------------------------------------------+\n\n",
|
||||
pad
|
||||
);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn wrap_or_truncate(&self, s: &str, width: usize) -> Vec<String> {
|
||||
let mut lines = Vec::new();
|
||||
let mut current = s;
|
||||
|
||||
while !current.is_empty() {
|
||||
if current.len() <= width {
|
||||
lines.push(current.to_string());
|
||||
break;
|
||||
}
|
||||
|
||||
// Try to wrap at whitespace if possible
|
||||
let mut split_index = current[..width].rfind(' ').unwrap_or(width);
|
||||
if split_index == 0 {
|
||||
split_index = width;
|
||||
}
|
||||
|
||||
lines.push(current[..split_index].trim_end().to_string());
|
||||
current = current[split_index..].trim_start();
|
||||
}
|
||||
|
||||
lines
|
||||
}
|
||||
|
||||
fn format_value_as_string(&self, val: &Value, indent: usize) -> String {
|
||||
let pad = " ".repeat(indent * 2);
|
||||
let mut output = String::new();
|
||||
|
||||
match val {
|
||||
Value::Bool(b) => output += &format!("{}{}\n", pad, b),
|
||||
Value::U8(u) => output += &format!("{}{}\n", pad, u),
|
||||
Value::U16(u) => output += &format!("{}{}\n", pad, u),
|
||||
Value::U32(u) => output += &format!("{}{}\n", pad, u),
|
||||
Value::U64(u) => output += &format!("{}{}\n", pad, u),
|
||||
Value::I8(i) => output += &format!("{}{}\n", pad, i),
|
||||
Value::I16(i) => output += &format!("{}{}\n", pad, i),
|
||||
Value::I32(i) => output += &format!("{}{}\n", pad, i),
|
||||
Value::I64(i) => output += &format!("{}{}\n", pad, i),
|
||||
Value::F32(f) => output += &format!("{}{}\n", pad, f),
|
||||
Value::F64(f) => output += &format!("{}{}\n", pad, f),
|
||||
Value::Char(c) => output += &format!("{}{}\n", pad, c),
|
||||
Value::String(s) => output += &format!("{}{:<48}\n", pad, s),
|
||||
Value::Unit => output += &format!("{}<unit>\n", pad),
|
||||
Value::Bytes(bytes) => output += &format!("{}{:?}\n", pad, bytes),
|
||||
|
||||
Value::Option(opt) => match opt {
|
||||
Some(inner) => {
|
||||
output += &format!("{}Option:\n", pad);
|
||||
output += &self.format_value_as_string(inner, indent + 1);
|
||||
}
|
||||
None => output += &format!("{}None\n", pad),
|
||||
},
|
||||
|
||||
Value::Newtype(inner) => {
|
||||
output += &format!("{}Newtype:\n", pad);
|
||||
output += &self.format_value_as_string(inner, indent + 1);
|
||||
}
|
||||
|
||||
Value::Seq(seq) => {
|
||||
if seq.is_empty() {
|
||||
output += &format!("{}[]\n", pad);
|
||||
} else {
|
||||
output += &format!("{}[\n", pad);
|
||||
for item in seq {
|
||||
output += &self.format_value_as_string(item, indent + 1);
|
||||
}
|
||||
output += &format!("{}]\n", pad);
|
||||
}
|
||||
}
|
||||
|
||||
Value::Map(map) => {
|
||||
if map.is_empty() {
|
||||
output += &format!("{}<empty map>\n", pad);
|
||||
} else if indent == 0 {
|
||||
output += &self.format_map(map, indent);
|
||||
} else {
|
||||
for (k, v) in map {
|
||||
let key_str = match k {
|
||||
Value::String(s) => s.clone(),
|
||||
other => format!("{:?}", other),
|
||||
};
|
||||
|
||||
let val_str = self
|
||||
.format_value_as_string(v, indent + 1)
|
||||
.trim()
|
||||
.to_string();
|
||||
let val_lines: Vec<_> = val_str.lines().collect();
|
||||
|
||||
output +=
|
||||
&format!("{}{}: {}\n", pad, key_str, val_lines.first().unwrap_or(&""));
|
||||
for line in val_lines.iter().skip(1) {
|
||||
output += &format!("{} {}\n", pad, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
//TODO write test to check that the output is what it should be
|
||||
//
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::modules::dns::DnsScore;
|
||||
use crate::topology::{self, HAClusterTopology};
|
||||
|
||||
#[test]
|
||||
fn test_format_values_as_string() {
|
||||
let dns_score = Box::new(DnsScore::new(vec![], None));
|
||||
let print_score_output =
|
||||
<DnsScore as ScoreToString<HAClusterTopology>>::print_score_details(&dns_score);
|
||||
let expected_empty_dns_score_table = "\n+--------------------------+--------------------------------------------------+\n| score_name | DnsScore |\n+--------------------------+--------------------------------------------------+\n| dns_entries | [] |\n| register_dhcp_leases | None |\n+--------------------------+--------------------------------------------------+\n\n\n";
|
||||
assert_eq!(print_score_output, expected_empty_dns_score_table);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ pub struct LoadBalancerService {
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||
pub struct BackendServer {
|
||||
// TODO should not be a string, probably IPAddress
|
||||
pub address: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user