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 } 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
118async 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
145async 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}