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