soroban_cli/
print.rs

1use std::io::{self, Write};
2use std::{env, fmt::Display};
3
4use crate::xdr::{Error as XdrError, Transaction};
5
6use crate::{
7    config::network::Network, utils::explorer_url_for_transaction, utils::transaction_hash,
8};
9
10#[derive(Clone)]
11pub struct Print {
12    pub quiet: bool,
13}
14
15impl Print {
16    pub fn new(quiet: bool) -> Print {
17        Print { quiet }
18    }
19
20    pub fn print<T: Display + Sized>(&self, message: T) {
21        if !self.quiet {
22            eprint!("{message}");
23        }
24    }
25
26    pub fn println<T: Display + Sized>(&self, message: T) {
27        if !self.quiet {
28            eprintln!("{message}");
29        }
30    }
31
32    pub fn clear_previous_line(&self) {
33        if !self.quiet {
34            if cfg!(windows) {
35                eprint!("\x1b[2A\r\x1b[2K");
36            } else {
37                eprint!("\x1b[1A\x1b[2K\r");
38            }
39
40            io::stderr().flush().unwrap();
41        }
42    }
43
44    // Some terminals like vscode's and macOS' default terminal will not render
45    // the subsequent space if the emoji codepoints size is 2; in this case,
46    // we need an additional space. We also need an additional space if `TERM_PROGRAM` is not
47    // defined (e.g. vhs running in a docker container).
48    pub fn compute_emoji<T: Display + Sized>(&self, emoji: T) -> String {
49        if should_add_additional_space()
50            && (emoji.to_string().chars().count() == 2 || format!("{emoji}") == " ")
51        {
52            return format!("{emoji} ");
53        }
54
55        emoji.to_string()
56    }
57
58    /// # Errors
59    ///
60    /// Might return an error
61    pub fn log_transaction(
62        &self,
63        tx: &Transaction,
64        network: &Network,
65        show_link: bool,
66    ) -> Result<(), XdrError> {
67        let tx_hash = transaction_hash(tx, &network.network_passphrase)?;
68        let hash = hex::encode(tx_hash);
69
70        self.infoln(format!("Transaction hash is {hash}").as_str());
71
72        if show_link {
73            if let Some(url) = explorer_url_for_transaction(network, &hash) {
74                self.linkln(url);
75            }
76        }
77
78        Ok(())
79    }
80}
81
82macro_rules! create_print_functions {
83    ($name:ident, $nameln:ident, $icon:expr) => {
84        impl Print {
85            #[allow(dead_code)]
86            pub fn $name<T: Display + Sized>(&self, message: T) {
87                if !self.quiet {
88                    eprint!("{} {}", self.compute_emoji($icon), message);
89                }
90            }
91
92            #[allow(dead_code)]
93            pub fn $nameln<T: Display + Sized>(&self, message: T) {
94                if !self.quiet {
95                    eprintln!("{} {}", self.compute_emoji($icon), message);
96                }
97            }
98        }
99    };
100}
101
102fn should_add_additional_space() -> bool {
103    const TERMS: &[&str] = &["Apple_Terminal", "vscode", "unknown"];
104    let term_program = env::var("TERM_PROGRAM").unwrap_or("unknown".to_string());
105
106    if TERMS.contains(&term_program.as_str()) {
107        return true;
108    }
109
110    false
111}
112
113create_print_functions!(bucket, bucketln, "đŸĒŖ");
114create_print_functions!(check, checkln, "✅");
115create_print_functions!(error, errorln, "❌");
116create_print_functions!(globe, globeln, "🌎");
117create_print_functions!(info, infoln, "â„šī¸");
118create_print_functions!(link, linkln, "🔗");
119create_print_functions!(plus, plusln, "➕");
120create_print_functions!(save, saveln, "💾");
121create_print_functions!(search, searchln, "🔎");
122create_print_functions!(warn, warnln, "âš ī¸");
123create_print_functions!(exclaim, exclaimln, "â—ī¸");
124create_print_functions!(arrow, arrowln, "âžĄī¸");
125create_print_functions!(log, logln, "📔");
126create_print_functions!(event, eventln, "📅");
127create_print_functions!(blank, blankln, "  ");
128create_print_functions!(gear, gearln, "âš™ī¸");
129create_print_functions!(dir, dirln, "📁");