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                }
99                sh_println!("(To exit, press Ctrl+C again)")?;
100                interrupt = true;
101            }
102            Err(ReadlineError::Eof) => break,
103            Err(err) => {
104                sh_err!("{err}")?;
105                break;
106            }
107        }
108    }
109
110    if let Some(path) = chisel_history_file() {
111        let _ = rl.save_history(&path);
112    }
113
114    Ok(())
115}
116
117/// Evaluate multiple Solidity source files contained within a
118/// Chisel prelude directory.
119async fn evaluate_prelude(
120    dispatcher: &mut ChiselDispatcher,
121    maybe_prelude: Option<PathBuf>,
122) -> Result<()> {
123    let Some(prelude_dir) = maybe_prelude else { return Ok(()) };
124    if prelude_dir.is_file() {
125        sh_println!("{} {}", "Loading prelude source file:".yellow(), prelude_dir.display())?;
126        try_cf!(load_prelude_file(dispatcher, prelude_dir).await?);
127        sh_println!("{}\n", "Prelude source file loaded successfully!".green())?;
128    } else {
129        let prelude_sources = fs::files_with_ext(&prelude_dir, "sol");
130        let mut print_success_msg = false;
131        for source_file in prelude_sources {
132            print_success_msg = true;
133            sh_println!("{} {}", "Loading prelude source file:".yellow(), source_file.display())?;
134            try_cf!(load_prelude_file(dispatcher, source_file).await?);
135        }
136
137        if print_success_msg {
138            sh_println!("{}\n", "All prelude source files loaded successfully!".green())?;
139        }
140    }
141    Ok(())
142}
143
144/// Loads a single Solidity file into the prelude.
145async fn load_prelude_file(
146    dispatcher: &mut ChiselDispatcher,
147    file: PathBuf,
148) -> Result<ControlFlow<()>> {
149    let prelude = fs::read_to_string(file)
150        .wrap_err("Could not load source file. Are you sure this path is correct?")?;
151    dispatcher.dispatch(&prelude).await
152}
153
154async fn handle_cli_command(
155    d: &mut ChiselDispatcher,
156    cmd: ChiselSubcommand,
157) -> Result<ControlFlow<()>> {
158    match cmd {
159        ChiselSubcommand::List => d.dispatch_command(ChiselCommand::ListSessions).await,
160        ChiselSubcommand::Load { id } => d.dispatch_command(ChiselCommand::Load { id }).await,
161        ChiselSubcommand::View { id } => {
162            let ControlFlow::Continue(()) = d.dispatch_command(ChiselCommand::Load { id }).await?
163            else {
164                return Ok(ControlFlow::Break(()));
165            };
166            d.dispatch_command(ChiselCommand::Source).await
167        }
168        ChiselSubcommand::ClearCache => d.dispatch_command(ChiselCommand::ClearCache).await,
169        ChiselSubcommand::Eval { command } => d.dispatch(&command).await,
170    }
171}
172
173fn chisel_history_file() -> Option<PathBuf> {
174    foundry_config::Config::foundry_dir().map(|p| p.join(".chisel_history"))
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180    use clap::CommandFactory;
181
182    #[test]
183    fn verify_cli() {
184        Chisel::command().debug_assert();
185    }
186}