Skip to main content

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