diff --git a/Cargo.lock b/Cargo.lock index 2641a8007287..9c28e01d0c61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2324,17 +2324,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "comfy-table" -version = "7.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" -dependencies = [ - "crossterm", - "unicode-segmentation", - "unicode-width 0.2.1", -] - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -2806,28 +2795,6 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "crossterm" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" -dependencies = [ - "bitflags 2.9.1", - "crossterm_winapi", - "parking_lot", - "rustix 0.38.44", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - [[package]] name = "crowd-funding" version = "0.1.0" @@ -5772,7 +5739,6 @@ dependencies = [ "clap", "clap-markdown", "colored", - "comfy-table", "convert_case", "counter", "counter-no-graphql", @@ -10845,12 +10811,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" -[[package]] -name = "unicode-width" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" - [[package]] name = "unicode-xid" version = "0.2.6" @@ -11453,7 +11413,7 @@ dependencies = [ "bumpalo", "leb128", "memchr", - "unicode-width 0.1.14", + "unicode-width", "wasm-encoder 0.217.1", ] diff --git a/Cargo.toml b/Cargo.toml index 7d7c1a4130de..67fcdde97995 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,7 +100,6 @@ chrono = { version = "0.4.35", default-features = false } clap = { version = "4", features = ["cargo", "derive", "env"] } clap-markdown = "0.1.3" colored = "2.1.0" -comfy-table = "7.1.0" console_error_panic_hook = "0.1.7" convert_case = "0.6.0" criterion = { version = "0.5.1", default-features = false } diff --git a/linera-base/src/data_types.rs b/linera-base/src/data_types.rs index 5b1f58ed990e..64a068bb1234 100644 --- a/linera-base/src/data_types.rs +++ b/linera-base/src/data_types.rs @@ -751,6 +751,14 @@ impl ChainOrigin { pub fn is_child(&self) -> bool { matches!(self, ChainOrigin::Child { .. }) } + + /// Returns the root chain number, if this is a root chain. + pub fn root(&self) -> Option { + match self { + ChainOrigin::Root(i) => Some(*i), + ChainOrigin::Child { .. } => None, + } + } } /// A number identifying the configuration of the chain (aka the committee). diff --git a/linera-service/Cargo.toml b/linera-service/Cargo.toml index 6d678b15c715..fe3f698c2882 100644 --- a/linera-service/Cargo.toml +++ b/linera-service/Cargo.toml @@ -80,7 +80,6 @@ chrono = { workspace = true, features = ["clock"] } clap.workspace = true clap-markdown.workspace = true colored.workspace = true -comfy-table.workspace = true convert_case.workspace = true current_platform = "0.2.0" custom_debug_derive.workspace = true diff --git a/linera-service/src/cli/main.rs b/linera-service/src/cli/main.rs index 4b9c7f9ee830..f330430da09f 100644 --- a/linera-service/src/cli/main.rs +++ b/linera-service/src/cli/main.rs @@ -2417,7 +2417,6 @@ async fn run(options: &ClientOptions) -> Result { short, owned, } => { - let start_time = Instant::now(); let wallet = options.wallet()?; let chain_ids = if let Some(chain_id) = chain_id { ensure!(!owned, "Cannot specify both --owned and a chain ID"); @@ -2434,7 +2433,6 @@ async fn run(options: &ClientOptions) -> Result { } else { wallet::pretty_print(&wallet, chain_ids); } - info!("Wallet shown in {} ms", start_time.elapsed().as_millis()); Ok(0) } diff --git a/linera-service/src/wallet.rs b/linera-service/src/wallet.rs index b27cbaf404fb..8ba89a6cf299 100644 --- a/linera-service/src/wallet.rs +++ b/linera-service/src/wallet.rs @@ -1,64 +1,95 @@ // Copyright (c) Zefchain Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use comfy_table::{ - modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL, Attribute, Cell, Color, ContentArrangement, - Table, +use linera_base::{ + data_types::{ChainDescription, ChainOrigin}, + identifiers::ChainId, }; -use linera_base::identifiers::ChainId; pub use linera_client::wallet::*; pub fn pretty_print(wallet: &Wallet, chain_ids: impl IntoIterator) { - let mut table = Table::new(); - table - .load_preset(UTF8_FULL) - .apply_modifier(UTF8_ROUND_CORNERS) - .set_content_arrangement(ContentArrangement::Dynamic) - .set_header(vec![ - Cell::new("Chain ID").add_attribute(Attribute::Bold), - Cell::new("Latest Block").add_attribute(Attribute::Bold), - ]); - for chain_id in chain_ids { + let chain_ids: Vec<_> = chain_ids.into_iter().collect(); + let total_chains = chain_ids.len(); + + if total_chains == 0 { + println!("No chains in wallet."); + return; + } + + let plural_s = if total_chains == 1 { "" } else { "s" }; + println!("\n\x1b[1mWALLET ({total_chains} chain{plural_s} in total)\x1b[0m",); + + let mut chains = chain_ids + .into_iter() + .map(|chain_id| ChainDetails::new(chain_id, wallet)) + .collect::>(); + // Print first the default, then the admin chain, then other root chains, and finally the + // child chains. + chains.sort_unstable_by_key(|chain| { + let root_id = chain + .origin + .and_then(|origin| origin.root()) + .unwrap_or(u32::MAX); + let chain_id = chain.user_chain.chain_id; + (!chain.is_default, !chain.is_admin, root_id, chain_id) + }); + for chain in chains { + println!(); + chain.print_paragraph(); + } +} + +struct ChainDetails<'a> { + is_default: bool, + is_admin: bool, + origin: Option, + user_chain: &'a UserChain, +} + +impl<'a> ChainDetails<'a> { + fn new(chain_id: ChainId, wallet: &'a Wallet) -> Self { let Some(user_chain) = wallet.chains.get(&chain_id) else { panic!("Chain {} not found.", chain_id); }; - update_table_with_chain( - &mut table, - chain_id, + ChainDetails { + is_default: Some(chain_id) == wallet.default, + is_admin: chain_id == wallet.genesis_admin_chain(), + origin: wallet + .genesis_config() + .chains + .iter() + .find(|description| description.id() == chain_id) + .map(ChainDescription::origin), user_chain, - Some(chain_id) == wallet.default, - ); + } } - println!("{}", table); -} -fn update_table_with_chain( - table: &mut Table, - chain_id: ChainId, - user_chain: &UserChain, - is_default_chain: bool, -) { - let chain_id_cell = if is_default_chain { - Cell::new(format!("{}", chain_id)).fg(Color::Green) - } else { - Cell::new(format!("{}", chain_id)) - }; - let account_owner = user_chain.owner; - table.add_row(vec![ - chain_id_cell, - Cell::new(format!( - r#"AccountOwner: {} -Block Hash: {} -Timestamp: {} -Next Block Height: {}"#, - account_owner - .as_ref() - .map_or_else(|| "-".to_string(), |o| o.to_string()), - user_chain - .block_hash - .map_or_else(|| "-".to_string(), |bh| bh.to_string()), - user_chain.timestamp, - user_chain.next_block_height - )), - ]); + fn print_paragraph(&self) { + let title = if self.is_admin { + "Admin Chain".to_string() + } else { + match self.origin { + Some(ChainOrigin::Root(i)) => format!("Root Chain {i}"), + _ => "Child Chain".to_string(), + } + }; + let default_marker = if self.is_default { " [DEFAULT]" } else { "" }; + + // Print chain header in bold + println!("\x1b[1m{}{}\x1b[0m", title, default_marker); + println!(" Chain ID: {}", self.user_chain.chain_id); + if let Some(owner) = &self.user_chain.owner { + println!(" Owner: {owner}"); + } else { + println!(" Owner: No owner key"); + } + println!(" Timestamp: {}", self.user_chain.timestamp); + println!(" Blocks: {}", self.user_chain.next_block_height); + if let Some(hash) = self.user_chain.block_hash { + println!(" Latest Block: {}", hash); + } + if self.user_chain.pending_proposal.is_some() { + println!(" Status: ⚠ Pending proposal"); + } + } }