chisel/
args.rs

1use crate::{
2    history::chisel_history_file,
3    prelude::{ChiselCommand, ChiselDispatcher, DispatchResult, SolidityHelper},
4};
5use clap::Parser;
6use eyre::{Context, Result};
7use foundry_cli::{
8    handler,
9    utils::{self, LoadConfig},
10};
11use foundry_common::fs;
12use foundry_config::{
13    Config,
14    figment::{
15        Metadata, Profile, Provider,
16        value::{Dict, Map},
17    },
18};
19use rustyline::{Editor, config::Configurer, error::ReadlineError};
20use std::path::PathBuf;
21use tracing::debug;
22use yansi::Paint;
23
24use crate::opts::{Chisel, ChiselSubcommand};
25
26/// Run the `chisel` command line interface.
27pub fn run() -> Result<()> {
28    setup()?;
29
30    let args = Chisel::parse();
31    args.global.init()?;
32    args.global.tokio_runtime().block_on(run_command(args))
33}
34
35/// Setup the global logger and other utilities.
36pub fn setup() -> Result<()> {
37    utils::install_crypto_provider();
38    handler::install();
39    utils::subscriber();
40    utils::load_dotenv();
41
42    Ok(())
43}
44
45/// Run the subcommand.
46pub async fn run_command(args: Chisel) -> Result<()> {
47    // Keeps track of whether or not an interrupt was the last input
48    let mut interrupt = false;
49
50    // Load configuration
51    let (config, evm_opts) = args.load_config_and_evm_opts()?;
52
53    // Create a new cli dispatcher
54    let mut dispatcher = ChiselDispatcher::new(crate::session_source::SessionSourceConfig {
55        // Enable traces if any level of verbosity was passed
56        traces: config.verbosity > 0,
57        foundry_config: config,
58        no_vm: args.no_vm,
59        evm_opts,
60        backend: None,
61        calldata: None,
62    })?;
63
64    // Execute prelude Solidity source files
65    evaluate_prelude(&mut dispatcher, args.prelude).await?;
66
67    // Check for chisel subcommands
68    match &args.cmd {
69        Some(ChiselSubcommand::List) => {
70            let sessions = dispatcher.dispatch_command(ChiselCommand::ListSessions, &[]).await;
71            match sessions {
72                DispatchResult::CommandSuccess(Some(session_list)) => {
73                    sh_println!("{session_list}")?;
74                }
75                DispatchResult::CommandFailed(e) => sh_err!("{e}")?,
76                _ => panic!("Unexpected result: Please report this bug."),
77            }
78            return Ok(());
79        }
80        Some(ChiselSubcommand::Load { id }) | Some(ChiselSubcommand::View { id }) => {
81            // For both of these subcommands, we need to attempt to load the session from cache
82            match dispatcher.dispatch_command(ChiselCommand::Load, &[id]).await {
83                DispatchResult::CommandSuccess(_) => { /* Continue */ }
84                DispatchResult::CommandFailed(e) => {
85                    sh_err!("{e}")?;
86                    return Ok(());
87                }
88                _ => panic!("Unexpected result! Please report this bug."),
89            }
90
91            // If the subcommand was `view`, print the source and exit.
92            if matches!(args.cmd, Some(ChiselSubcommand::View { .. })) {
93                match dispatcher.dispatch_command(ChiselCommand::Source, &[]).await {
94                    DispatchResult::CommandSuccess(Some(source)) => {
95                        sh_println!("{source}")?;
96                    }
97                    _ => panic!("Unexpected result! Please report this bug."),
98                }
99                return Ok(());
100            }
101        }
102        Some(ChiselSubcommand::ClearCache) => {
103            match dispatcher.dispatch_command(ChiselCommand::ClearCache, &[]).await {
104                DispatchResult::CommandSuccess(Some(msg)) => sh_println!("{}", msg.green())?,
105                DispatchResult::CommandFailed(e) => sh_err!("{e}")?,
106                _ => panic!("Unexpected result! Please report this bug."),
107            }
108            return Ok(());
109        }
110        Some(ChiselSubcommand::Eval { command }) => {
111            dispatch_repl_line(&mut dispatcher, command).await?;
112            return Ok(());
113        }
114        None => { /* No chisel subcommand present; Continue */ }
115    }
116
117    // Create a new rustyline Editor
118    let mut rl = Editor::<SolidityHelper, _>::new()?;
119    rl.set_helper(Some(SolidityHelper::default()));
120
121    // automatically add lines to history
122    rl.set_auto_add_history(true);
123
124    // load history
125    if let Some(chisel_history) = chisel_history_file() {
126        let _ = rl.load_history(&chisel_history);
127    }
128
129    // Print welcome header
130    sh_println!("Welcome to Chisel! Type `{}` to show available commands.", "!help".green())?;
131
132    // Begin Rustyline loop
133    loop {
134        // Get the prompt from the dispatcher
135        // Variable based on status of the last entry
136        let prompt = dispatcher.get_prompt();
137
138        // Read the next line
139        let next_string = rl.readline(prompt.as_ref());
140
141        // Try to read the string
142        match next_string {
143            Ok(line) => {
144                debug!("dispatching next line: {line}");
145                // Clear interrupt flag
146                interrupt = false;
147
148                // Dispatch and match results
149                let errored = dispatch_repl_line(&mut dispatcher, &line).await?;
150                rl.helper_mut().unwrap().set_errored(errored);
151            }
152            Err(ReadlineError::Interrupted) => {
153                if interrupt {
154                    break;
155                } else {
156                    sh_println!("(To exit, press Ctrl+C again)")?;
157                    interrupt = true;
158                }
159            }
160            Err(ReadlineError::Eof) => break,
161            Err(err) => {
162                sh_err!("{err:?}")?;
163                break;
164            }
165        }
166    }
167
168    if let Some(chisel_history) = chisel_history_file() {
169        let _ = rl.save_history(&chisel_history);
170    }
171
172    Ok(())
173}
174
175/// [Provider] impl
176impl Provider for Chisel {
177    fn metadata(&self) -> Metadata {
178        Metadata::named("Script Args Provider")
179    }
180
181    fn data(&self) -> Result<Map<Profile, Dict>, foundry_config::figment::Error> {
182        Ok(Map::from([(Config::selected_profile(), Dict::default())]))
183    }
184}
185
186/// Evaluate a single Solidity line.
187async fn dispatch_repl_line(dispatcher: &mut ChiselDispatcher, line: &str) -> Result<bool> {
188    let r = dispatcher.dispatch(line).await;
189    match &r {
190        DispatchResult::Success(msg) | DispatchResult::CommandSuccess(msg) => {
191            debug!(%line, ?msg, "dispatch success");
192            if let Some(msg) = msg {
193                sh_println!("{}", msg.green())?;
194            }
195        }
196        DispatchResult::UnrecognizedCommand(e) => sh_err!("{e}")?,
197        DispatchResult::SolangParserFailed(e) => {
198            sh_err!("{}", "Compilation error".red())?;
199            sh_eprintln!("{}", format!("{e:?}").red())?;
200        }
201        DispatchResult::FileIoError(e) => sh_err!("{}", format!("File IO - {e}").red())?,
202        DispatchResult::CommandFailed(msg) | DispatchResult::Failure(Some(msg)) => {
203            sh_err!("{}", msg.red())?
204        }
205        DispatchResult::Failure(None) => sh_err!(
206            "Please report this bug as a github issue if it persists: https://github.com/foundry-rs/foundry/issues/new/choose"
207        )?,
208    }
209    Ok(r.is_error())
210}
211
212/// Evaluate multiple Solidity source files contained within a
213/// Chisel prelude directory.
214async fn evaluate_prelude(
215    dispatcher: &mut ChiselDispatcher,
216    maybe_prelude: Option<PathBuf>,
217) -> Result<()> {
218    let Some(prelude_dir) = maybe_prelude else { return Ok(()) };
219    if prelude_dir.is_file() {
220        sh_println!("{} {}", "Loading prelude source file:".yellow(), prelude_dir.display())?;
221        load_prelude_file(dispatcher, prelude_dir).await?;
222        sh_println!("{}\n", "Prelude source file loaded successfully!".green())?;
223    } else {
224        let prelude_sources = fs::files_with_ext(&prelude_dir, "sol");
225        let mut print_success_msg = false;
226        for source_file in prelude_sources {
227            print_success_msg = true;
228            sh_println!("{} {}", "Loading prelude source file:".yellow(), source_file.display())?;
229            load_prelude_file(dispatcher, source_file).await?;
230        }
231
232        if print_success_msg {
233            sh_println!("{}\n", "All prelude source files loaded successfully!".green())?;
234        }
235    }
236    Ok(())
237}
238
239/// Loads a single Solidity file into the prelude.
240async fn load_prelude_file(dispatcher: &mut ChiselDispatcher, file: PathBuf) -> Result<()> {
241    let prelude = fs::read_to_string(file)
242        .wrap_err("Could not load source file. Are you sure this path is correct?")?;
243    dispatch_repl_line(dispatcher, &prelude).await?;
244    Ok(())
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250    use clap::CommandFactory;
251
252    #[test]
253    fn verify_cli() {
254        Chisel::command().debug_assert();
255    }
256}