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 tracing::debug;
12use yansi::Paint;
13
14pub fn run() -> Result<()> {
16 setup()?;
17
18 let args = Chisel::parse();
19 args.global.init()?;
20 args.global.tokio_runtime().block_on(run_command(args))
21}
22
23pub fn setup() -> Result<()> {
25 utils::common_setup();
26 utils::subscriber();
27
28 Ok(())
29}
30
31macro_rules! try_cf {
32 ($e:expr) => {
33 match $e {
34 ControlFlow::Continue(()) => {}
35 ControlFlow::Break(()) => return Ok(()),
36 }
37 };
38}
39
40pub async fn run_command(args: Chisel) -> Result<()> {
42 let (config, evm_opts) = args.load_config_and_evm_opts()?;
44
45 let mut dispatcher = ChiselDispatcher::new(crate::source::SessionSourceConfig {
47 traces: config.verbosity > 0,
49 foundry_config: config,
50 no_vm: args.no_vm,
51 evm_opts,
52 backend: None,
53 calldata: None,
54 })?;
55
56 evaluate_prelude(&mut dispatcher, args.prelude).await?;
58
59 if let Some(cmd) = args.cmd {
60 try_cf!(handle_cli_command(&mut dispatcher, cmd).await?);
61 return Ok(());
62 }
63
64 let mut rl = Editor::<SolidityHelper, _>::new()?;
65 rl.set_helper(Some(dispatcher.helper.clone()));
66 rl.set_auto_add_history(true);
67 if let Some(path) = chisel_history_file() {
68 let _ = rl.load_history(&path);
69 }
70
71 sh_println!("Welcome to Chisel! Type `{}` to show available commands.", "!help".green())?;
72
73 let mut interrupt = false;
75 loop {
76 match rl.readline(&dispatcher.get_prompt()) {
77 Ok(line) => {
78 debug!("dispatching next line: {line}");
79 interrupt = false;
81
82 let r = dispatcher.dispatch(&line).await;
84 dispatcher.helper.set_errored(r.is_err());
85 match r {
86 Ok(ControlFlow::Continue(())) => {}
87 Ok(ControlFlow::Break(())) => break,
88 Err(e) => {
89 sh_err!("{}", foundry_common::errors::display_chain(&e))?;
90 }
91 }
92 }
93 Err(ReadlineError::Interrupted) => {
94 if interrupt {
95 break;
96 } else {
97 sh_println!("(To exit, press Ctrl+C again)")?;
98 interrupt = true;
99 }
100 }
101 Err(ReadlineError::Eof) => break,
102 Err(err) => {
103 sh_err!("{err}")?;
104 break;
105 }
106 }
107 }
108
109 if let Some(path) = chisel_history_file() {
110 let _ = rl.save_history(&path);
111 }
112
113 Ok(())
114}
115
116async fn evaluate_prelude(
119 dispatcher: &mut ChiselDispatcher,
120 maybe_prelude: Option<PathBuf>,
121) -> Result<()> {
122 let Some(prelude_dir) = maybe_prelude else { return Ok(()) };
123 if prelude_dir.is_file() {
124 sh_println!("{} {}", "Loading prelude source file:".yellow(), prelude_dir.display())?;
125 try_cf!(load_prelude_file(dispatcher, prelude_dir).await?);
126 sh_println!("{}\n", "Prelude source file loaded successfully!".green())?;
127 } else {
128 let prelude_sources = fs::files_with_ext(&prelude_dir, "sol");
129 let mut print_success_msg = false;
130 for source_file in prelude_sources {
131 print_success_msg = true;
132 sh_println!("{} {}", "Loading prelude source file:".yellow(), source_file.display())?;
133 try_cf!(load_prelude_file(dispatcher, source_file).await?);
134 }
135
136 if print_success_msg {
137 sh_println!("{}\n", "All prelude source files loaded successfully!".green())?;
138 }
139 }
140 Ok(())
141}
142
143async fn load_prelude_file(
145 dispatcher: &mut ChiselDispatcher,
146 file: PathBuf,
147) -> Result<ControlFlow<()>> {
148 let prelude = fs::read_to_string(file)
149 .wrap_err("Could not load source file. Are you sure this path is correct?")?;
150 dispatcher.dispatch(&prelude).await
151}
152
153async fn handle_cli_command(
154 d: &mut ChiselDispatcher,
155 cmd: ChiselSubcommand,
156) -> Result<ControlFlow<()>> {
157 match cmd {
158 ChiselSubcommand::List => d.dispatch_command(ChiselCommand::ListSessions).await,
159 ChiselSubcommand::Load { id } => d.dispatch_command(ChiselCommand::Load { id }).await,
160 ChiselSubcommand::View { id } => {
161 let ControlFlow::Continue(()) = d.dispatch_command(ChiselCommand::Load { id }).await?
162 else {
163 return Ok(ControlFlow::Break(()));
164 };
165 d.dispatch_command(ChiselCommand::Source).await
166 }
167 ChiselSubcommand::ClearCache => d.dispatch_command(ChiselCommand::ClearCache).await,
168 ChiselSubcommand::Eval { command } => d.dispatch(&command).await,
169 }
170}
171
172fn chisel_history_file() -> Option<PathBuf> {
173 foundry_config::Config::foundry_dir().map(|p| p.join(".chisel_history"))
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use clap::CommandFactory;
180
181 #[test]
182 fn verify_cli() {
183 Chisel::command().debug_assert();
184 }
185}