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
13pub 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
24pub 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
41pub async fn run_command(args: Chisel) -> Result<()> {
43 let (config, evm_opts) = args.load_config_and_evm_opts()?;
45
46 let mut dispatcher = ChiselDispatcher::new(crate::source::SessionSourceConfig {
48 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 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 let mut interrupt = false;
77 loop {
78 match rl.readline(&dispatcher.get_prompt()) {
79 Ok(line) => {
80 debug!("dispatching next line: {line}");
81 interrupt = false;
83
84 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
117async 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
144async 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}