chisel/
args.rs

1use crate::{
2    opts::{Chisel, ChiselSubcommand},
3    prelude::{ChiselCommand, ChiselDispatcher, SolidityHelper},
4};
5use clap::Parser;
6use eyre::{Context, Result};
7use foundry_cli::utils::{self, LoadConfig};
8use foundry_common::fs;
9use rustyline::{Editor, config::Configurer, error::ReadlineError};
10use std::{ops::ControlFlow, path::PathBuf};
11use tracing::debug;
12use yansi::Paint;
13
14/// Run the `chisel` command line interface.
15pub fn run() -> Result<()> {
16    setup()?;
17
18    let args = Chisel::parse();
19    args.global.init()?;
20    args.global.tokio_runtime().block_on(run_command(args))
21}
22
23/// Setup the global logger and other utilities.
24pub fn setup() -> Result<()> {
25    utils::common_setup();
26    utils::subscriber();
27
28    Ok(())
29}
30
31macro_rules! try_cf {
32    ($e:expr) => {
33        match $e {
34            ControlFlow::Continue(()) => {}
35            ControlFlow::Break(()) => return Ok(()),
36        }
37    };
38}
39
40/// Run the subcommand.
41pub async fn run_command(args: Chisel) -> Result<()> {
42    // Load configuration
43    let (config, evm_opts) = args.load_config_and_evm_opts()?;
44
45    // Create a new cli dispatcher
46    let mut dispatcher = ChiselDispatcher::new(crate::source::SessionSourceConfig {
47        // Enable traces if any level of verbosity was passed
48        traces: config.verbosity > 0,
49        foundry_config: config,
50        no_vm: args.no_vm,
51        evm_opts,
52        backend: None,
53        calldata: None,
54    })?;
55
56    // Execute prelude Solidity source files
57    evaluate_prelude(&mut dispatcher, args.prelude).await?;
58
59    if let Some(cmd) = args.cmd {
60        try_cf!(handle_cli_command(&mut dispatcher, cmd).await?);
61        return Ok(());
62    }
63
64    let mut rl = Editor::<SolidityHelper, _>::new()?;
65    rl.set_helper(Some(dispatcher.helper.clone()));
66    rl.set_auto_add_history(true);
67    if let Some(path) = chisel_history_file() {
68        let _ = rl.load_history(&path);
69    }
70
71    sh_println!("Welcome to Chisel! Type `{}` to show available commands.", "!help".green())?;
72
73    // REPL loop.
74    let mut interrupt = false;
75    loop {
76        match rl.readline(&dispatcher.get_prompt()) {
77            Ok(line) => {
78                debug!("dispatching next line: {line}");
79                // Clear interrupt flag.
80                interrupt = false;
81
82                // Dispatch and match results.
83                let r = dispatcher.dispatch(&line).await;
84                dispatcher.helper.set_errored(r.is_err());
85                match r {
86                    Ok(ControlFlow::Continue(())) => {}
87                    Ok(ControlFlow::Break(())) => break,
88                    Err(e) => {
89                        sh_err!("{}", foundry_common::errors::display_chain(&e))?;
90                    }
91                }
92            }
93            Err(ReadlineError::Interrupted) => {
94                if interrupt {
95                    break;
96                } else {
97                    sh_println!("(To exit, press Ctrl+C again)")?;
98                    interrupt = true;
99                }
100            }
101            Err(ReadlineError::Eof) => break,
102            Err(err) => {
103                sh_err!("{err}")?;
104                break;
105            }
106        }
107    }
108
109    if let Some(path) = chisel_history_file() {
110        let _ = rl.save_history(&path);
111    }
112
113    Ok(())
114}
115
116/// Evaluate multiple Solidity source files contained within a
117/// Chisel prelude directory.
118async fn evaluate_prelude(
119    dispatcher: &mut ChiselDispatcher,
120    maybe_prelude: Option<PathBuf>,
121) -> Result<()> {
122    let Some(prelude_dir) = maybe_prelude else { return Ok(()) };
123    if prelude_dir.is_file() {
124        sh_println!("{} {}", "Loading prelude source file:".yellow(), prelude_dir.display())?;
125        try_cf!(load_prelude_file(dispatcher, prelude_dir).await?);
126        sh_println!("{}\n", "Prelude source file loaded successfully!".green())?;
127    } else {
128        let prelude_sources = fs::files_with_ext(&prelude_dir, "sol");
129        let mut print_success_msg = false;
130        for source_file in prelude_sources {
131            print_success_msg = true;
132            sh_println!("{} {}", "Loading prelude source file:".yellow(), source_file.display())?;
133            try_cf!(load_prelude_file(dispatcher, source_file).await?);
134        }
135
136        if print_success_msg {
137            sh_println!("{}\n", "All prelude source files loaded successfully!".green())?;
138        }
139    }
140    Ok(())
141}
142
143/// Loads a single Solidity file into the prelude.
144async fn load_prelude_file(
145    dispatcher: &mut ChiselDispatcher,
146    file: PathBuf,
147) -> Result<ControlFlow<()>> {
148    let prelude = fs::read_to_string(file)
149        .wrap_err("Could not load source file. Are you sure this path is correct?")?;
150    dispatcher.dispatch(&prelude).await
151}
152
153async fn handle_cli_command(
154    d: &mut ChiselDispatcher,
155    cmd: ChiselSubcommand,
156) -> Result<ControlFlow<()>> {
157    match cmd {
158        ChiselSubcommand::List => d.dispatch_command(ChiselCommand::ListSessions).await,
159        ChiselSubcommand::Load { id } => d.dispatch_command(ChiselCommand::Load { id }).await,
160        ChiselSubcommand::View { id } => {
161            let ControlFlow::Continue(()) = d.dispatch_command(ChiselCommand::Load { id }).await?
162            else {
163                return Ok(ControlFlow::Break(()));
164            };
165            d.dispatch_command(ChiselCommand::Source).await
166        }
167        ChiselSubcommand::ClearCache => d.dispatch_command(ChiselCommand::ClearCache).await,
168        ChiselSubcommand::Eval { command } => d.dispatch(&command).await,
169    }
170}
171
172fn chisel_history_file() -> Option<PathBuf> {
173    foundry_config::Config::foundry_dir().map(|p| p.join(".chisel_history"))
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179    use clap::CommandFactory;
180
181    #[test]
182    fn verify_cli() {
183        Chisel::command().debug_assert();
184    }
185}