Skip to main content

chisel/
cmd.rs

1use crate::prelude::CHISEL_CHAR;
2use alloy_primitives::Address;
3use clap::{CommandFactory, Parser};
4use itertools::Itertools;
5use yansi::Paint;
6
7/// Chisel REPL commands.
8#[derive(Debug, Parser)]
9#[command(disable_help_flag = true, disable_help_subcommand = true)]
10pub enum ChiselCommand {
11    /// Display all commands.
12    #[command(visible_alias = "h", next_help_heading = "General")]
13    Help,
14
15    /// Quit the REPL.
16    #[command(visible_alias = "q")]
17    Quit,
18
19    /// Executes a shell command.
20    #[command(visible_alias = "e")]
21    Exec {
22        /// Command to execute.
23        command: String,
24        /// Command arguments.
25        #[arg(trailing_var_arg = true)]
26        args: Vec<String>,
27    },
28
29    /// Clear the current session source.
30    #[command(visible_alias = "c", next_help_heading = "Session")]
31    Clear,
32
33    /// Print the generated source contract.
34    #[command(visible_alias = "so")]
35    Source,
36
37    /// Save the current session to the cache.
38    ///
39    /// RPC endpoints and credentials are excluded from saved sessions.
40    #[command(visible_alias = "s")]
41    Save {
42        /// Optional session ID.
43        id: Option<String>,
44    },
45
46    /// Load a previous session from cache.
47    /// WARNING: This will overwrite the current session (though the current session will be
48    /// optimistically cached).
49    ///
50    /// Credentials come from this invocation. Use `!fork <url>` or start Chisel with `--fork-url`
51    /// to restore a forked session.
52    #[command(visible_alias = "l")]
53    Load {
54        /// Session ID to load.
55        id: String,
56    },
57
58    /// List all cached sessions.
59    #[command(name = "list", visible_alias = "ls")]
60    ListSessions,
61
62    /// Clear the cache of all stored sessions.
63    #[command(name = "clearcache", visible_alias = "cc")]
64    ClearCache,
65
66    /// Export the current REPL session source to a Script file.
67    #[command(visible_alias = "ex")]
68    Export,
69
70    /// Fetch an interface of a verified contract on Etherscan.
71    #[command(visible_alias = "fe")]
72    Fetch {
73        /// Contract address.
74        addr: Address,
75        /// Interface name.
76        name: String,
77    },
78
79    /// Open the current session in an editor.
80    Edit,
81
82    /// Fork an RPC in the current session.
83    #[command(visible_alias = "f", next_help_heading = "Environment")]
84    Fork {
85        /// Fork URL, environment variable, or RPC endpoints alias (empty to return to local
86        /// network).
87        url: Option<String>,
88    },
89
90    /// Enable / disable traces for the current session.
91    #[command(visible_alias = "t")]
92    Traces,
93
94    /// Set calldata (`msg.data`) for the current session (appended after function selector). Clears
95    /// it if no argument provided.
96    #[command(visible_alias = "cd")]
97    Calldata {
98        /// Calldata (empty to clear).
99        data: Option<String>,
100    },
101
102    /// Dump the raw memory.
103    #[command(name = "memdump", visible_alias = "md", next_help_heading = "Debug")]
104    MemDump,
105
106    /// Dump the raw stack.
107    #[command(name = "stackdump", visible_alias = "sd")]
108    StackDump,
109
110    /// Display the raw value of a variable's stack allocation. For variables that are > 32 bytes in
111    /// length, this will display their memory pointer.
112    #[command(name = "rawstack", visible_alias = "rs")]
113    RawStack {
114        /// Variable name.
115        var: String,
116    },
117}
118
119impl ChiselCommand {
120    pub fn parse(input: &str) -> eyre::Result<Self> {
121        let args = input.split_whitespace();
122        let args = std::iter::once("chisel").chain(args);
123        Self::try_parse_from(args)
124            .map_err(|e| eyre::eyre!("{}; for more information, see `!help`", e.kind()))
125    }
126
127    pub fn format_help() -> String {
128        let cmd = Self::command();
129        let mut categories = Vec::new();
130        let mut cat = None;
131        for sub in cmd.get_subcommands() {
132            if let Some(cat_) = sub.get_next_help_heading()
133                && Some(cat_) != cat
134            {
135                cat = Some(cat_);
136                categories.push((cat_, vec![]));
137            }
138            categories.last_mut().unwrap().1.push(sub);
139        }
140        format!(
141            "{}\n{}",
142            format!("{CHISEL_CHAR} Chisel help\n=============").cyan(),
143            categories
144                .iter()
145                .map(|(cat, cat_cmds)| {
146                    format!(
147                        "{}\n{}\n",
148                        cat.magenta(),
149                        cat_cmds
150                            .iter()
151                            .map(|&cmd| format!(
152                                "\t{}{} - {}",
153                                std::iter::once(cmd.get_name())
154                                    .chain(cmd.get_visible_aliases())
155                                    .map(|s| format!("!{}", s.green()))
156                                    .format(" | "),
157                                {
158                                    let usage = get_usage(cmd);
159                                    if usage.is_empty() {
160                                        String::new()
161                                    } else {
162                                        format!(" {usage}")
163                                    }
164                                }
165                                .green(),
166                                cmd.get_about().expect("command is missing about"),
167                            ))
168                            .format("\n")
169                    )
170                })
171                .format("\n")
172        )
173    }
174}
175
176fn get_usage(cmd: &clap::Command) -> String {
177    let s = cmd.clone().render_usage().to_string();
178    if let Some(idx) = s.find(['[', '<']) {
179        return s[idx..].to_string();
180    }
181    String::new()
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    #[test]
189    fn print_help() {
190        let _ = sh_eprintln!("{}", ChiselCommand::format_help());
191    }
192}