near-cli-rs 0.25.1

human-friendly console utility that helps to interact with NEAR Protocol from command line.
Documentation
#![allow(clippy::arc_with_non_send_sync)]
pub use common::CliResult;

use inquire::ui::{Color, RenderConfig, StyleSheet, Styled};

pub mod commands;
pub mod common;
pub mod config;
pub mod js_command_match;
pub mod network;
pub mod network_for_transaction;
pub mod network_view_at_block;
pub mod transaction_signature_options;
pub mod types;
pub mod utils_command;

#[derive(Debug, Clone)]
pub struct GlobalContext {
    pub config: crate::config::Config,
    pub offline: bool,
    pub verbosity: Verbosity,
}

#[derive(Debug, Copy, Clone, Default)]
pub enum Verbosity {
    #[default]
    Interactive,
    TeachMe,
    Quiet,
}

pub fn setup_tracing(verbosity: Verbosity) -> CliResult {
    setup_tracing_with_extra_directives(verbosity, &[])
}

pub fn setup_tracing_with_extra_directives(
    verbosity: Verbosity,
    extra_directives: &[&str],
) -> CliResult {
    use tracing::field::{Field, Visit};
    use tracing::{Event, Level, Subscriber};
    use tracing_indicatif::IndicatifLayer;
    use tracing_indicatif::style::ProgressStyle;
    use tracing_subscriber::EnvFilter;
    use tracing_subscriber::layer::SubscriberExt;
    use tracing_subscriber::util::SubscriberInitExt;
    use tracing_subscriber::{
        fmt::{FmtContext, FormatEvent, FormatFields, format::Writer},
        registry::LookupSpan,
    };

    struct RawMessageVisitor<'a> {
        writer: Writer<'a>,
        result: std::fmt::Result,
        is_message_printed: bool,
    }

    impl<'a> Visit for RawMessageVisitor<'a> {
        fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
            if field.name() == "message" {
                self.result = write!(self.writer, "{:?}", value);
                self.is_message_printed = true;
            }
        }

        fn record_str(&mut self, field: &Field, value: &str) {
            if field.name() == "message" {
                self.result = write!(self.writer, "{}", value);
                self.is_message_printed = true;
            }
        }
    }

    struct SimpleFormatter;

    impl<S, N> FormatEvent<S, N> for SimpleFormatter
    where
        S: Subscriber + for<'a> LookupSpan<'a>,
        N: for<'a> FormatFields<'a> + 'static,
    {
        fn format_event(
            &self,
            _ctx: &FmtContext<'_, S, N>,
            mut writer: Writer<'_>,
            event: &Event<'_>,
        ) -> std::fmt::Result {
            let level = *event.metadata().level();
            let (icon, color_code) = match level {
                Level::TRACE => ("TRACE ", "\x1b[35m"),   // Magenta
                Level::DEBUG => ("DEBUG ", "\x1b[34m"),   // Blue
                Level::INFO => ("", ""),                  // Default
                Level::WARN => ("Warning: ", "\x1b[33m"), // Yellow
                Level::ERROR => ("ERROR ", "\x1b[31m"),   // Red
            };

            write!(writer, "{color_code}{icon}")?;

            let mut visitor = RawMessageVisitor {
                writer: writer.by_ref(),
                result: Ok(()),
                is_message_printed: false,
            };
            event.record(&mut visitor);
            visitor.result?;

            let mut has_fields = false;
            event.record(
                &mut |field: &tracing::field::Field, value: &dyn std::fmt::Debug| {
                    if field.name() != "message" {
                        if !has_fields {
                            let _ = write!(writer, " ");
                            has_fields = true;
                        }
                        let _ = write!(writer, " {}={:?}", field.name(), value);
                    }
                },
            );

            write!(writer, "\x1b[0m")?;

            writeln!(writer)
        }
    }

    match verbosity {
        Verbosity::TeachMe => {
            let mut env_filter = EnvFilter::from_default_env()
                .add_directive(tracing::Level::WARN.into())
                .add_directive("near_teach_me=info".parse()?)
                .add_directive("near_cli_rs=info".parse()?);
            for directive in extra_directives {
                env_filter = env_filter.add_directive(directive.parse()?);
            }
            tracing_subscriber::registry()
                .with(tracing_subscriber::fmt::layer().event_format(SimpleFormatter))
                .with(env_filter)
                .init();
        }
        Verbosity::Interactive => {
            let indicatif_layer = IndicatifLayer::new()
                .with_progress_style(
                    ProgressStyle::with_template(
                        "{spinner:.blue}{span_child_prefix} {span_name} {msg} {span_fields}",
                    )
                    .unwrap()
                    .tick_strings(&["", "", "", ""]),
                )
                .with_span_child_prefix_symbol("");
            let mut env_filter = EnvFilter::from_default_env()
                .add_directive(tracing::Level::WARN.into())
                .add_directive("near_cli_rs=info".parse()?);
            for directive in extra_directives {
                env_filter = env_filter.add_directive(directive.parse()?);
            }
            tracing_subscriber::registry()
                .with(
                    tracing_subscriber::fmt::layer()
                        .event_format(SimpleFormatter)
                        .with_writer(indicatif_layer.get_stderr_writer()),
                )
                .with(indicatif_layer)
                .with(env_filter)
                .init();
        }
        Verbosity::Quiet => {}
    };
    Ok(())
}

pub fn get_global_render_config() -> RenderConfig<'static> {
    let mut render_config = RenderConfig::default_colored();
    render_config.prompt_prefix = Styled::new("").with_fg(Color::DarkGreen);
    render_config.answered_prompt_prefix = Styled::new("").with_fg(Color::DarkGreen);
    render_config.highlighted_option_prefix = Styled::new("").with_fg(Color::DarkGreen);
    render_config.unhighlighted_option_prefix = Styled::new("").with_fg(Color::DarkGrey);
    render_config.selected_checkbox = Styled::new("").with_fg(Color::LightGreen);
    render_config.scroll_up_prefix = Styled::new("↑○").with_fg(Color::DarkGrey);
    render_config.scroll_down_prefix = Styled::new("↓○").with_fg(Color::DarkGrey);
    render_config.unselected_checkbox = Styled::new("").with_fg(Color::DarkGrey);
    render_config.option = StyleSheet::new().with_fg(Color::DarkGrey);
    render_config.selected_option = Some(StyleSheet::new().with_fg(Color::Grey));

    render_config.new_line_prefix = Some(Styled::new("").with_fg(Color::LightBlue));
    render_config.answer_from_new_line = true;

    render_config.error_message = render_config
        .error_message
        .with_prefix(Styled::new("").with_fg(Color::LightRed));

    render_config.text_input = StyleSheet::new().with_fg(Color::LightYellow);

    render_config.answer = StyleSheet::new().with_fg(Color::DarkGrey);

    render_config.help_message = StyleSheet::new().with_fg(Color::DarkYellow);

    render_config
}