Closed wbuck closed 1 year ago
Hi @wbuck
So I clearly see 4 ways to do it.
tabled::settings::Format
(The shortest/simpliest one)TableOption
/CellOption
(Probably more efficient)Object
to pass to Modify::new
(I think good one)Bellow I'll demonstrate first 2.
REMEMBER that color
feature must be set if colors are used for proper rendering.
In both cases, you'll get the following
use tabled::{
grid::{
records::{ExactRecords, Records},
color::Color as Colorization,
config::{ColoredConfig, Entity},
records::vec_records::{CellInfo, VecRecords},
},
settings::{object::Rows, style::Style, Alignment, CellOption, Color, Modify},
Table, Tabled,
};
#[derive(Tabled)]
struct AzureVM {
subscription_id: String,
resource_group: String,
#[tabled(display_with("display_vm_status", self))]
status: VMStatus,
#[tabled(display_with = "display_vm_path")]
path: Option<String>,
}
impl AzureVM {
fn new(
subscription_id: &str,
resource_group: &str,
status: VMStatus,
path: Option<String>,
) -> Self {
Self {
subscription_id: subscription_id.to_string(),
resource_group: resource_group.to_string(),
status,
path,
}
}
}
#[derive(Tabled)]
enum VMStatus {
Up,
Down,
Stopped,
Unknown,
}
fn display_vm_status(vm: &AzureVM) -> String {
let mut status = display_status(&vm.status);
let is_wbuck_owner = matches!(&vm.path, Some(path) if path.starts_with("wbuck/"));
if is_wbuck_owner {
status.push_str(" (don't worry)");
}
status
}
fn display_status(status: &VMStatus) -> String {
match status {
VMStatus::Up => "up".to_string(),
VMStatus::Down => "down".to_string(),
VMStatus::Stopped => "stopped".to_string(),
VMStatus::Unknown => "unknown".to_string(),
}
}
fn display_vm_path(path: &Option<String>) -> String {
match path {
Some(path) => format!("vm/{path}"),
None => String::from("vm/*"),
}
}
fn collect_vms() -> Vec<AzureVM> {
vec![
AzureVM::new("123-123-123", "prod", VMStatus::Up, None),
AzureVM::new("123-123-123", "prod", VMStatus::Up, None),
AzureVM::new("123-123-123", "prod", VMStatus::Unknown, None),
AzureVM::new("123-123-123", "integration", VMStatus::Down, None),
AzureVM::new("123-000-000", "dev", VMStatus::Stopped, None),
AzureVM::new("123-111-111", "dev", VMStatus::Up, Some("wbuck/1".to_string())),
]
}
fn main() {
let vms = collect_vms();
let mut table = Table::new(&vms);
table
.with(Style::re_structured_text())
.with(Modify::new(Rows::first()).with(Alignment::center()));
println!("{table}");
}
fn colorize_status(status: &str) -> String {
let mut buf = String::new();
let color = vm_status_color(status);
let _ = color.colorize(&mut buf, status);
buf
}
fn vm_status_color(status: &str) -> Color {
match status {
"up" => Color::BG_BLACK | Color::FG_BRIGHT_GREEN,
"down" => Color::BG_BLUE | Color::FG_BRIGHT_GREEN,
"stopped" => Color::BG_BRIGHT_RED | Color::FG_BRIGHT_GREEN | Color::BOLD,
"unknown" => Color::BG_YELLOW | Color::FG_BRIGHT_GREEN,
_ => Color::default(),
}
}
use tabled::settings::object::{Object, Columns, Rows};
use tabled::settings::Format;
let status_column = Columns::single(2).not(Rows::first());
table.with(Modify::new(status_column).with(Format::content(colorize_status)));
#[derive(Clone)]
struct StatusColorization;
impl CellOption<VecRecords<CellInfo<String>>, ColoredConfig> for StatusColorization {
fn change(
self,
records: &mut VecRecords<CellInfo<String>>,
cfg: &mut ColoredConfig,
entity: Entity,
) {
let (count_rows, count_cols) = (records.count_rows(), records.count_columns());
for (row, col) in entity.iter(count_rows, count_cols) {
let status = records[row][col].as_ref();
let color = vm_status_color(status);
cfg.set_color(Entity::Cell(row, col), color.into());
}
}
}
use tabled::settings::object::{Object, Columns, Rows};
let status_column = Columns::single(2).not(Rows::first());
table.with(Modify::new(status_column).with(StatusColorization));
I think it shall help, Let me know if you have any questions.
Take care.
Clearly such a minor task but ... it's too hard than it shall be ....
At first it would be much MUCH MUCH better to operate not on string but on real values (IMHO).
I was already thinking a bit how to not lose data types. Essentially to be able to configure table
before we convert data to string. But yet haven't came up with a good interface for it.......
(It's already possible but quite tediously by configuring a *Config
)
If you have ideas please you can share.
As a second improvement your issue give me a though that we could provide a new locator
to set a lambda, something like Modify::new(Search::content(|content|))
We already have tabled::settings::locator::ByColumnName
.
Ahhh yes the example might be too large, but it's mainly a declaration and initialization of AzureVM
structures.
I hope the examples are clear.
So with a new locator you could do. (But it would be a bit inefficient :()
table
.with(Modify::new(ByColumnName("status").and(ByContent("UP").or(ByContent("UNKNOWN"))).with(Color::RED))
.with(Modify::new(ByColumnName("status").and(ByContent("DOWN")).with(Color::BLUE))
Self-Note: ByColumnName
could be replaced by ByContent
and rows::first combination.
@zhiburt thank-you for the excellent answer(s) (and library I'm impressed by the API you've designed).
I really like the second option you provided using CellOption
and I'm going to give it a go.
What I did for the time being is to add the owo-colors crate and then use it in combination with Format::content
:
let format = Format::content(|status| {
match status {
"VM deallocated" => status.on_red().black().to_string(),
"VM running" => status.on_green().black().to_string(),
// other statuses
}
});
table
.with(get_style())
.with(Modify::new(Columns::last().not(Rows::first())).with(format));
But as I noted above I'd like to use the CellOption
example you showed instead and not depend on yet another library in this case.
Again, thank-you for your response and detailed examples, I really appreciate it!
This issue solved my problem. Thank you!
I have a table displaying information about some Azure virtual machines.
I was wondering if there was a built in way to conditionally change the background color of the cells in the status column based on what the status is?
I see that I could individually apply a
Cell
style via the following:Here I'm applying a red background unconditionally to a single cell. I could use this of course but I'm just wondering if I could set a "rule" for that column which would change the color of each cell depending on what the value of the cell is.