use pulse_wire::terminal::{ Alert, AlertLevel, InspectItem, InspectTarget, MarketItem, MarketOverview, Status, }; use pulse_wire::general::{Direction, EventLog, LogKind, Position, Signal}; use pulse_wire::units::format_f64; pub trait Formatted { fn get_formatted(&self) -> Vec; } impl Formatted for InspectTarget { fn get_formatted(&self) -> Vec { match self { Self::None => vec!["\x1b[2mnothing to inspect\x1b[0m".to_string()], Self::Some(items) => items .iter() .map(|item| match item { InspectItem::F64(v) => format_f64(*v), InspectItem::USD(v) => format!("${}", format_f64(*v)), InspectItem::Symbol(v) => format!("\x1b[35m{v}\x1b[0m"), InspectItem::String(v) => v.clone(), }) .collect(), } } } impl Formatted for Alert { fn get_formatted(&self) -> Vec { vec![ format!( "{}{}\x1b[0m", match &self.level { AlertLevel::Low => "\x1b[34m", AlertLevel::Medium => "\x1b[33m", AlertLevel::High => "\x1b[31m", }, self.level ), self.message.clone(), ] } } impl Formatted for EventLog { fn get_formatted(&self) -> Vec { vec![ format!( "{}{}\x1b[0m", match &self.kind { LogKind::Info => "\x1b[34m", LogKind::Debug => "\x1b[36m", LogKind::Err => "\x1b[31m", LogKind::Warn => "\x1b[33m", }, self.kind ), format!("(\x1b[37m{}\x1b[0m)", self.name), self.message.clone(), ] } } impl Formatted for Signal { fn get_formatted(&self) -> Vec { vec![ if matches!(self.kind, Direction::Buy) { format!("\x1b[32m{}\x1b[0m", self.kind) } else { format!("\x1b[31m{}\x1b[0m", self.kind) }, format!("\x1b[35m{}\x1b[0m", self.symbol), self.price.to_string(), ] } } impl Formatted for MarketItem { fn get_formatted(&self) -> Vec { vec![ self.symbol.to_string(), self.price.to_string(), self.volume_24h.to_string(), format!( "{} {}", if self.trend.is_sign_positive() { "\x1b[32m▲\x1b[0m" } else { "\x1b[31m▼\x1b[0m" }, format_f64(self.trend.abs()) ), ] } } impl Formatted for Position { fn get_formatted(&self) -> Vec { vec![ self.symbol.to_string(), format_f64(self.size), self.entry_price.to_string(), format!( "{}{}\x1b[0m", if self.profit.0.is_sign_positive() { "\x1b[32m" } else { "\x1b[31m" }, self.profit, ), ] } } struct Property(&'static str, String); impl Formatted for MarketOverview { fn get_formatted(&self) -> Vec { let mut o = vec![ Property("TREND", format!("{}", self.trend)), Property("VOLATILITY", format!("{}", self.volatility)), Property( "PRESSURE", if self.pressure.is_sign_positive() { format!("\x1b[32m{:.3}\x1b[0m", self.pressure) } else { format!("\x1b[31m{:.3}\x1b[0m", self.pressure) }, ), ] .get_formatted(); o.push(format!( "\n{}", apply_padding(self.alerts.get_formatted()).join("\n") )); o } } impl Formatted for Status { fn get_formatted(&self) -> Vec { vec![ Property("Feed", format!("{:?}", self.feed)), Property("Exchange", self.exchange.clone()), Property("DEX", self.dex.clone()), Property("Latency", format!("{} ms", self.latency)), ] .get_formatted() } } impl Formatted for Property { fn get_formatted(&self) -> Vec { vec![format!("\x1b[91m{}\x1b[0m", self.0), self.1.clone()] } } impl Formatted for Vec { fn get_formatted(&self) -> Vec { let mut output = Vec::new(); let rows: Vec> = self.iter().map(|v| v.get_formatted()).collect(); if rows.is_empty() { return output; } let column_count = rows.iter().map(|r| r.len()).max().unwrap_or(0); let mut widths = vec![0; column_count]; for row in &rows { for (i, col) in row.iter().enumerate() { widths[i] = widths[i].max(col.len()); } } for row in rows { let mut row_out = String::new(); for (col_idx, col) in row.iter().enumerate() { let width = widths[col_idx]; row_out.push_str(&format!("{:) -> Vec { for item in &mut items { item.insert(0, ' '); } items }