wallet_rs_cli/
cli.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5/// Main entry point for the wallet cli.
6/// Structue of the CLI is extremely influenced from reth.
7/// https://github.com/paradigmxyz/reth/tree/main/bin/reth
8use crate::metamask;
9use clap::{ArgAction, Args, Parser, Subcommand};
10use tracing::{metadata::LevelFilter, Level};
11use tracing_subscriber::{filter::Directive, EnvFilter};
12
13/// Parse CLI options, set up logging and run the chosen command.
14pub async fn run() -> eyre::Result<()> {
15    // Parse CLI options
16    let opt = Cli::parse();
17
18    // Set up logging based on the verbosity level
19    let filter =
20        EnvFilter::builder().with_default_directive(opt.verbosity.directive()).from_env_lossy();
21    tracing_subscriber::fmt().with_env_filter(filter).init();
22
23    // Run the chosen command
24    match opt.command {
25        Commands::Metamask(m) => m.run().await,
26    }
27}
28
29/// Commands to be executed
30#[derive(Subcommand)]
31pub enum Commands {
32    /// Run the metamask command utilities
33    #[command(name = "metamask")]
34    Metamask(metamask::Command),
35}
36
37#[derive(Parser)]
38#[command(author, version = "0.1", about = "wallet-rs-cli", long_about = None)]
39struct Cli {
40    /// The command to run
41    #[clap(subcommand)]
42    command: Commands,
43
44    #[clap(flatten)]
45    verbosity: Verbosity,
46}
47
48#[derive(Args)]
49#[command(next_help_heading = "Display")]
50struct Verbosity {
51    /// Set the minimum log level.
52    ///
53    /// -v      Errors
54    /// -vv     Warnings
55    /// -vvv    Info
56    /// -vvvv   Debug
57    /// -vvvvv  Traces
58    #[clap(short, long, action = ArgAction::Count, global = true, default_value_t = 3, verbatim_doc_comment, help_heading = "Display")]
59    verbosity: u8,
60
61    /// Silence all log output.
62    #[clap(long, alias = "silent", short = 'q', global = true, help_heading = "Display")]
63    quiet: bool,
64}
65
66impl Verbosity {
67    /// Get the corresponding [Directive] for the given verbosity, or none if the verbosity
68    /// corresponds to silent.
69    fn directive(&self) -> Directive {
70        if self.quiet {
71            LevelFilter::OFF.into()
72        } else {
73            let level = match self.verbosity - 1 {
74                0 => Level::ERROR,
75                1 => Level::WARN,
76                2 => Level::INFO,
77                3 => Level::DEBUG,
78                _ => Level::TRACE,
79            };
80
81            level.into()
82        }
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use eyre::Result;
90
91    #[test]
92    fn test_cli_parse() -> Result<()> {
93        // Test that the Cli struct can be parsed from command line arguments
94        let cli = Cli::parse_from(["wallet-rs-cli", "metamask"]);
95        assert!(matches!(cli.command, Commands::Metamask(_)));
96        Ok(())
97    }
98
99    #[test]
100    fn test_verbosity() {
101        // Test that the verbosity level is correctly parsed
102        let verbosity = Verbosity { verbosity: 1, quiet: false };
103        assert_eq!(verbosity.directive(), LevelFilter::ERROR.into());
104
105        let verbosity = Verbosity { verbosity: 2, quiet: false };
106        assert_eq!(verbosity.directive(), LevelFilter::WARN.into());
107
108        let verbosity = Verbosity { verbosity: 3, quiet: false };
109        assert_eq!(verbosity.directive(), LevelFilter::INFO.into());
110
111        let verbosity = Verbosity { verbosity: 4, quiet: false };
112        assert_eq!(verbosity.directive(), LevelFilter::DEBUG.into());
113
114        let verbosity = Verbosity { verbosity: 5, quiet: false };
115        assert_eq!(verbosity.directive(), LevelFilter::TRACE.into());
116
117        let verbosity = Verbosity { verbosity: 1, quiet: true };
118        assert_eq!(verbosity.directive(), LevelFilter::OFF.into());
119    }
120}