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