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
26pub fn run() -> Result<()> {
28 setup()?;
29
30 let args = Chisel::parse();
31 args.global.init()?;
32
33 run_command(args)
34}
35
36pub fn setup() -> Result<()> {
38 handler::install();
39 utils::subscriber();
40 utils::load_dotenv();
41
42 Ok(())
43}
44
45#[tokio::main]
47pub async fn run_command(args: Chisel) -> Result<()> {
48 let mut interrupt = false;
50
51 let (config, evm_opts) = args.load_config_and_evm_opts()?;
53
54 let mut dispatcher = ChiselDispatcher::new(crate::session_source::SessionSourceConfig {
56 traces: config.verbosity > 0,
58 foundry_config: config,
59 no_vm: args.no_vm,
60 evm_opts,
61 backend: None,
62 calldata: None,
63 })?;
64
65 evaluate_prelude(&mut dispatcher, args.prelude).await?;
67
68 match &args.cmd {
70 Some(ChiselSubcommand::List) => {
71 let sessions = dispatcher.dispatch_command(ChiselCommand::ListSessions, &[]).await;
72 match sessions {
73 DispatchResult::CommandSuccess(Some(session_list)) => {
74 sh_println!("{session_list}")?;
75 }
76 DispatchResult::CommandFailed(e) => sh_err!("{e}")?,
77 _ => panic!("Unexpected result: Please report this bug."),
78 }
79 return Ok(())
80 }
81 Some(ChiselSubcommand::Load { id }) | Some(ChiselSubcommand::View { id }) => {
82 match dispatcher.dispatch_command(ChiselCommand::Load, &[id]).await {
84 DispatchResult::CommandSuccess(_) => { }
85 DispatchResult::CommandFailed(e) => {
86 sh_err!("{e}")?;
87 return Ok(())
88 }
89 _ => panic!("Unexpected result! Please report this bug."),
90 }
91
92 if matches!(args.cmd, Some(ChiselSubcommand::View { .. })) {
94 match dispatcher.dispatch_command(ChiselCommand::Source, &[]).await {
95 DispatchResult::CommandSuccess(Some(source)) => {
96 sh_println!("{source}")?;
97 }
98 _ => panic!("Unexpected result! Please report this bug."),
99 }
100 return Ok(())
101 }
102 }
103 Some(ChiselSubcommand::ClearCache) => {
104 match dispatcher.dispatch_command(ChiselCommand::ClearCache, &[]).await {
105 DispatchResult::CommandSuccess(Some(msg)) => sh_println!("{}", msg.green())?,
106 DispatchResult::CommandFailed(e) => sh_err!("{e}")?,
107 _ => panic!("Unexpected result! Please report this bug."),
108 }
109 return Ok(())
110 }
111 Some(ChiselSubcommand::Eval { command }) => {
112 dispatch_repl_line(&mut dispatcher, command).await?;
113 return Ok(())
114 }
115 None => { }
116 }
117
118 let mut rl = Editor::<SolidityHelper, _>::new()?;
120 rl.set_helper(Some(SolidityHelper::default()));
121
122 rl.set_auto_add_history(true);
124
125 if let Some(chisel_history) = chisel_history_file() {
127 let _ = rl.load_history(&chisel_history);
128 }
129
130 sh_println!("Welcome to Chisel! Type `{}` to show available commands.", "!help".green())?;
132
133 loop {
135 let prompt = dispatcher.get_prompt();
138
139 let next_string = rl.readline(prompt.as_ref());
141
142 match next_string {
144 Ok(line) => {
145 debug!("dispatching next line: {line}");
146 interrupt = false;
148
149 let errored = dispatch_repl_line(&mut dispatcher, &line).await?;
151 rl.helper_mut().unwrap().set_errored(errored);
152 }
153 Err(ReadlineError::Interrupted) => {
154 if interrupt {
155 break
156 } else {
157 sh_println!("(To exit, press Ctrl+C again)")?;
158 interrupt = true;
159 }
160 }
161 Err(ReadlineError::Eof) => break,
162 Err(err) => {
163 sh_err!("{err:?}")?;
164 break
165 }
166 }
167 }
168
169 if let Some(chisel_history) = chisel_history_file() {
170 let _ = rl.save_history(&chisel_history);
171 }
172
173 Ok(())
174}
175
176impl Provider for Chisel {
178 fn metadata(&self) -> Metadata {
179 Metadata::named("Script Args Provider")
180 }
181
182 fn data(&self) -> Result<Map<Profile, Dict>, foundry_config::figment::Error> {
183 Ok(Map::from([(Config::selected_profile(), Dict::default())]))
184 }
185}
186
187async fn dispatch_repl_line(dispatcher: &mut ChiselDispatcher, line: &str) -> Result<bool> {
189 let r = dispatcher.dispatch(line).await;
190 match &r {
191 DispatchResult::Success(msg) | DispatchResult::CommandSuccess(msg) => {
192 debug!(%line, ?msg, "dispatch success");
193 if let Some(msg) = msg {
194 sh_println!("{}", msg.green())?;
195 }
196 },
197 DispatchResult::UnrecognizedCommand(e) => sh_err!("{e}")?,
198 DispatchResult::SolangParserFailed(e) => {
199 sh_err!("{}", "Compilation error".red())?;
200 sh_eprintln!("{}", format!("{e:?}").red())?;
201 }
202 DispatchResult::FileIoError(e) => sh_err!("{}", format!("File IO - {e}").red())?,
203 DispatchResult::CommandFailed(msg) | DispatchResult::Failure(Some(msg)) => sh_err!("{}", msg.red())?,
204 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")?,
205 }
206 Ok(r.is_error())
207}
208
209async fn evaluate_prelude(
212 dispatcher: &mut ChiselDispatcher,
213 maybe_prelude: Option<PathBuf>,
214) -> Result<()> {
215 let Some(prelude_dir) = maybe_prelude else { return Ok(()) };
216 if prelude_dir.is_file() {
217 sh_println!("{} {}", "Loading prelude source file:".yellow(), prelude_dir.display())?;
218 load_prelude_file(dispatcher, prelude_dir).await?;
219 sh_println!("{}\n", "Prelude source file loaded successfully!".green())?;
220 } else {
221 let prelude_sources = fs::files_with_ext(&prelude_dir, "sol");
222 let mut print_success_msg = false;
223 for source_file in prelude_sources {
224 print_success_msg = true;
225 sh_println!("{} {}", "Loading prelude source file:".yellow(), source_file.display())?;
226 load_prelude_file(dispatcher, source_file).await?;
227 }
228
229 if print_success_msg {
230 sh_println!("{}\n", "All prelude source files loaded successfully!".green())?;
231 }
232 }
233 Ok(())
234}
235
236async fn load_prelude_file(dispatcher: &mut ChiselDispatcher, file: PathBuf) -> Result<()> {
238 let prelude = fs::read_to_string(file)
239 .wrap_err("Could not load source file. Are you sure this path is correct?")?;
240 dispatch_repl_line(dispatcher, &prelude).await?;
241 Ok(())
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use clap::CommandFactory;
248
249 #[test]
250 fn verify_cli() {
251 Chisel::command().debug_assert();
252 }
253}