diff --git a/crates/cli-client/Cargo.toml b/crates/cli-client/Cargo.toml index 7b29ec9..5f52337 100644 --- a/crates/cli-client/Cargo.toml +++ b/crates/cli-client/Cargo.toml @@ -41,6 +41,7 @@ bincode = "2" toml = { version = "0.8" } hex = { version = "0.4" } dotenvy = { version = "0.15" } +comfy-table = { version = "7.2.1" } nostr = "0.44.2" nostr-sdk = "0.44.1" diff --git a/crates/cli-client/src/cli/browse.rs b/crates/cli-client/src/cli/browse.rs index 0654cc3..fe8b124 100644 --- a/crates/cli-client/src/cli/browse.rs +++ b/crates/cli-client/src/cli/browse.rs @@ -1,8 +1,8 @@ use crate::cli::Cli; use crate::cli::interactive::{ - SwapDisplay, TokenDisplay, display_swap_table, display_token_table, format_relative_time, format_settlement_asset, - truncate_with_ellipsis, + SwapDisplay, TokenDisplay, format_relative_time, format_settlement_asset, truncate_with_ellipsis, }; +use crate::cli::tables::{display_swap_table, display_token_table}; use crate::config::Config; use crate::error::Error; diff --git a/crates/cli-client/src/cli/interactive.rs b/crates/cli-client/src/cli/interactive.rs index b7d2335..0c78026 100644 --- a/crates/cli-client/src/cli/interactive.rs +++ b/crates/cli-client/src/cli/interactive.rs @@ -1,3 +1,4 @@ +use crate::cli::tables::display_token_table; use crate::error::Error; use std::io::{self, Write}; @@ -116,46 +117,6 @@ pub fn format_relative_time(expiry_timestamp: i64) -> String { } } -pub fn display_token_table(tokens: &[TokenDisplay]) { - if tokens.is_empty() { - println!(" (No tokens found)"); - return; - } - - println!( - " {:<3} | {:<18} | {:<14} | {:<18} | Contract", - "#", "Collateral/Token", "Strike/Token", "Expires" - ); - println!("{}", "-".repeat(80)); - - for token in tokens { - println!( - " {:<3} | {:<18} | {:<14} | {:<18} | {}", - token.index, token.collateral, token.settlement, token.expires, token.status - ); - } -} - -pub fn display_swap_table(swaps: &[SwapDisplay]) { - if swaps.is_empty() { - println!(" (No swaps found)"); - return; - } - - println!( - " {:<3} | {:<20} | {:<14} | {:<15} | Seller", - "#", "Price", "Wants", "Expires" - ); - println!("{}", "-".repeat(80)); - - for swap in swaps { - println!( - " {:<3} | {:<20} | {:<14} | {:<15} | {}", - swap.index, swap.offering, swap.wants, swap.expires, swap.seller - ); - } -} - pub fn prompt_selection(prompt: &str, max: usize) -> io::Result> { print!("{prompt} (1-{max}, or 'q' to quit): "); io::stdout().flush()?; diff --git a/crates/cli-client/src/cli/mod.rs b/crates/cli-client/src/cli/mod.rs index 7fdc903..b87f676 100644 --- a/crates/cli-client/src/cli/mod.rs +++ b/crates/cli-client/src/cli/mod.rs @@ -5,6 +5,7 @@ mod option; mod positions; mod swap; mod sync; +mod tables; mod tx; mod wallet; diff --git a/crates/cli-client/src/cli/positions.rs b/crates/cli-client/src/cli/positions.rs index 85327cf..e4f4e82 100644 --- a/crates/cli-client/src/cli/positions.rs +++ b/crates/cli-client/src/cli/positions.rs @@ -1,9 +1,10 @@ use crate::cli::Cli; use crate::cli::interactive::{ - EnrichedTokenEntry, GRANTOR_TOKEN_TAG, OPTION_TOKEN_TAG, TokenDisplay, display_token_table, - format_asset_value_with_tag, format_asset_with_tag, format_relative_time, format_settlement_asset, format_time_ago, + EnrichedTokenEntry, GRANTOR_TOKEN_TAG, OPTION_TOKEN_TAG, TokenDisplay, format_asset_value_with_tag, + format_asset_with_tag, format_relative_time, format_settlement_asset, format_time_ago, get_grantor_tokens_from_wallet, get_option_tokens_from_wallet, truncate_with_ellipsis, }; +use crate::cli::tables::{display_collateral_table, display_token_table, display_user_token_table}; use crate::config::Config; use crate::error::Error; use crate::metadata::ContractMetadata; @@ -167,46 +168,6 @@ pub struct UserTokenDisplay { pub contract: String, } -fn display_collateral_table(displays: &[CollateralDisplay]) { - if displays.is_empty() { - println!(" (No locked assets found)"); - return; - } - - println!( - " {:<3} | {:<18} | {:<14} | {:<18} | Contract", - "#", "Locked Assets", "Settlement", "Expires" - ); - println!("{}", "-".repeat(80)); - - for display in displays { - println!( - " {:<3} | {:<18} | {:<14} | {:<18} | {}", - display.index, display.collateral, display.settlement, display.expires, display.contract - ); - } -} - -fn display_user_token_table(displays: &[UserTokenDisplay]) { - if displays.is_empty() { - println!(" (No option/grantor tokens found)"); - return; - } - - println!( - " {:<3} | {:<8} | {:<10} | {:<14} | {:<18} | Contract", - "#", "Type", "Amount", "Strike/Token", "Expires" - ); - println!("{}", "-".repeat(90)); - - for display in displays { - println!( - " {:<3} | {:<8} | {:<10} | {:<14} | {:<18} | {}", - display.index, display.token_type, display.amount, display.strike, display.expires, display.contract - ); - } -} - /// Build locked asset displays, filtering to only show collateral or settlement assets (not reissuance tokens) async fn build_collateral_displays( wallet: &crate::wallet::Wallet, diff --git a/crates/cli-client/src/cli/tables.rs b/crates/cli-client/src/cli/tables.rs new file mode 100644 index 0000000..d4ac14b --- /dev/null +++ b/crates/cli-client/src/cli/tables.rs @@ -0,0 +1,123 @@ +use crate::cli::interactive::{SwapDisplay, TokenDisplay}; +use crate::cli::positions::{CollateralDisplay, UserTokenDisplay}; +use comfy_table::presets::UTF8_FULL; +use comfy_table::{Attribute, Cell, Table}; + +trait TableData { + fn get_header() -> Vec; + fn to_row(&self) -> Vec; +} + +impl TableData for TokenDisplay { + fn get_header() -> Vec { + vec!["#", "Collateral/Token", "Strike/Token", "Expires", "Contract"] + .into_iter() + .map(String::from) + .collect() + } + fn to_row(&self) -> Vec { + vec![ + self.index.to_string(), + self.collateral.clone(), + self.settlement.clone(), + self.expires.clone(), + self.status.clone(), + ] + } +} + +impl TableData for SwapDisplay { + fn get_header() -> Vec { + vec!["#", "Price", "Wants", "Expires", "Seller"] + .into_iter() + .map(String::from) + .collect() + } + fn to_row(&self) -> Vec { + vec![ + self.index.to_string(), + self.offering.clone(), + self.wants.clone(), + self.expires.clone(), + self.seller.clone(), + ] + } +} + +impl TableData for CollateralDisplay { + fn get_header() -> Vec { + vec!["#", "Locked Assets", "Settlement", "Expires", "Contract"] + .into_iter() + .map(String::from) + .collect() + } + fn to_row(&self) -> Vec { + vec![ + self.index.to_string(), + self.collateral.clone(), + self.settlement.clone(), + self.expires.clone(), + self.contract.clone(), + ] + } +} + +impl TableData for UserTokenDisplay { + fn get_header() -> Vec { + vec!["#", "Type", "Amount", "Strike/Token", "Expires", "Contract"] + .into_iter() + .map(String::from) + .collect() + } + fn to_row(&self) -> Vec { + vec![ + self.index.to_string(), + self.token_type.clone(), + self.amount.clone(), + self.strike.clone(), + self.expires.clone(), + self.contract.clone(), + ] + } +} + +fn render_table(items: &[T], empty_msg: &str) { + if items.is_empty() { + println!(" ({empty_msg})"); + return; + } + + let mut table = Table::new(); + + table.load_preset(UTF8_FULL); + + let header_cells: Vec = T::get_header() + .into_iter() + .map(|h| Cell::new(h).add_attribute(Attribute::Bold)) + .collect(); + table.set_header(header_cells); + + for item in items { + table.add_row(item.to_row()); + } + + for line in table.to_string().lines() { + println!(" {line}"); + } +} + +pub fn display_token_table(tokens: &[TokenDisplay]) { + render_table(tokens, "No tokens found"); +} + +pub fn display_swap_table(swaps: &[SwapDisplay]) { + render_table(swaps, "No swaps found"); +} + +pub fn display_collateral_table(displays: &[CollateralDisplay]) { + render_table(displays, "No locked assets found"); +} + +pub fn display_user_token_table(displays: &[UserTokenDisplay]) { + render_table(displays, "No option/grantor tokens found"); +}