1use cache::Cache;
2use clap::{
3 Arg, Command, Parser, Subcommand,
4 builder::{PossibleValuesParser, TypedValueParser},
5};
6use eyre::Result;
7use foundry_common::sh_warn;
8use foundry_config::{Chain, Config, NamedChain, cache};
9use std::{ffi::OsStr, str::FromStr};
10
11#[derive(Debug, Parser)]
13pub struct CacheArgs {
14 #[command(subcommand)]
15 pub sub: CacheSubcommands,
16}
17
18#[derive(Debug, Subcommand)]
19pub enum CacheSubcommands {
20 Clean(CleanArgs),
22
23 Ls(LsArgs),
25}
26
27#[derive(Debug, Parser)]
29#[command(group = clap::ArgGroup::new("etherscan-blocks").multiple(false))]
30pub struct CleanArgs {
31 #[arg(
35 env = "CHAIN",
36 default_value = "all",
37 value_parser = ChainOrAllValueParser::default(),
38 )]
39 chains: Vec<ChainOrAll>,
40
41 #[arg(
43 short,
44 long,
45 num_args(1..),
46 value_delimiter(','),
47 group = "etherscan-blocks"
48 )]
49 blocks: Vec<u64>,
50
51 #[arg(long, group = "etherscan-blocks")]
53 etherscan: bool,
54}
55
56impl CleanArgs {
57 pub fn run(self) -> Result<()> {
58 let Self { chains, blocks, etherscan } = self;
59
60 eyre::ensure!(
61 blocks.is_empty() || !chains.iter().any(|chain| matches!(chain, ChainOrAll::All)),
62 "--blocks requires explicit chains and cannot be used with 'all'"
63 );
64
65 for chain_or_all in chains {
66 match chain_or_all {
67 ChainOrAll::NamedChain(chain) => {
68 clean_chain_cache(chain, blocks.clone(), etherscan)?
69 }
70 ChainOrAll::All => {
71 let warnings = if etherscan {
72 Config::clean_foundry_etherscan_cache()?
73 } else {
74 Config::clean_foundry_cache()?
75 };
76 for warning in warnings {
77 let _ = sh_warn!("{warning}");
78 }
79 }
80 }
81 }
82
83 Ok(())
84 }
85}
86
87#[derive(Debug, Parser)]
88pub struct LsArgs {
89 #[arg(
93 env = "CHAIN",
94 default_value = "all",
95 value_parser = ChainOrAllValueParser::default(),
96 )]
97 chains: Vec<ChainOrAll>,
98}
99
100impl LsArgs {
101 pub fn run(self) -> Result<()> {
102 let Self { chains } = self;
103 let mut cache = Cache::default();
104 for chain_or_all in chains {
105 match chain_or_all {
106 ChainOrAll::NamedChain(chain) => {
107 cache.chains.push(Config::list_foundry_chain_cache(chain.into())?)
108 }
109 ChainOrAll::All => cache = Config::list_foundry_cache()?,
110 }
111 }
112 sh_eprint!("{cache}")?;
113 Ok(())
114 }
115}
116
117#[derive(Clone, Debug)]
118pub enum ChainOrAll {
119 NamedChain(NamedChain),
120 All,
121}
122
123impl FromStr for ChainOrAll {
124 type Err = String;
125
126 fn from_str(s: &str) -> Result<Self, Self::Err> {
127 if let Ok(chain) = NamedChain::from_str(s) {
128 Ok(Self::NamedChain(chain))
129 } else if s == "all" {
130 Ok(Self::All)
131 } else {
132 Err(format!("Expected known chain or all, found: {s}"))
133 }
134 }
135}
136
137fn clean_chain_cache(chain: impl Into<Chain>, blocks: Vec<u64>, etherscan: bool) -> Result<()> {
138 let chain = chain.into();
139 let mut warnings = Vec::new();
140 if blocks.is_empty() {
141 warnings.extend(Config::clean_foundry_etherscan_chain_cache(chain)?);
142 if etherscan {
143 for warning in warnings {
144 let _ = sh_warn!("{warning}");
145 }
146 return Ok(());
147 }
148 warnings.extend(Config::clean_foundry_chain_cache(chain)?);
149 } else {
150 for block in blocks {
151 warnings.extend(Config::clean_foundry_block_cache(chain, block)?);
152 }
153 }
154 for warning in warnings {
155 let _ = sh_warn!("{warning}");
156 }
157 Ok(())
158}
159
160#[derive(Clone, Debug)]
162pub struct ChainOrAllValueParser {
163 inner: PossibleValuesParser,
164}
165
166impl Default for ChainOrAllValueParser {
167 fn default() -> Self {
168 let inner = Some(&"all").into_iter().chain(NamedChain::VARIANT_NAMES).into();
169 Self { inner }
170 }
171}
172
173impl TypedValueParser for ChainOrAllValueParser {
174 type Value = ChainOrAll;
175
176 fn parse_ref(
177 &self,
178 cmd: &Command,
179 arg: Option<&Arg>,
180 value: &OsStr,
181 ) -> Result<Self::Value, clap::Error> {
182 self.inner.parse_ref(cmd, arg, value)?.parse::<ChainOrAll>().map_err(|_| {
183 clap::Error::raw(
184 clap::error::ErrorKind::InvalidValue,
185 "chain argument did not match any possible chain variant",
186 )
187 })
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn can_parse_cache_ls() {
197 let args: CacheArgs = CacheArgs::parse_from(["cache", "ls"]);
198 assert!(matches!(args.sub, CacheSubcommands::Ls(_)));
199 }
200}